diff --git a/src/services/completions.ts b/src/services/completions.ts index 65de51651a0f1..3291902986fa0 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -2666,7 +2666,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. @@ -2745,26 +2745,33 @@ 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)) { + if (closestSymbolDeclaration === symbolDeclaration && contextToken?.kind === SyntaxKind.ExtendsKeyword) { + // 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; + } + } } } @@ -6001,20 +6008,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.kind === SyntaxKind.EqualsToken; + } + node = parent; + parent = parent.parent; + } + + return false; } function isArrowFunctionBody(node: Node) { diff --git a/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts b/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts new file mode 100644 index 0000000000000..1ef26bc74dc24 --- /dev/null +++ b/tests/cases/fourslash/completionsForLatterTypeParametersInConstraints1.ts @@ -0,0 +1,17 @@ +/// + +//// // https://github.com/microsoft/TypeScript/issues/56474 +//// function test(a: First, b: Second) {} +//// type A1 = K + +verify.completions({ + marker: ["1"], + includes: ["Second"], + excludes: ["First"], +}); + +verify.completions({ + marker: ["2"], + includes: ["L"], + excludes: ["K"], +}); 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"], +}); 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"], })