Skip to content

Commit

Permalink
Better reuse of package.json cache, module resolution cache, and pack…
Browse files Browse the repository at this point in the history
…age.json auto import filter (#47388)

* Use package.json cache in module specifier generation

* Let AutoImportProviderProject reuse module resolution cache of host project

* Add missing module resolution cache access, add logging to getRootFileNames

* Reuse packageJsonImportFilter

* Only log when the project will be created, update API baseline

* Remove override that could mess up watches
  • Loading branch information
andrewbranch committed Jan 18, 2022
1 parent 8153475 commit d0b3ac3
Show file tree
Hide file tree
Showing 10 changed files with 38 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4722,6 +4722,7 @@ namespace ts {
getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "",
getCurrentDirectory: () => host.getCurrentDirectory(),
getSymlinkCache: maybeBind(host, host.getSymlinkCache),
getPackageJsonInfoCache: () => host.getPackageJsonInfoCache?.(),
useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames),
redirectTargetsMap: host.redirectTargetsMap,
getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName),
Expand Down
7 changes: 4 additions & 3 deletions src/compiler/moduleSpecifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace ts.moduleSpecifiers {
&& getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) {
return false;
}
return getImpliedNodeFormatForFile(importingSourceFileName, /*packageJsonInfoCache*/ undefined, getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS;
return getImpliedNodeFormatForFile(importingSourceFileName, host.getPackageJsonInfoCache?.(), getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS;
}

function getModuleResolutionHost(host: ModuleSpecifierResolutionHost): ModuleResolutionHost {
Expand Down Expand Up @@ -715,8 +715,9 @@ namespace ts.moduleSpecifiers {
const packageRootPath = path.substring(0, packageRootIndex);
const packageJsonPath = combinePaths(packageRootPath, "package.json");
let moduleFileToTry = path;
if (host.fileExists(packageJsonPath)) {
const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!);
const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath);
if (typeof cachedPackageJson === "object" || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) {
const packageJsonContent = cachedPackageJson?.packageJsonContent || JSON.parse(host.readFile!(packageJsonPath)!);
if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) {
// `conditions` *could* be made to go against `importingSourceFile.impliedNodeFormat` if something wanted to generate
// an ImportEqualsDeclaration in an ESM-implied file or an ImportCall in a CJS-implied file. But since this function is
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8345,6 +8345,7 @@ namespace ts {
realpath?(path: string): string;
getSymlinkCache?(): SymlinkCache;
getModuleSpecifierCache?(): ModuleSpecifierCache;
getPackageJsonInfoCache?(): PackageJsonInfoCache | undefined;
getGlobalTypingsCacheLocation?(): string | undefined;
getNearestAncestorDirectoryWithPackageJson?(fileName: string, rootDir?: string): string | undefined;

Expand Down
17 changes: 15 additions & 2 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ namespace ts.server {
return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, containingSourceFile);
}

getModuleResolutionCache(): ModuleResolutionCache | undefined {
return this.resolutionCache.getModuleResolutionCache();
}

getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined {
return this.resolutionCache.getResolvedModuleWithFailedLookupLocationsFromCache(moduleName, containingFile, resolutionMode);
}
Expand Down Expand Up @@ -1915,6 +1919,7 @@ namespace ts.server {
return ts.emptyArray;
}

const start = timestamp();
let dependencyNames: Set<string> | undefined;
let rootNames: string[] | undefined;
const rootFileName = combinePaths(hostProject.currentDirectory, inferredTypesContainingFile);
Expand All @@ -1924,13 +1929,13 @@ namespace ts.server {
packageJson.peerDependencies?.forEach((_, dependencyName) => addDependency(dependencyName));
}

let dependenciesAdded = 0;
if (dependencyNames) {
let dependenciesAdded = 0;
const symlinkCache = hostProject.getSymlinkCache();
for (const name of arrayFrom(dependencyNames.keys())) {
// Avoid creating a large project that would significantly slow down time to editor interactivity
if (dependencySelection === PackageJsonAutoImportPreference.Auto && dependenciesAdded > this.maxDependencies) {
hostProject.log(`Auto-import provider attempted to add more than ${this.maxDependencies} dependencies.`);
hostProject.log(`AutoImportProviderProject: attempted to add more than ${this.maxDependencies} dependencies. Aborting.`);
return ts.emptyArray;
}

Expand Down Expand Up @@ -1978,6 +1983,9 @@ namespace ts.server {
}
}

if (rootNames?.length) {
hostProject.log(`AutoImportProviderProject: found ${rootNames.length} root files in ${dependenciesAdded} dependencies in ${timestamp() - start} ms`);
}
return rootNames || ts.emptyArray;

function addDependency(dependency: string) {
Expand Down Expand Up @@ -2146,6 +2154,11 @@ namespace ts.server {
getSymlinkCache() {
return this.hostProject.getSymlinkCache();
}

/*@internal*/
getModuleResolutionCache() {
return this.hostProject.getCurrentProgram()?.getModuleResolutionCache();
}
}

/**
Expand Down
19 changes: 10 additions & 9 deletions src/services/codefixes/importFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,8 @@ namespace ts.codefix {

function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, isValidTypeOnlyUseSite: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol || info.symbol.parent === moduleSymbol), "Some exportInfo should match the specified moduleSymbol");
return getBestFix(getImportFixes(exportInfos, symbolName, position, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences), sourceFile, program, host, preferences);
const packageJsonImportFilter = createPackageJsonImportFilter(sourceFile, preferences, host);
return getBestFix(getImportFixes(exportInfos, symbolName, position, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences), sourceFile, program, packageJsonImportFilter);
}

function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
Expand Down Expand Up @@ -369,6 +370,7 @@ namespace ts.codefix {
program: Program,
host: LanguageServiceHost,
preferences: UserPreferences,
packageJsonImportFilter?: PackageJsonImportFilter,
fromCacheOnly?: boolean,
): { exportInfo?: SymbolExportInfo, moduleSpecifier: string, computedWithoutCacheCount: number } | undefined {
const { fixes, computedWithoutCacheCount } = getNewImportFixes(
Expand All @@ -381,7 +383,7 @@ namespace ts.codefix {
host,
preferences,
fromCacheOnly);
const result = getBestFix(fixes, importingFile, program, host, preferences);
const result = getBestFix(fixes, importingFile, program, packageJsonImportFilter || createPackageJsonImportFilter(importingFile, preferences, host));
return result && { ...result, computedWithoutCacheCount };
}

Expand Down Expand Up @@ -652,24 +654,23 @@ 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.program, context.host, context.preferences) };
const packageJsonImportFilter = createPackageJsonImportFilter(context.sourceFile, context.preferences, context.host);
return info && { ...info, fixes: sortFixes(info.fixes, context.sourceFile, context.program, packageJsonImportFilter) };
}

function sortFixes(fixes: readonly ImportFix[], sourceFile: SourceFile, program: Program, 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, sourceFile, program, allowsImportingSpecifier));
function sortFixes(fixes: readonly ImportFix[], sourceFile: SourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter): readonly ImportFix[] {
return sort(fixes, (a, b) => compareValues(a.kind, b.kind) || compareModuleSpecifiers(a, b, sourceFile, program, packageJsonImportFilter.allowsImportingSpecifier));
}

function getBestFix<T extends ImportFix>(fixes: readonly T[], sourceFile: SourceFile, program: Program, host: LanguageServiceHost, preferences: UserPreferences): T | undefined {
function getBestFix<T extends ImportFix>(fixes: readonly T[], sourceFile: SourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter): 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, preferences, host);
return fixes.reduce((best, fix) =>
// Takes true branch of conditional if `fix` is better than `best`
compareModuleSpecifiers(fix, best, sourceFile, program, allowsImportingSpecifier) === Comparison.LessThan ? fix : best
compareModuleSpecifiers(fix, best, sourceFile, program, packageJsonImportFilter.allowsImportingSpecifier) === Comparison.LessThan ? fix : best
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ namespace ts.Completions {
cb: (context: ModuleSpecifierResolutioContext) => TReturn,
): TReturn {
const start = timestamp();
const packageJsonImportFilter = createPackageJsonImportFilter(sourceFile, preferences, host);
let resolutionLimitExceeded = false;
let ambientCount = 0;
let resolvedCount = 0;
Expand All @@ -202,7 +203,7 @@ namespace ts.Completions {
const shouldResolveModuleSpecifier = isForImportStatementCompletion || preferences.allowIncompleteCompletions && resolvedCount < moduleSpecifierResolutionLimit;
const shouldGetModuleSpecifierFromCache = !shouldResolveModuleSpecifier && preferences.allowIncompleteCompletions && cacheAttemptCount < moduleSpecifierResolutionCacheAttemptLimit;
const result = (shouldResolveModuleSpecifier || shouldGetModuleSpecifierFromCache)
? codefix.getModuleSpecifierForBestExportInfo(exportInfo, sourceFile, program, host, preferences, shouldGetModuleSpecifierFromCache)
? codefix.getModuleSpecifierForBestExportInfo(exportInfo, sourceFile, program, host, preferences, packageJsonImportFilter, shouldGetModuleSpecifierFromCache)
: undefined;

if (!shouldResolveModuleSpecifier && !shouldGetModuleSpecifierFromCache || shouldGetModuleSpecifierFromCache && !result) {
Expand Down
1 change: 1 addition & 0 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,7 @@ namespace ts {
hasChangedAutomaticTypeDirectiveNames,
trace: parseConfigHost.trace,
resolveModuleNames: maybeBind(host, host.resolveModuleNames),
getModuleResolutionCache: maybeBind(host, host.getModuleResolutionCache),
resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives),
useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect),
getParsedCommandLine,
Expand Down
2 changes: 2 additions & 0 deletions src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ namespace ts {
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: HasChangedAutomaticTypeDirectiveNames;
/* @internal */ getGlobalTypingsCacheLocation?(): string | undefined;
/* @internal */ getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache;
/* Lets the Program from a AutoImportProviderProject use its host project's ModuleResolutionCache */
/* @internal */ getModuleResolutionCache?(): ModuleResolutionCache | undefined;

/*
* Required for full import and type reference completions.
Expand Down
1 change: 1 addition & 0 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1907,6 +1907,7 @@ namespace ts {
useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames),
getSymlinkCache: maybeBind(host, host.getSymlinkCache) || program.getSymlinkCache,
getModuleSpecifierCache: maybeBind(host, host.getModuleSpecifierCache),
getPackageJsonInfoCache: () => program.getModuleResolutionCache()?.getPackageJsonInfoCache(),
getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation),
redirectTargetsMap: program.redirectTargetsMap,
getProjectReferenceRedirect: fileName => program.getProjectReferenceRedirect(fileName),
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9874,6 +9874,7 @@ declare namespace ts.server {
writeFile(fileName: string, content: string): void;
fileExists(file: string): boolean;
resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference, _options?: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[];
getModuleResolutionCache(): ModuleResolutionCache | undefined;
getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined;
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
directoryExists(path: string): boolean;
Expand Down

0 comments on commit d0b3ac3

Please sign in to comment.