From 65296fee817bfb6fdca7fdc6f4b8544c4937ce81 Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Tue, 20 May 2025 16:00:08 -0400 Subject: [PATCH 1/4] Make `getTypeArgumentConstraint` work for type arguments of expressions Previously, `getTypeArgumentConstraint` could only find constraints for type arguments of generic type instantiations. Now it additionally supports type arguments of the following expression kinds: - function calls - `new` expressions - tagged templates - JSX expressions - decorators - instantiation expressions --- src/compiler/checker.ts | 104 +++++++++++++++++- src/services/completions.ts | 6 +- ...etionListInTypeLiteralInTypeParameter10.ts | 38 +++++++ ...etionListInTypeLiteralInTypeParameter11.ts | 32 ++++++ ...etionListInTypeLiteralInTypeParameter12.ts | 25 +++++ ...etionListInTypeLiteralInTypeParameter13.ts | 18 +++ ...etionListInTypeLiteralInTypeParameter14.ts | 10 ++ ...etionListInTypeLiteralInTypeParameter15.ts | 15 +++ ...etionListInTypeLiteralInTypeParameter16.ts | 35 ++++++ 9 files changed, 274 insertions(+), 9 deletions(-) create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter10.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter11.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter12.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter13.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter14.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter15.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter16.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 75be36474222f..0c7dbe1fd0c54 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -42624,6 +42624,48 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefined; } + function getSignaturesFromCallLike(node: CallLikeExpression): readonly Signature[] { + switch (node.kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.Decorator: + return getSignaturesOfType( + getTypeOfExpression(node.expression), + SignatureKind.Call, + ); + case SyntaxKind.NewExpression: + return getSignaturesOfType( + getTypeOfExpression(node.expression), + SignatureKind.Construct, + ); + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.JsxOpeningElement: + if (isJsxIntrinsicTagName(node.tagName)) return []; + return getSignaturesOfType( + getTypeOfExpression(node.tagName), + SignatureKind.Call, + ); + case SyntaxKind.TaggedTemplateExpression: + return getSignaturesOfType( + getTypeOfExpression(node.tag), + SignatureKind.Call, + ); + case SyntaxKind.BinaryExpression: + case SyntaxKind.JsxOpeningFragment: + return []; + } + } + + function getTypeParameterConstraintForPositionAcrossSignatures(signatures: readonly Signature[], position: number) { + const relevantTypeParameterConstraints = flatMap(signatures, signature => { + const relevantTypeParameter = signature.typeParameters?.[position]; + if (relevantTypeParameter === undefined) return []; + const relevantConstraint = getConstraintOfTypeParameter(relevantTypeParameter); + if (relevantConstraint === undefined) return []; + return [relevantConstraint]; + }); + return getUnionType(relevantTypeParameterConstraints); + } + function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { checkGrammarTypeArguments(node, node.typeArguments); if (node.kind === SyntaxKind.TypeReference && !isInJSFile(node) && !isInJSDoc(node) && node.typeArguments && node.typeName.end !== node.typeArguments.pos) { @@ -42662,12 +42704,62 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getTypeArgumentConstraint(node: TypeNode): Type | undefined { - const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); - if (!typeReferenceNode) return undefined; - const typeParameters = getTypeParametersForTypeReferenceOrImport(typeReferenceNode); - if (!typeParameters) return undefined; - const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); - return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); + let typeArgumentPosition; + if ( + "typeArguments" in node.parent && // eslint-disable-line local/no-in-operator + Array.isArray(node.parent.typeArguments) + ) { + typeArgumentPosition = node.parent.typeArguments.indexOf(node); + } + + if (typeArgumentPosition !== undefined) { + // The node could be a type argument of a call, a `new` expression, a decorator, an + // instantiation expression, or a generic type instantiation. + + if (isCallLikeExpression(node.parent)) { + return getTypeParameterConstraintForPositionAcrossSignatures( + getSignaturesFromCallLike(node.parent), + typeArgumentPosition, + ); + } + + if (isDecorator(node.parent.parent)) { + return getTypeParameterConstraintForPositionAcrossSignatures( + getSignaturesFromCallLike(node.parent.parent), + typeArgumentPosition, + ); + } + + if (isExpressionWithTypeArguments(node.parent) && isExpressionStatement(node.parent.parent)) { + const uninstantiatedType = checkExpression(node.parent.expression); + + const callConstraint = getTypeParameterConstraintForPositionAcrossSignatures( + getSignaturesOfType(uninstantiatedType, SignatureKind.Call), + typeArgumentPosition, + ); + const constructConstraint = getTypeParameterConstraintForPositionAcrossSignatures( + getSignaturesOfType(uninstantiatedType, SignatureKind.Construct), + typeArgumentPosition, + ); + + // An instantiation expression instantiates both call and construct signatures, so + // if both exist type arguments must be assignable to both constraints. + if (constructConstraint.flags & TypeFlags.Never) return callConstraint; + if (callConstraint.flags & TypeFlags.Never) return constructConstraint; + return getIntersectionType([callConstraint, constructConstraint]); + } + + if (isTypeReferenceType(node.parent)) { + const typeParameters = getTypeParametersForTypeReferenceOrImport(node.parent); + if (!typeParameters) return undefined; + const relevantTypeParameter = typeParameters[typeArgumentPosition]; + const constraint = getConstraintOfTypeParameter(relevantTypeParameter); + return constraint && instantiateType( + constraint, + createTypeMapper(typeParameters, getEffectiveTypeArguments(node.parent, typeParameters)), + ); + } + } } function checkTypeQuery(node: TypeQueryNode) { diff --git a/src/services/completions.ts b/src/services/completions.ts index dc01ea8ede4b9..9e90922d445d4 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -256,7 +256,6 @@ import { isTypeOnlyImportDeclaration, isTypeOnlyImportOrExportDeclaration, isTypeParameterDeclaration, - isTypeReferenceType, isValidTypeOnlyAliasUseSite, isVariableDeclaration, isVariableLike, @@ -5769,8 +5768,9 @@ function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined { function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined { if (!node) return undefined; - if (isTypeNode(node) && isTypeReferenceType(node.parent)) { - return checker.getTypeArgumentConstraint(node); + if (isTypeNode(node)) { + const constraint = checker.getTypeArgumentConstraint(node); + if (constraint) return constraint; } const t = getConstraintOfTypeArgumentProperty(node.parent, checker); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter10.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter10.ts new file mode 100644 index 0000000000000..892e88a882558 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter10.ts @@ -0,0 +1,38 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +////interface Bar { +//// three: boolean; +//// four: { +//// five: unknown; +//// }; +////} +//// +////function a() {} +////a<{/*0*/}>(); +//// +////var b = () => () => {}; +////b()<{/*1*/}>(); +//// +////declare function c(): void +////declare function c(): void +////c<{/*2*/}>(); +//// +////function d() {} +////d<{/*3*/}, {/*4*/}>(); +////d(); +//// +////(() => {})<{/*6*/}>(); + +verify.completions( + { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true }, + { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: true }, + { marker: "2", unsorted: ["one", "two", "three", "four"], isNewIdentifierLocation: true }, + { marker: "3", unsorted: ["one", "two"], isNewIdentifierLocation: true }, + { marker: "4", unsorted: ["three", "four"], isNewIdentifierLocation: true }, + { marker: "5", unsorted: ["five"], isNewIdentifierLocation: true }, + { marker: "6", unsorted: ["one", "two"], isNewIdentifierLocation: true }, +); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter11.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter11.ts new file mode 100644 index 0000000000000..634746dd38582 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter11.ts @@ -0,0 +1,32 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +////interface Bar { +//// three: boolean; +//// four: symbol; +////} +//// +////class A {} +////new A<{/*0*/}>(); +//// +////class B {} +////new B<{/*1*/}, {/*2*/}>(); +//// +////declare const C: { +//// new (): unknown +//// new (): unknown +////} +////new C<{/*3*/}>() +//// +////new (class {})<{/*4*/}>(); + +verify.completions( + { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true }, + { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: true }, + { marker: "2", unsorted: ["three", "four"], isNewIdentifierLocation: true }, + { marker: "3", unsorted: ["one", "two", "three", "four"], isNewIdentifierLocation: true }, + { marker: "4", unsorted: ["one", "two"], isNewIdentifierLocation: true }, +); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter12.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter12.ts new file mode 100644 index 0000000000000..9bf8ec21afaae --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter12.ts @@ -0,0 +1,25 @@ +/// + +////interface Foo { +//// kind: 'foo'; +//// one: string; +////} +////interface Bar { +//// kind: 'bar'; +//// two: number; +////} +//// +////declare function a(): void +////declare function a(): void +////a<{ kind: 'bar', /*0*/ }>(); +//// +////declare function b(kind: 'foo'): void +////declare function b(kind: 'bar'): void +////b<{/*1*/}>('bar'); + +// The completion lists are unfortunately not narrowed here (ideally only +// properties of `Bar` would be suggested). +verify.completions( + { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true }, + { marker: "1", unsorted: ["kind", "one", "two"], isNewIdentifierLocation: true }, +); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter13.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter13.ts new file mode 100644 index 0000000000000..5797134b2b15e --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter13.ts @@ -0,0 +1,18 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////interface Foo { +//// one: string; +//// two: number; +////} +//// +////const Component = () => <>; +//// +////>; +/////>; + +verify.completions( + { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true }, + { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: true }, +); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter14.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter14.ts new file mode 100644 index 0000000000000..79aa45875715e --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter14.ts @@ -0,0 +1,10 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +////declare function f(x: TemplateStringsArray): void; +////f<{/*0*/}>``; + +verify.completions({ marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter15.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter15.ts new file mode 100644 index 0000000000000..352facb90a46d --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter15.ts @@ -0,0 +1,15 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +//// +////declare function decorator(originalMethod: unknown, _context: unknown): never +//// +////class { +//// @decorator<{/*0*/}> +//// method() {} +////} + +verify.completions({ marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true }); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter16.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter16.ts new file mode 100644 index 0000000000000..4c32c2d19fc22 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter16.ts @@ -0,0 +1,35 @@ +/// + +////interface Foo { +//// one: string; +//// two: number; +////} +////interface Bar { +//// three: boolean; +//// four: { +//// five: unknown; +//// }; +////} +//// +////(() => {})<{/*0*/}>; +//// +////(class {})<{/*1*/}>; +//// +////declare const a: { +//// new (): {}; +//// (): {}; +////} +////a<{/*2*/}>; +//// +////declare const b: { +//// new (): {}; +//// (): {}; +////} +////b<{/*3*/}>; + +verify.completions( + { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: true }, + { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: true }, + { marker: "2", unsorted: ["one", "two", "three", "four"], isNewIdentifierLocation: true }, + { marker: "3", unsorted: [], isNewIdentifierLocation: true }, +); From c086eb482c5bf4d5189c69670f653b36cb98d62b Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Thu, 22 May 2025 17:47:29 -0400 Subject: [PATCH 2/4] Suggest completions of property values in type arguments --- src/services/completions.ts | 16 +++++++++++++--- ...letionListInTypeLiteralInTypeParameter17.ts | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter17.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 9e90922d445d4..42e9c82edc4af 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -3625,17 +3625,20 @@ function getCompletionData( } log("getCompletionData: Semantic work: " + (timestamp() - semanticStart)); - const contextualType = previousToken && getContextualType(previousToken, position, sourceFile, typeChecker); + const contextualTypeOrConstraint = previousToken && ( + getContextualType(previousToken, position, sourceFile, typeChecker) ?? + getConstraintOfTypeArgumentProperty(previousToken, typeChecker) + ); // exclude literal suggestions after (#51667) and after closing quote (#52675) // for strings getStringLiteralCompletions handles completions const isLiteralExpected = !tryCast(previousToken, isStringLiteralLike) && !isJsxIdentifierExpected; const literals = !isLiteralExpected ? [] : mapDefined( - contextualType && (contextualType.isUnion() ? contextualType.types : [contextualType]), + contextualTypeOrConstraint && (contextualTypeOrConstraint.isUnion() ? contextualTypeOrConstraint.types : [contextualTypeOrConstraint]), t => t.isLiteral() && !(t.flags & TypeFlags.EnumLiteral) ? t.value : undefined, ); - const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion(previousToken, contextualType, typeChecker); + const recommendedCompletion = previousToken && contextualTypeOrConstraint && getRecommendedCompletion(previousToken, contextualTypeOrConstraint, typeChecker); return { kind: CompletionDataKind.Data, symbols, @@ -5779,6 +5782,13 @@ function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): switch (node.kind) { case SyntaxKind.PropertySignature: return checker.getTypeOfPropertyOfContextualType(t, (node as PropertySignature).symbol.escapedName); + case SyntaxKind.ColonToken: + if (node.parent.kind === SyntaxKind.PropertySignature) { + // The cursor is at a property value location like `Foo<{ x: | }`. + // `t` already refers to the appropriate property type. + return t; + } + break; case SyntaxKind.IntersectionType: case SyntaxKind.TypeLiteral: case SyntaxKind.UnionType: diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter17.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter17.ts new file mode 100644 index 0000000000000..87e0d33dbb3b0 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter17.ts @@ -0,0 +1,18 @@ +/// + +////class Foo {} +////function foo() {} +//// +////type A = Foo<{ x: /*0*/ }>; +////new Foo<{ x: /*1*/ }>(); +////foo<{ x: /*2*/ }>(); +////foo<{ x: /*3*/ }>; +////Foo<{ x: /*4*/ }>; + +verify.completions( + { marker: "0", includes: ['"one"', '2'], isNewIdentifierLocation: false }, + { marker: "1", includes: ['"one"', '2'], isNewIdentifierLocation: false }, + { marker: "2", includes: ['"one"', '2'], isNewIdentifierLocation: false }, + { marker: "3", includes: ['"one"', '2'], isNewIdentifierLocation: false }, + { marker: "4", includes: ['"one"', '2'], isNewIdentifierLocation: false }, +); From 21369876054003b5850acd91b52983c948b7b5ef Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Sat, 24 May 2025 09:06:54 -0400 Subject: [PATCH 3/4] Suggest completions within string literals in type arguments --- src/services/completions.ts | 3 ++- src/services/stringCompletions.ts | 8 +++++++ ...etionListInTypeLiteralInTypeParameter18.ts | 24 +++++++++++++++++++ ...etionListInTypeLiteralInTypeParameter19.ts | 24 +++++++++++++++++++ ...etionListInTypeLiteralInTypeParameter20.ts | 18 ++++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter18.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter19.ts create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter20.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 42e9c82edc4af..2fff294e89fcd 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -5768,7 +5768,8 @@ function tryGetTypeLiteralNode(node: Node): TypeLiteralNode | undefined { return undefined; } -function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined { +/** @internal */ +export function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChecker): Type | undefined { if (!node) return undefined; if (isTypeNode(node)) { diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 6c5523ae1953c..5ce81d089992e 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -3,6 +3,7 @@ import { createCompletionDetails, createCompletionDetailsForSymbol, getCompletionEntriesFromSymbols, + getConstraintOfTypeArgumentProperty, getDefaultCommitCharacters, getPropertiesForObjectExpression, Log, @@ -509,7 +510,12 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL function fromUnionableLiteralType(grandParent: Node): StringLiteralCompletionsFromTypes | StringLiteralCompletionsFromProperties | undefined { switch (grandParent.kind) { + case SyntaxKind.CallExpression: case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + case SyntaxKind.NewExpression: + case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.TypeReference: { const typeArgument = findAncestor(parent, n => n.parent === grandParent) as LiteralTypeNode; if (typeArgument) { @@ -529,6 +535,8 @@ function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringL return undefined; } return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode(objectType)); + case SyntaxKind.PropertySignature: + return { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(getConstraintOfTypeArgumentProperty(grandParent, typeChecker)), isNewIdentifier: false }; case SyntaxKind.UnionType: { const result = fromUnionableLiteralType(walkUpParentheses(grandParent.parent)); if (!result) { diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter18.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter18.ts new file mode 100644 index 0000000000000..0d3c8e7b5a474 --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter18.ts @@ -0,0 +1,24 @@ +/// + +////class Foo {} +////function foo() {} +////declare function tag(x: TemplateStringsArray): void; +////declare function decorator(...args: unknown[]): never +//// +////type A = Foo<{ x: '/*0*/' }>; +////new Foo<{ x: '/*1*/' }>(); +////foo<{ x: '/*2*/' }>(); +////foo<{ x: '/*3*/' }>; +////Foo<{ x: '/*4*/' }>; +////tag<{ x: '/*5*/' }>``; +////class { @decorator<{ x: '/*6*/' }>; method() {} } + +verify.completions( + { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "2", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "3", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "4", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "5", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "6", unsorted: ["one", "two"], isNewIdentifierLocation: false }, +); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter19.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter19.ts new file mode 100644 index 0000000000000..96b8348dbaf8a --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter19.ts @@ -0,0 +1,24 @@ +/// + +////class Foo {} +////function foo() {} +////declare function tag(x: TemplateStringsArray): void; +////declare function decorator(...args: unknown[]): never +//// +////type A = Foo<'/*0*/'>; +////new Foo<'/*1*/'>(); +////foo<'/*2*/'>(); +////foo<'/*3*/'>; +////Foo<'/*4*/'>; +////tag<'/*5*/'>``; +////class { @decorator<'/*6*/'>; method() {} } + +verify.completions( + { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "2", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "3", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "4", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "5", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "6", unsorted: ["one", "two"], isNewIdentifierLocation: false }, +); diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter20.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter20.ts new file mode 100644 index 0000000000000..d2649be7f95ab --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter20.ts @@ -0,0 +1,18 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////const Component1 = () => <>; +////const Component2 = () => <>; +//// +////>; +/////>; +////>; +/////>; + +verify.completions( + { marker: "0", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "1", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "2", unsorted: ["one", "two"], isNewIdentifierLocation: false }, + { marker: "3", unsorted: ["one", "two"], isNewIdentifierLocation: false }, +); From d5bbf02c218fc1be83b2b732e4c721584f563d58 Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Mon, 2 Jun 2025 09:00:13 -0400 Subject: [PATCH 4/4] Suggest completions within tuple type arguments --- src/services/completions.ts | 2 ++ ...letionListInTypeLiteralInTypeParameter21.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/cases/fourslash/completionListInTypeLiteralInTypeParameter21.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 2fff294e89fcd..f9e99d5171c60 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -5794,6 +5794,8 @@ export function getConstraintOfTypeArgumentProperty(node: Node, checker: TypeChe case SyntaxKind.TypeLiteral: case SyntaxKind.UnionType: return t; + case SyntaxKind.OpenBracketToken: + return checker.getElementTypeOfArrayType(t); } } diff --git a/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter21.ts b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter21.ts new file mode 100644 index 0000000000000..b2e6a6cea4b2a --- /dev/null +++ b/tests/cases/fourslash/completionListInTypeLiteralInTypeParameter21.ts @@ -0,0 +1,18 @@ +/// + +////class Foo {} +////function foo() {} +//// +////type A = Foo<[/*0*/]>; +////new Foo<[/*1*/]>(); +////foo<[/*2*/]>(); +////foo<[/*3*/]>; +////Foo<[/*4*/]>; + +verify.completions( + { marker: "0", includes: ['"one"', '2'], isNewIdentifierLocation: true }, + { marker: "1", includes: ['"one"', '2'], isNewIdentifierLocation: true }, + { marker: "2", includes: ['"one"', '2'], isNewIdentifierLocation: true }, + { marker: "3", includes: ['"one"', '2'], isNewIdentifierLocation: true }, + { marker: "4", includes: ['"one"', '2'], isNewIdentifierLocation: true } +);