Merge pull request #21910 from Microsoft/projectUpdateOnFileOpenClose
Fix for No Default project scenario
This commit is contained in:
commit
274bb5dab4
5 changed files with 173 additions and 163 deletions
|
@ -2111,9 +2111,6 @@ namespace ts.projectSystem {
|
|||
/*closedFiles*/ undefined);
|
||||
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
const changedFiles = projectService.getChangedFiles_TestOnly();
|
||||
assert(changedFiles && changedFiles.length === 1, `expected 1 changed file, got ${JSON.stringify(changedFiles && changedFiles.length || 0)}`);
|
||||
|
||||
projectService.ensureInferredProjectsUpToDate_TestOnly();
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 2 });
|
||||
});
|
||||
|
@ -2904,6 +2901,83 @@ namespace ts.projectSystem {
|
|||
tags: []
|
||||
});
|
||||
});
|
||||
|
||||
it("files opened, closed affecting multiple projects", () => {
|
||||
const file: FileOrFolder = {
|
||||
path: "/a/b/projects/config/file.ts",
|
||||
content: `import {a} from "../files/file1"; export let b = a;`
|
||||
};
|
||||
const config: FileOrFolder = {
|
||||
path: "/a/b/projects/config/tsconfig.json",
|
||||
content: ""
|
||||
};
|
||||
const filesFile1: FileOrFolder = {
|
||||
path: "/a/b/projects/files/file1.ts",
|
||||
content: "export let a = 10;"
|
||||
};
|
||||
const filesFile2: FileOrFolder = {
|
||||
path: "/a/b/projects/files/file2.ts",
|
||||
content: "export let aa = 10;"
|
||||
};
|
||||
|
||||
const files = [config, file, filesFile1, filesFile2, libFile];
|
||||
const host = createServerHost(files);
|
||||
const session = createSession(host);
|
||||
// Create configured project
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: {
|
||||
file: file.path
|
||||
}
|
||||
});
|
||||
|
||||
const projectService = session.getProjectService();
|
||||
const configuredProject = projectService.configuredProjects.get(config.path);
|
||||
verifyConfiguredProject();
|
||||
|
||||
// open files/file1 = should not create another project
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: {
|
||||
file: filesFile1.path
|
||||
}
|
||||
});
|
||||
verifyConfiguredProject();
|
||||
|
||||
// Close the file = should still have project
|
||||
session.executeCommandSeq<protocol.CloseRequest>({
|
||||
command: protocol.CommandTypes.Close,
|
||||
arguments: {
|
||||
file: file.path
|
||||
}
|
||||
});
|
||||
verifyConfiguredProject();
|
||||
|
||||
// Open files/file2 - should create inferred project and close configured project
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: {
|
||||
file: filesFile2.path
|
||||
}
|
||||
});
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [libFile.path, filesFile2.path]);
|
||||
|
||||
// Actions on file1 would result in assert
|
||||
session.executeCommandSeq<protocol.OccurrencesRequest>({
|
||||
command: protocol.CommandTypes.Occurrences,
|
||||
arguments: {
|
||||
file: filesFile1.path,
|
||||
line: 1,
|
||||
offset: filesFile1.content.indexOf("a")
|
||||
}
|
||||
});
|
||||
|
||||
function verifyConfiguredProject() {
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
checkProjectActualFiles(configuredProject, [file.path, filesFile1.path, libFile.path, config.path]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("tsserverProjectSystem Proper errors", () => {
|
||||
|
|
|
@ -376,9 +376,9 @@ namespace ts.server {
|
|||
private safelist: SafeList = defaultTypeSafeList;
|
||||
private legacySafelist: { [key: string]: string } = {};
|
||||
|
||||
private changedFiles: ScriptInfo[];
|
||||
private pendingProjectUpdates = createMap<Project>();
|
||||
private pendingInferredProjectUpdate: boolean;
|
||||
/* @internal */
|
||||
pendingEnsureProjectForOpenFiles: boolean;
|
||||
|
||||
readonly currentDirectory: string;
|
||||
readonly toCanonicalFileName: (f: string) => string;
|
||||
|
@ -483,11 +483,6 @@ namespace ts.server {
|
|||
return getNormalizedAbsolutePath(fileName, this.host.getCurrentDirectory());
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
getChangedFiles_TestOnly() {
|
||||
return this.changedFiles;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
ensureInferredProjectsUpToDate_TestOnly() {
|
||||
this.ensureProjectStructuresUptoDate();
|
||||
|
@ -552,19 +547,18 @@ namespace ts.server {
|
|||
this.typingsCache.deleteTypingsForProject(response.projectName);
|
||||
break;
|
||||
}
|
||||
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
|
||||
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
|
||||
}
|
||||
|
||||
private delayInferredProjectsRefresh() {
|
||||
this.pendingInferredProjectUpdate = true;
|
||||
this.throttledOperations.schedule("*refreshInferredProjects*", /*delay*/ 250, () => {
|
||||
private delayEnsureProjectForOpenFiles() {
|
||||
this.pendingEnsureProjectForOpenFiles = true;
|
||||
this.throttledOperations.schedule("*ensureProjectForOpenFiles*", /*delay*/ 250, () => {
|
||||
if (this.pendingProjectUpdates.size !== 0) {
|
||||
this.delayInferredProjectsRefresh();
|
||||
this.delayEnsureProjectForOpenFiles();
|
||||
}
|
||||
else {
|
||||
if (this.pendingInferredProjectUpdate) {
|
||||
this.pendingInferredProjectUpdate = false;
|
||||
this.refreshInferredProjects();
|
||||
if (this.pendingEnsureProjectForOpenFiles) {
|
||||
this.ensureProjectForOpenFiles();
|
||||
}
|
||||
// Send the event to notify that there were background project updates
|
||||
// send current list of open files
|
||||
|
@ -574,6 +568,7 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
private delayUpdateProjectGraph(project: Project) {
|
||||
project.markAsDirty();
|
||||
const projectName = project.getProjectName();
|
||||
this.pendingProjectUpdates.set(projectName, project);
|
||||
this.throttledOperations.schedule(projectName, /*delay*/ 250, () => {
|
||||
|
@ -603,17 +598,16 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
/* @internal */
|
||||
delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) {
|
||||
project.markAsDirty();
|
||||
delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project: Project) {
|
||||
this.delayUpdateProjectGraph(project);
|
||||
this.delayInferredProjectsRefresh();
|
||||
this.delayEnsureProjectForOpenFiles();
|
||||
}
|
||||
|
||||
private delayUpdateProjectGraphs(projects: Project[]) {
|
||||
private delayUpdateProjectGraphs(projects: ReadonlyArray<Project>) {
|
||||
for (const project of projects) {
|
||||
this.delayUpdateProjectGraph(project);
|
||||
}
|
||||
this.delayInferredProjectsRefresh();
|
||||
this.delayEnsureProjectForOpenFiles();
|
||||
}
|
||||
|
||||
setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions, projectRootPath?: string): void {
|
||||
|
@ -632,7 +626,6 @@ namespace ts.server {
|
|||
this.compilerOptionsForInferredProjects = compilerOptions;
|
||||
}
|
||||
|
||||
const projectsToUpdate: Project[] = [];
|
||||
for (const project of this.inferredProjects) {
|
||||
// Only update compiler options in the following cases:
|
||||
// - Inferred projects without a projectRootPath, if the new options do not apply to
|
||||
|
@ -648,11 +641,11 @@ namespace ts.server {
|
|||
project.setCompilerOptions(compilerOptions);
|
||||
project.compileOnSaveEnabled = compilerOptions.compileOnSave;
|
||||
project.markAsDirty();
|
||||
projectsToUpdate.push(project);
|
||||
this.delayUpdateProjectGraph(project);
|
||||
}
|
||||
}
|
||||
|
||||
this.delayUpdateProjectGraphs(projectsToUpdate);
|
||||
this.delayEnsureProjectForOpenFiles();
|
||||
}
|
||||
|
||||
findProject(projectName: string): Project | undefined {
|
||||
|
@ -687,41 +680,27 @@ namespace ts.server {
|
|||
/**
|
||||
* Ensures the project structures are upto date
|
||||
* This means,
|
||||
* - if there are changedFiles (the files were updated but their containing project graph was not upto date),
|
||||
* their project graph is updated
|
||||
* - If there are pendingProjectUpdates (scheduled to be updated with delay so they can batch update the graph if there are several changes in short time span)
|
||||
* their project graph is updated
|
||||
* - If there were project graph updates and/or there was pending inferred project update and/or called forced the inferred project structure refresh
|
||||
* Inferred projects are created/updated/deleted based on open files states
|
||||
* @param forceInferredProjectsRefresh when true updates the inferred projects even if there is no pending work to update the files/project structures
|
||||
* - we go through all the projects and update them if they are dirty
|
||||
* - if updates reflect some change in structure or there was pending request to ensure projects for open files
|
||||
* ensure that each open script info has project
|
||||
*/
|
||||
private ensureProjectStructuresUptoDate(forceInferredProjectsRefresh?: boolean) {
|
||||
if (this.changedFiles) {
|
||||
let projectsToUpdate: Project[];
|
||||
if (this.changedFiles.length === 1) {
|
||||
// simpliest case - no allocations
|
||||
projectsToUpdate = this.changedFiles[0].containingProjects;
|
||||
}
|
||||
else {
|
||||
projectsToUpdate = [];
|
||||
for (const f of this.changedFiles) {
|
||||
addRange(projectsToUpdate, f.containingProjects);
|
||||
}
|
||||
}
|
||||
this.changedFiles = undefined;
|
||||
this.updateProjectGraphs(projectsToUpdate);
|
||||
}
|
||||
private ensureProjectStructuresUptoDate() {
|
||||
let hasChanges = this.pendingEnsureProjectForOpenFiles;
|
||||
this.pendingProjectUpdates.clear();
|
||||
const updateGraph = (project: Project) => {
|
||||
hasChanges = this.updateProjectIfDirty(project) || hasChanges;
|
||||
};
|
||||
|
||||
if (this.pendingProjectUpdates.size !== 0) {
|
||||
const projectsToUpdate = arrayFrom(this.pendingProjectUpdates.values());
|
||||
this.pendingProjectUpdates.clear();
|
||||
this.updateProjectGraphs(projectsToUpdate);
|
||||
this.externalProjects.forEach(updateGraph);
|
||||
this.configuredProjects.forEach(updateGraph);
|
||||
this.inferredProjects.forEach(updateGraph);
|
||||
if (hasChanges) {
|
||||
this.ensureProjectForOpenFiles();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.pendingInferredProjectUpdate || forceInferredProjectsRefresh) {
|
||||
this.pendingInferredProjectUpdate = false;
|
||||
this.refreshInferredProjects();
|
||||
}
|
||||
private updateProjectIfDirty(project: Project) {
|
||||
return project.dirty && project.updateGraph();
|
||||
}
|
||||
|
||||
getFormatCodeOptions(file?: NormalizedPath) {
|
||||
|
@ -735,14 +714,6 @@ namespace ts.server {
|
|||
return formatCodeSettings || this.hostConfiguration.formatCodeOptions;
|
||||
}
|
||||
|
||||
private updateProjectGraphs(projects: Project[]) {
|
||||
for (const p of projects) {
|
||||
if (!p.updateGraph()) {
|
||||
this.pendingInferredProjectUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onSourceFileChanged(fileName: NormalizedPath, eventKind: FileWatcherEventKind) {
|
||||
const info = this.getScriptInfoForNormalizedPath(fileName);
|
||||
if (!info) {
|
||||
|
@ -770,8 +741,6 @@ namespace ts.server {
|
|||
private handleDeletedFile(info: ScriptInfo) {
|
||||
this.stopWatchingScriptInfo(info);
|
||||
|
||||
// TODO: handle isOpen = true case
|
||||
|
||||
if (!info.isScriptOpen()) {
|
||||
this.deleteScriptInfo(info);
|
||||
|
||||
|
@ -808,7 +777,7 @@ namespace ts.server {
|
|||
// Reload is pending, do the reload
|
||||
if (project.pendingReload !== ConfigFileProgramReloadLevel.Full) {
|
||||
project.pendingReload = ConfigFileProgramReloadLevel.Partial;
|
||||
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
|
||||
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
|
||||
}
|
||||
},
|
||||
flags,
|
||||
|
@ -1317,7 +1286,11 @@ namespace ts.server {
|
|||
|
||||
this.logger.info("Open files: ");
|
||||
this.openFiles.forEach((projectRootPath, path) => {
|
||||
this.logger.info(`\tFileName: ${this.getScriptInfoForPath(path as Path).fileName} ProjectRootPath: ${projectRootPath}`);
|
||||
const info = this.getScriptInfoForPath(path as Path);
|
||||
this.logger.info(`\tFileName: ${info.fileName} ProjectRootPath: ${projectRootPath}`);
|
||||
if (writeProjectFileNames) {
|
||||
this.logger.info(`\t\tProjects: ${info.containingProjects.map(p => p.getProjectName())}`);
|
||||
}
|
||||
});
|
||||
|
||||
this.logger.endGroup();
|
||||
|
@ -1896,7 +1869,7 @@ namespace ts.server {
|
|||
|
||||
// Reload Projects
|
||||
this.reloadConfiguredProjectForFiles(this.openFiles, /*delayReload*/ false, returnTrue);
|
||||
this.refreshInferredProjects();
|
||||
this.ensureProjectForOpenFiles();
|
||||
}
|
||||
|
||||
private delayReloadConfiguredProjectForFiles(configFileExistenceInfo: ConfigFileExistenceInfo, ignoreIfNotRootOfInferredProject: boolean) {
|
||||
|
@ -1908,7 +1881,7 @@ namespace ts.server {
|
|||
isRootOfInferredProject => isRootOfInferredProject : // Reload open files if they are root of inferred project
|
||||
returnTrue // Reload all the open files impacted by config file
|
||||
);
|
||||
this.delayInferredProjectsRefresh();
|
||||
this.delayEnsureProjectForOpenFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1992,8 +1965,8 @@ namespace ts.server {
|
|||
* This will go through open files and assign them to inferred project if open file is not part of any other project
|
||||
* After that all the inferred project graphs are updated
|
||||
*/
|
||||
private refreshInferredProjects() {
|
||||
this.logger.info("refreshInferredProjects: updating project structure from ...");
|
||||
private ensureProjectForOpenFiles() {
|
||||
this.logger.info("Structure before ensureProjectForOpenFiles:");
|
||||
this.printProjects();
|
||||
|
||||
this.openFiles.forEach((projectRootPath, path) => {
|
||||
|
@ -2007,12 +1980,10 @@ namespace ts.server {
|
|||
this.removeRootOfInferredProjectIfNowPartOfOtherProject(info);
|
||||
}
|
||||
});
|
||||
this.pendingEnsureProjectForOpenFiles = false;
|
||||
this.inferredProjects.forEach(p => this.updateProjectIfDirty(p));
|
||||
|
||||
for (const p of this.inferredProjects) {
|
||||
p.updateGraph();
|
||||
}
|
||||
|
||||
this.logger.info("refreshInferredProjects: updated project structure ...");
|
||||
this.logger.info("Structure after ensureProjectForOpenFiles:");
|
||||
this.printProjects();
|
||||
}
|
||||
|
||||
|
@ -2038,7 +2009,6 @@ namespace ts.server {
|
|||
|
||||
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult {
|
||||
let configFileName: NormalizedPath;
|
||||
let sendConfigFileDiagEvent = false;
|
||||
let configFileErrors: ReadonlyArray<Diagnostic>;
|
||||
|
||||
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent);
|
||||
|
@ -2049,8 +2019,15 @@ namespace ts.server {
|
|||
project = this.findConfiguredProjectByProjectName(configFileName);
|
||||
if (!project) {
|
||||
project = this.createConfiguredProject(configFileName);
|
||||
// Send the event only if the project got created as part of this open request
|
||||
sendConfigFileDiagEvent = true;
|
||||
// Send the event only if the project got created as part of this open request and info is part of the project
|
||||
if (info.isOrphan()) {
|
||||
// Since the file isnt part of configured project, do not send config file info
|
||||
configFileName = undefined;
|
||||
}
|
||||
else {
|
||||
configFileErrors = project.getAllProjectErrors();
|
||||
this.sendConfigFileDiagEvent(project as ConfiguredProject, fileName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Ensure project is ready to check if it contains opened script info
|
||||
|
@ -2058,30 +2035,20 @@ namespace ts.server {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (project && !project.languageServiceEnabled) {
|
||||
// if project language service is disabled then we create a program only for open files.
|
||||
// this means that project should be marked as dirty to force rebuilding of the program
|
||||
// on the next request
|
||||
project.markAsDirty();
|
||||
}
|
||||
|
||||
// Project we have at this point is going to be updated since its either found through
|
||||
// - external project search, which updates the project before checking if info is present in it
|
||||
// - configured project - either created or updated to ensure we know correct status of info
|
||||
|
||||
// At this point if file is part of any any configured or external project, then it would be present in the containing projects
|
||||
// So if it still doesnt have any containing projects, it needs to be part of inferred project
|
||||
if (info.isOrphan()) {
|
||||
// Since the file isnt part of configured project, do not send config file event
|
||||
configFileName = undefined;
|
||||
sendConfigFileDiagEvent = false;
|
||||
|
||||
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
|
||||
}
|
||||
|
||||
Debug.assert(!info.isOrphan());
|
||||
this.openFiles.set(info.path, projectRootPath);
|
||||
|
||||
if (sendConfigFileDiagEvent) {
|
||||
configFileErrors = project.getAllProjectErrors();
|
||||
this.sendConfigFileDiagEvent(project as ConfiguredProject, fileName);
|
||||
}
|
||||
|
||||
// Remove the configured projects that have zero references from open files.
|
||||
// This was postponed from closeOpenFile to after opening next file,
|
||||
// so that we can reuse the project if we need to right away
|
||||
|
@ -2153,11 +2120,6 @@ namespace ts.server {
|
|||
this.closeClientFile(file);
|
||||
}
|
||||
}
|
||||
// if files were open or closed then explicitly refresh list of inferred projects
|
||||
// otherwise if there were only changes in files - record changed files in `changedFiles` and defer the update
|
||||
if (openFiles || closedFiles) {
|
||||
this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
@ -2167,49 +2129,33 @@ namespace ts.server {
|
|||
const change = changes[i];
|
||||
scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText);
|
||||
}
|
||||
if (!this.changedFiles) {
|
||||
this.changedFiles = [scriptInfo];
|
||||
}
|
||||
else if (!contains(this.changedFiles, scriptInfo)) {
|
||||
this.changedFiles.push(scriptInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private closeConfiguredProjectReferencedFromExternalProject(configFile: NormalizedPath): boolean {
|
||||
private closeConfiguredProjectReferencedFromExternalProject(configFile: NormalizedPath) {
|
||||
const configuredProject = this.findConfiguredProjectByProjectName(configFile);
|
||||
if (configuredProject) {
|
||||
configuredProject.deleteExternalProjectReference();
|
||||
if (!configuredProject.hasOpenRef()) {
|
||||
this.removeProject(configuredProject);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
closeExternalProject(uncheckedFileName: string, suppressRefresh = false): void {
|
||||
closeExternalProject(uncheckedFileName: string): void {
|
||||
const fileName = toNormalizedPath(uncheckedFileName);
|
||||
const configFiles = this.externalProjectToConfiguredProjectMap.get(fileName);
|
||||
if (configFiles) {
|
||||
let shouldRefreshInferredProjects = false;
|
||||
for (const configFile of configFiles) {
|
||||
if (this.closeConfiguredProjectReferencedFromExternalProject(configFile)) {
|
||||
shouldRefreshInferredProjects = true;
|
||||
}
|
||||
this.closeConfiguredProjectReferencedFromExternalProject(configFile);
|
||||
}
|
||||
this.externalProjectToConfiguredProjectMap.delete(fileName);
|
||||
if (shouldRefreshInferredProjects && !suppressRefresh) {
|
||||
this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// close external project
|
||||
const externalProject = this.findExternalProjectByProjectName(uncheckedFileName);
|
||||
if (externalProject) {
|
||||
this.removeProject(externalProject);
|
||||
if (!suppressRefresh) {
|
||||
this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2222,17 +2168,15 @@ namespace ts.server {
|
|||
});
|
||||
|
||||
for (const externalProject of projects) {
|
||||
this.openExternalProject(externalProject, /*suppressRefreshOfInferredProjects*/ true);
|
||||
this.openExternalProject(externalProject);
|
||||
// delete project that is present in input list
|
||||
projectsToClose.delete(externalProject.projectFileName);
|
||||
}
|
||||
|
||||
// close projects that were missing in the input list
|
||||
forEachKey(projectsToClose, externalProjectName => {
|
||||
this.closeExternalProject(externalProjectName, /*suppressRefresh*/ true);
|
||||
this.closeExternalProject(externalProjectName);
|
||||
});
|
||||
|
||||
this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true);
|
||||
}
|
||||
|
||||
/** Makes a filename safe to insert in a RegExp */
|
||||
|
@ -2353,7 +2297,7 @@ namespace ts.server {
|
|||
return excludedFiles;
|
||||
}
|
||||
|
||||
openExternalProject(proj: protocol.ExternalProject, suppressRefreshOfInferredProjects = false): void {
|
||||
openExternalProject(proj: protocol.ExternalProject): void {
|
||||
// typingOptions has been deprecated and is only supported for backward compatibility
|
||||
// purposes. It should be removed in future releases - use typeAcquisition instead.
|
||||
if (proj.typingOptions && !proj.typeAcquisition) {
|
||||
|
@ -2407,13 +2351,13 @@ namespace ts.server {
|
|||
}
|
||||
// some config files were added to external project (that previously were not there)
|
||||
// close existing project and later we'll open a set of configured projects for these files
|
||||
this.closeExternalProject(proj.projectFileName, /*suppressRefresh*/ true);
|
||||
this.closeExternalProject(proj.projectFileName);
|
||||
}
|
||||
else if (this.externalProjectToConfiguredProjectMap.get(proj.projectFileName)) {
|
||||
// this project used to include config files
|
||||
if (!tsConfigFiles) {
|
||||
// config files were removed from the project - close existing external project which in turn will close configured projects
|
||||
this.closeExternalProject(proj.projectFileName, /*suppressRefresh*/ true);
|
||||
this.closeExternalProject(proj.projectFileName);
|
||||
}
|
||||
else {
|
||||
// project previously had some config files - compare them with new set of files and close all configured projects that correspond to unused files
|
||||
|
@ -2463,9 +2407,6 @@ namespace ts.server {
|
|||
this.externalProjectToConfiguredProjectMap.delete(proj.projectFileName);
|
||||
this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition, excludedFiles);
|
||||
}
|
||||
if (!suppressRefreshOfInferredProjects) {
|
||||
this.ensureProjectStructuresUptoDate(/*refreshInferredProjects*/ true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,6 +168,9 @@ namespace ts.server {
|
|||
*/
|
||||
private projectStateVersion = 0;
|
||||
|
||||
/*@internal*/
|
||||
dirty = false;
|
||||
|
||||
/*@internal*/
|
||||
hasChangedAutomaticTypeDirectiveNames = false;
|
||||
|
||||
|
@ -250,6 +253,7 @@ namespace ts.server {
|
|||
this.disableLanguageService(lastFileExceededProgramSize);
|
||||
}
|
||||
this.markAsDirty();
|
||||
this.projectService.pendingEnsureProjectForOpenFiles = true;
|
||||
}
|
||||
|
||||
isKnownTypesPackageName(name: string): boolean {
|
||||
|
@ -399,7 +403,7 @@ namespace ts.server {
|
|||
|
||||
/*@internal*/
|
||||
onInvalidatedResolution() {
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
|
@ -417,7 +421,7 @@ namespace ts.server {
|
|||
/*@internal*/
|
||||
onChangedAutomaticTypeDirectiveNames() {
|
||||
this.hasChangedAutomaticTypeDirectiveNames = true;
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
|
@ -565,6 +569,7 @@ namespace ts.server {
|
|||
for (const root of this.rootFiles) {
|
||||
root.detachFromProject(this);
|
||||
}
|
||||
this.projectService.pendingEnsureProjectForOpenFiles = true;
|
||||
|
||||
this.rootFiles = undefined;
|
||||
this.rootFilesMap = undefined;
|
||||
|
@ -748,7 +753,10 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
markAsDirty() {
|
||||
this.projectStateVersion++;
|
||||
if (!this.dirty) {
|
||||
this.projectStateVersion++;
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
@ -823,7 +831,9 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasChanges);
|
||||
if (this.setTypings(cachedTypings)) {
|
||||
if (!arrayIsEqualTo(this.typingFiles, cachedTypings)) {
|
||||
this.typingFiles = cachedTypings;
|
||||
this.markAsDirty();
|
||||
hasChanges = this.updateGraphWorker() || hasChanges;
|
||||
}
|
||||
}
|
||||
|
@ -847,15 +857,6 @@ namespace ts.server {
|
|||
return include.filter(i => existing.indexOf(i) < 0);
|
||||
}
|
||||
|
||||
private setTypings(typings: SortedReadonlyArray<string>): boolean {
|
||||
if (arrayIsEqualTo(this.typingFiles, typings)) {
|
||||
return false;
|
||||
}
|
||||
this.typingFiles = typings;
|
||||
this.markAsDirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
private updateGraphWorker() {
|
||||
const oldProgram = this.program;
|
||||
Debug.assert(!this.isClosed(), "Called update graph worker of closed project");
|
||||
|
@ -864,6 +865,7 @@ namespace ts.server {
|
|||
this.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution();
|
||||
this.resolutionCache.startCachingPerDirectoryResolution();
|
||||
this.program = this.languageService.getProgram();
|
||||
this.dirty = false;
|
||||
this.resolutionCache.finishCachingPerDirectoryResolution();
|
||||
|
||||
// bump up the version if
|
||||
|
@ -910,7 +912,7 @@ namespace ts.server {
|
|||
compareStringsCaseSensitive
|
||||
);
|
||||
const elapsed = timestamp() - start;
|
||||
this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`);
|
||||
this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`);
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
|
@ -936,7 +938,7 @@ namespace ts.server {
|
|||
fileWatcher.close();
|
||||
|
||||
// When a missing file is created, we should update the graph.
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this);
|
||||
}
|
||||
},
|
||||
WatchType.MissingFilePath,
|
||||
|
|
|
@ -1769,7 +1769,7 @@ namespace ts.server {
|
|||
return this.requiredResponse(response);
|
||||
},
|
||||
[CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => {
|
||||
this.projectService.openExternalProject(request.arguments, /*suppressRefreshOfInferredProjects*/ false);
|
||||
this.projectService.openExternalProject(request.arguments);
|
||||
// TODO: GH#20447 report errors
|
||||
return this.requiredResponse(/*response*/ true);
|
||||
},
|
||||
|
|
|
@ -7544,7 +7544,6 @@ declare namespace ts.server {
|
|||
*/
|
||||
updateGraph(): boolean;
|
||||
protected removeExistingTypings(include: string[]): string[];
|
||||
private setTypings(typings);
|
||||
private updateGraphWorker();
|
||||
private detachScriptInfoFromProject(uncheckedFileName);
|
||||
private addMissingFileWatcher(missingFilePath);
|
||||
|
@ -7780,9 +7779,7 @@ declare namespace ts.server {
|
|||
private readonly hostConfiguration;
|
||||
private safelist;
|
||||
private legacySafelist;
|
||||
private changedFiles;
|
||||
private pendingProjectUpdates;
|
||||
private pendingInferredProjectUpdate;
|
||||
readonly currentDirectory: string;
|
||||
readonly toCanonicalFileName: (f: string) => string;
|
||||
readonly host: ServerHost;
|
||||
|
@ -7804,7 +7801,7 @@ declare namespace ts.server {
|
|||
toPath(fileName: string): Path;
|
||||
private loadTypesMap();
|
||||
updateTypingsForProject(response: SetTypings | InvalidateCachedTypings | PackageInstalledResponse): void;
|
||||
private delayInferredProjectsRefresh();
|
||||
private delayEnsureProjectForOpenFiles();
|
||||
private delayUpdateProjectGraph(project);
|
||||
private sendProjectsUpdatedInBackgroundEvent();
|
||||
private delayUpdateProjectGraphs(projects);
|
||||
|
@ -7815,17 +7812,13 @@ declare namespace ts.server {
|
|||
/**
|
||||
* Ensures the project structures are upto date
|
||||
* This means,
|
||||
* - if there are changedFiles (the files were updated but their containing project graph was not upto date),
|
||||
* their project graph is updated
|
||||
* - If there are pendingProjectUpdates (scheduled to be updated with delay so they can batch update the graph if there are several changes in short time span)
|
||||
* their project graph is updated
|
||||
* - If there were project graph updates and/or there was pending inferred project update and/or called forced the inferred project structure refresh
|
||||
* Inferred projects are created/updated/deleted based on open files states
|
||||
* @param forceInferredProjectsRefresh when true updates the inferred projects even if there is no pending work to update the files/project structures
|
||||
* - we go through all the projects and update them if they are dirty
|
||||
* - if updates reflect some change in structure or there was pending request to ensure projects for open files
|
||||
* ensure that each open script info has project
|
||||
*/
|
||||
private ensureProjectStructuresUptoDate(forceInferredProjectsRefresh?);
|
||||
private ensureProjectStructuresUptoDate();
|
||||
private updateProjectIfDirty(project);
|
||||
getFormatCodeOptions(file?: NormalizedPath): FormatCodeSettings;
|
||||
private updateProjectGraphs(projects);
|
||||
private onSourceFileChanged(fileName, eventKind);
|
||||
private handleDeletedFile(info);
|
||||
private onConfigChangedForConfiguredProject(project, eventKind);
|
||||
|
@ -7938,7 +7931,7 @@ declare namespace ts.server {
|
|||
* This will go through open files and assign them to inferred project if open file is not part of any other project
|
||||
* After that all the inferred project graphs are updated
|
||||
*/
|
||||
private refreshInferredProjects();
|
||||
private ensureProjectForOpenFiles();
|
||||
/**
|
||||
* Open file whose contents is managed by the client
|
||||
* @param filename is absolute pathname
|
||||
|
@ -7954,14 +7947,14 @@ declare namespace ts.server {
|
|||
closeClientFile(uncheckedFileName: string): void;
|
||||
private collectChanges(lastKnownProjectVersions, currentProjects, result);
|
||||
private closeConfiguredProjectReferencedFromExternalProject(configFile);
|
||||
closeExternalProject(uncheckedFileName: string, suppressRefresh?: boolean): void;
|
||||
closeExternalProject(uncheckedFileName: string): void;
|
||||
openExternalProjects(projects: protocol.ExternalProject[]): void;
|
||||
/** Makes a filename safe to insert in a RegExp */
|
||||
private static readonly filenameEscapeRegexp;
|
||||
private static escapeFilenameForRegex(filename);
|
||||
resetSafeList(): void;
|
||||
applySafeList(proj: protocol.ExternalProject): NormalizedPath[];
|
||||
openExternalProject(proj: protocol.ExternalProject, suppressRefreshOfInferredProjects?: boolean): void;
|
||||
openExternalProject(proj: protocol.ExternalProject): void;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue