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);