From 0a8f9c0dfaa589a1cf9e9d5abc09c5903366280b Mon Sep 17 00:00:00 2001 From: sw1tch3roo Date: Sat, 12 Jul 2025 16:54:26 +0300 Subject: [PATCH 1/2] Fix typeof parameter resolution inconsistency in JSDoc comments --- src/compiler/checker.ts | 35 ++- src/compiler/utilities.ts | 26 ++ .../jsdocTypeofParameterConsistency.symbols | 28 ++ .../jsdocTypeofParameterConsistency.types | 34 +++ ...ocTypeofParameterResolution.baseline.jsonc | 286 ++++++++++++++++++ .../jsdocTypeofParameterConsistency.ts | 22 ++ .../jsdocTypeofParameterResolution.ts | 24 ++ 7 files changed, 452 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/jsdocTypeofParameterConsistency.symbols create mode 100644 tests/baselines/reference/jsdocTypeofParameterConsistency.types create mode 100644 tests/baselines/reference/jsdocTypeofParameterResolution.baseline.jsonc create mode 100644 tests/cases/compiler/jsdocTypeofParameterConsistency.ts create mode 100644 tests/cases/fourslash/jsdocTypeofParameterResolution.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4875b43d4e867..66fc769096b78 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -49630,6 +49630,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function getParameterSymbolFromJSDocHost(node: Identifier): Symbol | undefined { + if (!isIdentifier(node)) { + return undefined; + } + const name = node.escapedText; + const decl = getHostSignatureFromJSDoc(node); + if (!decl) { + return undefined; + } + const parameter = find(decl.parameters, p => p.name.kind === SyntaxKind.Identifier && p.name.escapedText === name); + if (parameter && parameter.symbol) { + return parameter.symbol; + } + // If parameter.symbol is not available, try to get it from the function's locals + if (parameter && decl.locals) { + return decl.locals.get(name); + } + return undefined; + } + + function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { if (isDeclarationName(name)) { return getSymbolOfNode(name.parent); @@ -49716,20 +49737,28 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const isJSDoc = findAncestor(name, or(isJSDocLinkLike, isJSDocNameReference, isJSDocMemberName)); - const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value; + const isJSDocContext = !!(name.flags & NodeFlags.JSDoc) || isJSDoc; + const meaning = isJSDocContext ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value; if (name.kind === SyntaxKind.Identifier) { if (isJSXTagName(name) && isJsxIntrinsicTagName(name)) { const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); return symbol === unknownSymbol ? undefined : symbol; } + // Check for parameter symbol in JSDoc typeof context + if (isJSDocContext && isInTypeQuery(name)) { + const parameterSymbol = getParameterSymbolFromJSDocHost(name); + if (parameterSymbol) { + return parameterSymbol; + } + } const result = resolveEntityName(name, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); - if (!result && isJSDoc) { + if (!result && isJSDocContext) { const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); if (container) { return resolveJSDocMemberName(name, /*ignoreErrors*/ true, getSymbolOfDeclaration(container)); } } - if (result && isJSDoc) { + if (result && isJSDocContext) { const container = getJSDocHost(name); if (container && isEnumMember(container) && container === result.valueDeclaration) { return resolveEntityName(name, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, getSourceFileOfNode(container)) || result; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f5b785dc5db8f..a1c59487ec14b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -11829,6 +11829,13 @@ export function createNameResolver({ return undefined; } if (!result) { + // Check for parameter symbol in JSDoc typeof context before failing + if (originalLocation && !isString(nameArg) && (originalLocation.flags & NodeFlags.JSDoc) && isInTypeQuery(originalLocation)) { + const parameterSymbol = getParameterSymbolFromJSDocHost(originalLocation, (nameArg as Identifier).escapedText); + if (parameterSymbol) { + return parameterSymbol; + } + } onFailedToResolveSymbol(originalLocation, nameArg, meaning, nameNotFoundMessage); } else { @@ -11839,6 +11846,25 @@ export function createNameResolver({ return result; } + function getParameterSymbolFromJSDocHost(node: Node, name: __String): Symbol | undefined { + const decl = getHostSignatureFromJSDoc(node); + if (!decl) { + return undefined; + } + const parameter = find(decl.parameters, p => p.name && p.name.kind === SyntaxKind.Identifier && p.name.escapedText === name); + if (parameter && parameter.symbol) { + return parameter.symbol; + } + // If parameter.symbol is not available, try to get it from the function's locals + if (parameter && decl.locals) { + const localSymbol = decl.locals.get(name); + if (localSymbol) { + return localSymbol; + } + } + return undefined; + } + function useOuterVariableScopeInParameter(result: Symbol, location: Node, lastLocation: Node) { const target = getEmitScriptTarget(compilerOptions); const functionLocation = location as FunctionLikeDeclaration; diff --git a/tests/baselines/reference/jsdocTypeofParameterConsistency.symbols b/tests/baselines/reference/jsdocTypeofParameterConsistency.symbols new file mode 100644 index 0000000000000..4bdc461217990 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeofParameterConsistency.symbols @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/jsdocTypeofParameterConsistency.ts] //// + +=== jsdocTypeofParameterConsistency.js === +/** + * @template T + * @param {T} a + * @return {typeof a} + */ +function f(a) { +>f : Symbol(f, Decl(jsdocTypeofParameterConsistency.js, 0, 0)) +>a : Symbol(a, Decl(jsdocTypeofParameterConsistency.js, 5, 11)) + + return a; +>a : Symbol(a, Decl(jsdocTypeofParameterConsistency.js, 5, 11)) +} + +/** + * @template T + * @param {T} b + * @return {typeof b} + */ +function g(b) { +>g : Symbol(g, Decl(jsdocTypeofParameterConsistency.js, 7, 1)) +>b : Symbol(b, Decl(jsdocTypeofParameterConsistency.js, 14, 11)) + + return b; +>b : Symbol(b, Decl(jsdocTypeofParameterConsistency.js, 14, 11)) +} diff --git a/tests/baselines/reference/jsdocTypeofParameterConsistency.types b/tests/baselines/reference/jsdocTypeofParameterConsistency.types new file mode 100644 index 0000000000000..9baf2bdaba2d2 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeofParameterConsistency.types @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/jsdocTypeofParameterConsistency.ts] //// + +=== jsdocTypeofParameterConsistency.js === +/** + * @template T + * @param {T} a + * @return {typeof a} + */ +function f(a) { +>f : (a: T) => typeof a +> : ^ ^^ ^^ ^^^^^ +>a : T +> : ^ + + return a; +>a : T +> : ^ +} + +/** + * @template T + * @param {T} b + * @return {typeof b} + */ +function g(b) { +>g : (b: T) => typeof b +> : ^ ^^ ^^ ^^^^^ +>b : T +> : ^ + + return b; +>b : T +> : ^ +} diff --git a/tests/baselines/reference/jsdocTypeofParameterResolution.baseline.jsonc b/tests/baselines/reference/jsdocTypeofParameterResolution.baseline.jsonc new file mode 100644 index 0000000000000..4b1b59a61a45d --- /dev/null +++ b/tests/baselines/reference/jsdocTypeofParameterResolution.baseline.jsonc @@ -0,0 +1,286 @@ +// === findAllReferences === +// === /tests/cases/fourslash/jsdocTypeofParameterResolution.ts === +// /** +// * @template T +// * @param {T} /*FIND ALL REFS*/[|a|] +// * @return {typeof [|a|]} +// */ +// function f([|{| isWriteAccess: true |}a|]) { +// return [|a|]; +// } +// +// let a = 123; +// --- (line: 11) skipped --- + + // === Definitions === + // === /tests/cases/fourslash/jsdocTypeofParameterResolution.ts === + // /** + // * @template T + // * @param {T} /*FIND ALL REFS*/a + // * @return {typeof a} + // */ + // function f([|a|]) { + // return a; + // } + // + // --- (line: 10) skipped --- + + // === Details === + [ + { + "containerKind": "", + "containerName": "", + "kind": "parameter", + "name": "(parameter) a: any", + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "parameter", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "a", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "any", + "kind": "keyword" + } + ] + } + ] + + + +// === findAllReferences === +// === /tests/cases/fourslash/jsdocTypeofParameterResolution.ts === +// /** +// * @template T +// * @param {T} [|a|] +// * @return {typeof /*FIND ALL REFS*/[|a|]} +// */ +// function f([|{| isWriteAccess: true |}a|]) { +// return [|a|]; +// } +// +// let a = 123; +// --- (line: 11) skipped --- + + // === Definitions === + // === /tests/cases/fourslash/jsdocTypeofParameterResolution.ts === + // /** + // * @template T + // * @param {T} a + // * @return {typeof /*FIND ALL REFS*/a} + // */ + // function f([|a|]) { + // return a; + // } + // + // --- (line: 10) skipped --- + + // === Details === + [ + { + "containerKind": "", + "containerName": "", + "kind": "parameter", + "name": "(parameter) a: any", + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "parameter", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "a", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "any", + "kind": "keyword" + } + ] + } + ] + + + +// === findAllReferences === +// === /tests/cases/fourslash/jsdocTypeofParameterResolution.ts === +// --- (line: 10) skipped --- +// +// /** +// * @template T +// * @param {T} /*FIND ALL REFS*/[|a|] +// * @return {typeof [|a|]} +// */ +// function g([|{| isWriteAccess: true |}a|]) { +// return [|a|]; +// } + + // === Definitions === + // === /tests/cases/fourslash/jsdocTypeofParameterResolution.ts === + // --- (line: 10) skipped --- + // + // /** + // * @template T + // * @param {T} /*FIND ALL REFS*/a + // * @return {typeof a} + // */ + // function g([|a|]) { + // return a; + // } + + // === Details === + [ + { + "containerKind": "", + "containerName": "", + "kind": "parameter", + "name": "(parameter) a: any", + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "parameter", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "a", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "any", + "kind": "keyword" + } + ] + } + ] + + + +// === findAllReferences === +// === /tests/cases/fourslash/jsdocTypeofParameterResolution.ts === +// --- (line: 10) skipped --- +// +// /** +// * @template T +// * @param {T} [|a|] +// * @return {typeof /*FIND ALL REFS*/[|a|]} +// */ +// function g([|{| isWriteAccess: true |}a|]) { +// return [|a|]; +// } + + // === Definitions === + // === /tests/cases/fourslash/jsdocTypeofParameterResolution.ts === + // --- (line: 11) skipped --- + // /** + // * @template T + // * @param {T} a + // * @return {typeof /*FIND ALL REFS*/a} + // */ + // function g([|a|]) { + // return a; + // } + + // === Details === + [ + { + "containerKind": "", + "containerName": "", + "kind": "parameter", + "name": "(parameter) a: any", + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "parameter", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "a", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "any", + "kind": "keyword" + } + ] + } + ] \ No newline at end of file diff --git a/tests/cases/compiler/jsdocTypeofParameterConsistency.ts b/tests/cases/compiler/jsdocTypeofParameterConsistency.ts new file mode 100644 index 0000000000000..ef9c74c0a57a3 --- /dev/null +++ b/tests/cases/compiler/jsdocTypeofParameterConsistency.ts @@ -0,0 +1,22 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @filename: jsdocTypeofParameterConsistency.js +/** + * @template T + * @param {T} a + * @return {typeof a} + */ +function f(a) { + return a; +} + +/** + * @template T + * @param {T} b + * @return {typeof b} + */ +function g(b) { + return b; +} \ No newline at end of file diff --git a/tests/cases/fourslash/jsdocTypeofParameterResolution.ts b/tests/cases/fourslash/jsdocTypeofParameterResolution.ts new file mode 100644 index 0000000000000..b187a3c0771fb --- /dev/null +++ b/tests/cases/fourslash/jsdocTypeofParameterResolution.ts @@ -0,0 +1,24 @@ +/// + +/////** +//// * @template T +//// * @param {T} /*1*/a +//// * @return {typeof /*2*/a} +//// */ +////function f(a) { +//// return a; +////} +//// +////let a = 123; +//// +/////** +//// * @template T +//// * @param {T} /*3*/a +//// * @return {typeof /*4*/a} +//// */ +////function g(a) { +//// return a; +////} + +// Test that typeof a in JSDoc resolves to parameter consistently +verify.baselineFindAllReferences("1", "2", "3", "4"); \ No newline at end of file From 836d98fd7f128b663a32a3fac80183c29f134986 Mon Sep 17 00:00:00 2001 From: sw1tch3roo Date: Sat, 12 Jul 2025 17:12:03 +0300 Subject: [PATCH 2/2] hereby format --- src/compiler/checker.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1590e7548b73f..c2514af2af229 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -49720,7 +49720,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefined; } - function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { if (isDeclarationName(name)) { return getSymbolOfNode(name.parent); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy