Skip to content

Commit 7d4f8a7

Browse files
authored
fix: prevent completion using empty cache (#2258)
The export info cache is created with an edit that doesn't trigger auto imports. And then The onFileChanged call marks the cache to be usable even though nothing is cached yet. #2260
1 parent 57a548c commit 7d4f8a7

File tree

2 files changed

+57
-3
lines changed

2 files changed

+57
-3
lines changed

packages/language-server/src/plugins/typescript/service.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -719,11 +719,16 @@ async function createLanguageService(
719719

720720
dirty = false;
721721

722-
if (!oldProgram) {
722+
// https://github.com/microsoft/TypeScript/blob/23faef92703556567ddbcb9afb893f4ba638fc20/src/server/project.ts#L1624
723+
// host.getCachedExportInfoMap will create the cache if it doesn't exist
724+
// so we need to check the property instead
725+
const exportMapCache = project?.exportMapCache;
726+
if (!oldProgram || !exportMapCache || exportMapCache.isEmpty()) {
723727
changedFilesForExportCache.clear();
724728
return;
725729
}
726730

731+
exportMapCache.releaseSymbols();
727732
for (const fileName of changedFilesForExportCache) {
728733
const oldFile = oldProgram.getSourceFile(fileName);
729734
const newFile = program?.getSourceFile(fileName);
@@ -734,10 +739,10 @@ async function createLanguageService(
734739
}
735740

736741
if (oldFile && newFile) {
737-
host.getCachedExportInfoMap?.().onFileChanged?.(oldFile, newFile, false);
742+
exportMapCache.onFileChanged?.(oldFile, newFile, false);
738743
} else {
739744
// new file or deleted file
740-
host.getCachedExportInfoMap?.().clear();
745+
exportMapCache.clear();
741746
}
742747
}
743748
changedFilesForExportCache.clear();

packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,55 @@ describe('CompletionProviderImpl', function () {
14601460
assert.strictEqual(detail, 'Add import from "./Bar.svelte"\n\nclass Bar');
14611461
});
14621462

1463+
it("doesn't use empty cache", async () => {
1464+
const virtualTestDir = getRandomVirtualDirPath(testFilesDir);
1465+
const { document, lsAndTsDocResolver, lsConfigManager, docManager } =
1466+
setupVirtualEnvironment({
1467+
filename: 'index.svelte',
1468+
fileContent: '<script>b</script>',
1469+
testDir: virtualTestDir
1470+
});
1471+
1472+
const completionProvider = new CompletionsProviderImpl(lsAndTsDocResolver, lsConfigManager);
1473+
1474+
await lsAndTsDocResolver.getLSAndTSDoc(document);
1475+
1476+
docManager.updateDocument(document, [
1477+
{
1478+
range: Range.create(
1479+
Position.create(0, document.content.length),
1480+
Position.create(0, document.content.length)
1481+
),
1482+
text: ' '
1483+
}
1484+
]);
1485+
1486+
docManager.openClientDocument({
1487+
text: '',
1488+
uri: pathToUrl(join(virtualTestDir, 'Bar.svelte'))
1489+
});
1490+
1491+
docManager.updateDocument(document, [
1492+
{
1493+
range: Range.create(
1494+
Position.create(0, document.content.length),
1495+
Position.create(0, document.content.length)
1496+
),
1497+
text: ' '
1498+
}
1499+
]);
1500+
1501+
const completions = await completionProvider.getCompletions(document, {
1502+
line: 0,
1503+
character: 9
1504+
});
1505+
1506+
const item2 = completions?.items.find((item) => item.label === 'Bar');
1507+
const { detail } = await completionProvider.resolveCompletion(document, item2!);
1508+
1509+
assert.strictEqual(detail, 'Add import from "./Bar.svelte"\n\nclass Bar');
1510+
});
1511+
14631512
it('can auto import new export', async () => {
14641513
const virtualTestDir = getRandomVirtualDirPath(testFilesDir);
14651514
const { document, lsAndTsDocResolver, lsConfigManager, virtualSystem } =

0 commit comments

Comments
 (0)