Skip to content

Delay load configured project referenced from external project when opening it #25884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 113 additions & 103 deletions src/server/editorServices.ts

Large diffs are not rendered by default.

61 changes: 41 additions & 20 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ namespace ts.server {
*/
private projectStateVersion = 0;

protected isInitialLoadPending: () => boolean = returnFalse;

/*@internal*/
dirty = false;

Expand Down Expand Up @@ -1033,7 +1035,10 @@ namespace ts.server {

/* @internal */
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
updateProjectIfDirty(this);
// Update the graph only if initial configured project load is not pending
if (!this.isInitialLoadPending()) {
updateProjectIfDirty(this);
}

const info: protocol.ProjectVersionInfo = {
projectName: this.getProjectName(),
Expand Down Expand Up @@ -1091,9 +1096,8 @@ namespace ts.server {
this.rootFilesMap.delete(info.path);
}

protected enableGlobalPlugins() {
protected enableGlobalPlugins(options: CompilerOptions) {
const host = this.projectService.host;
const options = this.getCompilationSettings();

if (!host.require) {
this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
Expand Down Expand Up @@ -1244,7 +1248,7 @@ namespace ts.server {
if (!projectRootPath && !projectService.useSingleInferredProject) {
this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory);
}
this.enableGlobalPlugins();
this.enableGlobalPlugins(this.getCompilerOptions());
}

addRoot(info: ScriptInfo) {
Expand Down Expand Up @@ -1316,46 +1320,53 @@ namespace ts.server {

private projectErrors: Diagnostic[] | undefined;

private projectReferences: ReadonlyArray<ProjectReference> | undefined;

/*@internal*/
projectOptions?: ProjectOptions | true;

protected isInitialLoadPending: () => boolean = returnTrue;

/*@internal*/
constructor(configFileName: NormalizedPath,
projectService: ProjectService,
documentRegistry: DocumentRegistry,
hasExplicitListOfFiles: boolean,
compilerOptions: CompilerOptions,
lastFileExceededProgramSize: string | undefined,
public compileOnSaveEnabled: boolean,
cachedDirectoryStructureHost: CachedDirectoryStructureHost,
private projectReferences: ReadonlyArray<ProjectReference> | undefined) {
cachedDirectoryStructureHost: CachedDirectoryStructureHost) {
super(configFileName,
ProjectKind.Configured,
projectService,
documentRegistry,
hasExplicitListOfFiles,
lastFileExceededProgramSize,
compilerOptions,
compileOnSaveEnabled,
/*hasExplicitListOfFiles*/ false,
/*lastFileExceededProgramSize*/ undefined,
/*compilerOptions*/ {},
/*compileOnSaveEnabled*/ false,
cachedDirectoryStructureHost,
getDirectoryPath(configFileName));
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
this.enablePlugins();
}

/**
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
* @returns: true if set of files in the project stays the same and false - otherwise.
*/
updateGraph(): boolean {
this.isInitialLoadPending = returnFalse;
const reloadLevel = this.pendingReload;
this.pendingReload = ConfigFileProgramReloadLevel.None;
let result: boolean;
switch (reloadLevel) {
case ConfigFileProgramReloadLevel.Partial:
return this.projectService.reloadFileNamesOfConfiguredProject(this);
result = this.projectService.reloadFileNamesOfConfiguredProject(this);
break;
case ConfigFileProgramReloadLevel.Full:
this.projectService.reloadConfiguredProject(this);
return true;
result = true;
break;
default:
return super.updateGraph();
result = super.updateGraph();
}
this.projectService.sendProjectTelemetry(this);
return result;
}

/*@internal*/
Expand All @@ -1382,8 +1393,12 @@ namespace ts.server {
}

enablePlugins() {
this.enablePluginsWithOptions(this.getCompilerOptions());
}

/*@internal*/
enablePluginsWithOptions(options: CompilerOptions) {
const host = this.projectService.host;
const options = this.getCompilationSettings();

if (!host.require) {
this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
Expand All @@ -1407,7 +1422,7 @@ namespace ts.server {
}
}

this.enableGlobalPlugins();
this.enableGlobalPlugins(options);
}

/**
Expand Down Expand Up @@ -1547,6 +1562,12 @@ namespace ts.server {
getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName)));
}

updateGraph() {
const result = super.updateGraph();
this.projectService.sendProjectTelemetry(this);
return result;
}

getExcludedFiles() {
return this.excludedFiles;
}
Expand Down
11 changes: 1 addition & 10 deletions src/server/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ namespace ts.server {
};
}

/*@internal*/
export interface ProjectOptions {
configHasExtendsProperty: boolean;
/**
Expand All @@ -128,16 +129,6 @@ namespace ts.server {
configHasFilesProperty: boolean;
configHasIncludeProperty: boolean;
configHasExcludeProperty: boolean;

projectReferences: ReadonlyArray<ProjectReference> | undefined;
/**
* these fields can be present in the project file
*/
files?: string[];
wildcardDirectories?: Map<WatchDirectoryFlags>;
compilerOptions?: CompilerOptions;
typeAcquisition?: TypeAcquisition;
compileOnSave?: boolean;
}

export function isInferredProjectName(name: string) {
Expand Down
2 changes: 2 additions & 0 deletions src/testRunner/unittests/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ namespace ts.projectSystem {
}, "/hunter2/foo.csproj");

// Also test that opening an external project only sends an event once.
et.service.closeClientFile(file1.path);

et.service.closeExternalProject(projectFileName);
checkNumberOfProjects(et.service, { externalProjects: 0 });
Expand All @@ -82,6 +83,7 @@ namespace ts.projectSystem {
projectFileName,
});
checkNumberOfProjects(et.service, { externalProjects: 1 });
et.service.openClientFile(file1.path); // Only on file open the project will be updated
}
});

Expand Down
29 changes: 20 additions & 9 deletions src/testRunner/unittests/tsserverProjectSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,12 +663,15 @@ namespace ts.projectSystem {
options: {}
});
service.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]);
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);
service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 });

checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]);
assert.equal(project.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project is updated
checkProjectActualFiles(project, [upperCaseConfigFilePath]);
checkProjectActualFiles(service.inferredProjects[0], [f1.path]);
});

Expand Down Expand Up @@ -778,7 +781,7 @@ namespace ts.projectSystem {

// Add a tsconfig file
host.reloadFS(filesWithConfig);
host.checkTimeoutQueueLengthAndRun(1);
host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles

projectService.checkNumberOfProjects({ inferredProjects: 2, configuredProjects: 1 });
assert.isTrue(projectService.inferredProjects[0].isOrphan());
Expand Down Expand Up @@ -1229,7 +1232,7 @@ namespace ts.projectSystem {


host.reloadFS([file1, configFile, file2, file3, libFile]);
host.checkTimeoutQueueLengthAndRun(1);
host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles
checkNumberOfConfiguredProjects(projectService, 1);
checkNumberOfInferredProjects(projectService, 1);
checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]);
Expand Down Expand Up @@ -1893,7 +1896,7 @@ namespace ts.projectSystem {
checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]);

host.reloadFS([file1, file2, file3, configFile]);
host.checkTimeoutQueueLengthAndRun(1);
host.checkTimeoutQueueLengthAndRun(2); // load configured project from disk + ensureProjectsForOpenFiles
checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 2 });
checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]);
assert.isTrue(projectService.inferredProjects[0].isOrphan());
Expand Down Expand Up @@ -2973,10 +2976,7 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 1, externalProjects: 0, inferredProjects: 0 });

const configProject = configuredProjectAt(projectService, 0);
checkProjectActualFiles(configProject, [libFile.path, configFile.path]);

const diagnostics = configProject.getAllProjectErrors();
assert.equal(diagnostics[0].code, Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code);
checkProjectActualFiles(configProject, []); // Since no files opened from this project, its not loaded

host.reloadFS([libFile, site]);
host.checkTimeoutQueueLengthAndRun(1);
Expand Down Expand Up @@ -3334,6 +3334,9 @@ namespace ts.projectSystem {
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.
Expand Down Expand Up @@ -3975,6 +3978,8 @@ namespace ts.projectSystem {
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
Expand Down Expand Up @@ -4032,6 +4037,9 @@ namespace ts.projectSystem {
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]);

Expand Down Expand Up @@ -4063,6 +4071,9 @@ namespace ts.projectSystem {
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]);

