Avoid caching duplicate export info from AutoImportProvider (#52683)

This commit is contained in:
Andrew Branch 2023-02-08 12:36:12 -08:00 committed by GitHub
parent 963f913014
commit a0afacd262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 2 deletions

View File

@ -422,7 +422,16 @@ export function forEachExternalModuleToImportFrom(
const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.();
if (autoImportProvider) {
const start = timestamp();
forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), excludePatterns, (module, file) => cb(module, file, autoImportProvider, /*isFromPackageJson*/ true));
const checker = program.getTypeChecker();
forEachExternalModule(autoImportProvider.getTypeChecker(), autoImportProvider.getSourceFiles(), excludePatterns, (module, file) => {
if (file && !program.getSourceFile(file.fileName) || !file && !checker.resolveName(module.name, /*location*/ undefined, SymbolFlags.Module, /*excludeGlobals*/ false)) {
// The AutoImportProvider filters files already in the main program out of its *root* files,
// but non-root files can still be present in both programs, and already in the export info map
// at this point. This doesn't create any incorrect behavior, but is a waste of time and memory,
// so we filter them out here.
cb(module, file, autoImportProvider, /*isFromPackageJson*/ true);
}
});
host.log?.(`forEachExternalModuleToImportFrom autoImportProvider: ${timestamp() - start}`);
}
}

View File

@ -260,6 +260,41 @@ describe("unittests:: tsserver:: autoImportProvider", () => {
const project = projectService.configuredProjects.get(tsconfig.path)!;
assert.isUndefined(project.getPackageJsonAutoImportProvider());
});
it("Shared source files between AutoImportProvider and main program do not cause duplicate entries in export info map", () => {
const files = [
// node_modules/memfs - AutoImportProvider only
{ path: "/node_modules/memfs/package.json", content: `{ "name": "memfs", "version": "1.0.0", "types": "lib/index.d.ts" }` },
{ path: "/node_modules/memfs/lib/index.d.ts", content: `/// <reference types="node" />\nexport declare class Volume {}` },
// node_modules/@types/node - AutoImportProvider and main program
{ path: "/node_modules/@types/node/package.json", content: `{ "name": "@types/node", "version": "1.0.0" }` },
{ path: "/node_modules/@types/node/index.d.ts", content: `export declare class Stats {}` },
// root
{ path: "/package.json", content: `{ "dependencies": { "memfs": "*" }, "devDependencies": { "@types/node": "*" } }` },
{ path: "/tsconfig.json", content: `{ "compilerOptions": { "types": ["node"] }` },
{ path: "/index.ts", content: `export {};` },
];
const { projectService, session, triggerCompletions } = setup(files);
openFilesForSession([files[files.length - 1]], session);
const project = projectService.configuredProjects.get("/tsconfig.json")!;
const autoImportProvider = project.getPackageJsonAutoImportProvider()!;
assert.isDefined(autoImportProvider);
// Trigger completions to ensure export info map is populated
triggerCompletions("/index.ts", 0, 0);
const exportInfoMap = project.getCachedExportInfoMap();
const seenSymbolNames = new Set<string>();
exportInfoMap.search("/index.ts" as ts.Path, /*preferCapitalized*/ false, ts.returnTrue, (info, symbolName) => {
assert.lengthOf(info, 1);
seenSymbolNames.add(symbolName);
});
assert.equal(seenSymbolNames.size, 2);
assert.ok(seenSymbolNames.has("Stats"));
assert.ok(seenSymbolNames.has("Volume"));
});
});
describe("unittests:: tsserver:: autoImportProvider - monorepo", () => {
@ -333,7 +368,8 @@ function setup(files: File[]) {
projectService,
session,
updateFile,
findAllReferences
findAllReferences,
triggerCompletions,
};
function updateFile(path: string, newText: string) {
@ -360,4 +396,19 @@ function setup(files: File[]) {
}
});
}
function triggerCompletions(file: string, line: number, offset: number) {
const requestLocation: ts.server.protocol.FileLocationRequestArgs = {
file,
line,
offset,
};
session.executeCommandSeq<ts.server.protocol.CompletionsRequest>({
command: ts.server.protocol.CommandTypes.CompletionInfo,
arguments: {
...requestLocation,
includeExternalModuleExports: true,
}
});
}
}