mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-12 03:20:56 -06:00
Retain the configured project opened during opening client file even if opened file isnt included in that project
This helps not create and remove project on every open if tsconfig file isnt referenced by any open file
This commit is contained in:
parent
ee623c1ae6
commit
10ee85c98c
@ -284,6 +284,10 @@ namespace ts.server {
|
||||
configFileErrors?: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
|
||||
interface AssignProjectResult extends OpenConfiguredProjectResult {
|
||||
defaultConfigProject: ConfiguredProject | undefined;
|
||||
}
|
||||
|
||||
interface FilePropertyReader<T> {
|
||||
getFileName(f: T): string;
|
||||
getScriptKind(f: T, extraFileExtensions?: FileExtensionInfo[]): ScriptKind;
|
||||
@ -2635,10 +2639,11 @@ namespace ts.server {
|
||||
return info;
|
||||
}
|
||||
|
||||
private assignProjectToOpenedScriptInfo(info: ScriptInfo): OpenConfiguredProjectResult {
|
||||
private assignProjectToOpenedScriptInfo(info: ScriptInfo): AssignProjectResult {
|
||||
let configFileName: NormalizedPath | undefined;
|
||||
let configFileErrors: ReadonlyArray<Diagnostic> | undefined;
|
||||
let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info);
|
||||
let defaultConfigProject: ConfiguredProject | undefined;
|
||||
if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization
|
||||
configFileName = this.getConfigFileNameForFile(info);
|
||||
if (configFileName) {
|
||||
@ -2659,6 +2664,7 @@ namespace ts.server {
|
||||
// Ensure project is ready to check if it contains opened script info
|
||||
updateProjectIfDirty(project);
|
||||
}
|
||||
defaultConfigProject = project;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2678,13 +2684,13 @@ namespace ts.server {
|
||||
this.assignOrphanScriptInfoToInferredProject(info, this.openFiles.get(info.path));
|
||||
}
|
||||
Debug.assert(!info.isOrphan());
|
||||
return { configFileName, configFileErrors };
|
||||
return { configFileName, configFileErrors, defaultConfigProject };
|
||||
}
|
||||
|
||||
private cleanupAfterOpeningFile() {
|
||||
private cleanupAfterOpeningFile(toRetainConfigProjects: ConfiguredProject[] | ConfiguredProject | undefined) {
|
||||
// This was postponed from closeOpenFile to after opening next file,
|
||||
// so that we can reuse the project if we need to right away
|
||||
this.removeOrphanConfiguredProjects();
|
||||
this.removeOrphanConfiguredProjects(toRetainConfigProjects);
|
||||
|
||||
// Remove orphan inferred projects now that we have reused projects
|
||||
// We need to create a duplicate because we cant guarantee order after removal
|
||||
@ -2705,14 +2711,22 @@ namespace ts.server {
|
||||
|
||||
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult {
|
||||
const info = this.getOrCreateOpenScriptInfo(fileName, fileContent, scriptKind, hasMixedContent, projectRootPath);
|
||||
const result = this.assignProjectToOpenedScriptInfo(info);
|
||||
this.cleanupAfterOpeningFile();
|
||||
const { defaultConfigProject, ...result } = this.assignProjectToOpenedScriptInfo(info);
|
||||
this.cleanupAfterOpeningFile(defaultConfigProject);
|
||||
this.telemetryOnOpenFile(info);
|
||||
return result;
|
||||
}
|
||||
|
||||
private removeOrphanConfiguredProjects() {
|
||||
private removeOrphanConfiguredProjects(toRetainConfiguredProjects: ConfiguredProject[] | ConfiguredProject | undefined) {
|
||||
const toRemoveConfiguredProjects = cloneMap(this.configuredProjects);
|
||||
if (toRetainConfiguredProjects) {
|
||||
if (isArray(toRetainConfiguredProjects)) {
|
||||
toRetainConfiguredProjects.forEach(retainConfiguredProject);
|
||||
}
|
||||
else {
|
||||
retainConfiguredProject(toRetainConfiguredProjects);
|
||||
}
|
||||
}
|
||||
|
||||
// Do not remove configured projects that are used as original projects of other
|
||||
this.inferredProjects.forEach(markOriginalProjectsAsUsed);
|
||||
@ -2720,7 +2734,7 @@ namespace ts.server {
|
||||
this.configuredProjects.forEach(project => {
|
||||
// If project has open ref (there are more than zero references from external project/open file), keep it alive as well as any project it references
|
||||
if (project.hasOpenRef()) {
|
||||
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
|
||||
retainConfiguredProject(project);
|
||||
markOriginalProjectsAsUsed(project);
|
||||
}
|
||||
else {
|
||||
@ -2729,7 +2743,7 @@ namespace ts.server {
|
||||
if (ref) {
|
||||
const refProject = this.configuredProjects.get(ref.sourceFile.path);
|
||||
if (refProject && refProject.hasOpenRef()) {
|
||||
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
|
||||
retainConfiguredProject(project);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -2744,6 +2758,10 @@ namespace ts.server {
|
||||
project.originalConfiguredProjects.forEach((_value, configuredProjectPath) => toRemoveConfiguredProjects.delete(configuredProjectPath));
|
||||
}
|
||||
}
|
||||
|
||||
function retainConfiguredProject(project: ConfiguredProject) {
|
||||
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
private removeOrphanScriptInfos() {
|
||||
@ -2886,8 +2904,9 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
// All the script infos now exist, so ok to go update projects for open files
|
||||
let defaultConfigProjects: ConfiguredProject[] | undefined;
|
||||
if (openScriptInfos) {
|
||||
openScriptInfos.forEach(info => this.assignProjectToOpenedScriptInfo(info));
|
||||
defaultConfigProjects = mapDefined(openScriptInfos, info => this.assignProjectToOpenedScriptInfo(info).defaultConfigProject);
|
||||
}
|
||||
|
||||
// While closing files there could be open files that needed assigning new inferred projects, do it now
|
||||
@ -2896,7 +2915,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
// Cleanup projects
|
||||
this.cleanupAfterOpeningFile();
|
||||
this.cleanupAfterOpeningFile(defaultConfigProjects);
|
||||
|
||||
// Telemetry
|
||||
forEach(openScriptInfos, info => this.telemetryOnOpenFile(info));
|
||||
|
||||
@ -866,12 +866,12 @@ namespace ts.projectSystem {
|
||||
const projectService = createProjectService(host);
|
||||
projectService.openClientFile(file1.path);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
// Since there is no file open from configFile it would be closed
|
||||
checkNumberOfConfiguredProjects(projectService, 0);
|
||||
checkNumberOfInferredProjects(projectService, 1);
|
||||
|
||||
// Since file1 refers to config file as the default project, it needs to be kept alive
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 });
|
||||
const inferredProject = projectService.inferredProjects[0];
|
||||
assert.isTrue(inferredProject.containsFile(<server.NormalizedPath>file1.path));
|
||||
assert.isFalse(projectService.configuredProjects.get(configFile.path)!.containsFile(<server.NormalizedPath>file1.path));
|
||||
});
|
||||
|
||||
it("should be able to handle @types if input file list is empty", () => {
|
||||
@ -898,8 +898,8 @@ namespace ts.projectSystem {
|
||||
const projectService = createProjectService(host);
|
||||
|
||||
projectService.openClientFile(f.path);
|
||||
// Since no file from the configured project is open, it would be closed immediately
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 0, inferredProjects: 1 });
|
||||
// Since f refers to config file as the default project, it needs to be kept alive
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
|
||||
});
|
||||
|
||||
it("should tolerate invalid include files that start in subDirectory", () => {
|
||||
@ -924,8 +924,8 @@ namespace ts.projectSystem {
|
||||
const projectService = createProjectService(host);
|
||||
|
||||
projectService.openClientFile(f.path);
|
||||
// Since no file from the configured project is open, it would be closed immediately
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 0, inferredProjects: 1 });
|
||||
// Since f refers to config file as the default project, it needs to be kept alive
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
|
||||
});
|
||||
|
||||
it("Changed module resolution reflected when specifying files list", () => {
|
||||
|
||||
@ -381,30 +381,54 @@ namespace ts.projectSystem {
|
||||
return originalDelete.call(projectService.configuredProjects, key);
|
||||
};
|
||||
|
||||
// Do not remove config project when opening jsFile that is not present as part of config project
|
||||
projectService.openClientFile(jsFile1.path);
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 });
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [jsFile1.path, libFile.path]);
|
||||
checkConfiguredProjectCreatedAndDeleted();
|
||||
const project = projectService.configuredProjects.get(config.path)!;
|
||||
checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]);
|
||||
checkConfiguredProjectCreatedAndNotDeleted();
|
||||
|
||||
// Do not remove config project when opening jsFile that is not present as part of config project
|
||||
projectService.closeClientFile(jsFile1.path);
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 });
|
||||
projectService.openClientFile(jsFile2.path);
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1, configuredProjects: 1 });
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]);
|
||||
checkConfiguredProjectCreatedAndDeleted();
|
||||
checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]);
|
||||
checkConfiguredProjectNotCreatedAndNotDeleted();
|
||||
|
||||
// Do not remove config project when opening jsFile that is not present as part of config project
|
||||
projectService.openClientFile(jsFile1.path);
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 2, configuredProjects: 1 });
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]);
|
||||
checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]);
|
||||
checkProjectActualFiles(project, [appFile.path, config.path, libFile.path]);
|
||||
checkConfiguredProjectNotCreatedAndNotDeleted();
|
||||
|
||||
// When opening file that doesnt fall back to the config file, we remove the config project
|
||||
projectService.openClientFile(libFile.path);
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 2 });
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [jsFile2.path, libFile.path]);
|
||||
checkProjectActualFiles(projectService.inferredProjects[1], [jsFile1.path, libFile.path]);
|
||||
checkConfiguredProjectCreatedAndDeleted();
|
||||
checkConfiguredProjectNotCreatedButDeleted();
|
||||
|
||||
function checkConfiguredProjectCreatedAndDeleted() {
|
||||
function checkConfiguredProjectCreatedAndNotDeleted() {
|
||||
assert.equal(configuredCreated.size, 1);
|
||||
assert.isTrue(configuredCreated.has(config.path));
|
||||
assert.equal(configuredRemoved.size, 0);
|
||||
configuredCreated.clear();
|
||||
}
|
||||
|
||||
function checkConfiguredProjectNotCreatedAndNotDeleted() {
|
||||
assert.equal(configuredCreated.size, 0);
|
||||
assert.equal(configuredRemoved.size, 0);
|
||||
}
|
||||
|
||||
function checkConfiguredProjectNotCreatedButDeleted() {
|
||||
assert.equal(configuredCreated.size, 0);
|
||||
assert.equal(configuredRemoved.size, 1);
|
||||
assert.isTrue(configuredRemoved.has(config.path));
|
||||
configuredCreated.clear();
|
||||
configuredRemoved.clear();
|
||||
}
|
||||
});
|
||||
|
||||
@ -634,13 +634,17 @@ namespace ts.projectSystem {
|
||||
path: "/a/main.js",
|
||||
content: "var y = 1"
|
||||
};
|
||||
const f3 = {
|
||||
path: "/main.js",
|
||||
content: "var y = 1"
|
||||
};
|
||||
const config = {
|
||||
path: "/a/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
compilerOptions: { allowJs: true }
|
||||
})
|
||||
};
|
||||
const host = createServerHost([f1, f2, config]);
|
||||
const host = createServerHost([f1, f2, f3, config]);
|
||||
const projectService = createProjectService(host);
|
||||
projectService.setHostConfiguration({
|
||||
extraFileExtensions: [
|
||||
@ -652,13 +656,19 @@ namespace ts.projectSystem {
|
||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]);
|
||||
|
||||
// Should close configured project with next file open
|
||||
// Since f2 refers to config file as the default project, it needs to be kept alive
|
||||
projectService.closeClientFile(f1.path);
|
||||
|
||||
projectService.openClientFile(f2.path);
|
||||
projectService.checkNumberOfProjects({ inferredProjects: 1, configuredProjects: 1 });
|
||||
assert.isDefined(projectService.configuredProjects.get(config.path));
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]);
|
||||
|
||||
// Should close configured project with next file open
|
||||
projectService.closeClientFile(f2.path);
|
||||
projectService.openClientFile(f3.path);
|
||||
projectService.checkNumberOfProjects({ inferredProjects: 1 });
|
||||
assert.isUndefined(projectService.configuredProjects.get(config.path));
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]);
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [f3.path]);
|
||||
});
|
||||
|
||||
it("tsconfig script block support", () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user