Expand Down
1 change: 0 additions & 1 deletion src/testRunner/unittests/typingsInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,6 @@ namespace ts.projectSystem {

const p = projectService.externalProjects[0];
projectService.checkNumberOfProjects({ externalProjects: 1 });

checkProjectActualFiles(p, [jqueryJs.path]);

installer.checkPendingCommands(/*expectedCount*/ 0);
Expand Down
32 changes: 7 additions & 25 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5748,24 +5748,6 @@ declare namespace ts.server {
remove(path: NormalizedPath): void;
}
function createNormalizedPathMap<T>(): NormalizedPathMap<T>;
interface ProjectOptions {
configHasExtendsProperty: boolean;
/**
* true if config file explicitly listed files
*/
configHasFilesProperty: boolean;
configHasIncludeProperty: boolean;
configHasExcludeProperty: boolean;
projectReferences: ReadonlyArray<ProjectReference> | undefined;
/**
* these fields can be present in the project file
*/
files?: string[];
wildcardDirectories?: Map<WatchDirectoryFlags>;
compilerOptions?: CompilerOptions;
typeAcquisition?: TypeAcquisition;
compileOnSave?: boolean;
}
function isInferredProjectName(name: string): boolean;
function makeInferredProjectName(counter: number): string;
function createSortedArray<T>(): SortedArray<T>;
Expand Down Expand Up @@ -8216,6 +8198,7 @@ declare namespace ts.server {
* This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project
*/
private projectStateVersion;
protected isInitialLoadPending: () => boolean;
private readonly cancellationToken;
isNonTsProject(): boolean;
isJsOnlyProject(): boolean;
Expand Down Expand Up @@ -8300,7 +8283,7 @@ declare namespace ts.server {
filesToString(writeProjectFileNames: boolean): string;
setCompilerOptions(compilerOptions: CompilerOptions): void;
protected removeRoot(info: ScriptInfo): void;
protected enableGlobalPlugins(): void;
protected enableGlobalPlugins(options: CompilerOptions): void;
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]): void;
/** Starts a new check for diagnostics. Call this if some file has updated that would cause diagnostics to be changed. */
refreshDiagnostics(): void;
Expand Down Expand Up @@ -8329,14 +8312,14 @@ declare namespace ts.server {
* Otherwise it will create an InferredProject.
*/
class ConfiguredProject extends Project {
compileOnSaveEnabled: boolean;
private projectReferences;
private typeAcquisition;
private directoriesWatchedForWildcards;
readonly canonicalConfigFilePath: NormalizedPath;
/** Ref count to the project when opened from external project */
private externalProjectRefCount;
private projectErrors;
private projectReferences;
protected isInitialLoadPending: () => boolean;
/**
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
* @returns: true if set of files in the project stays the same and false - otherwise.
Expand Down Expand Up @@ -8369,6 +8352,7 @@ declare namespace ts.server {
compileOnSaveEnabled: boolean;
excludedFiles: ReadonlyArray<NormalizedPath>;
private typeAcquisition;
updateGraph(): boolean;
getExcludedFiles(): ReadonlyArray<NormalizedPath>;
getTypeAcquisition(): TypeAcquisition;
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void;
Expand Down Expand Up @@ -8661,15 +8645,13 @@ declare namespace ts.server {
private findConfiguredProjectByProjectName;
private getConfiguredProjectByCanonicalConfigFilePath;
private findExternalProjectByProjectName;
private convertConfigFileContentToProjectOptions;
/** Get a filename if the language service exceeds the maximum allowed program size; otherwise returns undefined. */
private getFilenameForExceededTotalSizeLimitForNonTsFiles;
private createExternalProject;
private sendProjectTelemetry;
private addFilesToNonInferredProjectAndUpdateGraph;
private addFilesToNonInferredProject;
private createConfiguredProject;
private updateNonInferredProjectFiles;
private updateNonInferredProject;
private updateRootAndOptionsOfNonInferredProject;
private sendConfigFileDiagEvent;
private getOrCreateInferredProjectForProjectRootPathIfEnabled;
private getOrCreateSingleInferredProjectIfEnabled;
Expand Down