diff --git a/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts b/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts index bf3c6a611..a28f89368 100644 --- a/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts +++ b/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts @@ -1,5 +1,5 @@ import ts from 'typescript'; -import { flatten, isNotNullOrUndefined } from '../../utils'; +import { isNotNullOrUndefined } from '../../utils'; import { findContainingNode } from './features/utils'; export type ComponentPartInfo = Array<{ name: string; type: string; doc?: string }>; @@ -8,7 +8,6 @@ export interface ComponentInfoProvider { getEvents(): ComponentPartInfo; getSlotLets(slot?: string): ComponentPartInfo; getProps(): ComponentPartInfo; - getProp(propName: string): ts.CompletionEntry[]; } export class JsOrTsComponentInfoProvider implements ComponentInfoProvider { @@ -54,61 +53,6 @@ export class JsOrTsComponentInfoProvider implements ComponentInfoProvider { return this.mapPropertiesOfType(props); } - getProp(propName: string): ts.CompletionEntry[] { - const props = this.getType('$$prop_def'); - if (!props) { - return []; - } - - const prop = props.getProperties().find((prop) => prop.name === propName); - if (!prop?.valueDeclaration) { - return []; - } - - const propDef = this.typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration); - - if (!propDef.isUnion()) { - return []; - } - - const types = flatten(propDef.types.map((type) => this.getStringLiteralTypes(type))); - - // adopted from https://github.com/microsoft/TypeScript/blob/0921eac6dc9eba0be6319dff10b85d60c90155ea/src/services/stringCompletions.ts#L61 - return types.map((v) => ({ - name: v.value, - kindModifiers: ts.ScriptElementKindModifier.none, - kind: ts.ScriptElementKind.string, - sortText: /**LocationPriority: */ '11' - })); - } - - /** - * adopted from https://github.com/microsoft/TypeScript/blob/0921eac6dc9eba0be6319dff10b85d60c90155ea/src/services/stringCompletions.ts#L310 - */ - private getStringLiteralTypes( - type: ts.Type | undefined, - uniques = new Set() - ): ts.StringLiteralType[] { - if (!type) { - return []; - } - - type = type.isTypeParameter() ? type.getConstraint() || type : type; - - if (type.isUnion()) { - return flatten(type.types.map((t) => this.getStringLiteralTypes(t, uniques))); - } - - if ( - type.isStringLiteral() && - !(type.flags & ts.TypeFlags.EnumLiteral) && - !uniques.has(type.value) - ) { - return [type]; - } - return []; - } - private getType(classProperty: string) { const symbol = this.classType.getProperty(classProperty); if (!symbol?.valueDeclaration) { diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index 96db365f9..2eee5ecee 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -205,6 +205,32 @@ export class CompletionsProviderImpl implements CompletionsProvider 500 && svelteNode?.type === 'InlineComponent' && - [' ', ' >', ' /'].includes( - document.getText().substring(originalOffset - 1, originalOffset + 1) - ) + this.mightBeAtStartTagWhitespace(document, originalOffset) ) { // Very likely false global completions inside component start tag -> narrow - const props = - (!attributeContext?.inValue && - componentInfo - ?.getProps() - .map((entry) => - this.componentInfoToCompletionEntry( - entry, - '', - CompletionItemKind.Field, - document, - wordRange - ) - )) || - []; - return CompletionList.create( - [...eventAndSlotLetCompletions, ...props], - !!tsDoc.parserError + return this.getCompletionListForDirectiveOrProps( + attributeContext, + componentInfo, + document, + wordRange, + eventAndSlotLetCompletions, + tsDoc ); } @@ -369,6 +383,12 @@ export class CompletionsProviderImpl implements CompletionsProvider', ' /'].includes( + document.getText().substring(originalOffset - 1, originalOffset + 1) + ); + } + private canReuseLastCompletion( lastCompletion: LastCompletion | undefined, triggerKind: number | undefined, @@ -463,6 +483,34 @@ export class CompletionsProviderImpl implements CompletionsProvider[], + tsDoc: SvelteDocumentSnapshot + ) { + const props = + (!attributeContext?.inValue && + componentInfo + ?.getProps() + .map((entry) => + this.componentInfoToCompletionEntry( + entry, + '', + CompletionItemKind.Field, + document, + wordRange + ) + )) || + []; + return CompletionList.create( + [...eventAndSlotLetCompletions, ...props], + !!tsDoc.parserError + ); + } + private toCompletionItem( snapshot: SvelteDocumentSnapshot, comp: ts.CompletionEntry, diff --git a/packages/language-server/src/plugins/typescript/features/utils.ts b/packages/language-server/src/plugins/typescript/features/utils.ts index cfbaf4643..81e0fc472 100644 --- a/packages/language-server/src/plugins/typescript/features/utils.ts +++ b/packages/language-server/src/plugins/typescript/features/utils.ts @@ -44,11 +44,22 @@ export function getComponentAtPosition( return null; } - const generatedPosition = tsDoc.getGeneratedPosition(doc.positionAt(node.start + 1)); - const def = lang.getDefinitionAtPosition( - tsDoc.filePath, - tsDoc.offsetAt(generatedPosition) - )?.[0]; + const symbolPosWithinNode = node.tag?.includes('.') ? node.tag.lastIndexOf('.') + 1 : 0; + + const generatedPosition = tsDoc.getGeneratedPosition( + doc.positionAt(node.start + symbolPosWithinNode + 1) + ); + + let def = lang.getDefinitionAtPosition(tsDoc.filePath, tsDoc.offsetAt(generatedPosition))?.[0]; + + while (def != null && def.kind !== ts.ScriptElementKind.classElement) { + const newDef = lang.getDefinitionAtPosition(tsDoc.filePath, def.textSpan.start)?.[0]; + if (newDef?.fileName === def.fileName && newDef?.textSpan.start === def.textSpan.start) { + break; + } + def = newDef; + } + if (!def) { return null; } 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 ee6ee4acf..f8dedcc9e 100644 --- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts @@ -1493,6 +1493,26 @@ describe('CompletionProviderImpl', function () { ); }); + it(`provide props completions for namespaced component`, async () => { + const namespacedComponentTestList: [Position, string][] = [ + [Position.create(9, 26), 'namespace import after tag name'], + [Position.create(9, 35), 'namespace import before tag end'], + [Position.create(10, 27), 'object namespace after tag name'], + [Position.create(10, 36), 'object namespace before tag end'] + ]; + + for (const [position, name] of namespacedComponentTestList) { + const { completionProvider, document } = setup('namespaced.svelte'); + + const completions = await completionProvider.getCompletions(document, position, { + triggerKind: CompletionTriggerKind.Invoked + }); + + const item = completions?.items.find((item) => item.label === 'hi2'); + assert.ok(item, `expected to have completion for ${name}`); + } + }); + // Hacky, but it works. Needed due to testing both new and old transformation after(() => { __resetCache(); diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/ComponentDef.ts b/packages/language-server/test/plugins/typescript/testfiles/completions/ComponentDef.ts index 06126a45d..a49af65f4 100644 --- a/packages/language-server/test/plugins/typescript/testfiles/completions/ComponentDef.ts +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/ComponentDef.ts @@ -33,3 +33,7 @@ export class ComponentDef2 extends SvelteComponentTyped< }, {} > {} + +export class ComponentDef3 extends SvelteComponentTyped< + { hi: string, hi2: string } +> {} diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/namespaced.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/namespaced.svelte new file mode 100644 index 000000000..3e735e9b9 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/namespaced.svelte @@ -0,0 +1,11 @@ + + + + \ No newline at end of file