diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 56026bb70d46f..9ff4f4ef9b8b7 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,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) @@ -2606,8 +2613,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 @@ -2617,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/server/project.ts b/src/server/project.ts index 6cbe97db7be64..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(), @@ -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 | 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 | 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; } 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..64b4a4622f1fc 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]); @@ -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]); diff --git a/src/testRunner/unittests/typingsInstaller.ts b/src/testRunner/unittests/typingsInstaller.ts index b9f6565c07997..6a7e76e15dea0 100644 --- a/src/testRunner/unittests/typingsInstaller.ts +++ b/src/testRunner/unittests/typingsInstaller.ts @@ -430,7 +430,6 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(p, [jqueryJs.path]); installer.checkPendingCommands(/*expectedCount*/ 0); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 188ae835b5b27..373b1738608fc 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; @@ -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; @@ -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; @@ -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. @@ -8369,6 +8352,7 @@ declare namespace ts.server { compileOnSaveEnabled: boolean; excludedFiles: ReadonlyArray; private typeAcquisition; + updateGraph(): boolean; getExcludedFiles(): ReadonlyArray; getTypeAcquisition(): TypeAcquisition; setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void; @@ -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;