From 52d24dee962b46402161d98376e2d63871c491bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 26 Nov 2023 10:27:23 +0100 Subject: [PATCH 1/5] Include all type parameters in completions within type parameters' constraints --- src/services/completions.ts | 69 ++++++++++++------- ...onListInTypeParameterOfClassExpression1.ts | 2 +- ...etionsForAllTypeParametersInConstraints.ts | 15 ++++ ...sForCurrentOrLaterParametersInDefaults.ts} | 3 +- 4 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 tests/cases/fourslash/completionsForAllTypeParametersInConstraints.ts rename tests/cases/fourslash/{noCompletionsForCurrentOrLaterParameters.ts => noCompletionsForCurrentOrLaterParametersInDefaults.ts} (92%) diff --git a/src/services/completions.ts b/src/services/completions.ts index 9c558f194ab5a..969bdd25830e9 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2534,7 +2534,7 @@ export function getCompletionEntriesFromSymbols( includeSymbol = false, ): UniqueNameSet { const start = timestamp(); - const variableOrParameterDeclaration = getVariableOrParameterDeclaration(contextToken, location); + const closestSymbolDeclaration = getClosestSymbolDeclaration(contextToken, location); const useSemicolons = probablyUsesSemicolons(sourceFile); const typeChecker = program.getTypeChecker(); // Tracks unique names. @@ -2613,26 +2613,30 @@ export function getCompletionEntriesFromSymbols( } // Filter out variables from their own initializers // `const a = /* no 'a' here */` - if (tryCast(variableOrParameterDeclaration, isVariableDeclaration) && symbol.valueDeclaration === variableOrParameterDeclaration) { + if (tryCast(closestSymbolDeclaration, isVariableDeclaration) && symbol.valueDeclaration === closestSymbolDeclaration) { return false; } - // Filter out parameters from their own initializers + // Filter out current and latter parameters from defaults // `function f(a = /* no 'a' and 'b' here */, b) { }` or - // `function f(a: T) { }` + // `function f(a: T, b: T2) { }` const symbolDeclaration = symbol.valueDeclaration ?? symbol.declarations?.[0]; - if ( - variableOrParameterDeclaration && symbolDeclaration && ( - (isTypeParameterDeclaration(variableOrParameterDeclaration) && isTypeParameterDeclaration(symbolDeclaration)) || - (isParameter(variableOrParameterDeclaration) && isParameter(symbolDeclaration)) - ) - ) { - const symbolDeclarationPos = symbolDeclaration.pos; - const parameters = isParameter(variableOrParameterDeclaration) ? variableOrParameterDeclaration.parent.parameters : - isInferTypeNode(variableOrParameterDeclaration.parent) ? undefined : - variableOrParameterDeclaration.parent.typeParameters; - if (symbolDeclarationPos >= variableOrParameterDeclaration.pos && parameters && symbolDeclarationPos < parameters.end) { - return false; + if (closestSymbolDeclaration && symbolDeclaration) { + if (isParameter(closestSymbolDeclaration) && isParameter(symbolDeclaration)) { + const parameters = closestSymbolDeclaration.parent.parameters; + if (symbolDeclaration.pos >= closestSymbolDeclaration.pos && symbolDeclaration.pos < parameters.end) { + return false; + } + } + else if ( + isTypeParameterDeclaration(closestSymbolDeclaration) && isTypeParameterDeclaration(symbolDeclaration) + && isInTypeParameterDefault(contextToken) + && !isInferTypeNode(closestSymbolDeclaration.parent) + ) { + const typeParameters = closestSymbolDeclaration.parent.typeParameters; + if (typeParameters && symbolDeclaration.pos >= closestSymbolDeclaration.pos && symbolDeclaration.pos < typeParameters.end) { + return false; + } } } @@ -5767,20 +5771,39 @@ function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text; } -function getVariableOrParameterDeclaration(contextToken: Node | undefined, location: Node) { +function getClosestSymbolDeclaration(contextToken: Node | undefined, location: Node) { if (!contextToken) return; - const possiblyParameterDeclaration = findAncestor(contextToken, node => + let closestDeclaration = findAncestor(contextToken, node => isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) ? "quit" : ((isParameter(node) || isTypeParameterDeclaration(node)) && !isIndexSignatureDeclaration(node.parent))); - const possiblyVariableDeclaration = findAncestor(location, node => - isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) - ? "quit" - : isVariableDeclaration(node)); + if (!closestDeclaration) { + closestDeclaration = findAncestor(location, node => + isFunctionBlock(node) || isArrowFunctionBody(node) || isBindingPattern(node) + ? "quit" + : isVariableDeclaration(node)); + } + return closestDeclaration as ParameterDeclaration | TypeParameterDeclaration | VariableDeclaration | undefined; +} + +function isInTypeParameterDefault(contextToken: Node | undefined) { + if (!contextToken) { + return false; + } - return (possiblyParameterDeclaration || possiblyVariableDeclaration) as ParameterDeclaration | TypeParameterDeclaration | VariableDeclaration | undefined; + let node = contextToken; + let parent = contextToken.parent; + while (parent) { + if (isTypeParameterDeclaration(parent)) { + return parent.default === node; + } + node = parent; + parent = parent.parent; + } + + return false; } function isArrowFunctionBody(node: Node) { diff --git a/tests/cases/fourslash/completionListInTypeParameterOfClassExpression1.ts b/tests/cases/fourslash/completionListInTypeParameterOfClassExpression1.ts index 15f150cef62c1..7435289eb5246 100644 --- a/tests/cases/fourslash/completionListInTypeParameterOfClassExpression1.ts +++ b/tests/cases/fourslash/completionListInTypeParameterOfClassExpression1.ts @@ -7,4 +7,4 @@ ////var C4 = class D{} verify.completions({ marker: ["0", "1", "2", "3"], exact: undefined }); -verify.completions({ marker: "4", exact: completion.globalTypesPlus(["D"]) }); +verify.completions({ marker: "4", exact: completion.globalTypesPlus(["D", "T"]) }); diff --git a/tests/cases/fourslash/completionsForAllTypeParametersInConstraints.ts b/tests/cases/fourslash/completionsForAllTypeParametersInConstraints.ts new file mode 100644 index 0000000000000..5ec429a4cb4e2 --- /dev/null +++ b/tests/cases/fourslash/completionsForAllTypeParametersInConstraints.ts @@ -0,0 +1,15 @@ +/// + +//// // https://github.com/microsoft/TypeScript/issues/56474 +//// function test(a: First, b: Second) {} +//// type A1 = K + +verify.completions({ + marker: ["1"], + includes: ["First", "Second"], +}) + +verify.completions({ + marker: ["2"], + includes: ["K", "L"], +}) diff --git a/tests/cases/fourslash/noCompletionsForCurrentOrLaterParameters.ts b/tests/cases/fourslash/noCompletionsForCurrentOrLaterParametersInDefaults.ts similarity index 92% rename from tests/cases/fourslash/noCompletionsForCurrentOrLaterParameters.ts rename to tests/cases/fourslash/noCompletionsForCurrentOrLaterParametersInDefaults.ts index 6ed833c16f428..46035a64fb388 100644 --- a/tests/cases/fourslash/noCompletionsForCurrentOrLaterParameters.ts +++ b/tests/cases/fourslash/noCompletionsForCurrentOrLaterParametersInDefaults.ts @@ -9,7 +9,6 @@ //// const f5 = (a, b = (c = /*7*/, e) => { }, d = b) => { } //// //// type A1 = K -//// type A2 = K verify.completions({ marker: ["1", "2"], @@ -42,6 +41,6 @@ verify.completions({ }) verify.completions({ - marker: ["T1", "T2"], + marker: ["T1"], excludes: ["K", "L"], }) From 756569ca3519e25b5ab81703c8578aba1d10fb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 30 Aug 2024 01:03:26 +0200 Subject: [PATCH 2/5] filter out directly self-recursive type parameters in their constraints --- src/services/completions.ts | 17 ++++++++++------- ...onsForLatterTypeParametersInConstraints1.ts} | 12 ++++++------ ...pletionsForSelfTypeParameterInConstraint1.ts | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 13 deletions(-) rename tests/cases/fourslash/{completionsForAllTypeParametersInConstraints.ts => completionsForLatterTypeParametersInConstraints1.ts} (70%) create mode 100644 tests/cases/fourslash/completionsForSelfTypeParameterInConstraint1.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index 62810107c8a66..907b411b964e3 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2760,15 +2760,18 @@ export function getCompletionEntriesFromSymbols( return false; } } - else if ( - isTypeParameterDeclaration(closestSymbolDeclaration) && isTypeParameterDeclaration(symbolDeclaration) - && isInTypeParameterDefault(contextToken) - && !isInferTypeNode(closestSymbolDeclaration.parent) - ) { - const typeParameters = closestSymbolDeclaration.parent.typeParameters; - if (typeParameters && symbolDeclaration.pos >= closestSymbolDeclaration.pos && symbolDeclaration.pos < typeParameters.end) { + else if (isTypeParameterDeclaration(closestSymbolDeclaration) && isTypeParameterDeclaration(symbolDeclaration)) { + if (closestSymbolDeclaration === symbolDeclaration && location === symbolDeclaration.constraint) { + // filter out the directly self-recursive type parameters + // `type A = K` return false; } + if (isInTypeParameterDefault(contextToken) && !isInferTypeNode(closestSymbolDeclaration.parent)) { + const typeParameters = closestSymbolDeclaration.parent.typeParameters; + if (typeParameters && symbolDeclaration.pos >= closestSymbolDeclaration.pos && symbolDeclaration.pos < typeParameters.end) { + return false; + } + } } } diff --git a/tests/cases/fourslash/completionsForAllTypeParametersInConstraints.ts b/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts similarity index 70% rename from tests/cases/fourslash/completionsForAllTypeParametersInConstraints.ts rename to tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts index 5ec429a4cb4e2..81f10e89d27b4 100644 --- a/tests/cases/fourslash/completionsForAllTypeParametersInConstraints.ts +++ b/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts @@ -5,11 +5,11 @@ //// type A1 = K verify.completions({ - marker: ["1"], - includes: ["First", "Second"], -}) + marker: ["1"], + includes: ["Second"], +}); verify.completions({ - marker: ["2"], - includes: ["K", "L"], -}) + marker: ["2"], + includes: ["L"], +}); diff --git a/tests/cases/fourslash/completionsForSelfTypeParameterInConstraint1.ts b/tests/cases/fourslash/completionsForSelfTypeParameterInConstraint1.ts new file mode 100644 index 0000000000000..89eb4631afc5a --- /dev/null +++ b/tests/cases/fourslash/completionsForSelfTypeParameterInConstraint1.ts @@ -0,0 +1,15 @@ +/// + +//// type StateMachine = { +//// initial?: "states" extends keyof Config ? keyof Config["states"] : never; +//// states?: Record; +//// }; + +//// declare function createMachine>( +//// config: Config, +//// ): void; + +verify.completions({ + marker: ["1"], + includes: ["Config"], +}); From 4efc823cf75b67c63081437f48804522b14c4e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 30 Aug 2024 01:29:08 +0200 Subject: [PATCH 3/5] use a better strategy --- src/services/completions.ts | 2 +- .../completionsForLatterTypeParametersInConstraints1.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 907b411b964e3..ba57c16f90015 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2761,7 +2761,7 @@ export function getCompletionEntriesFromSymbols( } } else if (isTypeParameterDeclaration(closestSymbolDeclaration) && isTypeParameterDeclaration(symbolDeclaration)) { - if (closestSymbolDeclaration === symbolDeclaration && location === symbolDeclaration.constraint) { + if (closestSymbolDeclaration === symbolDeclaration && contextToken?.kind === SyntaxKind.ExtendsKeyword) { // filter out the directly self-recursive type parameters // `type A = K` return false; diff --git a/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts b/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts index 81f10e89d27b4..1ef26bc74dc24 100644 --- a/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts +++ b/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts @@ -7,9 +7,11 @@ verify.completions({ marker: ["1"], includes: ["Second"], + excludes: ["First"], }); verify.completions({ marker: ["2"], includes: ["L"], + excludes: ["K"], }); From 70a7b155fbd94c1f2224ef65b8bfe53c361ac5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 30 Aug 2024 11:12:16 +0200 Subject: [PATCH 4/5] fixed `isInTypeParameterDefault` --- src/services/completions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index ba57c16f90015..3291902986fa0 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -6034,7 +6034,7 @@ function isInTypeParameterDefault(contextToken: Node | undefined) { let parent = contextToken.parent; while (parent) { if (isTypeParameterDeclaration(parent)) { - return parent.default === node; + return parent.default === node || node.kind === SyntaxKind.EqualsToken; } node = parent; parent = parent.parent; From 4b174f4bf6adca9d42d777921c8de2ac01c4dd5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 30 Aug 2024 11:40:50 +0200 Subject: [PATCH 5/5] fixed test case --- .../completionListInTypeParameterOfClassExpression1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/fourslash/completionListInTypeParameterOfClassExpression1.ts b/tests/cases/fourslash/completionListInTypeParameterOfClassExpression1.ts index 7435289eb5246..15f150cef62c1 100644 --- a/tests/cases/fourslash/completionListInTypeParameterOfClassExpression1.ts +++ b/tests/cases/fourslash/completionListInTypeParameterOfClassExpression1.ts @@ -7,4 +7,4 @@ ////var C4 = class D{} verify.completions({ marker: ["0", "1", "2", "3"], exact: undefined }); -verify.completions({ marker: "4", exact: completion.globalTypesPlus(["D", "T"]) }); +verify.completions({ marker: "4", exact: completion.globalTypesPlus(["D"]) });