mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-11 10:00:13 -06:00
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:
parent
130e16d73b
commit
7c293c8d46
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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}
|
||||
@ -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}
|
||||
@ -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}
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user