Merge pull request #26716 from Microsoft/lazyConfiguredProjectsFromExternalProject

Add option --lazyConfiguredProjectsFromExternalProject to enable lazy load of configured projects referenced by external project
This commit is contained in:
Sheetal Nandi 2018-08-28 16:19:01 -07:00 committed by GitHub
commit 199d496ef1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 384 additions and 243 deletions

View file

@ -218,9 +218,15 @@ namespace ts.server {
} }
} }
/*@internal*/
export function convertUserPreferences(preferences: protocol.UserPreferences): UserPreferences {
const { lazyConfiguredProjectsFromExternalProject, ...userPreferences } = preferences;
return userPreferences;
}
export interface HostConfiguration { export interface HostConfiguration {
formatCodeOptions: FormatCodeSettings; formatCodeOptions: FormatCodeSettings;
preferences: UserPreferences; preferences: protocol.UserPreferences;
hostInfo: string; hostInfo: string;
extraFileExtensions?: FileExtensionInfo[]; extraFileExtensions?: FileExtensionInfo[];
} }
@ -802,7 +808,7 @@ namespace ts.server {
return info && info.getFormatCodeSettings() || this.hostConfiguration.formatCodeOptions; return info && info.getFormatCodeSettings() || this.hostConfiguration.formatCodeOptions;
} }
getPreferences(file: NormalizedPath): UserPreferences { getPreferences(file: NormalizedPath): protocol.UserPreferences {
const info = this.getScriptInfoForNormalizedPath(file); const info = this.getScriptInfoForNormalizedPath(file);
return info && info.getPreferences() || this.hostConfiguration.preferences; return info && info.getPreferences() || this.hostConfiguration.preferences;
} }
@ -811,7 +817,7 @@ namespace ts.server {
return this.hostConfiguration.formatCodeOptions; return this.hostConfiguration.formatCodeOptions;
} }
getHostPreferences(): UserPreferences { getHostPreferences(): protocol.UserPreferences {
return this.hostConfiguration.preferences; return this.hostConfiguration.preferences;
} }
@ -1561,6 +1567,13 @@ namespace ts.server {
return project; return project;
} }
/* @internal */
private createLoadAndUpdateConfiguredProject(configFileName: NormalizedPath) {
const project = this.createAndLoadConfiguredProject(configFileName);
project.updateGraph();
return project;
}
/** /**
* Read the config file of the project, and update the project root file names. * Read the config file of the project, and update the project root file names.
*/ */
@ -1979,7 +1992,19 @@ namespace ts.server {
this.logger.info("Format host information updated"); this.logger.info("Format host information updated");
} }
if (args.preferences) { if (args.preferences) {
const { lazyConfiguredProjectsFromExternalProject } = this.hostConfiguration.preferences;
this.hostConfiguration.preferences = { ...this.hostConfiguration.preferences, ...args.preferences }; this.hostConfiguration.preferences = { ...this.hostConfiguration.preferences, ...args.preferences };
if (lazyConfiguredProjectsFromExternalProject && !this.hostConfiguration.preferences.lazyConfiguredProjectsFromExternalProject) {
// Load configured projects for external projects that are pending reload
this.configuredProjects.forEach(project => {
if (project.hasExternalProjectRef() &&
project.pendingReload === ConfigFileProgramReloadLevel.Full &&
!this.pendingProjectUpdates.has(project.getProjectName())) {
this.loadConfiguredProject(project);
project.updateGraph();
}
});
}
} }
if (args.extraFileExtensions) { if (args.extraFileExtensions) {
this.hostConfiguration.extraFileExtensions = args.extraFileExtensions; this.hostConfiguration.extraFileExtensions = args.extraFileExtensions;
@ -2192,8 +2217,7 @@ namespace ts.server {
if (configFileName) { if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName); project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) { if (!project) {
project = this.createAndLoadConfiguredProject(configFileName); project = this.createLoadAndUpdateConfiguredProject(configFileName);
project.updateGraph();
// Send the event only if the project got created as part of this open request and info is part of the project // 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()) { if (info.isOrphan()) {
// Since the file isnt part of configured project, do not send config file info // Since the file isnt part of configured project, do not send config file info
@ -2633,7 +2657,9 @@ namespace ts.server {
let project = this.findConfiguredProjectByProjectName(tsconfigFile); let project = this.findConfiguredProjectByProjectName(tsconfigFile);
if (!project) { if (!project) {
// errors are stored in the project, do not need to update the graph // errors are stored in the project, do not need to update the graph
project = this.createConfiguredProjectWithDelayLoad(tsconfigFile); project = this.getHostPreferences().lazyConfiguredProjectsFromExternalProject ?
this.createConfiguredProjectWithDelayLoad(tsconfigFile) :
this.createLoadAndUpdateConfiguredProject(tsconfigFile);
} }
if (project && !contains(exisingConfigFiles, tsconfigFile)) { if (project && !contains(exisingConfigFiles, tsconfigFile)) {
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project // keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project

View file

@ -1520,6 +1520,11 @@ namespace ts.server {
) || false; ) || false;
} }
/*@internal*/
hasExternalProjectRef() {
return !!this.externalProjectRefCount;
}
getEffectiveTypeRoots() { getEffectiveTypeRoots() {
return getEffectiveTypeRoots(this.getCompilationSettings(), this.directoryStructureHost) || []; return getEffectiveTypeRoots(this.getCompilationSettings(), this.directoryStructureHost) || [];
} }

View file

@ -2823,6 +2823,7 @@ namespace ts.server.protocol {
readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithInsertText?: boolean;
readonly importModuleSpecifierPreference?: "relative" | "non-relative"; readonly importModuleSpecifierPreference?: "relative" | "non-relative";
readonly allowTextChangesInNewFiles?: boolean; readonly allowTextChangesInNewFiles?: boolean;
readonly lazyConfiguredProjectsFromExternalProject?: boolean;
} }
export interface CompilerOptions { export interface CompilerOptions {

View file

@ -234,7 +234,7 @@ namespace ts.server {
*/ */
readonly containingProjects: Project[] = []; readonly containingProjects: Project[] = [];
private formatSettings: FormatCodeSettings | undefined; private formatSettings: FormatCodeSettings | undefined;
private preferences: UserPreferences | undefined; private preferences: protocol.UserPreferences | undefined;
/* @internal */ /* @internal */
fileWatcher: FileWatcher | undefined; fileWatcher: FileWatcher | undefined;
@ -333,7 +333,7 @@ namespace ts.server {
} }
getFormatCodeSettings(): FormatCodeSettings | undefined { return this.formatSettings; } getFormatCodeSettings(): FormatCodeSettings | undefined { return this.formatSettings; }
getPreferences(): UserPreferences | undefined { return this.preferences; } getPreferences(): protocol.UserPreferences | undefined { return this.preferences; }
attachToProject(project: Project): boolean { attachToProject(project: Project): boolean {
const isNew = !this.isAttached(project); const isNew = !this.isAttached(project);
@ -432,7 +432,7 @@ namespace ts.server {
} }
} }
setOptions(formatSettings: FormatCodeSettings, preferences: UserPreferences | undefined): void { setOptions(formatSettings: FormatCodeSettings, preferences: protocol.UserPreferences | undefined): void {
if (formatSettings) { if (formatSettings) {
if (!this.formatSettings) { if (!this.formatSettings) {
this.formatSettings = getDefaultFormatCodeSettings(this.host); this.formatSettings = getDefaultFormatCodeSettings(this.host);

View file

@ -1423,7 +1423,7 @@ namespace ts.server {
const position = this.getPosition(args, scriptInfo); const position = this.getPosition(args, scriptInfo);
const completions = project.getLanguageService().getCompletionsAtPosition(file, position, { const completions = project.getLanguageService().getCompletionsAtPosition(file, position, {
...this.getPreferences(file), ...convertUserPreferences(this.getPreferences(file)),
triggerCharacter: args.triggerCharacter, triggerCharacter: args.triggerCharacter,
includeExternalModuleExports: args.includeExternalModuleExports, includeExternalModuleExports: args.includeExternalModuleExports,
includeInsertTextCompletions: args.includeInsertTextCompletions includeInsertTextCompletions: args.includeInsertTextCompletions
@ -2352,7 +2352,7 @@ namespace ts.server {
return this.projectService.getFormatCodeOptions(file); return this.projectService.getFormatCodeOptions(file);
} }
private getPreferences(file: NormalizedPath): UserPreferences { private getPreferences(file: NormalizedPath): protocol.UserPreferences {
return this.projectService.getPreferences(file); return this.projectService.getPreferences(file);
} }
@ -2360,7 +2360,7 @@ namespace ts.server {
return this.projectService.getHostFormatCodeOptions(); return this.projectService.getHostFormatCodeOptions();
} }
private getHostPreferences(): UserPreferences { private getHostPreferences(): protocol.UserPreferences {
return this.projectService.getHostPreferences(); return this.projectService.getHostPreferences();
} }
} }

View file

@ -642,37 +642,54 @@ namespace ts.projectSystem {
checkWatchedDirectories(host, [combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true); checkWatchedDirectories(host, [combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true);
}); });
it("can handle tsconfig file name with difference casing", () => { describe("can handle tsconfig file name with difference casing", () => {
const f1 = { function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) {
path: "/a/b/app.ts", const f1 = {
content: "let x = 1" path: "/a/b/app.ts",
}; content: "let x = 1"
const config = { };
path: "/a/b/tsconfig.json", const config = {
content: JSON.stringify({ path: "/a/b/tsconfig.json",
include: [] content: JSON.stringify({
}) include: []
}; })
};
const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false });
const service = createProjectService(host); const service = createProjectService(host);
const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
service.openExternalProject(<protocol.ExternalProject>{ const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path));
projectFileName: "/a/b/project.csproj", service.openExternalProject(<protocol.ExternalProject>{
rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), projectFileName: "/a/b/project.csproj",
options: {} rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]),
options: {}
});
service.checkNumberOfProjects({ configuredProjects: 1 });
const project = service.configuredProjects.get(config.path)!;
if (lazyConfiguredProjectsFromExternalProject) {
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
checkProjectActualFiles(project, emptyArray);
}
else {
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
}
service.openClientFile(f1.path);
service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ false);
}); });
service.checkNumberOfProjects({ configuredProjects: 1 });
const project = service.configuredProjects.get(config.path)!;
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
checkProjectActualFiles(project, emptyArray);
service.openClientFile(f1.path); it("when lazyConfiguredProjectsFromExternalProject is set", () => {
service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
}); });
it("create configured project without file list", () => { it("create configured project without file list", () => {
@ -2950,45 +2967,56 @@ namespace ts.projectSystem {
assert.equal(navbar[0].spans[0].length, f1.content.length); assert.equal(navbar[0].spans[0].length, f1.content.length);
}); });
it("deleting config file opened from the external project works", () => { describe("deleting config file opened from the external project works", () => {
const site = { function verifyDeletingConfigFile(lazyConfiguredProjectsFromExternalProject: boolean) {
path: "/user/someuser/project/js/site.js", const site = {
content: "" path: "/user/someuser/project/js/site.js",
}; content: ""
const configFile = { };
path: "/user/someuser/project/tsconfig.json", const configFile = {
content: "{}" path: "/user/someuser/project/tsconfig.json",
}; content: "{}"
const projectFileName = "/user/someuser/project/WebApplication6.csproj"; };
const host = createServerHost([libFile, site, configFile]); const projectFileName = "/user/someuser/project/WebApplication6.csproj";
const projectService = createProjectService(host); const host = createServerHost([libFile, site, configFile]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
const externalProject: protocol.ExternalProject = { const externalProject: protocol.ExternalProject = {
projectFileName, projectFileName,
rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)], rootFiles: [toExternalFile(site.path), toExternalFile(configFile.path)],
options: { allowJs: false }, options: { allowJs: false },
typeAcquisition: { include: [] } typeAcquisition: { include: [] }
}; };
projectService.openExternalProjects([externalProject]); projectService.openExternalProjects([externalProject]);
let knownProjects = projectService.synchronizeProjectList([]); let knownProjects = projectService.synchronizeProjectList([]);
checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 }); checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 });
const configProject = configuredProjectAt(projectService, 0); const configProject = configuredProjectAt(projectService, 0);
checkProjectActualFiles(configProject, []); // Since no files opened from this project, its not loaded checkProjectActualFiles(configProject, lazyConfiguredProjectsFromExternalProject ?
emptyArray : // Since no files opened from this project, its not loaded
[libFile.path, configFile.path]);
host.reloadFS([libFile, site]); host.reloadFS([libFile, site]);
host.checkTimeoutQueueLengthAndRun(1); host.checkTimeoutQueueLengthAndRun(1);
knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039 knownProjects = projectService.synchronizeProjectList(map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039
checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 }); checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 0, inferredProjects: 0 });
externalProject.rootFiles.length = 1; externalProject.rootFiles.length = 1;
projectService.openExternalProjects([externalProject]); projectService.openExternalProjects([externalProject]);
checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 });
checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false);
});
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
}); });
it("Getting errors from closed script info does not throw exception (because of getting project from orphan script info)", () => { it("Getting errors from closed script info does not throw exception (because of getting project from orphan script info)", () => {
@ -3298,49 +3326,64 @@ namespace ts.projectSystem {
}); });
it("includes deferred files in the project context", () => { describe("includes deferred files in the project context", () => {
const file1 = { function verifyDeferredContext(lazyConfiguredProjectsFromExternalProject: boolean) {
path: "/a.deferred", const file1 = {
content: "const a = 1;" path: "/a.deferred",
}; content: "const a = 1;"
// Deferred extensions should not affect JS files. };
const file2 = { // Deferred extensions should not affect JS files.
path: "/b.js", const file2 = {
content: "const b = 1;" path: "/b.js",
}; content: "const b = 1;"
const tsconfig = { };
path: "/tsconfig.json", const tsconfig = {
content: "" path: "/tsconfig.json",
}; content: ""
};
const host = createServerHost([file1, file2, tsconfig]); const host = createServerHost([file1, file2, tsconfig]);
const session = createSession(host); const session = createSession(host);
const projectService = session.getProjectService(); const projectService = session.getProjectService();
session.executeCommandSeq<protocol.ConfigureRequest>({
command: protocol.CommandTypes.Configure,
arguments: { preferences: { lazyConfiguredProjectsFromExternalProject } }
});
// Configure the deferred extension. // Configure the deferred extension.
const extraFileExtensions = [{ extension: ".deferred", scriptKind: ScriptKind.Deferred, isMixedContent: true }]; const extraFileExtensions = [{ extension: ".deferred", scriptKind: ScriptKind.Deferred, isMixedContent: true }];
const configureHostRequest = makeSessionRequest<protocol.ConfigureRequestArguments>(CommandNames.Configure, { extraFileExtensions }); const configureHostRequest = makeSessionRequest<protocol.ConfigureRequestArguments>(CommandNames.Configure, { extraFileExtensions });
session.executeCommand(configureHostRequest); session.executeCommand(configureHostRequest);
// Open external project // Open external project
const projectName = "/proj1"; const projectName = "/proj1";
projectService.openExternalProject({ projectService.openExternalProject({
projectFileName: projectName, projectFileName: projectName,
rootFiles: toExternalFiles([file1.path, file2.path, tsconfig.path]), rootFiles: toExternalFiles([file1.path, file2.path, tsconfig.path]),
options: {} options: {}
});
// Assert
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const configuredProject = configuredProjectAt(projectService, 0);
if (lazyConfiguredProjectsFromExternalProject) {
// configured project is just created and not yet loaded
checkProjectActualFiles(configuredProject, emptyArray);
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
checkProjectActualFiles(configuredProject, [file1.path, tsconfig.path]);
// Allow allowNonTsExtensions will be set to true for deferred extensions.
assert.isTrue(configuredProject.getCompilerOptions().allowNonTsExtensions);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyDeferredContext(/*lazyConfiguredProjectsFromExternalProject*/ false);
});
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
verifyDeferredContext(/*lazyConfiguredProjectsFromExternalProject*/ true);
}); });
// Assert
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const configuredProject = configuredProjectAt(projectService, 0);
// configured project is just created and not yet loaded
checkProjectActualFiles(configuredProject, emptyArray);
projectService.ensureInferredProjectsUpToDate_TestOnly();
checkProjectActualFiles(configuredProject, [file1.path, tsconfig.path]);
// Allow allowNonTsExtensions will be set to true for deferred extensions.
assert.isTrue(configuredProject.getCompilerOptions().allowNonTsExtensions);
}); });
it("Orphan source files are handled correctly on watch trigger", () => { it("Orphan source files are handled correctly on watch trigger", () => {
@ -3943,143 +3986,167 @@ namespace ts.projectSystem {
}); });
describe("tsserverProjectSystem external projects", () => { describe("tsserverProjectSystem external projects", () => {
it("correctly handling add/remove tsconfig - 1", () => { describe("correctly handling add/remove tsconfig - 1", () => {
const f1 = { function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) {
path: "/a/b/app.ts", const f1 = {
content: "let x = 1;" path: "/a/b/app.ts",
}; content: "let x = 1;"
const f2 = { };
path: "/a/b/lib.ts", const f2 = {
content: "" path: "/a/b/lib.ts",
}; content: ""
const tsconfig = { };
path: "/a/b/tsconfig.json", const tsconfig = {
content: "" path: "/a/b/tsconfig.json",
}; content: ""
const host = createServerHost([f1, f2]); };
const projectService = createProjectService(host); const host = createServerHost([f1, f2]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
// open external project // open external project
const projectName = "/a/b/proj1"; const projectName = "/a/b/proj1";
projectService.openExternalProject({ projectService.openExternalProject({
projectFileName: projectName, projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]), rootFiles: toExternalFiles([f1.path, f2.path]),
options: {} options: {}
});
projectService.openClientFile(f1.path);
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
// rename lib.ts to tsconfig.json
host.reloadFS([f1, tsconfig]);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, tsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
if (lazyConfiguredProjectsFromExternalProject) {
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]);
// rename tsconfig.json back to lib.ts
host.reloadFS([f1, f2]);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false);
}); });
projectService.openClientFile(f1.path); it("when lazyConfiguredProjectsFromExternalProject is set", () => {
projectService.checkNumberOfProjects({ externalProjects: 1 }); verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true);
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
// rename lib.ts to tsconfig.json
host.reloadFS([f1, tsconfig]);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, tsconfig.path]),
options: {}
}); });
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]);
// rename tsconfig.json back to lib.ts
host.reloadFS([f1, f2]);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
}); });
describe("correctly handling add/remove tsconfig - 2", () => {
function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1;"
};
const cLib = {
path: "/a/b/c/lib.ts",
content: ""
};
const cTsconfig = {
path: "/a/b/c/tsconfig.json",
content: "{}"
};
const dLib = {
path: "/a/b/d/lib.ts",
content: ""
};
const dTsconfig = {
path: "/a/b/d/tsconfig.json",
content: "{}"
};
const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
it("correctly handling add/remove tsconfig - 2", () => { // open external project
const f1 = { const projectName = "/a/b/proj1";
path: "/a/b/app.ts", projectService.openExternalProject({
content: "let x = 1;" projectFileName: projectName,
}; rootFiles: toExternalFiles([f1.path]),
const cLib = { options: {}
path: "/a/b/c/lib.ts", });
content: ""
};
const cTsconfig = {
path: "/a/b/c/tsconfig.json",
content: "{}"
};
const dLib = {
path: "/a/b/d/lib.ts",
content: ""
};
const dTsconfig = {
path: "/a/b/d/tsconfig.json",
content: "{}"
};
const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]);
const projectService = createProjectService(host);
// open external project projectService.checkNumberOfProjects({ externalProjects: 1 });
const projectName = "/a/b/proj1"; checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
projectService.openExternalProject({
projectFileName: projectName, // add two config file as root files
rootFiles: toExternalFiles([f1.path]), projectService.openExternalProject({
options: {} projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
if (lazyConfiguredProjectsFromExternalProject) {
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// remove one config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]);
// remove second config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// open two config files
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
if (lazyConfiguredProjectsFromExternalProject) {
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
}
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// close all projects - no projects should be opened
projectService.closeExternalProject(projectName);
projectService.checkNumberOfProjects({});
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false);
}); });
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
projectService.checkNumberOfProjects({ externalProjects: 1 }); verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true);
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
}); });
projectService.checkNumberOfProjects({ configuredProjects: 2 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// remove one config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), [dLib.path, dTsconfig.path]);
// remove second config file
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path]),
options: {}
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path]);
// open two config files
// add two config file as root files
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]),
options: {}
});
projectService.checkNumberOfProjects({ configuredProjects: 2 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), emptyArray); // Configured project created but not loaded till actually needed
checkProjectActualFiles(configuredProjectAt(projectService, 1), emptyArray); // Configured project created but not loaded till actually needed
projectService.ensureInferredProjectsUpToDate_TestOnly();
checkProjectActualFiles(configuredProjectAt(projectService, 0), [cLib.path, cTsconfig.path]);
checkProjectActualFiles(configuredProjectAt(projectService, 1), [dLib.path, dTsconfig.path]);
// close all projects - no projects should be opened
projectService.closeExternalProject(projectName);
projectService.checkNumberOfProjects({});
}); });
it("correctly handles changes in lib section of config file", () => { it("correctly handles changes in lib section of config file", () => {
@ -4174,6 +4241,47 @@ namespace ts.projectSystem {
assert.isTrue(project.hasOpenRef()); // f assert.isTrue(project.hasOpenRef()); // f
assert.isFalse(project.isClosed()); assert.isFalse(project.isClosed());
}); });
it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1"
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({})
};
const projectFileName = "/a/b/project.csproj";
const host = createServerHost([f1, config]);
const service = createProjectService(host);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } });
service.openExternalProject(<protocol.ExternalProject>{
projectFileName,
rootFiles: toExternalFiles([f1.path, config.path]),
options: {}
});
service.checkNumberOfProjects({ configuredProjects: 1 });
const project = service.configuredProjects.get(config.path)!;
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.Full); // External project referenced configured project pending to be reloaded
checkProjectActualFiles(project, emptyArray);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } });
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded
checkProjectActualFiles(project, [config.path, f1.path]);
service.closeExternalProject(projectFileName);
service.checkNumberOfProjects({});
service.openExternalProject(<protocol.ExternalProject>{
projectFileName,
rootFiles: toExternalFiles([f1.path, config.path]),
options: {}
});
service.checkNumberOfProjects({ configuredProjects: 1 });
const project2 = service.configuredProjects.get(config.path)!;
assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded
checkProjectActualFiles(project2, [config.path, f1.path]);
});
}); });
describe("tsserverProjectSystem prefer typings to js", () => { describe("tsserverProjectSystem prefer typings to js", () => {

View file

@ -7797,6 +7797,7 @@ declare namespace ts.server.protocol {
readonly includeCompletionsWithInsertText?: boolean; readonly includeCompletionsWithInsertText?: boolean;
readonly importModuleSpecifierPreference?: "relative" | "non-relative"; readonly importModuleSpecifierPreference?: "relative" | "non-relative";
readonly allowTextChangesInNewFiles?: boolean; readonly allowTextChangesInNewFiles?: boolean;
readonly lazyConfiguredProjectsFromExternalProject?: boolean;
} }
interface CompilerOptions { interface CompilerOptions {
allowJs?: boolean; allowJs?: boolean;
@ -7928,14 +7929,14 @@ declare namespace ts.server {
getSnapshot(): IScriptSnapshot; getSnapshot(): IScriptSnapshot;
private ensureRealPath; private ensureRealPath;
getFormatCodeSettings(): FormatCodeSettings | undefined; getFormatCodeSettings(): FormatCodeSettings | undefined;
getPreferences(): UserPreferences | undefined; getPreferences(): protocol.UserPreferences | undefined;
attachToProject(project: Project): boolean; attachToProject(project: Project): boolean;
isAttached(project: Project): boolean; isAttached(project: Project): boolean;
detachFromProject(project: Project): void; detachFromProject(project: Project): void;
detachAllProjects(): void; detachAllProjects(): void;
getDefaultProject(): Project; getDefaultProject(): Project;
registerFileUpdate(): void; registerFileUpdate(): void;
setOptions(formatSettings: FormatCodeSettings, preferences: UserPreferences | undefined): void; setOptions(formatSettings: FormatCodeSettings, preferences: protocol.UserPreferences | undefined): void;
getLatestVersion(): string; getLatestVersion(): string;
saveTo(fileName: string): void; saveTo(fileName: string): void;
reloadFromFile(tempFileName?: NormalizedPath): boolean; reloadFromFile(tempFileName?: NormalizedPath): boolean;
@ -8313,7 +8314,7 @@ declare namespace ts.server {
function convertScriptKindName(scriptKindName: protocol.ScriptKindName): ScriptKind.Unknown | ScriptKind.JS | ScriptKind.JSX | ScriptKind.TS | ScriptKind.TSX; function convertScriptKindName(scriptKindName: protocol.ScriptKindName): ScriptKind.Unknown | ScriptKind.JS | ScriptKind.JSX | ScriptKind.TS | ScriptKind.TSX;
interface HostConfiguration { interface HostConfiguration {
formatCodeOptions: FormatCodeSettings; formatCodeOptions: FormatCodeSettings;
preferences: UserPreferences; preferences: protocol.UserPreferences;
hostInfo: string; hostInfo: string;
extraFileExtensions?: FileExtensionInfo[]; extraFileExtensions?: FileExtensionInfo[];
} }
@ -8432,9 +8433,9 @@ declare namespace ts.server {
*/ */
private ensureProjectStructuresUptoDate; private ensureProjectStructuresUptoDate;
getFormatCodeOptions(file: NormalizedPath): FormatCodeSettings; getFormatCodeOptions(file: NormalizedPath): FormatCodeSettings;
getPreferences(file: NormalizedPath): UserPreferences; getPreferences(file: NormalizedPath): protocol.UserPreferences;
getHostFormatCodeOptions(): FormatCodeSettings; getHostFormatCodeOptions(): FormatCodeSettings;
getHostPreferences(): UserPreferences; getHostPreferences(): protocol.UserPreferences;
private onSourceFileChanged; private onSourceFileChanged;
private handleDeletedFile; private handleDeletedFile;
private onConfigChangedForConfiguredProject; private onConfigChangedForConfiguredProject;