Merge pull request #25884 from Microsoft/optimizeOpenExternalProject

Delay load configured project referenced from external project when opening it
This commit is contained in:
Sheetal Nandi 2018-08-17 12:26:14 -07:00 committed by GitHub
commit 0f97620b9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 184 additions and 168 deletions

View file

@ -350,6 +350,12 @@ namespace ts.server {
return project.dirty && project.updateGraph();
}
function setProjectOptionsUsed(project: ConfiguredProject | ExternalProject) {
if (project.projectKind === ProjectKind.Configured) {
(project as ConfiguredProject).projectOptions = true;
}
}
export class ProjectService {
/*@internal*/
@ -1404,47 +1410,6 @@ namespace ts.server {
return findProjectByName(projectFileName, this.externalProjects);
}
private convertConfigFileContentToProjectOptions(configFilename: string, cachedDirectoryStructureHost: CachedDirectoryStructureHost) {
configFilename = normalizePath(configFilename);
const configFileContent = this.host.readFile(configFilename)!; // TODO: GH#18217
const result = parseJsonText(configFilename, configFileContent);
if (!result.endOfFileToken) {
result.endOfFileToken = <EndOfFileToken>{ kind: SyntaxKind.EndOfFileToken };
}
const errors = result.parseDiagnostics as Diagnostic[];
const parsedCommandLine = parseJsonSourceFileConfigFileContent(
result,
cachedDirectoryStructureHost,
getDirectoryPath(configFilename),
/*existingOptions*/ {},
configFilename,
/*resolutionStack*/[],
this.hostConfiguration.extraFileExtensions);
if (parsedCommandLine.errors.length) {
errors.push(...parsedCommandLine.errors);
}
Debug.assert(!!parsedCommandLine.fileNames);
const projectOptions: ProjectOptions = {
files: parsedCommandLine.fileNames,
compilerOptions: parsedCommandLine.options,
configHasExtendsProperty: parsedCommandLine.raw.extends !== undefined,
configHasFilesProperty: parsedCommandLine.raw.files !== undefined,
configHasIncludeProperty: parsedCommandLine.raw.include !== undefined,
configHasExcludeProperty: parsedCommandLine.raw.exclude !== undefined,
wildcardDirectories: createMapFromTemplate(parsedCommandLine.wildcardDirectories!), // TODO: GH#18217
typeAcquisition: parsedCommandLine.typeAcquisition,
compileOnSave: parsedCommandLine.compileOnSave,
projectReferences: parsedCommandLine.projectReferences
};
return { projectOptions, configFileErrors: errors, configFileSpecs: parsedCommandLine.configFileSpecs };
}
/** Get a filename if the language service exceeds the maximum allowed program size; otherwise returns undefined. */
private getFilenameForExceededTotalSizeLimitForNonTsFiles<T>(name: string, options: CompilerOptions | undefined, fileNames: T[], propertyReader: FilePropertyReader<T>): string | undefined {
if (options && options.disableSizeLimit || !this.host.getFileSize) {
@ -1501,24 +1466,28 @@ namespace ts.server {
options.compileOnSave === undefined ? true : options.compileOnSave);
project.excludedFiles = excludedFiles;
this.addFilesToNonInferredProjectAndUpdateGraph(project, files, externalFilePropertyReader, typeAcquisition);
this.addFilesToNonInferredProject(project, files, externalFilePropertyReader, typeAcquisition);
this.externalProjects.push(project);
this.sendProjectTelemetry(projectFileName, project);
return project;
}
private sendProjectTelemetry(projectKey: string, project: ExternalProject | ConfiguredProject, projectOptions?: ProjectOptions): void {
if (this.seenProjects.has(projectKey)) {
/*@internal*/
sendProjectTelemetry(project: ExternalProject | ConfiguredProject): void {
if (this.seenProjects.has(project.projectName)) {
setProjectOptionsUsed(project);
return;
}
this.seenProjects.set(projectKey, true);
this.seenProjects.set(project.projectName, true);
if (!this.eventHandler || !this.host.createSHA256Hash) {
setProjectOptionsUsed(project);
return;
}
const projectOptions = project.projectKind === ProjectKind.Configured ? (project as ConfiguredProject).projectOptions as ProjectOptions : undefined;
setProjectOptionsUsed(project);
const data: ProjectInfoTelemetryEventData = {
projectId: this.host.createSHA256Hash(projectKey),
projectId: this.host.createSHA256Hash(project.projectName),
fileStats: countEachFileTypes(project.getScriptInfos()),
compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilationSettings()),
typeAcquisition: convertTypeAcquisition(project.getTypeAcquisition()),
@ -1539,8 +1508,7 @@ namespace ts.server {
return "other";
}
const configFilePath = project instanceof ConfiguredProject ? project.getConfigFilePath() : undefined!; // TODO: GH#18217
return getBaseConfigFileName(configFilePath) || "other";
return getBaseConfigFileName(project.getConfigFilePath()) || "other";
}
function convertTypeAcquisition({ enable, include, exclude }: TypeAcquisition): ProjectInfoTypeAcquisitionData {
@ -1552,30 +1520,19 @@ namespace ts.server {
}
}
private addFilesToNonInferredProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, typeAcquisition: TypeAcquisition): void {
private addFilesToNonInferredProject<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, typeAcquisition: TypeAcquisition): void {
this.updateNonInferredProjectFiles(project, files, propertyReader);
project.setTypeAcquisition(typeAcquisition);
// This doesnt need scheduling since its either creation or reload of the project
project.updateGraph();
}
private createConfiguredProject(configFileName: NormalizedPath) {
const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames)!; // TODO: GH#18217
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedDirectoryStructureHost);
this.logger.info(`Opened configuration file ${configFileName}`);
const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files!, fileNamePropertyReader); // TODO: GH#18217
const project = new ConfiguredProject(
configFileName,
this,
this.documentRegistry,
projectOptions.configHasFilesProperty,
projectOptions.compilerOptions!, // TODO: GH#18217
lastFileExceededProgramSize,
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave,
cachedDirectoryStructureHost,
projectOptions.projectReferences);
project.configFileSpecs = configFileSpecs;
cachedDirectoryStructureHost);
// TODO: We probably should also watch the configFiles that are extended
project.configFileWatcher = this.watchFactory.watchFile(
this.host,
@ -1585,19 +1542,82 @@ namespace ts.server {
WatchType.ConfigFilePath,
project
);
if (!lastFileExceededProgramSize) {
project.watchWildcards(projectOptions.wildcardDirectories!); // TODO: GH#18217
}
project.setProjectErrors(configFileErrors);
const filesToAdd = projectOptions.files!.concat(project.getExternalFiles());
this.addFilesToNonInferredProjectAndUpdateGraph(project, filesToAdd, fileNamePropertyReader, projectOptions.typeAcquisition!); // TODO: GH#18217
this.configuredProjects.set(project.canonicalConfigFilePath, project);
this.setConfigFileExistenceByNewConfiguredProject(project);
this.sendProjectTelemetry(configFileName, project, projectOptions);
return project;
}
/* @internal */
private createConfiguredProjectWithDelayLoad(configFileName: NormalizedPath) {
const project = this.createConfiguredProject(configFileName);
project.pendingReload = ConfigFileProgramReloadLevel.Full;
return project;
}
/* @internal */
private createAndLoadConfiguredProject(configFileName: NormalizedPath) {
const project = this.createConfiguredProject(configFileName);
this.loadConfiguredProject(project);
return project;
}
/**
* Read the config file of the project, and update the project root file names.
*/
/* @internal */
private loadConfiguredProject(project: ConfiguredProject) {
// Read updated contents from disk
const configFilename = normalizePath(project.getConfigFilePath());
const configFileContent = this.host.readFile(configFilename)!; // TODO: GH#18217
const result = parseJsonText(configFilename, configFileContent);
if (!result.endOfFileToken) {
result.endOfFileToken = <EndOfFileToken>{ kind: SyntaxKind.EndOfFileToken };
}
const configFileErrors = result.parseDiagnostics as Diagnostic[];
const parsedCommandLine = parseJsonSourceFileConfigFileContent(
result,
project.getCachedDirectoryStructureHost(),
getDirectoryPath(configFilename),
/*existingOptions*/ {},
configFilename,
/*resolutionStack*/[],
this.hostConfiguration.extraFileExtensions);
if (parsedCommandLine.errors.length) {
configFileErrors.push(...parsedCommandLine.errors);
}
Debug.assert(!!parsedCommandLine.fileNames);
const compilerOptions = parsedCommandLine.options;
// Update the project
if (!project.projectOptions) {
project.projectOptions = {
configHasExtendsProperty: parsedCommandLine.raw.extends !== undefined,
configHasFilesProperty: parsedCommandLine.raw.files !== undefined,
configHasIncludeProperty: parsedCommandLine.raw.include !== undefined,
configHasExcludeProperty: parsedCommandLine.raw.exclude !== undefined
};
}
project.configFileSpecs = parsedCommandLine.configFileSpecs;
project.setProjectErrors(configFileErrors);
project.updateReferences(parsedCommandLine.projectReferences);
const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, compilerOptions, parsedCommandLine.fileNames, fileNamePropertyReader);
if (lastFileExceededProgramSize) {
project.disableLanguageService(lastFileExceededProgramSize);
project.stopWatchingWildCards();
}
else {
project.enableLanguageService();
project.watchWildcards(createMapFromTemplate(parsedCommandLine.wildcardDirectories!)); // TODO: GH#18217
}
project.enablePluginsWithOptions(compilerOptions);
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave!); // TODO: GH#18217
}
private updateNonInferredProjectFiles<T>(project: ExternalProject | ConfiguredProject, files: T[], propertyReader: FilePropertyReader<T>) {
const projectRootFilesMap = project.getRootFilesMap();
const newRootScriptInfoMap = createMap<ProjectRoot>();
@ -1656,31 +1676,31 @@ namespace ts.server {
project.markAsDirty();
}
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean | undefined) {
private updateRootAndOptionsOfNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean | undefined) {
project.setCompilerOptions(newOptions);
// VS only set the CompileOnSaveEnabled option in the request if the option was changed recently
// therefore if it is undefined, it should not be updated.
if (compileOnSave !== undefined) {
project.compileOnSaveEnabled = compileOnSave;
}
this.addFilesToNonInferredProjectAndUpdateGraph(project, newUncheckedFiles, propertyReader, newTypeAcquisition);
this.addFilesToNonInferredProject(project, newUncheckedFiles, propertyReader, newTypeAcquisition);
}
/**
* Reload the file names from config file specs and update the project graph
*/
/*@internal*/
reloadFileNamesOfConfiguredProject(project: ConfiguredProject): boolean {
reloadFileNamesOfConfiguredProject(project: ConfiguredProject) {
const configFileSpecs = project.configFileSpecs!; // TODO: GH#18217
const configFileName = project.getConfigFilePath();
const fileNamesResult = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), project.getCompilationSettings(), project.getCachedDirectoryStructureHost(), this.hostConfiguration.extraFileExtensions);
project.updateErrorOnNoInputFiles(fileNamesResult.fileNames.length !== 0);
this.updateNonInferredProjectFiles(project, fileNamesResult.fileNames, fileNamePropertyReader);
this.updateNonInferredProjectFiles(project, fileNamesResult.fileNames.concat(project.getExternalFiles()), fileNamePropertyReader);
return project.updateGraph();
}
/**
* Read the config file of the project again and update the project
* Read the config file of the project again by clearing the cache and update the project graph
*/
/* @internal */
reloadConfiguredProject(project: ConfiguredProject) {
@ -1692,23 +1712,10 @@ namespace ts.server {
const configFileName = project.getConfigFilePath();
this.logger.info(`Reloading configured project ${configFileName}`);
// Read updated contents from disk
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, host);
// Load project from the disk
this.loadConfiguredProject(project);
project.updateGraph();
// Update the project
project.configFileSpecs = configFileSpecs;
project.setProjectErrors(configFileErrors);
project.updateReferences(projectOptions.projectReferences);
const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files!, fileNamePropertyReader); // TODO: GH#18217
if (lastFileExceededProgramSize) {
project.disableLanguageService(lastFileExceededProgramSize);
project.stopWatchingWildCards();
}
else {
project.enableLanguageService();
project.watchWildcards(projectOptions.wildcardDirectories!); // TODO: GH#18217
}
this.updateNonInferredProject(project, projectOptions.files!, fileNamePropertyReader, projectOptions.compilerOptions!, projectOptions.typeAcquisition!, projectOptions.compileOnSave!); // TODO: GH#18217
this.sendConfigFileDiagEvent(project, configFileName);
}
@ -2040,17 +2047,14 @@ namespace ts.server {
// otherwise we create a new one.
const configFileName = this.getConfigFileNameForFile(info);
if (configFileName) {
const project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
this.createConfiguredProject(configFileName);
updatedProjects.set(configFileName, true);
}
else if (!updatedProjects.has(configFileName)) {
const project = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
if (!updatedProjects.has(configFileName)) {
if (delayReload) {
project.pendingReload = ConfigFileProgramReloadLevel.Full;
this.delayUpdateProjectGraph(project);
}
else {
// reload from the disk
this.reloadConfiguredProject(project);
}
updatedProjects.set(configFileName, true);
@ -2138,7 +2142,7 @@ namespace ts.server {
const configFileName = this.getConfigFileNameForFile(originalFileInfo);
if (!configFileName) return undefined;
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createAndLoadConfiguredProject(configFileName);
updateProjectIfDirty(configuredProject);
// Keep this configured project as referenced from project
addOriginalConfiguredProject(configuredProject);
@ -2188,7 +2192,8 @@ namespace ts.server {
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
project = this.createConfiguredProject(configFileName);
project = this.createAndLoadConfiguredProject(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
if (info.isOrphan()) {
// Since the file isnt part of configured project, do not send config file info
@ -2578,7 +2583,9 @@ namespace ts.server {
externalProject.enableLanguageService();
}
// external project already exists and not config files were added - update the project and return;
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave);
// The graph update here isnt postponed since any file open operation needs all updated external projects
this.updateRootAndOptionsOfNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave);
externalProject.updateGraph();
return;
}
// some config files were added to external project (that previously were not there)
@ -2625,8 +2632,8 @@ namespace ts.server {
for (const tsconfigFile of tsConfigFiles) {
let project = this.findConfiguredProjectByProjectName(tsconfigFile);
if (!project) {
// errors are stored in the project
project = this.createConfiguredProject(tsconfigFile);
// errors are stored in the project, do not need to update the graph
project = this.createConfiguredProjectWithDelayLoad(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
@ -2636,8 +2643,11 @@ namespace ts.server {
}
else {
// no config files - remove the item from the collection
// Create external project and update its graph, do not delay update since
// any file open operation needs all updated external projects
this.externalProjectToConfiguredProjectMap.delete(proj.projectFileName);
this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition, excludedFiles);
const project = this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition, excludedFiles);
project.updateGraph();
}
}

View file

@ -149,6 +149,8 @@ namespace ts.server {
*/
private projectStateVersion = 0;
protected isInitialLoadPending: () => boolean = returnFalse;
/*@internal*/
dirty = false;
@ -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(),
@ -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");
@ -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) {
@ -1316,28 +1320,29 @@ 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();
}
/**
@ -1345,17 +1350,23 @@ namespace ts.server {
* @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*/
@ -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");
@ -1407,7 +1422,7 @@ namespace ts.server {
}
}
this.enableGlobalPlugins();
this.enableGlobalPlugins(options);
}
/**
@ -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;
}

View file

@ -120,6 +120,7 @@ namespace ts.server {
};
}
/*@internal*/
export interface ProjectOptions {
configHasExtendsProperty: boolean;
/**
@ -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) {

View file

@ -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 });
@ -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
}
});

View file

@ -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]);
});
@ -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());
@ -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]);
@ -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());
@ -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);
@ -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.
@ -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
@ -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]);
@ -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]);

View file

@ -430,7 +430,6 @@ namespace ts.projectSystem {
const p = projectService.externalProjects[0];
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(p, [jqueryJs.path]);
installer.checkPendingCommands(/*expectedCount*/ 0);

View file

@ -5751,24 +5751,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>;
@ -8238,6 +8220,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;
@ -8322,7 +8305,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;
@ -8351,14 +8334,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.
@ -8391,6 +8374,7 @@ declare namespace ts.server {
compileOnSaveEnabled: boolean;
excludedFiles: ReadonlyArray<NormalizedPath>;
private typeAcquisition;
updateGraph(): boolean;
getExcludedFiles(): ReadonlyArray<NormalizedPath>;
getTypeAcquisition(): TypeAcquisition;
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void;
@ -8692,15 +8676,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;