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 {
formatCodeOptions: FormatCodeSettings;
preferences: UserPreferences;
preferences: protocol.UserPreferences;
hostInfo: string;
extraFileExtensions?: FileExtensionInfo[];
}
@ -802,7 +808,7 @@ namespace ts.server {
return info && info.getFormatCodeSettings() || this.hostConfiguration.formatCodeOptions;
}
getPreferences(file: NormalizedPath): UserPreferences {
getPreferences(file: NormalizedPath): protocol.UserPreferences {
const info = this.getScriptInfoForNormalizedPath(file);
return info && info.getPreferences() || this.hostConfiguration.preferences;
}
@ -811,7 +817,7 @@ namespace ts.server {
return this.hostConfiguration.formatCodeOptions;
}
getHostPreferences(): UserPreferences {
getHostPreferences(): protocol.UserPreferences {
return this.hostConfiguration.preferences;
}
@ -1561,6 +1567,13 @@ namespace ts.server {
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.
*/
@ -1979,7 +1992,19 @@ namespace ts.server {
this.logger.info("Format host information updated");
}
if (args.preferences) {
const { lazyConfiguredProjectsFromExternalProject } = this.hostConfiguration.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) {
this.hostConfiguration.extraFileExtensions = args.extraFileExtensions;
@ -2192,8 +2217,7 @@ namespace ts.server {
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
project = this.createAndLoadConfiguredProject(configFileName);
project.updateGraph();
project = this.createLoadAndUpdateConfiguredProject(configFileName);
// 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
@ -2633,7 +2657,9 @@ namespace ts.server {
let project = this.findConfiguredProjectByProjectName(tsconfigFile);
if (!project) {
// 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)) {
// 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;
}
/*@internal*/
hasExternalProjectRef() {
return !!this.externalProjectRefCount;
}
getEffectiveTypeRoots() {
return getEffectiveTypeRoots(this.getCompilationSettings(), this.directoryStructureHost) || [];
}

View file

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

View file

@ -234,7 +234,7 @@ namespace ts.server {
*/
readonly containingProjects: Project[] = [];
private formatSettings: FormatCodeSettings | undefined;
private preferences: UserPreferences | undefined;
private preferences: protocol.UserPreferences | undefined;
/* @internal */
fileWatcher: FileWatcher | undefined;
@ -333,7 +333,7 @@ namespace ts.server {
}
getFormatCodeSettings(): FormatCodeSettings | undefined { return this.formatSettings; }
getPreferences(): UserPreferences | undefined { return this.preferences; }
getPreferences(): protocol.UserPreferences | undefined { return this.preferences; }
attachToProject(project: Project): boolean {
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 (!this.formatSettings) {
this.formatSettings = getDefaultFormatCodeSettings(this.host);

View file

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

View file

@ -642,7 +642,8 @@ namespace ts.projectSystem {
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", () => {
function verifyConfigFileCasing(lazyConfiguredProjectsFromExternalProject: boolean) {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1"
@ -656,6 +657,7 @@ namespace ts.projectSystem {
const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false });
const service = createProjectService(host);
service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path));
service.openExternalProject(<protocol.ExternalProject>{
projectFileName: "/a/b/project.csproj",
@ -664,8 +666,14 @@ namespace ts.projectSystem {
});
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 });
@ -673,6 +681,15 @@ namespace ts.projectSystem {
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);
});
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
verifyConfigFileCasing(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
});
it("create configured project without file list", () => {
@ -2950,7 +2967,8 @@ namespace ts.projectSystem {
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", () => {
function verifyDeletingConfigFile(lazyConfiguredProjectsFromExternalProject: boolean) {
const site = {
path: "/user/someuser/project/js/site.js",
content: ""
@ -2962,6 +2980,7 @@ namespace ts.projectSystem {
const projectFileName = "/user/someuser/project/WebApplication6.csproj";
const host = createServerHost([libFile, site, configFile]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
const externalProject: protocol.ExternalProject = {
projectFileName,
@ -2976,7 +2995,9 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 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.checkTimeoutQueueLengthAndRun(1);
@ -2989,6 +3010,13 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 });
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)", () => {
@ -3298,7 +3326,8 @@ namespace ts.projectSystem {
});
it("includes deferred files in the project context", () => {
describe("includes deferred files in the project context", () => {
function verifyDeferredContext(lazyConfiguredProjectsFromExternalProject: boolean) {
const file1 = {
path: "/a.deferred",
content: "const a = 1;"
@ -3316,6 +3345,10 @@ namespace ts.projectSystem {
const host = createServerHost([file1, file2, tsconfig]);
const session = createSession(host);
const projectService = session.getProjectService();
session.executeCommandSeq<protocol.ConfigureRequest>({
command: protocol.CommandTypes.Configure,
arguments: { preferences: { lazyConfiguredProjectsFromExternalProject } }
});
// Configure the deferred extension.
const extraFileExtensions = [{ extension: ".deferred", scriptKind: ScriptKind.Deferred, isMixedContent: true }];
@ -3334,13 +3367,23 @@ namespace ts.projectSystem {
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);
});
});
it("Orphan source files are handled correctly on watch trigger", () => {
@ -3943,7 +3986,8 @@ namespace ts.projectSystem {
});
describe("tsserverProjectSystem external projects", () => {
it("correctly handling add/remove tsconfig - 1", () => {
describe("correctly handling add/remove tsconfig - 1", () => {
function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1;"
@ -3958,6 +4002,7 @@ namespace ts.projectSystem {
};
const host = createServerHost([f1, f2]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
// open external project
const projectName = "/a/b/proj1";
@ -3978,8 +4023,10 @@ namespace ts.projectSystem {
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
@ -3992,10 +4039,17 @@ namespace ts.projectSystem {
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]);
}
it("when lazyConfiguredProjectsFromExternalProject not set", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false);
});
it("when lazyConfiguredProjectsFromExternalProject is set", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
});
it("correctly handling add/remove tsconfig - 2", () => {
describe("correctly handling add/remove tsconfig - 2", () => {
function verifyAddRemoveConfig(lazyConfiguredProjectsFromExternalProject: boolean) {
const f1 = {
path: "/a/b/app.ts",
content: "let x = 1;"
@ -4018,6 +4072,7 @@ namespace ts.projectSystem {
};
const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]);
const projectService = createProjectService(host);
projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } });
// open external project
const projectName = "/a/b/proj1";
@ -4037,9 +4092,11 @@ namespace ts.projectSystem {
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]);
@ -4071,15 +4128,25 @@ namespace ts.projectSystem {
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", () => {
verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ true);
});
});
it("correctly handles changes in lib section of config file", () => {
@ -4174,6 +4241,47 @@ namespace ts.projectSystem {
assert.isTrue(project.hasOpenRef()); // f
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", () => {

View file

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