From a0190e397abd2ca7fbff948f869f6e9573c0b117 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 20 Jul 2018 16:29:04 -0700 Subject: [PATCH 1/3] Delay update graph when opening external project, and delay load configured project referenced from external project when opening it --- src/server/editorServices.ts | 209 +++++++++--------- src/server/project.ts | 51 +++-- src/server/utilities.ts | 11 +- src/testRunner/unittests/telemetry.ts | 2 + .../unittests/tsserverProjectSystem.ts | 59 ++++- src/testRunner/unittests/typingsInstaller.ts | 17 +- .../reference/api/tsserverlibrary.d.ts | 30 +-- 7 files changed, 215 insertions(+), 164 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 56026bb70d46f..9dae626d91ea8 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -344,6 +344,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*/ @@ -1385,47 +1391,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 = { 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(name: string, options: CompilerOptions | undefined, fileNames: T[], propertyReader: FilePropertyReader): string | undefined { if (options && options.disableSizeLimit || !this.host.getFileSize) { @@ -1482,24 +1447,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()), @@ -1520,8 +1489,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 { @@ -1533,30 +1501,19 @@ namespace ts.server { } } - private addFilesToNonInferredProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, typeAcquisition: TypeAcquisition): void { + private addFilesToNonInferredProject(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, 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, @@ -1566,19 +1523,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 = { 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(project: ExternalProject | ConfiguredProject, files: T[], propertyReader: FilePropertyReader) { const projectRootFilesMap = project.getRootFilesMap(); const newRootScriptInfoMap = createMap(); @@ -1637,31 +1657,31 @@ namespace ts.server { project.markAsDirty(); } - private updateNonInferredProject(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean | undefined) { + private updateRootAndOptionsOfNonInferredProject(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader, 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) { @@ -1673,23 +1693,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); } @@ -2021,17 +2028,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); @@ -2119,7 +2123,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); @@ -2169,7 +2173,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 @@ -2559,7 +2564,7 @@ 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); + this.updateRootAndOptionsOfNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave); return; } // some config files were added to external project (that previously were not there) @@ -2606,8 +2611,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 diff --git a/src/server/project.ts b/src/server/project.ts index 6cbe97db7be64..ca8762da24256 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1091,9 +1091,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 +1243,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 +1315,27 @@ namespace ts.server { private projectErrors: Diagnostic[] | undefined; + private projectReferences: ReadonlyArray | undefined; + + /*@internal*/ + projectOptions?: ProjectOptions | true; + /*@internal*/ constructor(configFileName: NormalizedPath, projectService: ProjectService, documentRegistry: DocumentRegistry, - hasExplicitListOfFiles: boolean, - compilerOptions: CompilerOptions, - lastFileExceededProgramSize: string | undefined, - public compileOnSaveEnabled: boolean, - cachedDirectoryStructureHost: CachedDirectoryStructureHost, - private projectReferences: ReadonlyArray | 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(); } /** @@ -1347,15 +1345,20 @@ namespace ts.server { updateGraph(): boolean { 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 +1385,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 +1414,7 @@ namespace ts.server { } } - this.enableGlobalPlugins(); + this.enableGlobalPlugins(options); } /** @@ -1547,6 +1554,12 @@ namespace ts.server { getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName))); } + updateGraph() { + const result = super.updateGraph(); + this.projectService.sendProjectTelemetry(this); + return result; + } + getExcludedFiles() { return this.excludedFiles; } diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 36a013e6104d5..615361cdcd8a2 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -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 | undefined; - /** - * these fields can be present in the project file - */ - files?: string[]; - wildcardDirectories?: Map; - compilerOptions?: CompilerOptions; - typeAcquisition?: TypeAcquisition; - compileOnSave?: boolean; } export function isInferredProjectName(name: string) { diff --git a/src/testRunner/unittests/telemetry.ts b/src/testRunner/unittests/telemetry.ts index 7c3a05d02fc42..1c11a40c2ee79 100644 --- a/src/testRunner/unittests/telemetry.ts +++ b/src/testRunner/unittests/telemetry.ts @@ -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 } }); diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index c89d29ffc5b51..6e7a41eee122b 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -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]); @@ -1773,8 +1776,11 @@ namespace ts.projectSystem { try { projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) }); const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + // Since the file is not yet open, the project wont have program yet + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), emptyArray); assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); + projectService.openClientFile(file1.path); + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); } finally { projectService.resetSafeList(); } @@ -1815,8 +1821,11 @@ namespace ts.projectSystem { try { projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) }); const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + // Since the file is not yet open, the project wont have program yet + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), emptyArray); assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); + projectService.openClientFile(file1.path); + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); } finally { projectService.resetSafeList(); } @@ -1856,6 +1865,9 @@ namespace ts.projectSystem { try { projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); const proj = projectService.externalProjects[0]; + // Since the file is not yet open, the project wont have program yet + assert.deepEqual(proj.getFileNames(), emptyArray); + projectService.openClientFile(file2.path); assert.deepEqual(proj.getFileNames(), [file2.path]); } finally { projectService.resetSafeList(); @@ -1893,7 +1905,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()); @@ -2071,6 +2083,9 @@ namespace ts.projectSystem { projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) }); checkNumberOfProjects(projectService, { externalProjects: 1 }); + // Since the file is not yet open, the project wont have program yet + assert.deepEqual(projectService.externalProjects[0].getFileNames(), emptyArray); + projectService.openClientFile(file1.path); checkProjectActualFiles(projectService.externalProjects[0], [file1.path]); projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) }); @@ -2098,11 +2113,17 @@ namespace ts.projectSystem { projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) }); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + // Since the file is not yet open, the project wont have program yet + checkProjectActualFiles(projectService.externalProjects[0], emptyArray); + projectService.openClientFile(file1.path); checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) }); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + // The update doesnt happen right away until needed, so either open a file or ensure projects uptodate + checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]); }); @@ -2404,6 +2425,10 @@ namespace ts.projectSystem { projectService.openExternalProject({ projectFileName, options: {}, rootFiles: [{ fileName: file1.path, scriptKind: ScriptKind.JS, hasMixedContent: true }] }); checkNumberOfProjects(projectService, { externalProjects: 1 }); + // Since the external project is not updated till needed (eg opening client file/ensuringProjectStructureUptodate) + // watched files will be empty at first + checkWatchedFiles(host, emptyArray); + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkWatchedFiles(host, [libFile.path]); // watching the "missing" lib file const project = projectService.externalProjects[0]; @@ -2988,6 +3013,9 @@ namespace ts.projectSystem { projectService.openExternalProjects([externalProject]); checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); + // Since the external project is not updated till needed (eg opening client file/ensuringProjectStructureUptodate) + checkProjectActualFiles(projectService.externalProjects[0], emptyArray); + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); }); @@ -3334,6 +3362,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 +4006,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 @@ -3986,6 +4019,8 @@ namespace ts.projectSystem { }); projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], emptyArray); // external project created but not updated till actually needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); }); @@ -4023,6 +4058,8 @@ namespace ts.projectSystem { }); projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], emptyArray); // external project created but program is not created till its needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); // add two config file as root files @@ -4032,6 +4069,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]); @@ -4053,6 +4093,8 @@ namespace ts.projectSystem { }); projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], emptyArray); // external project created but program is not created till its needed + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); // open two config files @@ -4063,6 +4105,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]); diff --git a/src/testRunner/unittests/typingsInstaller.ts b/src/testRunner/unittests/typingsInstaller.ts index b9f6565c07997..0b8a62239e293 100644 --- a/src/testRunner/unittests/typingsInstaller.ts +++ b/src/testRunner/unittests/typingsInstaller.ts @@ -330,6 +330,8 @@ namespace ts.projectSystem { typeAcquisition: { enable: true, include: ["jquery"] } }); + assert.isFalse(enqueueIsCalled, "expected enqueueIsCalled to be false since external project isnt updated right away"); + projectService.ensureInferredProjectsUpToDate_TestOnly(); assert.isTrue(enqueueIsCalled, "expected enqueueIsCalled to be true"); installer.installAll(/*expectedCount*/ 1); @@ -386,6 +388,8 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(p, emptyArray); // external project created but not updated + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [file2Jsx.path, file3dts.path]); installer.installAll(/*expectedCount*/ 1); @@ -430,7 +434,8 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - + checkProjectActualFiles(p, emptyArray); // external project created but not updated + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [jqueryJs.path]); installer.checkPendingCommands(/*expectedCount*/ 0); @@ -474,6 +479,8 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(p, emptyArray); // external project created but not updated + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [jqueryJs.path, file2Ts.path]); installer.checkPendingCommands(/*expectedCount*/ 0); @@ -549,6 +556,8 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(p, emptyArray); // external project created but not updated + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [file3dts.path]); installer.installAll(/*expectedCount*/ 1); @@ -631,6 +640,8 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(p, emptyArray); // external project created but not updated + projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [file3.path]); installer.checkPendingCommands(/*expectedCount*/ 1); installer.executePendingCommands(); @@ -715,6 +726,8 @@ namespace ts.projectSystem { typeAcquisition: { include: ["jquery", "cordova"] } }); + checkProjectActualFiles(projectService.externalProjects[0], emptyArray); // external project created but not updated + projectService.ensureInferredProjectsUpToDate_TestOnly(); installer.checkPendingCommands(/*expectedCount*/ 1); assert.equal(installer.pendingRunRequests.length, 0, "expect no throttled requests"); @@ -726,6 +739,8 @@ namespace ts.projectSystem { rootFiles: [toExternalFile(file3.path)], typeAcquisition: { include: ["grunt", "gulp"] } }); + checkProjectActualFiles(projectService.externalProjects[1], emptyArray); // external project created but not updated + projectService.ensureInferredProjectsUpToDate_TestOnly(); assert.equal(installer.pendingRunRequests.length, 1, "expect one throttled request"); const p1 = projectService.externalProjects[0]; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 188ae835b5b27..b5c92c4c94a45 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5748,24 +5748,6 @@ declare namespace ts.server { remove(path: NormalizedPath): void; } function createNormalizedPathMap(): NormalizedPathMap; - interface ProjectOptions { - configHasExtendsProperty: boolean; - /** - * true if config file explicitly listed files - */ - configHasFilesProperty: boolean; - configHasIncludeProperty: boolean; - configHasExcludeProperty: boolean; - projectReferences: ReadonlyArray | undefined; - /** - * these fields can be present in the project file - */ - files?: string[]; - wildcardDirectories?: Map; - compilerOptions?: CompilerOptions; - typeAcquisition?: TypeAcquisition; - compileOnSave?: boolean; - } function isInferredProjectName(name: string): boolean; function makeInferredProjectName(counter: number): string; function createSortedArray(): SortedArray; @@ -8300,7 +8282,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; @@ -8329,14 +8311,13 @@ 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; /** * 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. @@ -8369,6 +8350,7 @@ declare namespace ts.server { compileOnSaveEnabled: boolean; excludedFiles: ReadonlyArray; private typeAcquisition; + updateGraph(): boolean; getExcludedFiles(): ReadonlyArray; getTypeAcquisition(): TypeAcquisition; setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void; @@ -8661,15 +8643,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; From 8c4607d8ebea0ee11b827409da2dc352da37c4d2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 25 Jul 2018 13:12:52 -0700 Subject: [PATCH 2/3] Dont delay external project update from openExternalProject Since external projects are needed to be uptodate when opening file, so in most likely scenarios these will be loaded anyways so there is no saving in postponing this work --- src/server/editorServices.ts | 7 +++- .../unittests/tsserverProjectSystem.ts | 35 ++----------------- src/testRunner/unittests/typingsInstaller.ts | 16 --------- 3 files changed, 8 insertions(+), 50 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 9dae626d91ea8..9ff4f4ef9b8b7 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2564,7 +2564,9 @@ namespace ts.server { externalProject.enableLanguageService(); } // external project already exists and not config files were added - update the project and return; + // 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) @@ -2622,8 +2624,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(); } } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 6e7a41eee122b..4f85cbf00b3a9 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -1776,11 +1776,8 @@ namespace ts.projectSystem { try { projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) }); const proj = projectService.externalProjects[0]; - // Since the file is not yet open, the project wont have program yet - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), emptyArray); - assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); - projectService.openClientFile(file1.path); assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); } finally { projectService.resetSafeList(); } @@ -1821,11 +1818,8 @@ namespace ts.projectSystem { try { projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) }); const proj = projectService.externalProjects[0]; - // Since the file is not yet open, the project wont have program yet - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), emptyArray); - assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); - projectService.openClientFile(file1.path); assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); } finally { projectService.resetSafeList(); } @@ -1865,9 +1859,6 @@ namespace ts.projectSystem { try { projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); const proj = projectService.externalProjects[0]; - // Since the file is not yet open, the project wont have program yet - assert.deepEqual(proj.getFileNames(), emptyArray); - projectService.openClientFile(file2.path); assert.deepEqual(proj.getFileNames(), [file2.path]); } finally { projectService.resetSafeList(); @@ -2083,9 +2074,6 @@ namespace ts.projectSystem { projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) }); checkNumberOfProjects(projectService, { externalProjects: 1 }); - // Since the file is not yet open, the project wont have program yet - assert.deepEqual(projectService.externalProjects[0].getFileNames(), emptyArray); - projectService.openClientFile(file1.path); checkProjectActualFiles(projectService.externalProjects[0], [file1.path]); projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) }); @@ -2113,17 +2101,11 @@ namespace ts.projectSystem { projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) }); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - // Since the file is not yet open, the project wont have program yet - checkProjectActualFiles(projectService.externalProjects[0], emptyArray); - projectService.openClientFile(file1.path); checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) }); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - // The update doesnt happen right away until needed, so either open a file or ensure projects uptodate - checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]); }); @@ -2425,10 +2407,6 @@ namespace ts.projectSystem { projectService.openExternalProject({ projectFileName, options: {}, rootFiles: [{ fileName: file1.path, scriptKind: ScriptKind.JS, hasMixedContent: true }] }); checkNumberOfProjects(projectService, { externalProjects: 1 }); - // Since the external project is not updated till needed (eg opening client file/ensuringProjectStructureUptodate) - // watched files will be empty at first - checkWatchedFiles(host, emptyArray); - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkWatchedFiles(host, [libFile.path]); // watching the "missing" lib file const project = projectService.externalProjects[0]; @@ -3013,9 +2991,6 @@ namespace ts.projectSystem { projectService.openExternalProjects([externalProject]); checkNumberOfProjects(projectService, { configuredProjects: 0, externalProjects: 1, inferredProjects: 0 }); - // Since the external project is not updated till needed (eg opening client file/ensuringProjectStructureUptodate) - checkProjectActualFiles(projectService.externalProjects[0], emptyArray); - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [site.path, libFile.path]); }); @@ -4019,8 +3994,6 @@ namespace ts.projectSystem { }); projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], emptyArray); // external project created but not updated till actually needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [f1.path, f2.path]); }); @@ -4058,8 +4031,6 @@ namespace ts.projectSystem { }); projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], emptyArray); // external project created but program is not created till its needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); // add two config file as root files @@ -4093,8 +4064,6 @@ namespace ts.projectSystem { }); projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], emptyArray); // external project created but program is not created till its needed - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(projectService.externalProjects[0], [f1.path]); // open two config files diff --git a/src/testRunner/unittests/typingsInstaller.ts b/src/testRunner/unittests/typingsInstaller.ts index 0b8a62239e293..6a7e76e15dea0 100644 --- a/src/testRunner/unittests/typingsInstaller.ts +++ b/src/testRunner/unittests/typingsInstaller.ts @@ -330,8 +330,6 @@ namespace ts.projectSystem { typeAcquisition: { enable: true, include: ["jquery"] } }); - assert.isFalse(enqueueIsCalled, "expected enqueueIsCalled to be false since external project isnt updated right away"); - projectService.ensureInferredProjectsUpToDate_TestOnly(); assert.isTrue(enqueueIsCalled, "expected enqueueIsCalled to be true"); installer.installAll(/*expectedCount*/ 1); @@ -388,8 +386,6 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, emptyArray); // external project created but not updated - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [file2Jsx.path, file3dts.path]); installer.installAll(/*expectedCount*/ 1); @@ -434,8 +430,6 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, emptyArray); // external project created but not updated - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [jqueryJs.path]); installer.checkPendingCommands(/*expectedCount*/ 0); @@ -479,8 +473,6 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, emptyArray); // external project created but not updated - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [jqueryJs.path, file2Ts.path]); installer.checkPendingCommands(/*expectedCount*/ 0); @@ -556,8 +548,6 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, emptyArray); // external project created but not updated - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [file3dts.path]); installer.installAll(/*expectedCount*/ 1); @@ -640,8 +630,6 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, emptyArray); // external project created but not updated - projectService.ensureInferredProjectsUpToDate_TestOnly(); checkProjectActualFiles(p, [file3.path]); installer.checkPendingCommands(/*expectedCount*/ 1); installer.executePendingCommands(); @@ -726,8 +714,6 @@ namespace ts.projectSystem { typeAcquisition: { include: ["jquery", "cordova"] } }); - checkProjectActualFiles(projectService.externalProjects[0], emptyArray); // external project created but not updated - projectService.ensureInferredProjectsUpToDate_TestOnly(); installer.checkPendingCommands(/*expectedCount*/ 1); assert.equal(installer.pendingRunRequests.length, 0, "expect no throttled requests"); @@ -739,8 +725,6 @@ namespace ts.projectSystem { rootFiles: [toExternalFile(file3.path)], typeAcquisition: { include: ["grunt", "gulp"] } }); - checkProjectActualFiles(projectService.externalProjects[1], emptyArray); // external project created but not updated - projectService.ensureInferredProjectsUpToDate_TestOnly(); assert.equal(installer.pendingRunRequests.length, 1, "expect one throttled request"); const p1 = projectService.externalProjects[0]; From f67bdd429a3d16313eefaf3bb33d697b33241faf Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 25 Jul 2018 14:01:48 -0700 Subject: [PATCH 3/3] Skip loading configured project just to report project info through synchronizeProjectList for project opened by external project --- src/server/project.ts | 10 +++++++++- src/testRunner/unittests/tsserverProjectSystem.ts | 5 +---- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 ++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index ca8762da24256..ff2c504a54a0a 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -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(), @@ -1320,6 +1325,8 @@ namespace ts.server { /*@internal*/ projectOptions?: ProjectOptions | true; + protected isInitialLoadPending: () => boolean = returnTrue; + /*@internal*/ constructor(configFileName: NormalizedPath, projectService: ProjectService, @@ -1343,6 +1350,7 @@ 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; diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 4f85cbf00b3a9..64b4a4622f1fc 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -2976,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); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b5c92c4c94a45..373b1738608fc 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8198,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; @@ -8318,6 +8319,7 @@ declare namespace ts.server { 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.