From ad98c177d3b79373edbb817fb790b12cdde3693f Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:09:12 -0400 Subject: [PATCH] Keep nested JSDoc properties after a property with a missing name When a nested @property type does not parse, the name of that tag is missing. The nested type literal then ended at that tag, so every later nested property was lost. Flat typedefs already keep the later properties. Fixes #65020 Co-Authored-By: Claude Opus 5.5 --- tsc/internal/parser/jsdoc.go | 1 + ...docNestedPropertyTagInvalidType.errors.txt | 33 ++++++++++++++ .../jsdocNestedPropertyTagInvalidType.symbols | 41 ++++++++++++++++++ .../jsdocNestedPropertyTagInvalidType.types | 43 +++++++++++++++++++ .../jsdocNestedPropertyTagInvalidType.ts | 29 +++++++++++++ 5 files changed, 147 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.types create mode 100644 tsc/testdata/tests/cases/compiler/jsdocNestedPropertyTagInvalidType.ts diff --git a/tsc/internal/parser/jsdoc.go b/tsc/internal/parser/jsdoc.go index 6d239d2f228fc..02ed4887d4585 100644 --- a/tsc/internal/parser/jsdoc.go +++ b/tsc/internal/parser/jsdoc.go @@ -1197,6 +1197,7 @@ func (p *Parser) parseChildParameterOrPropertyTag(target propertyLikeParse, inde child := p.tryParseChildTag(target, indent) if child != nil && name != nil && (child.Kind == ast.KindJSDocParameterTag || child.Kind == ast.KindJSDocPropertyTag) && + !ast.NodeIsMissing(child.Name()) && (ast.IsIdentifier(child.Name()) || !textsEqual(name, child.Name().AsQualifiedName().Left)) { return nil } diff --git a/tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.errors.txt b/tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.errors.txt new file mode 100644 index 0000000000000..38f1717287636 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.errors.txt @@ -0,0 +1,33 @@ +/a.js(5,23): error TS1005: '}' expected. +/a.js(12,23): error TS1005: '}' expected. + + +==== /a.js (2 errors) ==== + /** + * @typedef Sig + * @property {object} Args + * @property {string} Args.name + * @property {function(string)=} Args.onSave + ~ +!!! error TS1005: '}' expected. + * @property {((v: string) => void)=} Args.onCancel + */ + + /** + * @typedef Flat + * @property {string} name + * @property {function(string)=} onSave + ~ +!!! error TS1005: '}' expected. + * @property {((v: string) => void)=} onCancel + */ + + export {}; + +==== /b.ts (0 errors) ==== + import type { Sig, Flat } from "./a.js"; + + export const nested: Array = ["name", "onCancel"]; + export const flat: Array = ["name", "onCancel"]; + export const onCancel: Sig["Args"]["onCancel"] = (v: string) => {}; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.symbols b/tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.symbols new file mode 100644 index 0000000000000..a32c2b0e1d739 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.symbols @@ -0,0 +1,41 @@ +//// [tests/cases/compiler/jsdocNestedPropertyTagInvalidType.ts] //// + +=== /a.js === + +/** + * @typedef Sig + * @property {object} Args + * @property {string} Args.name + * @property {function(string)=} Args.onSave + * @property {((v: string) => void)=} Args.onCancel + */ + +/** + * @typedef Flat + * @property {string} name + * @property {function(string)=} onSave + * @property {((v: string) => void)=} onCancel + */ + +export {}; + +=== /b.ts === +import type { Sig, Flat } from "./a.js"; +>Sig : Symbol(Sig, Decl(b.ts, 0, 13)) +>Flat : Symbol(Flat, Decl(b.ts, 0, 18)) + +export const nested: Array = ["name", "onCancel"]; +>nested : Symbol(nested, Decl(b.ts, 2, 12)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>Sig : Symbol(Sig, Decl(b.ts, 0, 13)) + +export const flat: Array = ["name", "onCancel"]; +>flat : Symbol(flat, Decl(b.ts, 3, 12)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>Flat : Symbol(Flat, Decl(b.ts, 0, 18)) + +export const onCancel: Sig["Args"]["onCancel"] = (v: string) => {}; +>onCancel : Symbol(onCancel, Decl(b.ts, 4, 12)) +>Sig : Symbol(Sig, Decl(b.ts, 0, 13)) +>v : Symbol(v, Decl(b.ts, 4, 50)) + diff --git a/tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.types b/tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.types new file mode 100644 index 0000000000000..d6dab7e94792c --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/jsdocNestedPropertyTagInvalidType.types @@ -0,0 +1,43 @@ +//// [tests/cases/compiler/jsdocNestedPropertyTagInvalidType.ts] //// + +=== /a.js === + +/** + * @typedef Sig + * @property {object} Args + * @property {string} Args.name + * @property {function(string)=} Args.onSave + * @property {((v: string) => void)=} Args.onCancel + */ + +/** + * @typedef Flat + * @property {string} name + * @property {function(string)=} onSave + * @property {((v: string) => void)=} onCancel + */ + +export {}; + +=== /b.ts === +import type { Sig, Flat } from "./a.js"; +>Sig : Sig +>Flat : Flat + +export const nested: Array = ["name", "onCancel"]; +>nested : ("" | "name" | "onCancel")[] +>["name", "onCancel"] : ("name" | "onCancel")[] +>"name" : "name" +>"onCancel" : "onCancel" + +export const flat: Array = ["name", "onCancel"]; +>flat : (keyof Flat)[] +>["name", "onCancel"] : ("name" | "onCancel")[] +>"name" : "name" +>"onCancel" : "onCancel" + +export const onCancel: Sig["Args"]["onCancel"] = (v: string) => {}; +>onCancel : ((v: string) => void) | undefined +>(v: string) => {} : (v: string) => void +>v : string + diff --git a/tsc/testdata/tests/cases/compiler/jsdocNestedPropertyTagInvalidType.ts b/tsc/testdata/tests/cases/compiler/jsdocNestedPropertyTagInvalidType.ts new file mode 100644 index 0000000000000..b36652678755b --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/jsdocNestedPropertyTagInvalidType.ts @@ -0,0 +1,29 @@ +// @strict: true +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @Filename: /a.js +/** + * @typedef Sig + * @property {object} Args + * @property {string} Args.name + * @property {function(string)=} Args.onSave + * @property {((v: string) => void)=} Args.onCancel + */ + +/** + * @typedef Flat + * @property {string} name + * @property {function(string)=} onSave + * @property {((v: string) => void)=} onCancel + */ + +export {}; + +// @Filename: /b.ts +import type { Sig, Flat } from "./a.js"; + +export const nested: Array = ["name", "onCancel"]; +export const flat: Array = ["name", "onCancel"]; +export const onCancel: Sig["Args"]["onCancel"] = (v: string) => {};