Include actual generated module specifiers in module specifier cache (#44176)

* Add cache invalidation for node_modules, config, and preferences changes

* Share watches with closed script info

* Update API baseline

* Small adjustments for fewer object allocations

* Document overloaded return value

* Update baselines

* Store isAutoImportable separately from modulePaths

* Add back missing return

* Return wrapped watcher instead of underlying one

* Make user preferences part of the cache key instead of implicitly clearing in editor services

* Fix missed merge conflict
This commit is contained in:
Andrew Branch 2021-06-10 12:26:32 -05:00 committed by GitHub
parent 130e16d73b
commit 7c293c8d46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 348 additions and 130 deletions

View File

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

View File

@ -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

View File

@ -8155,10 +8155,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;
}

View File

@ -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

View File

@ -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> {

View File

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

View File

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

View File

@ -1755,7 +1755,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;
@ -1804,6 +1804,7 @@ namespace ts.Completions {
info.isFromPackageJson ? packageJsonAutoImportProvider! : program,
sourceFile,
moduleFile,
preferences,
packageJsonFilter,
getModuleSpecifierResolutionHost(info.isFromPackageJson),
moduleSpecifierCache);

View File

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

View File

@ -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 });
function triggerCompletions() {
const requestLocation: protocol.FileLocationRequestArgs = {
file: bTs.path,
line: 1,
offset: 3,
};
return { host, project, projectService, session, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions };
function triggerCompletions(requestLocation: protocol.FileLocationRequestArgs) {
executeSessionRequest<protocol.CompletionsRequest, protocol.CompletionInfoResponse>(session, protocol.CommandTypes.CompletionInfo, {
...requestLocation,
includeExternalModuleExports: true,
prefix: "foo",
});
}
}

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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