From f980e3cf3897c6630a9fd10aa4f67bbc5511c6cb Mon Sep 17 00:00:00 2001 From: jyx-07 Date: Thu, 27 Aug 2026 21:00:18 +0900 Subject: [PATCH] Fix false positive TS8030 for JSDoc @type on optional interface methods When a JSDoc @type annotation on a function resolves through an optional interface member (e.g. Example['method'] where method is optional), the resolved type includes | undefined. Three call sites in checker.go fed that raw union directly into signature-lookup helpers that don't handle unions with undefined, producing a false TS8030 diagnostic and an implicit any for the parameter. Strip the undefined via the existing removeMissingOrUndefinedType helper at all three sites before signature resolution, matching the pattern already used elsewhere in the checker for the same class of problem. --- tsc/internal/checker/checker.go | 6 ++-- ...sdocOptionalMethodFullSignature.errors.txt | 20 ++++++++++++ .../jsdocOptionalMethodFullSignature.symbols | 29 +++++++++++++++++ .../jsdocOptionalMethodFullSignature.types | 31 +++++++++++++++++++ .../jsdocOptionalMethodFullSignature.ts | 19 ++++++++++++ 5 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.types create mode 100644 tsc/testdata/tests/cases/compiler/jsdocOptionalMethodFullSignature.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index fb3c33c01b814..0732f7695d41c 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -3449,7 +3449,7 @@ func (c *Checker) checkFunctionOrMethodDeclaration(node *ast.Node) { c.checkSourceElement(body) c.checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, c.getReturnTypeFromAnnotation(node)) if node.FunctionLikeData().FullSignature != nil { - if c.getContextualCallSignature(c.getTypeFromTypeNode(node.FunctionLikeData().FullSignature), node) == nil { + if c.getContextualCallSignature(c.removeMissingOrUndefinedType(c.getTypeFromTypeNode(node.FunctionLikeData().FullSignature)), node) == nil { c.error(node.FunctionLikeData().FullSignature, diagnostics.A_JSDoc_type_tag_on_a_function_must_have_a_signature_with_the_correct_number_of_arguments) } } @@ -10233,7 +10233,7 @@ func (c *Checker) checkFunctionExpressionOrObjectLiteralMethod(node *ast.Node, c c.checkGrammarForGenerator(node) } if node.FunctionLikeData().FullSignature != nil { - if c.getContextualCallSignature(c.getTypeFromTypeNode(node.FunctionLikeData().FullSignature), node) == nil { + if c.getContextualCallSignature(c.removeMissingOrUndefinedType(c.getTypeFromTypeNode(node.FunctionLikeData().FullSignature)), node) == nil { c.error(node.FunctionLikeData().FullSignature, diagnostics.A_JSDoc_type_tag_on_a_function_must_have_a_signature_with_the_correct_number_of_arguments) } } @@ -20188,7 +20188,7 @@ func (c *Checker) getReturnTypeFromAnnotation(declaration *ast.Node) *Type { func (c *Checker) getSignatureOfFullSignatureType(node *ast.Node) *Signature { if ast.IsInJSFile(node) && (ast.IsFunctionDeclaration(node) || ast.IsMethodDeclaration(node) || ast.IsFunctionExpressionOrArrowFunction(node)) && node.FunctionLikeData().FullSignature != nil { - return c.getSingleCallSignature(c.getTypeFromTypeNode(node.FunctionLikeData().FullSignature)) + return c.getSingleCallSignature(c.removeMissingOrUndefinedType(c.getTypeFromTypeNode(node.FunctionLikeData().FullSignature))) } return nil } diff --git a/tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.errors.txt b/tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.errors.txt new file mode 100644 index 0000000000000..04781d1e0d8e9 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.errors.txt @@ -0,0 +1,20 @@ +/a.js(10,12): error TS8030: A JSDoc '@type' tag on a function must have a signature with the correct number of arguments. + + +==== /a.js (1 errors) ==== + /** @typedef {{ method?: (s: string) => number }} Example */ + + const example = { + /** @type {Example['method']} */ + method(s) { + return s.length; + } + }; + + /** @type {Example['method']} */ + ~~~~~~~~~~~~~~~~~ +!!! error TS8030: A JSDoc '@type' tag on a function must have a signature with the correct number of arguments. + function tooManyParams(s, extra) { + return 0; + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.symbols b/tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.symbols new file mode 100644 index 0000000000000..918b548af9223 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.symbols @@ -0,0 +1,29 @@ +//// [tests/cases/compiler/jsdocOptionalMethodFullSignature.ts] //// + +=== /a.js === +/** @typedef {{ method?: (s: string) => number }} Example */ + +const example = { +>example : Symbol(example, Decl(a.js, 2, 5)) + + /** @type {Example['method']} */ + method(s) { +>method : Symbol(method, Decl(a.js, 2, 17)) +>s : Symbol(s, Decl(a.js, 4, 9)) + + return s.length; +>s.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>s : Symbol(s, Decl(a.js, 4, 9)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + } +}; + +/** @type {Example['method']} */ +function tooManyParams(s, extra) { +>tooManyParams : Symbol(tooManyParams, Decl(a.js, 7, 2)) +>s : Symbol(s, Decl(a.js, 10, 23)) +>extra : Symbol(extra, Decl(a.js, 10, 25)) + + return 0; +} + diff --git a/tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.types b/tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.types new file mode 100644 index 0000000000000..51186de351988 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsdocOptionalMethodFullSignature.types @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/jsdocOptionalMethodFullSignature.ts] //// + +=== /a.js === +/** @typedef {{ method?: (s: string) => number }} Example */ + +const example = { +>example : { method(s: string): number; } +>{ /** @type {Example['method']} */ method(s) { return s.length; }} : { method(s: string): number; } + + /** @type {Example['method']} */ + method(s) { +>method : (s: string) => number +>s : string + + return s.length; +>s.length : number +>s : string +>length : number + } +}; + +/** @type {Example['method']} */ +function tooManyParams(s, extra) { +>tooManyParams : (s: string) => number +>s : string +>extra : any + + return 0; +>0 : 0 +} + diff --git a/tsc/testdata/tests/cases/compiler/jsdocOptionalMethodFullSignature.ts b/tsc/testdata/tests/cases/compiler/jsdocOptionalMethodFullSignature.ts new file mode 100644 index 0000000000000..2ab5f1ca0f298 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/jsdocOptionalMethodFullSignature.ts @@ -0,0 +1,19 @@ +// @target: es2015 +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @Filename: /a.js +/** @typedef {{ method?: (s: string) => number }} Example */ + +const example = { + /** @type {Example['method']} */ + method(s) { + return s.length; + } +}; + +/** @type {Example['method']} */ +function tooManyParams(s, extra) { + return 0; +}