diff --git a/.changeset/bumpy-buttons-prove.md b/.changeset/bumpy-buttons-prove.md new file mode 100644 index 000000000..a56af0025 --- /dev/null +++ b/.changeset/bumpy-buttons-prove.md @@ -0,0 +1,5 @@ +--- +'svelte-language-server': patch +--- + +perf: avoid global completion in component start tag diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index b19f48967..d833a6e74 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -74,6 +74,8 @@ interface CommitCharactersOptions { isNewIdentifierLocation?: boolean; } +const ENSURE_COMPONENT_HELPER = '__sveltets_2_ensureComponent'; + export class CompletionsProviderImpl implements CompletionsProvider { constructor( private readonly lsAndTsDocResolver: LSAndTSDocResolver, @@ -249,20 +251,32 @@ export class CompletionsProviderImpl implements CompletionsProvider nameEnd) { + // If it's in whitespace after the component name in the start tag, + // this should trigger only directive and prop completions + // Unless it's correctly mapped to props. Handle it by ourselves to avoid expensive global completions + if (!generatedText.slice(0, offset).endsWith('props: {')) { + return this.getCompletionListForDirectiveOrProps( + attributeContext, + componentInfo, + wordInfo.defaultTextEditRange, + eventAndSlotLetCompletions, + tsDoc + ); + } + } else { + const componentNameOffByOne = generatedText + .slice(0, offset + 1) + .endsWith(ENSURE_COMPONENT_HELPER + '(' + name); + if (componentNameOffByOne) { + offset++; + } } } diff --git a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts index f0f3ea933..23bff0491 100644 --- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts @@ -1880,4 +1880,50 @@ describe('CompletionProviderImpl', function () { const item = completions?.items.find((item) => item.label === 'hi'); assert.ok(item); }); + + it('provides namespace component completions for Svelte 5+', async () => { + const { completionProvider, document } = setup('namespaced-v5.svelte'); + + const completions = await completionProvider.getCompletions( + document, + Position.create(4, 12), + { + triggerKind: CompletionTriggerKind.Invoked + } + ); + + const item = completions?.items.find((item) => item.label === 'ComponentDef'); + assert.ok(item); + }); + + it('use correct position for component tag auto-import', async () => { + const { completionProvider, document } = setup('importcompletions_unclose-tag.svelte'); + + const completionsAssumption = await completionProvider.getCompletions( + document, + Position.create(0, 4) + ); + + const itemAssumption = completionsAssumption?.items.find( + (item) => item.label === 'Completions' + ); + assert.ok( + itemAssumption, + `expected this to exist. Otherwise, the "Completions" assertion is wrong` + ); + + const completions = await completionProvider.getCompletions( + document, + Position.create(1, 2) + ); + + const item = completions?.items.find((item) => item.label === 'EmptytextImported'); + assert.ok(item); + + const shouldNotExistItem = completions?.items.find((item) => item.label === 'Completions'); + assert.ok( + !shouldNotExistItem, + 'expected auto-import be filtered out by current identifier' + ); + }); }); diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions_unclose-tag.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions_unclose-tag.svelte new file mode 100644 index 000000000..73d8e9bde --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions_unclose-tag.svelte @@ -0,0 +1,2 @@ + + + import * as Components from './ComponentDef' + + +