diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 4aa77d573863d..cc42d82f48247 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -47,7 +47,7 @@ namespace ts.moduleSpecifiers { host: ModuleSpecifierResolutionHost, oldImportSpecifier: string, ): string | undefined { - const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier)); + const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier), {}); if (res === oldImportSpecifier) return undefined; return res; } @@ -59,9 +59,8 @@ namespace ts.moduleSpecifiers { importingSourceFileName: Path, toFileName: string, host: ModuleSpecifierResolutionHost, - preferences: UserPreferences = {}, ): string { - return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferences(preferences, compilerOptions, importingSourceFile)); + return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferences({}, compilerOptions, importingSourceFile), {}); } export function getNodeModulesPackageName( @@ -69,9 +68,10 @@ namespace ts.moduleSpecifiers { importingSourceFileName: Path, nodeModulesFileName: string, host: ModuleSpecifierResolutionHost, + preferences: UserPreferences, ): string | undefined { const info = getInfo(importingSourceFileName, host); - const modulePaths = getAllModulePaths(importingSourceFileName, nodeModulesFileName, host); + const modulePaths = getAllModulePaths(importingSourceFileName, nodeModulesFileName, host, preferences); return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions, /*packageNameOnly*/ true)); } @@ -81,10 +81,11 @@ namespace ts.moduleSpecifiers { importingSourceFileName: Path, toFileName: string, host: ModuleSpecifierResolutionHost, - preferences: Preferences + preferences: Preferences, + userPreferences: UserPreferences, ): string { const info = getInfo(importingSourceFileName, host); - const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host); + const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences); return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions)) || getLocalModuleSpecifier(toFileName, info, compilerOptions, host, preferences); } @@ -106,9 +107,17 @@ namespace ts.moduleSpecifiers { if (!moduleSourceFile) { return []; } - const modulePaths = getAllModulePaths(importingSourceFile.path, moduleSourceFile.originalFileName, host); - const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile); + const cache = host.getModuleSpecifierCache?.(); + const cached = cache?.get(importingSourceFile.path, moduleSourceFile.path, userPreferences); + let modulePaths; + if (cached) { + if (cached.moduleSpecifiers) return cached.moduleSpecifiers; + modulePaths = cached.modulePaths; + } + + modulePaths ||= getAllModulePathsWorker(importingSourceFile.path, moduleSourceFile.originalFileName, host); + const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile); const existingSpecifier = forEach(modulePaths, modulePath => forEach( host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)), reason => { @@ -120,7 +129,11 @@ namespace ts.moduleSpecifiers { undefined; } )); - if (existingSpecifier) return [existingSpecifier]; + if (existingSpecifier) { + const moduleSpecifiers = [existingSpecifier]; + cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, modulePaths, moduleSpecifiers); + return moduleSpecifiers; + } const importedFileIsInNodeModules = some(modulePaths, p => p.isInNodeModules); @@ -138,6 +151,7 @@ namespace ts.moduleSpecifiers { if (specifier && modulePath.isRedirect) { // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar", // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking. + cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, modulePaths, nodeModulesSpecifiers!); return nodeModulesSpecifiers!; } @@ -161,9 +175,11 @@ namespace ts.moduleSpecifiers { } } - return pathsSpecifiers?.length ? pathsSpecifiers : + const moduleSpecifiers = pathsSpecifiers?.length ? pathsSpecifiers : nodeModulesSpecifiers?.length ? nodeModulesSpecifiers : Debug.checkDefined(relativeSpecifiers); + cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, modulePaths, moduleSpecifiers); + return moduleSpecifiers; } interface Info { @@ -329,13 +345,27 @@ namespace ts.moduleSpecifiers { * Looks for existing imports that use symlinks to this module. * Symlinks will be returned first so they are preferred over the real path. */ - function getAllModulePaths(importingFileName: Path, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] { + function getAllModulePaths( + importingFilePath: Path, + importedFileName: string, + host: ModuleSpecifierResolutionHost, + preferences: UserPreferences, + importedFilePath = toPath(importedFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)) + ) { const cache = host.getModuleSpecifierCache?.(); - const getCanonicalFileName = hostGetCanonicalFileName(host); if (cache) { - const cached = cache.get(importingFileName, toPath(importedFileName, host.getCurrentDirectory(), getCanonicalFileName)); - if (typeof cached === "object") return cached; + const cached = cache.get(importingFilePath, importedFilePath, preferences); + if (cached?.modulePaths) return cached.modulePaths; + } + const modulePaths = getAllModulePathsWorker(importingFilePath, importedFileName, host); + if (cache) { + cache.setModulePaths(importingFilePath, importedFilePath, preferences, modulePaths); } + return modulePaths; + } + + function getAllModulePathsWorker(importingFileName: Path, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly ModulePath[] { + const getCanonicalFileName = hostGetCanonicalFileName(host); const allFileNames = new Map<string, { path: string, isRedirect: boolean, isInNodeModules: boolean }>(); let importedFileFromNodeModules = false; forEachFileNameOfModule( @@ -381,9 +411,6 @@ namespace ts.moduleSpecifiers { sortedPaths.push(...remainingPaths); } - if (cache) { - cache.set(importingFileName, toPath(importedFileName, host.getCurrentDirectory(), getCanonicalFileName), sortedPaths); - } return sortedPaths; } diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 6a9cb08fa7cec..cc58530f46633 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -380,7 +380,6 @@ namespace ts { toPath(outputFilePath, host.getCurrentDirectory(), host.getCanonicalFileName), toPath(declFileName, host.getCurrentDirectory(), host.getCanonicalFileName), host, - /*preferences*/ undefined, ); if (!pathIsRelative(specifier)) { // If some compiler option/symlink/whatever allows access to the file containing the ambient module declaration diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7df907413df06..c367e311a959d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -8154,10 +8154,19 @@ namespace ts { isRedirect: boolean; } + /*@internal*/ + export interface ResolvedModuleSpecifierInfo { + modulePaths: readonly ModulePath[] | undefined; + moduleSpecifiers: readonly string[] | undefined; + isAutoImportable: boolean | undefined; + } + /* @internal */ export interface ModuleSpecifierCache { - get(fromFileName: Path, toFileName: Path): boolean | readonly ModulePath[] | undefined; - set(fromFileName: Path, toFileName: Path, moduleSpecifiers: boolean | readonly ModulePath[]): void; + get(fromFileName: Path, toFileName: Path, preferences: UserPreferences): Readonly<ResolvedModuleSpecifierInfo> | undefined; + set(fromFileName: Path, toFileName: Path, preferences: UserPreferences, modulePaths: readonly ModulePath[], moduleSpecifiers: readonly string[]): void; + setIsAutoImportable(fromFileName: Path, toFileName: Path, preferences: UserPreferences, isAutoImportable: boolean): void; + setModulePaths(fromFileName: Path, toFileName: Path, preferences: UserPreferences, modulePaths: readonly ModulePath[]): void; clear(): void; count(): number; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d4c4277aba377..7378656c00115 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -589,8 +589,11 @@ namespace ts.server { ); } - interface ScriptInfoInNodeModulesWatcher extends FileWatcher { - refCount: number; + interface NodeModulesWatcher extends FileWatcher { + /** How many watchers of this directory were for closed ScriptInfo */ + refreshScriptInfoRefCount: number; + /** List of project names whose module specifier cache should be cleared when package.jsons change */ + affectedModuleSpecifierCacheProjects?: Set<string>; } function getDetailWatchInfo(watchType: WatchType, project: Project | NormalizedPath | undefined) { @@ -676,7 +679,7 @@ namespace ts.server { */ /*@internal*/ readonly filenameToScriptInfo = new Map<string, ScriptInfo>(); - private readonly scriptInfoInNodeModulesWatchers = new Map<string, ScriptInfoInNodeModulesWatcher>(); + private readonly nodeModulesWatchers = new Map<string, NodeModulesWatcher>(); /** * Contains all the deleted script info's version information so that * it does not reset when creating script info again @@ -2626,59 +2629,87 @@ namespace ts.server { } } - private watchClosedScriptInfoInNodeModules(dir: Path): ScriptInfoInNodeModulesWatcher { - // Watch only directory - const existing = this.scriptInfoInNodeModulesWatchers.get(dir); - if (existing) { - existing.refCount++; - return existing; - } - - const watchDir = dir + "/node_modules" as Path; + private createNodeModulesWatcher(dir: Path) { const watcher = this.watchFactory.watchDirectory( - watchDir, + dir, fileOrDirectory => { const fileOrDirectoryPath = removeIgnoredPath(this.toPath(fileOrDirectory)); if (!fileOrDirectoryPath) return; - // Has extension - Debug.assert(result.refCount > 0); - if (watchDir === fileOrDirectoryPath) { - this.refreshScriptInfosInDirectory(watchDir); + // Clear module specifier cache for any projects whose cache was affected by + // dependency package.jsons in this node_modules directory + const basename = getBaseFileName(fileOrDirectoryPath); + if (result.affectedModuleSpecifierCacheProjects?.size && ( + basename === "package.json" || basename === "node_modules" + )) { + result.affectedModuleSpecifierCacheProjects.forEach(projectName => { + this.findProject(projectName)?.getModuleSpecifierCache()?.clear(); + }); } - else { - const info = this.getScriptInfoForPath(fileOrDirectoryPath); - if (info) { - if (isScriptInfoWatchedFromNodeModules(info)) { - this.refreshScriptInfo(info); - } + + // Refresh closed script info after an npm install + if (result.refreshScriptInfoRefCount) { + if (dir === fileOrDirectoryPath) { + this.refreshScriptInfosInDirectory(dir); } - // Folder - else if (!hasExtension(fileOrDirectoryPath)) { - this.refreshScriptInfosInDirectory(fileOrDirectoryPath); + else { + const info = this.getScriptInfoForPath(fileOrDirectoryPath); + if (info) { + if (isScriptInfoWatchedFromNodeModules(info)) { + this.refreshScriptInfo(info); + } + } + // Folder + else if (!hasExtension(fileOrDirectoryPath)) { + this.refreshScriptInfosInDirectory(fileOrDirectoryPath); + } } } }, WatchDirectoryFlags.Recursive, this.hostConfiguration.watchOptions, - WatchType.NodeModulesForClosedScriptInfo + WatchType.NodeModules ); - const result: ScriptInfoInNodeModulesWatcher = { + const result: NodeModulesWatcher = { + refreshScriptInfoRefCount: 0, + affectedModuleSpecifierCacheProjects: undefined, close: () => { - if (result.refCount === 1) { + if (!result.refreshScriptInfoRefCount && !result.affectedModuleSpecifierCacheProjects?.size) { watcher.close(); - this.scriptInfoInNodeModulesWatchers.delete(dir); - } - else { - result.refCount--; + this.nodeModulesWatchers.delete(dir); } }, - refCount: 1 }; - this.scriptInfoInNodeModulesWatchers.set(dir, result); + this.nodeModulesWatchers.set(dir, result); return result; } + /*@internal*/ + watchPackageJsonsInNodeModules(dir: Path, project: Project): FileWatcher { + const watcher = this.nodeModulesWatchers.get(dir) || this.createNodeModulesWatcher(dir); + (watcher.affectedModuleSpecifierCacheProjects ||= new Set()).add(project.getProjectName()); + + return { + close: () => { + watcher.affectedModuleSpecifierCacheProjects?.delete(project.getProjectName()); + watcher.close(); + }, + }; + } + + private watchClosedScriptInfoInNodeModules(dir: Path): FileWatcher { + const watchDir = dir + "/node_modules" as Path; + const watcher = this.nodeModulesWatchers.get(watchDir) || this.createNodeModulesWatcher(watchDir); + watcher.refreshScriptInfoRefCount++; + + return { + close: () => { + watcher.refreshScriptInfoRefCount--; + watcher.close(); + }, + }; + } + private getModifiedTime(info: ScriptInfo) { return (this.host.getModifiedTime!(info.path) || missingFileModifiedTime).getTime(); } @@ -2954,7 +2985,11 @@ namespace ts.server { this.logger.info("Format host information updated"); } if (args.preferences) { - const { lazyConfiguredProjectsFromExternalProject, includePackageJsonAutoImports } = this.hostConfiguration.preferences; + const { + lazyConfiguredProjectsFromExternalProject, + includePackageJsonAutoImports, + } = this.hostConfiguration.preferences; + this.hostConfiguration.preferences = { ...this.hostConfiguration.preferences, ...args.preferences }; if (lazyConfiguredProjectsFromExternalProject && !this.hostConfiguration.preferences.lazyConfiguredProjectsFromExternalProject) { // Load configured projects for external projects that are pending reload diff --git a/src/server/project.ts b/src/server/project.ts index c1fb5cc3ca00d..a812d29eeac92 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -254,7 +254,7 @@ namespace ts.server { /*@internal*/ private changedFilesForExportMapCache: Set<Path> | undefined; /*@internal*/ - private moduleSpecifierCache = createModuleSpecifierCache(); + private moduleSpecifierCache = createModuleSpecifierCache(this); /*@internal*/ private symlinks: SymlinkCache | undefined; /*@internal*/ @@ -790,6 +790,7 @@ namespace ts.server { this.resolutionCache.clear(); this.resolutionCache = undefined!; this.cachedUnresolvedImportsPerFile = undefined!; + this.moduleSpecifierCache = undefined!; this.directoryStructureHost = undefined!; this.projectErrors = undefined; @@ -1394,6 +1395,7 @@ namespace ts.server { this.cachedUnresolvedImportsPerFile.clear(); this.lastCachedUnresolvedImportsList = undefined; this.resolutionCache.clear(); + this.moduleSpecifierCache.clear(); } this.markAsDirty(); } @@ -1735,6 +1737,11 @@ namespace ts.server { this.projectService.openFiles, (_, fileName) => this.projectService.tryGetDefaultProjectForFile(toNormalizedPath(fileName)) === this); } + + /*@internal*/ + watchNodeModulesForPackageJsonChanges(directoryPath: string) { + return this.projectService.watchPackageJsonsInNodeModules(this.toPath(directoryPath), this); + } } function getUnresolvedImports(program: Program, cachedUnresolvedImportsPerFile: ESMap<Path, readonly string[]>): SortedReadonlyArray<string> { diff --git a/src/server/watchType.ts b/src/server/watchType.ts index 8cc5014b0e57b..2b9edafb09870 100644 --- a/src/server/watchType.ts +++ b/src/server/watchType.ts @@ -4,17 +4,18 @@ namespace ts { export interface WatchTypeRegistry { ClosedScriptInfo: "Closed Script info", ConfigFileForInferredRoot: "Config file for the inferred project root", - NodeModulesForClosedScriptInfo: "node_modules for closed script infos in them", + NodeModules: "node_modules for closed script infos and package.jsons affecting module specifier cache", MissingSourceMapFile: "Missing source map file", NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root", MissingGeneratedFile: "Missing generated file", - PackageJsonFile: "package.json file for import suggestions" + PackageJsonFile: "package.json file for import suggestions", + NodeModulesForModuleSpecifierCache: "node_modules for module specifier cache invalidation", } WatchType.ClosedScriptInfo = "Closed Script info"; WatchType.ConfigFileForInferredRoot = "Config file for the inferred project root"; - WatchType.NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them"; + WatchType.NodeModules = "node_modules for closed script infos and package.jsons affecting module specifier cache"; WatchType.MissingSourceMapFile = "Missing source map file"; WatchType.NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root"; WatchType.MissingGeneratedFile = "Missing generated file"; WatchType.PackageJsonFile = "package.json file for import suggestions"; -} \ No newline at end of file +} diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index a1d5692324ee8..a76e70c7b9fb2 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -62,7 +62,7 @@ namespace ts.codefix { const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions)); const checker = program.getTypeChecker(); const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker)); - const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, host, program, useAutoImportProvider); + const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, host, program, preferences, useAutoImportProvider); const preferTypeOnlyImport = !!usageIsTypeOnly && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error; const useRequire = shouldUseRequire(sourceFile, program); const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, preferTypeOnlyImport, useRequire, host, preferences); @@ -202,7 +202,7 @@ namespace ts.codefix { const compilerOptions = program.getCompilerOptions(); const exportInfos = pathIsBareSpecifier(stripQuotes(moduleSymbol.name)) ? [getSymbolExportInfoForSymbol(exportedSymbol, moduleSymbol, program, host)] - : getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, host, program, /*useAutoImportProvider*/ true); + : getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, host, program, preferences, /*useAutoImportProvider*/ true); const useRequire = shouldUseRequire(sourceFile, program); const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && !isSourceFileJS(sourceFile) && isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position)); const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, preferTypeOnlyImport, useRequire, host, preferences)); @@ -211,7 +211,7 @@ namespace ts.codefix { function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, preferTypeOnlyImport: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) { Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol), "Some exportInfo should match the specified moduleSymbol"); - return getBestFix(getImportFixes(exportInfos, symbolName, position, preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences), sourceFile, host); + return getBestFix(getImportFixes(exportInfos, symbolName, position, preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences), sourceFile, host, preferences); } function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction { @@ -239,7 +239,7 @@ namespace ts.codefix { } } - function getAllReExportingModules(importingFile: SourceFile, exportedSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, host: LanguageServiceHost, program: Program, useAutoImportProvider: boolean): readonly SymbolExportInfo[] { + function getAllReExportingModules(importingFile: SourceFile, exportedSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, host: LanguageServiceHost, program: Program, preferences: UserPreferences, useAutoImportProvider: boolean): readonly SymbolExportInfo[] { const result: SymbolExportInfo[] = []; const compilerOptions = program.getCompilerOptions(); const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { @@ -267,7 +267,7 @@ namespace ts.codefix { return result; function isImportable(program: Program, moduleFile: SourceFile | undefined, isFromPackageJson: boolean) { - return !moduleFile || isImportableFile(program, importingFile, moduleFile, /*packageJsonFilter*/ undefined, getModuleSpecifierResolutionHost(isFromPackageJson), host.getModuleSpecifierCache?.()); + return !moduleFile || isImportableFile(program, importingFile, moduleFile, preferences, /*packageJsonFilter*/ undefined, getModuleSpecifierResolutionHost(isFromPackageJson), host.getModuleSpecifierCache?.()); } } @@ -277,7 +277,7 @@ namespace ts.codefix { host: LanguageServiceHost, preferences: UserPreferences ): { exportInfo?: SymbolExportInfo, moduleSpecifier: string } | undefined { - return getBestFix(getNewImportFixes(program, importingFile, /*position*/ undefined, /*preferTypeOnlyImport*/ false, /*useRequire*/ false, exportInfo, host, preferences), importingFile, host); + return getBestFix(getNewImportFixes(program, importingFile, /*position*/ undefined, /*preferTypeOnlyImport*/ false, /*useRequire*/ false, exportInfo, host, preferences), importingFile, host, preferences); } export function getSymbolToExportInfoMap(importingFile: SourceFile, host: LanguageServiceHost, program: Program) { @@ -523,21 +523,21 @@ namespace ts.codefix { const info = errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code ? getFixesInfoForUMDImport(context, symbolToken) : isIdentifier(symbolToken) ? getFixesInfoForNonUMDImport(context, symbolToken, useAutoImportProvider) : undefined; - return info && { ...info, fixes: sortFixes(info.fixes, context.sourceFile, context.host) }; + return info && { ...info, fixes: sortFixes(info.fixes, context.sourceFile, context.host, context.preferences) }; } - function sortFixes(fixes: readonly ImportFix[], sourceFile: SourceFile, host: LanguageServiceHost): readonly ImportFix[] { - const { allowsImportingSpecifier } = createPackageJsonImportFilter(sourceFile, host); + function sortFixes(fixes: readonly ImportFix[], sourceFile: SourceFile, host: LanguageServiceHost, preferences: UserPreferences): readonly ImportFix[] { + const { allowsImportingSpecifier } = createPackageJsonImportFilter(sourceFile, preferences, host); return sort(fixes, (a, b) => compareValues(a.kind, b.kind) || compareModuleSpecifiers(a, b, allowsImportingSpecifier)); } - function getBestFix<T extends ImportFix>(fixes: readonly T[], sourceFile: SourceFile, host: LanguageServiceHost): T | undefined { + function getBestFix<T extends ImportFix>(fixes: readonly T[], sourceFile: SourceFile, host: LanguageServiceHost, preferences: UserPreferences): T | undefined { if (!some(fixes)) return; // These will always be placed first if available, and are better than other kinds if (fixes[0].kind === ImportFixKind.UseNamespace || fixes[0].kind === ImportFixKind.AddToExisting) { return fixes[0]; } - const { allowsImportingSpecifier } = createPackageJsonImportFilter(sourceFile, host); + const { allowsImportingSpecifier } = createPackageJsonImportFilter(sourceFile, preferences, host); return fixes.reduce((best, fix) => compareModuleSpecifiers(fix, best, allowsImportingSpecifier) === Comparison.LessThan ? fix : best ); @@ -621,7 +621,7 @@ namespace ts.codefix { const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && isValidTypeOnlyAliasUseSite(symbolToken); const useRequire = shouldUseRequire(sourceFile, program); - const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, program, useAutoImportProvider, host); + const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, program, useAutoImportProvider, host, preferences); const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) => getImportFixes(exportInfos, symbolName, symbolToken.getStart(sourceFile), preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences))); return { fixes, symbolName }; @@ -646,19 +646,20 @@ namespace ts.codefix { fromFile: SourceFile, program: Program, useAutoImportProvider: boolean, - host: LanguageServiceHost + host: LanguageServiceHost, + preferences: UserPreferences, ): ReadonlyESMap<string, readonly SymbolExportInfo[]> { // For each original symbol, keep all re-exports of that symbol together so we can call `getCodeActionsForImport` on the whole group at once. // Maps symbol id to info for modules providing that symbol (original export + re-exports). const originalSymbolToExportInfos = createMultiMap<SymbolExportInfo>(); - const packageJsonFilter = createPackageJsonImportFilter(fromFile, host); + const packageJsonFilter = createPackageJsonImportFilter(fromFile, preferences, host); const moduleSpecifierCache = host.getModuleSpecifierCache?.(); const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host); }); function addSymbol(moduleSymbol: Symbol, toFile: SourceFile | undefined, exportedSymbol: Symbol, exportKind: ExportKind, program: Program, isFromPackageJson: boolean): void { const moduleSpecifierResolutionHost = getModuleSpecifierResolutionHost(isFromPackageJson); - if (toFile && isImportableFile(program, fromFile, toFile, packageJsonFilter, moduleSpecifierResolutionHost, moduleSpecifierCache) || + if (toFile && isImportableFile(program, fromFile, toFile, preferences, packageJsonFilter, moduleSpecifierResolutionHost, moduleSpecifierCache) || !toFile && packageJsonFilter.allowsImportingAmbientModule(moduleSymbol, moduleSpecifierResolutionHost) ) { const checker = program.getTypeChecker(); diff --git a/src/services/completions.ts b/src/services/completions.ts index fb1fb0fcfad52..0bd9594b7b302 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1754,7 +1754,7 @@ namespace ts.Completions { const lowerCaseTokenText = previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : ""; const exportInfo = codefix.getSymbolToExportInfoMap(sourceFile, host, program); const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.(); - const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, host); + const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host); exportInfo.forEach((info, key) => { const symbolName = key.substring(0, key.indexOf("|")); if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return; @@ -1803,6 +1803,7 @@ namespace ts.Completions { info.isFromPackageJson ? packageJsonAutoImportProvider! : program, sourceFile, moduleFile, + preferences, packageJsonFilter, getModuleSpecifierResolutionHost(info.isFromPackageJson), moduleSpecifierCache); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 2468ff0836fe5..70e3530b47c64 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2894,7 +2894,7 @@ namespace ts { allowsImportingSpecifier: (moduleSpecifier: string) => boolean; } - export function createPackageJsonImportFilter(fromFile: SourceFile, host: LanguageServiceHost): PackageJsonImportFilter { + export function createPackageJsonImportFilter(fromFile: SourceFile, preferences: UserPreferences, host: LanguageServiceHost): PackageJsonImportFilter { const packageJsons = ( (host.getPackageJsonsVisibleToFile && host.getPackageJsonsVisibleToFile(fromFile.fileName)) || getPackageJsonsVisibleToFile(fromFile.fileName, host) ).filter(p => p.parseable); @@ -2980,6 +2980,7 @@ namespace ts { fromFile.path, importedFileName, moduleSpecifierResolutionHost, + preferences, ); if (!specifier) { @@ -3275,47 +3276,112 @@ namespace ts { } } - export function createModuleSpecifierCache(): ModuleSpecifierCache { - let cache: ESMap<Path, boolean | readonly ModulePath[]> | undefined; - let importingFileName: Path | undefined; - const wrapped: ModuleSpecifierCache = { - get(fromFileName, toFileName) { - if (!cache || fromFileName !== importingFileName) return undefined; + export interface ModuleSpecifierResolutionCacheHost { + watchNodeModulesForPackageJsonChanges(directoryPath: string): FileWatcher; + } + + export function createModuleSpecifierCache(host: ModuleSpecifierResolutionCacheHost): ModuleSpecifierCache { + let containedNodeModulesWatchers: ESMap<string, FileWatcher> | undefined; + let cache: ESMap<Path, ResolvedModuleSpecifierInfo> | undefined; + let currentKey: string | undefined; + const result: ModuleSpecifierCache = { + get(fromFileName, toFileName, preferences) { + if (!cache || currentKey !== key(fromFileName, preferences)) return undefined; return cache.get(toFileName); }, - set(fromFileName, toFileName, moduleSpecifiers) { - if (cache && fromFileName !== importingFileName) { - cache.clear(); + set(fromFileName, toFileName, preferences, modulePaths, moduleSpecifiers) { + ensureCache(fromFileName, preferences).set(toFileName, createInfo(modulePaths, moduleSpecifiers, /*isAutoImportable*/ true)); + + // If any module specifiers were generated based off paths in node_modules, + // a package.json file in that package was read and is an input to the cached. + // Instead of watching each individual package.json file, set up a wildcard + // directory watcher for any node_modules referenced and clear the cache when + // it sees any changes. + if (moduleSpecifiers) { + for (const p of modulePaths) { + if (p.isInNodeModules) { + // No trailing slash + const nodeModulesPath = p.path.substring(0, p.path.indexOf(nodeModulesPathPart) + nodeModulesPathPart.length - 1); + if (!containedNodeModulesWatchers?.has(nodeModulesPath)) { + (containedNodeModulesWatchers ||= new Map()).set( + nodeModulesPath, + host.watchNodeModulesForPackageJsonChanges(nodeModulesPath), + ); + } + } + } + } + }, + setModulePaths(fromFileName, toFileName, preferences, modulePaths) { + const cache = ensureCache(fromFileName, preferences); + const info = cache.get(toFileName); + if (info) { + info.modulePaths = modulePaths; + } + else { + cache.set(toFileName, createInfo(modulePaths, /*moduleSpecifiers*/ undefined, /*isAutoImportable*/ undefined)); + } + }, + setIsAutoImportable(fromFileName, toFileName, preferences, isAutoImportable) { + const cache = ensureCache(fromFileName, preferences); + const info = cache.get(toFileName); + if (info) { + info.isAutoImportable = isAutoImportable; + } + else { + cache.set(toFileName, createInfo(/*modulePaths*/ undefined, /*moduleSpecifiers*/ undefined, isAutoImportable)); } - importingFileName = fromFileName; - (cache ||= new Map()).set(toFileName, moduleSpecifiers); }, clear() { - cache = undefined; - importingFileName = undefined; + containedNodeModulesWatchers?.forEach(watcher => watcher.close()); + cache?.clear(); + containedNodeModulesWatchers?.clear(); + currentKey = undefined; }, count() { return cache ? cache.size : 0; } }; if (Debug.isDebugging) { - Object.defineProperty(wrapped, "__cache", { get: () => cache }); + Object.defineProperty(result, "__cache", { get: () => cache }); + } + return result; + + function ensureCache(fromFileName: Path, preferences: UserPreferences) { + const newKey = key(fromFileName, preferences); + if (cache && (currentKey !== newKey)) { + result.clear(); + } + currentKey = newKey; + return cache ||= new Map(); + } + + function key(fromFileName: Path, preferences: UserPreferences) { + return `${fromFileName},${preferences.importModuleSpecifierEnding},${preferences.importModuleSpecifierPreference}`; + } + + function createInfo( + modulePaths: readonly ModulePath[] | undefined, + moduleSpecifiers: readonly string[] | undefined, + isAutoImportable: boolean | undefined, + ): ResolvedModuleSpecifierInfo { + return { modulePaths, moduleSpecifiers, isAutoImportable }; } - return wrapped; } export function isImportableFile( program: Program, from: SourceFile, to: SourceFile, + preferences: UserPreferences, packageJsonFilter: PackageJsonImportFilter | undefined, moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost, moduleSpecifierCache: ModuleSpecifierCache | undefined, ): boolean { if (from === to) return false; - const cachedResult = moduleSpecifierCache?.get(from.path, to.path); - if (cachedResult !== undefined) { - return !!cachedResult; + const cachedResult = moduleSpecifierCache?.get(from.path, to.path, preferences); + if (cachedResult?.isAutoImportable !== undefined) { + return cachedResult.isAutoImportable; } const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost); @@ -3335,9 +3401,9 @@ namespace ts { ); if (packageJsonFilter) { - const isImportable = hasImportablePath && packageJsonFilter.allowsImportingSourceFile(to, moduleSpecifierResolutionHost); - moduleSpecifierCache?.set(from.path, to.path, isImportable); - return isImportable; + const isAutoImportable = hasImportablePath && packageJsonFilter.allowsImportingSourceFile(to, moduleSpecifierResolutionHost); + moduleSpecifierCache?.setIsAutoImportable(from.path, to.path, preferences, isAutoImportable); + return isAutoImportable; } return hasImportablePath; diff --git a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts index f109cab9d1f6e..fd57438a2691e 100644 --- a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts +++ b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts @@ -4,23 +4,27 @@ namespace ts.projectSystem { content: `{ "dependencies": { "mobx": "*" } }` }; const aTs: File = { - path: "/a.ts", + path: "/src/a.ts", content: "export const foo = 0;", }; const bTs: File = { - path: "/b.ts", + path: "/src/b.ts", content: "foo", }; + const cTs: File = { + path: "/src/c.ts", + content: "import ", + }; const bSymlink: SymLink = { - path: "/b-link.ts", + path: "/src/b-link.ts", symLink: "./b.ts", }; const tsconfig: File = { path: "/tsconfig.json", - content: "{}", + content: `{ "include": ["src"] }`, }; const ambientDeclaration: File = { - path: "/ambient.d.ts", + path: "/src/ambient.d.ts", content: "declare module 'ambient' {}" }; const mobxDts: File = { @@ -31,50 +35,111 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: moduleSpecifierCache", () => { it("caches importability within a file", () => { const { moduleSpecifierCache } = setup(); - assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path)); + assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {})?.isAutoImportable); + }); + + it("caches module specifiers within a file", () => { + const { moduleSpecifierCache, triggerCompletions } = setup(); + // Completion at an import statement will calculate and cache module specifiers + triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); + const mobxCache = moduleSpecifierCache.get(cTs.path as Path, mobxDts.path as Path, {}); + assert.deepEqual(mobxCache, { + modulePaths: [{ + path: mobxDts.path, + isInNodeModules: true, + isRedirect: false + }], + moduleSpecifiers: ["mobx"], + isAutoImportable: true, + }); + }); + + it("invalidates module specifiers when changes happen in contained node_modules directories", () => { + const { host, moduleSpecifierCache, triggerCompletions } = setup(); + // Completion at an import statement will calculate and cache module specifiers + triggerCompletions({ file: cTs.path, line: 1, offset: cTs.content.length + 1 }); + checkWatchedDirectories(host, ["/src", "/node_modules"], /*recursive*/ true); + host.writeFile("/node_modules/.staging/mobx-12345678/package.json", "{}"); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); }); it("does not invalidate the cache when new files are added", () => { const { host, moduleSpecifierCache } = setup(); host.writeFile("/src/a2.ts", aTs.content); host.runQueuedTimeoutCallbacks(); - assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path)); + assert.isTrue(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, {})?.isAutoImportable); }); it("invalidates the cache when symlinks are added or removed", () => { const { host, moduleSpecifierCache } = setup(); - host.renameFile(bSymlink.path, "/b-link2.ts"); + host.renameFile(bSymlink.path, "/src/b-link2.ts"); host.runQueuedTimeoutCallbacks(); assert.equal(moduleSpecifierCache.count(), 0); }); - it("invalidates the cache when package.json changes", () => { + it("invalidates the cache when local package.json changes", () => { const { host, moduleSpecifierCache } = setup(); host.writeFile("/package.json", `{}`); host.runQueuedTimeoutCallbacks(); - assert.isUndefined(moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path)); + assert.equal(moduleSpecifierCache.count(), 0); + }); + + it("invalidates the cache when module resolution settings change", () => { + const { host, moduleSpecifierCache } = setup(); + host.writeFile(tsconfig.path, `{ "compilerOptions": { "moduleResolution": "classic" }, "include": ["src"] }`); + host.runQueuedTimeoutCallbacks(); + assert.equal(moduleSpecifierCache.count(), 0); + }); + + it("invalidates the cache when user preferences change", () => { + const { moduleSpecifierCache, session, triggerCompletions } = setup(); + const preferences: UserPreferences = { importModuleSpecifierPreference: "project-relative" }; + + assert.ok(getWithPreferences({})); + executeSessionRequest<protocol.ConfigureRequest, protocol.ConfigureResponse>(session, protocol.CommandTypes.Configure, { preferences }); + // Nothing changes yet + assert.ok(getWithPreferences({})); + assert.isUndefined(getWithPreferences(preferences)); + // Completions will request (getting nothing) and set the cache with new preferences + triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); + assert.isUndefined(getWithPreferences({})); + assert.ok(getWithPreferences(preferences)); + + // Test other affecting preference + executeSessionRequest<protocol.ConfigureRequest, protocol.ConfigureResponse>(session, protocol.CommandTypes.Configure, { + preferences: { importModuleSpecifierEnding: "js" }, + }); + triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); + assert.isUndefined(getWithPreferences(preferences)); + + function getWithPreferences(preferences: UserPreferences) { + return moduleSpecifierCache.get(bTs.path as Path, aTs.path as Path, preferences); + } }); }); function setup() { - const host = createServerHost([aTs, bTs, bSymlink, ambientDeclaration, tsconfig, packageJson, mobxDts]); + const host = createServerHost([aTs, bTs, cTs, bSymlink, ambientDeclaration, tsconfig, packageJson, mobxDts]); const session = createSession(host); - openFilesForSession([aTs, bTs], session); + openFilesForSession([aTs, bTs, cTs], session); const projectService = session.getProjectService(); const project = configuredProjectAt(projectService, 0); - triggerCompletions(); - return { host, project, projectService, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions }; + executeSessionRequest<protocol.ConfigureRequest, protocol.ConfigureResponse>(session, protocol.CommandTypes.Configure, { + preferences: { + includeCompletionsForImportStatements: true, + includeCompletionsForModuleExports: true, + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: true, + }, + }); + triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); + + return { host, project, projectService, session, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions }; - function triggerCompletions() { - const requestLocation: protocol.FileLocationRequestArgs = { - file: bTs.path, - line: 1, - offset: 3, - }; + function triggerCompletions(requestLocation: protocol.FileLocationRequestArgs) { executeSessionRequest<protocol.CompletionsRequest, protocol.CompletionInfoResponse>(session, protocol.CommandTypes.CompletionInfo, { ...requestLocation, - includeExternalModuleExports: true, - prefix: "foo", }); } } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 98f85301f5c3c..d237b81c503e0 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -9954,7 +9954,7 @@ declare namespace ts.server { errors: Diagnostic[] | undefined; } export class ProjectService { - private readonly scriptInfoInNodeModulesWatchers; + private readonly nodeModulesWatchers; /** * Contains all the deleted script info's version information so that * it does not reset when creating script info again @@ -10104,6 +10104,7 @@ declare namespace ts.server { private createInferredProject; getScriptInfo(uncheckedFileName: string): ScriptInfo | undefined; private watchClosedScriptInfo; + private createNodeModulesWatcher; private watchClosedScriptInfoInNodeModules; private getModifiedTime; private refreshScriptInfo; diff --git a/tests/baselines/reference/tsserver/projectErrors/correct-errors-when-resolution-resolves-to-file-that-has-same-ambient-module-and-is-also-module.js b/tests/baselines/reference/tsserver/projectErrors/correct-errors-when-resolution-resolves-to-file-that-has-same-ambient-module-and-is-also-module.js index 8cd0bada0c782..2bd9071b01162 100644 --- a/tests/baselines/reference/tsserver/projectErrors/correct-errors-when-resolution-resolves-to-file-that-has-same-ambient-module-and-is-also-module.js +++ b/tests/baselines/reference/tsserver/projectErrors/correct-errors-when-resolution-resolves-to-file-that-has-same-ambient-module-and-is-also-module.js @@ -18,8 +18,8 @@ DirectoryWatcher:: Added:: WatchInfo: /users/username/projects/myproject/src 1 u Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /users/username/projects/myproject/src 1 undefined Config: /users/username/projects/myproject/tsconfig.json WatchType: Wild card directory Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded Starting updateGraphWorker: Project: /users/username/projects/myproject/tsconfig.json -DirectoryWatcher:: Added:: WatchInfo: /users/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos in them -Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /users/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos in them +DirectoryWatcher:: Added:: WatchInfo: /users/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /users/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache DirectoryWatcher:: Added:: WatchInfo: /users/username/projects/myproject/node_modules 1 undefined Project: /users/username/projects/myproject/tsconfig.json WatchType: Failed Lookup Locations Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /users/username/projects/myproject/node_modules 1 undefined Project: /users/username/projects/myproject/tsconfig.json WatchType: Failed Lookup Locations FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 500 undefined WatchType: Closed Script info diff --git a/tests/baselines/reference/tsserver/projectErrors/npm-install-when-timeout-occurs-after-installation.js b/tests/baselines/reference/tsserver/projectErrors/npm-install-when-timeout-occurs-after-installation.js index 61a0843e82382..419c68f410458 100644 --- a/tests/baselines/reference/tsserver/projectErrors/npm-install-when-timeout-occurs-after-installation.js +++ b/tests/baselines/reference/tsserver/projectErrors/npm-install-when-timeout-occurs-after-installation.js @@ -222,8 +222,8 @@ Scheduled: /user/username/projects/myproject/tsconfig.json, Cancelled earlier on Scheduled: *ensureProjectForOpenFiles*, Cancelled earlier one Running: /user/username/projects/myproject/tsconfig.json Starting updateGraphWorker: Project: /user/username/projects/myproject/tsconfig.json -DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos in them -Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos in them +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache Finishing updateGraphWorker: Project: /user/username/projects/myproject/tsconfig.json Version: 3 structureChanged: true structureIsReused:: SafeModules Elapsed:: *ms Project '/user/username/projects/myproject/tsconfig.json' (Configured) Files (3) diff --git a/tests/baselines/reference/tsserver/projectErrors/npm-install-when-timeout-occurs-inbetween-installation.js b/tests/baselines/reference/tsserver/projectErrors/npm-install-when-timeout-occurs-inbetween-installation.js index 8c22a7cd8e362..b17e03d859139 100644 --- a/tests/baselines/reference/tsserver/projectErrors/npm-install-when-timeout-occurs-inbetween-installation.js +++ b/tests/baselines/reference/tsserver/projectErrors/npm-install-when-timeout-occurs-inbetween-installation.js @@ -245,8 +245,8 @@ Scheduled: /user/username/projects/myproject/tsconfig.json, Cancelled earlier on Scheduled: *ensureProjectForOpenFiles*, Cancelled earlier one Running: /user/username/projects/myproject/tsconfig.json Starting updateGraphWorker: Project: /user/username/projects/myproject/tsconfig.json -DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos in them -Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos in them +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache Finishing updateGraphWorker: Project: /user/username/projects/myproject/tsconfig.json Version: 3 structureChanged: true structureIsReused:: SafeModules Elapsed:: *ms Project '/user/username/projects/myproject/tsconfig.json' (Configured) Files (3) diff --git a/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project-when-built-with-disableSourceOfProjectReferenceRedirect.js b/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project-when-built-with-disableSourceOfProjectReferenceRedirect.js index e7dda323106b0..c257c3c0f2e73 100644 --- a/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project-when-built-with-disableSourceOfProjectReferenceRedirect.js +++ b/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project-when-built-with-disableSourceOfProjectReferenceRedirect.js @@ -96,4 +96,6 @@ Open files: Projects: /user/username/projects/myproject/app/src/program/tsconfig.json response:{"responseRequired":false} request:{"command":"getCodeFixes","arguments":{"file":"/user/username/projects/myproject/app/src/program/index.ts","startLine":1,"startOffset":1,"endLine":1,"endOffset":4,"errorCodes":[2304]},"seq":1,"type":"request"} +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache response:{"response":[{"fixName":"import","description":"Import 'foo' from module \"shared\"","changes":[{"fileName":"/user/username/projects/myproject/app/src/program/index.ts","textChanges":[{"start":{"line":1,"offset":1},"end":{"line":1,"offset":1},"newText":"import { foo } from \"shared\";\n\n"}]}]}],"responseRequired":true} \ No newline at end of file diff --git a/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project-when-built.js b/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project-when-built.js index c76675540b864..6f7e0f4c7773c 100644 --- a/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project-when-built.js +++ b/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project-when-built.js @@ -95,4 +95,6 @@ Open files: Projects: /user/username/projects/myproject/app/src/program/tsconfig.json response:{"responseRequired":false} request:{"command":"getCodeFixes","arguments":{"file":"/user/username/projects/myproject/app/src/program/index.ts","startLine":1,"startOffset":1,"endLine":1,"endOffset":4,"errorCodes":[2304]},"seq":1,"type":"request"} +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache response:{"response":[{"fixName":"import","description":"Import 'foo' from module \"shared\"","changes":[{"fileName":"/user/username/projects/myproject/app/src/program/index.ts","textChanges":[{"start":{"line":1,"offset":1},"end":{"line":1,"offset":1},"newText":"import { foo } from \"shared\";\n\n"}]}]}],"responseRequired":true} \ No newline at end of file diff --git a/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project.js b/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project.js index c76675540b864..6f7e0f4c7773c 100644 --- a/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project.js +++ b/tests/baselines/reference/tsserver/projectReferences/auto-import-with-referenced-project.js @@ -95,4 +95,6 @@ Open files: Projects: /user/username/projects/myproject/app/src/program/tsconfig.json response:{"responseRequired":false} request:{"command":"getCodeFixes","arguments":{"file":"/user/username/projects/myproject/app/src/program/index.ts","startLine":1,"startOffset":1,"endLine":1,"endOffset":4,"errorCodes":[2304]},"seq":1,"type":"request"} +DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /user/username/projects/myproject/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache response:{"response":[{"fixName":"import","description":"Import 'foo' from module \"shared\"","changes":[{"fileName":"/user/username/projects/myproject/app/src/program/index.ts","textChanges":[{"start":{"line":1,"offset":1},"end":{"line":1,"offset":1},"newText":"import { foo } from \"shared\";\n\n"}]}]}],"responseRequired":true} \ No newline at end of file diff --git a/tests/baselines/reference/tsserver/resolutionCache/npm-install-@types-works.js b/tests/baselines/reference/tsserver/resolutionCache/npm-install-@types-works.js index c9833a1189cd4..a8baf59905f81 100644 --- a/tests/baselines/reference/tsserver/resolutionCache/npm-install-@types-works.js +++ b/tests/baselines/reference/tsserver/resolutionCache/npm-install-@types-works.js @@ -64,8 +64,8 @@ Elapsed:: *ms DirectoryWatcher:: Triggered with /a/b/projects/temp/node_modules/ Running: /dev/null/inferredProject1* Scheduled: *ensureProjectForOpenFiles*, Cancelled earlier one Starting updateGraphWorker: Project: /dev/null/inferredProject1* -DirectoryWatcher:: Added:: WatchInfo: /a/b/projects/temp/node_modules 1 undefined WatchType: node_modules for closed script infos in them -Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /a/b/projects/temp/node_modules 1 undefined WatchType: node_modules for closed script infos in them +DirectoryWatcher:: Added:: WatchInfo: /a/b/projects/temp/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache +Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /a/b/projects/temp/node_modules 1 undefined WatchType: node_modules for closed script infos and package.jsons affecting module specifier cache Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 2 structureChanged: true structureIsReused:: SafeModules Elapsed:: *ms Project '/dev/null/inferredProject1*' (Inferred) Files (3)