diff --git a/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts b/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts index a114f645a..ae9a59133 100644 --- a/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts +++ b/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts @@ -8,7 +8,8 @@ export type ComponentPartInfo = ReturnType; export interface ComponentInfoProvider { getEvents(): ComponentPartInfo; getSlotLets(slot?: string): ComponentPartInfo; - getProps(propName: string): ts.CompletionEntry[]; + getProps(): ComponentPartInfo; + getProp(propName: string): ts.CompletionEntry[]; } export class JsOrTsComponentInfoProvider implements ComponentInfoProvider { @@ -45,7 +46,16 @@ export class JsOrTsComponentInfoProvider implements ComponentInfoProvider { return this.mapPropertiesOfType(slotLetsType); } - getProps(propName: string): ts.CompletionEntry[] { + getProps() { + const props = this.getType('$$prop_def'); + if (!props) { + return []; + } + + return this.mapPropertiesOfType(props); + } + + getProp(propName: string): ts.CompletionEntry[] { const props = this.getType('$$prop_def'); if (!props) { return []; diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index dd27474d9..544a24c15 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -3,6 +3,7 @@ import { CancellationToken, CompletionContext, CompletionItem, + CompletionItemKind, CompletionList, CompletionTriggerKind, MarkupContent, @@ -197,6 +198,41 @@ export class CompletionsProviderImpl implements CompletionsProvider 500 && + svelteNode?.type === 'Element' && + completions[0].kind !== ts.ScriptElementKind.memberVariableElement + ) { + // False global completions inside element start tag + return null; + } + + if ( + completions.length > 500 && + svelteNode?.type === 'InlineComponent' && + [' ', ' >', ' /'].includes( + document.getText().substring(originalOffset - 1, originalOffset + 1) + ) + ) { + // Very likely false global completions inside component start tag -> narrow + const props = + componentInfo + ?.getProps() + .map((entry) => + this.componentInfoToCompletionEntry( + entry, + '', + CompletionItemKind.Field, + document, + wordRange + ) + ) || []; + return CompletionList.create( + [...eventAndSlotLetCompletions, ...props], + !!tsDoc.parserError + ); + } + const existingImports = this.getExistingImports(document); const wordRangeStartPosition = document.positionAt(wordRange.start); const completionItems = completions @@ -255,33 +291,59 @@ export class CompletionsProviderImpl implements CompletionsProvider> { if (componentInfo === null) { return []; } + return [ + ...componentInfo + .getEvents() + .map((event) => + this.componentInfoToCompletionEntry( + event, + 'on:', + undefined, + document, + wordRange + ) + ), + ...componentInfo + .getSlotLets() + .map((slot) => + this.componentInfoToCompletionEntry( + slot, + 'let:', + undefined, + document, + wordRange + ) + ) + ]; + } + + private componentInfoToCompletionEntry( + info: ComponentPartInfo[0], + prefix: string, + kind: CompletionItemKind | undefined, + doc: Document, + wordRange: { start: number; end: number } + ): AppCompletionItem { const { start, end } = wordRange; - const events = componentInfo.getEvents().map((event) => mapToCompletionEntry(event, 'on:')); - const slotLets = componentInfo - .getSlotLets() - .map((slot) => mapToCompletionEntry(slot, 'let:')); - return [...events, ...slotLets]; - - function mapToCompletionEntry(info: ComponentPartInfo[0], prefix: string) { - const slotName = prefix + info.name; - return { - label: slotName, - sortText: '-1', - detail: info.name + ': ' + info.type, - documentation: info.doc && { kind: MarkupKind.Markdown, value: info.doc }, - textEdit: - start !== end - ? TextEdit.replace(toRange(doc.getText(), start, end), slotName) - : undefined - }; - } + const name = prefix + info.name; + return { + label: name, + kind, + sortText: '-1', + detail: info.name + ': ' + info.type, + documentation: info.doc && { kind: MarkupKind.Markdown, value: info.doc }, + textEdit: + start !== end + ? TextEdit.replace(toRange(doc.getText(), start, end), name) + : undefined + }; } private toCompletionItem( @@ -677,7 +739,7 @@ export class CompletionsProviderImpl implements CompletionsProvider ({ + return componentInfo.getProp(jsxAttribute.name.getText()).map((item) => ({ ...item, replacementSpan })); 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 09c55fce8..23e3d2253 100644 --- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts @@ -187,6 +187,7 @@ function test(useNewTransformation: boolean) { documentation: '', label: 'on:aa', sortText: '-1', + kind: undefined, textEdit: undefined }, { @@ -197,6 +198,7 @@ function test(useNewTransformation: boolean) { }, label: 'on:ab', sortText: '-1', + kind: undefined, textEdit: undefined }, { @@ -204,6 +206,7 @@ function test(useNewTransformation: boolean) { documentation: '', label: 'on:ac', sortText: '-1', + kind: undefined, textEdit: undefined } ]); @@ -237,6 +240,7 @@ function test(useNewTransformation: boolean) { documentation: '', label: 'on:aa', sortText: '-1', + kind: undefined, textEdit: { newText: 'on:aa', range: { @@ -259,6 +263,7 @@ function test(useNewTransformation: boolean) { }, label: 'on:ab', sortText: '-1', + kind: undefined, textEdit: { newText: 'on:ab', range: { @@ -278,6 +283,7 @@ function test(useNewTransformation: boolean) { documentation: '', label: 'on:ac', sortText: '-1', + kind: undefined, textEdit: { newText: 'on:ac', range: { @@ -319,6 +325,7 @@ function test(useNewTransformation: boolean) { }, label: 'on:c', sortText: '-1', + kind: undefined, textEdit: undefined } ]); @@ -347,6 +354,7 @@ function test(useNewTransformation: boolean) { documentation: '', label: 'on:event1', sortText: '-1', + kind: undefined, textEdit: { newText: 'on:event1', range: { @@ -369,6 +377,7 @@ function test(useNewTransformation: boolean) { }, label: 'on:event2', sortText: '-1', + kind: undefined, textEdit: { newText: 'on:event2', range: { @@ -409,6 +418,7 @@ function test(useNewTransformation: boolean) { label: 'on:event1', sortText: '-1', documentation: '', + kind: undefined, textEdit: { newText: 'on:event1', range: { @@ -1063,6 +1073,7 @@ function test(useNewTransformation: boolean) { documentation: '', label: 'let:let1', sortText: '-1', + kind: undefined, textEdit: { newText: 'let:let1', range: { @@ -1085,6 +1096,7 @@ function test(useNewTransformation: boolean) { }, label: 'let:let2', sortText: '-1', + kind: undefined, textEdit: { newText: 'let:let2', range: {