From 4b266f0c8b8678955579d742d3bcabb24f1fcea8 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 26 May 2026 14:49:51 -0700 Subject: [PATCH 1/5] Reparse non-identifier jsdoc names where possible, error otherwise --- internal/parser/reparser.go | 63 ++++++++++++++++--- .../jsdocNonIdentifierPropertiesAndParams.js | 50 +++++++++++++++ ...ocNonIdentifierPropertiesAndParams.symbols | 28 +++++++++ ...sdocNonIdentifierPropertiesAndParams.types | 29 +++++++++ .../compiler/jsEnumCrossFileExport.errors.txt | 5 +- .../jsEnumCrossFileExport.errors.txt.diff | 5 +- .../compiler/jsdocTypedefNoCrash.errors.txt | 11 ++++ .../jsdocTypedefNoCrash.errors.txt.diff | 15 +++++ .../compiler/jsdocTypedefNoCrash2.errors.txt | 5 +- .../jsdocTypedefNoCrash2.errors.txt.diff | 5 +- .../misspelledJsDocTypedefTags.errors.txt | 11 ++++ ...misspelledJsDocTypedefTags.errors.txt.diff | 15 +++++ .../compiler/misspelledJsDocTypedefTags.types | 4 +- .../misspelledJsDocTypedefTags.types.diff | 20 ++++++ ...checkJsdocTypedefOnlySourceFile.errors.txt | 5 +- ...JsdocTypedefOnlySourceFile.errors.txt.diff | 5 +- .../noAssertForUnparseableTypedefs.errors.txt | 5 +- ...sertForUnparseableTypedefs.errors.txt.diff | 5 +- .../typedefInnerNamepaths.errors.txt | 5 +- .../typedefInnerNamepaths.errors.txt.diff | 15 +++-- .../conformance/typedefTagWrapping.errors.txt | 15 ++++- .../typedefTagWrapping.errors.txt.diff | 21 +++++-- .../jsdocNonIdentifierPropertiesAndParams.ts | 25 ++++++++ 23 files changed, 334 insertions(+), 33 deletions(-) create mode 100644 testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.js create mode 100644 testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.symbols create mode 100644 testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.types create mode 100644 testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt create mode 100644 testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt create mode 100644 testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types.diff create mode 100644 testdata/tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts diff --git a/internal/parser/reparser.go b/internal/parser/reparser.go index 8a2aa2f158a..8fb804f9b97 100644 --- a/internal/parser/reparser.go +++ b/internal/parser/reparser.go @@ -1,8 +1,13 @@ package parser import ( + "strconv" + "strings" + "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/diagnostics" + "github.com/microsoft/typescript-go/internal/scanner" ) func (p *Parser) finishReparsedNode(node *ast.Node, locationNode *ast.Node) { @@ -26,6 +31,18 @@ func (p *Parser) addDeepCloneReparse(node *ast.Node) *ast.Node { return clone } +func (p *Parser) addTransformedReparse(new *ast.Node, old *ast.Node) *ast.Node { + p.reparsedClones = append(p.reparsedClones, new) + return new +} + +func (p *Parser) checkNonIdentifierName(name *ast.Node) *ast.Node { + if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) { + p.parseErrorAtRange(name.Loc, diagnostics.Identifier_expected) + } + return name +} + // Hosted tags find a host and add their children to the correct location under the host. // Unhosted tags add synthetic nodes to the reparse list. func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) { @@ -51,7 +68,7 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod if typeExpression == nil { break } - typeAlias := p.factory.NewJSTypeAliasDeclaration(nil, p.addDeepCloneReparse(tag.AsJSDocTypedefTag().Name()), nil, nil) + typeAlias := p.factory.NewJSTypeAliasDeclaration(nil, p.addDeepCloneReparse(p.checkNonIdentifierName(tag.AsJSDocTypedefTag().Name())), nil, nil) typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, tag) var t *ast.Node switch typeExpression.Kind { @@ -107,9 +124,9 @@ func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsD clonedModifiers := p.factory.DeepCloneReparseModifiers(modifiers) switch fun.Kind { case ast.KindFunctionDeclaration: - signature = p.factory.NewFunctionDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(fun.Name()), nil, nil, nil, nil, nil) + signature = p.factory.NewFunctionDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(p.checkNonIdentifierName(fun.Name())), nil, nil, nil, nil, nil) case ast.KindMethodDeclaration: - signature = p.factory.NewMethodDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(fun.Name()), nil, nil, nil, nil, nil, nil) + signature = p.factory.NewMethodDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(p.checkNonIdentifierName(fun.Name())), nil, nil, nil, nil, nil, nil) case ast.KindConstructor: signature = p.factory.NewConstructorDeclaration(clonedModifiers, nil, nil, nil, nil, nil) case ast.KindJSDocCallbackTag: @@ -122,7 +139,7 @@ func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsD signature.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, tag) } parameters := p.nodeSliceArena.NewSlice(0) - for _, param := range jsSignature.Parameters() { + for pi, param := range jsSignature.Parameters() { var parameter *ast.Node if param.Kind == ast.KindJSDocThisTag { thisTag := param.AsJSDocThisTag() @@ -155,8 +172,33 @@ func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsD paramType = p.reparseJSDocTypeLiteral(jsparam.TypeExpression.Type()) } } - - parameter = p.factory.NewParameterDeclaration(nil, dotDotDotToken, p.addDeepCloneReparse(jsparam.Name()), p.makeQuestionIfOptional(jsparam), paramType, nil) + name := jsparam.Name() + if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) { + // drop invalid chars for _, if empty, write _0, etc., so we have a valid param name to emit later + result := strings.Builder{} + for i, ch := range name.AsIdentifier().Text { + if i == 0 { + if !scanner.IsIdentifierStart(ch) { + result.WriteRune('_') + } else { + result.WriteRune(ch) + } + continue + } else if !scanner.IsIdentifierPart(ch) { + result.WriteRune('_') + } else { + result.WriteRune(ch) + } + } + if result.Len() == 0 { + result.WriteRune('_') + result.WriteString(strconv.Itoa(pi)) + } + name = p.addTransformedReparse(p.factory.NewIdentifier(result.String()), name) + } else { + name = p.addDeepCloneReparse(name) + } + parameter = p.factory.NewParameterDeclaration(nil, dotDotDotToken, name, p.makeQuestionIfOptional(jsparam), paramType, nil) } p.finishReparsedNode(parameter, param) parameters = append(parameters, parameter) @@ -192,7 +234,12 @@ func (p *Parser) reparseJSDocTypeLiteral(t *ast.TypeNode) *ast.Node { if name.Kind == ast.KindQualifiedName { name = name.AsQualifiedName().Right } - property := p.factory.NewPropertySignatureDeclaration(nil, p.addDeepCloneReparse(name), p.makeQuestionIfOptional(jsprop), nil, nil) + if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) { + name = p.addTransformedReparse(p.factory.NewStringLiteral(name.AsIdentifier().Text, ast.TokenFlagsNone), name) + } else { + name = p.addDeepCloneReparse(name) + } + property := p.factory.NewPropertySignatureDeclaration(nil, name, p.makeQuestionIfOptional(jsprop), nil, nil) if jsprop.TypeExpression != nil { property.AsPropertySignatureDeclaration().Type = p.reparseJSDocTypeLiteral(jsprop.TypeExpression.Type()) } @@ -258,7 +305,7 @@ func (p *Parser) gatherTypeParameters(j *ast.Node, tagWithTypeParameters *ast.No if constraint != nil && firstTypeParameter { reparse = p.factory.NewTypeParameterDeclaration( p.factory.DeepCloneReparseModifiers(tp.Modifiers()), - p.addDeepCloneReparse(tp.Name()), + p.addDeepCloneReparse(p.checkNonIdentifierName(tp.Name())), p.addDeepCloneReparse(constraint.Type()), nil, // expression p.addDeepCloneReparse(tp.AsTypeParameterDeclaration().DefaultType), diff --git a/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.js b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.js new file mode 100644 index 00000000000..e12a5ead9c3 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.js @@ -0,0 +1,50 @@ +//// [tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts] //// + +//// [index.js] +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ + +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export function Button(props) { + return { ...props } +} + +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ + + + + +//// [index.d.ts] +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ +export type ButtonProps = { + label: string; + "data-test-name"?: string | null | undefined; + "aria-label"?: string | null | undefined; +}; +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export declare function Button(props: ButtonProps): ButtonProps; +export type ButtonPropsCallback = (props_like?: ButtonProps) => ButtonProps; +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ diff --git a/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.symbols b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.symbols new file mode 100644 index 00000000000..99dde5ee929 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.symbols @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts] //// + +=== index.js === +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ + +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export function Button(props) { +>Button : Symbol(Button, Decl(index.js, 0, 0)) +>props : Symbol(props, Decl(index.js, 11, 23)) + + return { ...props } +>props : Symbol(props, Decl(index.js, 11, 23)) +} + +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ + diff --git a/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.types b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.types new file mode 100644 index 00000000000..18be6da01c2 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.types @@ -0,0 +1,29 @@ +//// [tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts] //// + +=== index.js === +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ + +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export function Button(props) { +>Button : (props: ButtonProps) => ButtonProps +>props : ButtonProps + + return { ...props } +>{ ...props } : { label: string; "data-test-name"?: string | null | undefined; "aria-label"?: string | null | undefined; } +>props : ButtonProps +} + +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ + diff --git a/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt b/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt index 4ec5325f9f6..4fdf9f2453c 100644 --- a/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt @@ -1,10 +1,11 @@ enumDef.js(14,21): error TS1003: Identifier expected. +enumDef.js(14,21): error TS1003: Identifier expected. index.js(4,17): error TS2702: 'Host' only refers to a type, but is being used as a namespace here. index.js(13,11): error TS2702: 'Host' only refers to a type, but is being used as a namespace here. index.js(18,11): error TS2702: 'Host' only refers to a type, but is being used as a namespace here. -==== enumDef.js (1 errors) ==== +==== enumDef.js (2 errors) ==== var Host = {}; Host.UserMetrics = {}; /** @enum {number} */ @@ -20,6 +21,8 @@ index.js(18,11): error TS2702: 'Host' only refers to a type, but is being used a /** * @typedef {string} +!!! error TS1003: Identifier expected. + */ !!! error TS1003: Identifier expected. diff --git a/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt.diff index de61884fc1d..5409379e47e 100644 --- a/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt.diff +++ b/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt.diff @@ -3,12 +3,13 @@ @@= skipped -0, +0 lines =@@ - +enumDef.js(14,21): error TS1003: Identifier expected. ++enumDef.js(14,21): error TS1003: Identifier expected. +index.js(4,17): error TS2702: 'Host' only refers to a type, but is being used as a namespace here. +index.js(13,11): error TS2702: 'Host' only refers to a type, but is being used as a namespace here. +index.js(18,11): error TS2702: 'Host' only refers to a type, but is being used as a namespace here. + + -+==== enumDef.js (1 errors) ==== ++==== enumDef.js (2 errors) ==== + var Host = {}; + Host.UserMetrics = {}; + /** @enum {number} */ @@ -24,6 +25,8 @@ + /** + * @typedef {string} + ++!!! error TS1003: Identifier expected. ++ + */ + +!!! error TS1003: Identifier expected. diff --git a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt new file mode 100644 index 00000000000..757dfa1f130 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt @@ -0,0 +1,11 @@ +export.js(3,6): error TS1003: Identifier expected. + + +==== export.js (1 errors) ==== + /** + * @typedef {{ + * }} + +!!! error TS1003: Identifier expected. + */ + export const foo = 5; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt.diff new file mode 100644 index 00000000000..0c0f17a4f94 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt.diff @@ -0,0 +1,15 @@ +--- old.jsdocTypedefNoCrash.errors.txt ++++ new.jsdocTypedefNoCrash.errors.txt +@@= skipped -0, +0 lines =@@ +- ++export.js(3,6): error TS1003: Identifier expected. ++ ++ ++==== export.js (1 errors) ==== ++ /** ++ * @typedef {{ ++ * }} ++ ++!!! error TS1003: Identifier expected. ++ */ ++ export const foo = 5; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt index 6239c728e61..8699e4947c2 100644 --- a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt @@ -1,12 +1,15 @@ export.js(1,13): error TS8008: Type aliases can only be used in TypeScript files. +export.js(4,6): error TS1003: Identifier expected. -==== export.js (1 errors) ==== +==== export.js (2 errors) ==== export type foo = 5; ~~~ !!! error TS8008: Type aliases can only be used in TypeScript files. /** * @typedef {{ * }} + +!!! error TS1003: Identifier expected. */ export const foo = 5; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt.diff index 294da8e1b74..e60c1ed4be0 100644 --- a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt.diff +++ b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt.diff @@ -7,9 +7,10 @@ - - -==== export.js (3 errors) ==== ++export.js(4,6): error TS1003: Identifier expected. + + -+==== export.js (1 errors) ==== ++==== export.js (2 errors) ==== export type foo = 5; ~~~ -!!! error TS2451: Cannot redeclare block-scoped variable 'foo'. @@ -18,6 +19,8 @@ /** * @typedef {{ * }} ++ ++!!! error TS1003: Identifier expected. */ export const foo = 5; - ~~~ diff --git a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt new file mode 100644 index 00000000000..4da72b8df45 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt @@ -0,0 +1,11 @@ +a.js(4,60): error TS1003: Identifier expected. + + +==== a.js (1 errors) ==== + /** @typedef {{ endTime: number, screenshots: number}} A.*/ + Animation.AnimationModel.ScreenshotCapture.Request; + + /** @typedef {{ endTime: number, screenshots: !B.}} */ + +!!! error TS1003: Identifier expected. + Animation.AnimationModel.ScreenshotCapture.Request; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt.diff new file mode 100644 index 00000000000..2ee295858ce --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt.diff @@ -0,0 +1,15 @@ +--- old.misspelledJsDocTypedefTags.errors.txt ++++ new.misspelledJsDocTypedefTags.errors.txt +@@= skipped -0, +0 lines =@@ +- ++a.js(4,60): error TS1003: Identifier expected. ++ ++ ++==== a.js (1 errors) ==== ++ /** @typedef {{ endTime: number, screenshots: number}} A.*/ ++ Animation.AnimationModel.ScreenshotCapture.Request; ++ ++ /** @typedef {{ endTime: number, screenshots: !B.}} */ ++ ++!!! error TS1003: Identifier expected. ++ Animation.AnimationModel.ScreenshotCapture.Request; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types index 3d26fba20dc..e8624ba0160 100644 --- a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types +++ b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types @@ -3,7 +3,7 @@ === a.js === /** @typedef {{ endTime: number, screenshots: number}} A.*/ Animation.AnimationModel.ScreenshotCapture.Request; ->Animation.AnimationModel.ScreenshotCapture.Request : error +>Animation.AnimationModel.ScreenshotCapture.Request : any >Animation.AnimationModel.ScreenshotCapture : any >Animation.AnimationModel : any >Animation : { new (effect?: AnimationEffect | null, timeline?: AnimationTimeline | null): Animation; prototype: Animation; } @@ -13,7 +13,7 @@ Animation.AnimationModel.ScreenshotCapture.Request; /** @typedef {{ endTime: number, screenshots: !B.}} */ Animation.AnimationModel.ScreenshotCapture.Request; ->Animation.AnimationModel.ScreenshotCapture.Request : error +>Animation.AnimationModel.ScreenshotCapture.Request : any >Animation.AnimationModel.ScreenshotCapture : any >Animation.AnimationModel : any >Animation : { new (effect?: AnimationEffect | null, timeline?: AnimationTimeline | null): Animation; prototype: Animation; } diff --git a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types.diff b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types.diff new file mode 100644 index 00000000000..d2e04e3fa4f --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types.diff @@ -0,0 +1,20 @@ +--- old.misspelledJsDocTypedefTags.types ++++ new.misspelledJsDocTypedefTags.types +@@= skipped -2, +2 lines =@@ + === a.js === + /** @typedef {{ endTime: number, screenshots: number}} A.*/ + Animation.AnimationModel.ScreenshotCapture.Request; +->Animation.AnimationModel.ScreenshotCapture.Request : error ++>Animation.AnimationModel.ScreenshotCapture.Request : any + >Animation.AnimationModel.ScreenshotCapture : any + >Animation.AnimationModel : any + >Animation : { new (effect?: AnimationEffect | null, timeline?: AnimationTimeline | null): Animation; prototype: Animation; } +@@= skipped -10, +10 lines =@@ + + /** @typedef {{ endTime: number, screenshots: !B.}} */ + Animation.AnimationModel.ScreenshotCapture.Request; +->Animation.AnimationModel.ScreenshotCapture.Request : error ++>Animation.AnimationModel.ScreenshotCapture.Request : any + >Animation.AnimationModel.ScreenshotCapture : any + >Animation.AnimationModel : any + >Animation : { new (effect?: AnimationEffect | null, timeline?: AnimationTimeline | null): Animation; prototype: Animation; } \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt b/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt index c58053a43c1..842b12d5b83 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt @@ -1,8 +1,9 @@ 0.js(6,21): error TS1003: Identifier expected. +0.js(6,21): error TS1003: Identifier expected. 0.js(10,12): error TS2503: Cannot find namespace 'exports'. -==== 0.js (2 errors) ==== +==== 0.js (3 errors) ==== // @ts-check var exports = {}; @@ -10,6 +11,8 @@ /** * @typedef {string} +!!! error TS1003: Identifier expected. + */ !!! error TS1003: Identifier expected. diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt.diff index d1155e5273a..00819a24a52 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt.diff @@ -6,16 +6,19 @@ - -==== 0.js (1 errors) ==== +0.js(6,21): error TS1003: Identifier expected. ++0.js(6,21): error TS1003: Identifier expected. +0.js(10,12): error TS2503: Cannot find namespace 'exports'. + + -+==== 0.js (2 errors) ==== ++==== 0.js (3 errors) ==== // @ts-check var exports = {}; /** * @typedef {string} ++ ++!!! error TS1003: Identifier expected. + */ + diff --git a/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt b/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt index e13db6e492b..cbc9006aaac 100644 --- a/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt @@ -1,12 +1,15 @@ bug26693.js(1,15): error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. +bug26693.js(1,21): error TS1003: Identifier expected. bug26693.js(1,21): error TS1005: '}' expected. bug26693.js(2,22): error TS2307: Cannot find module 'nope' or its corresponding type declarations. -==== bug26693.js (3 errors) ==== +==== bug26693.js (4 errors) ==== /** @typedef {module:locale} hi */ ~~~~~~ !!! error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. + +!!! error TS1003: Identifier expected. ~ !!! error TS1005: '}' expected. import { nope } from 'nope'; diff --git a/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt.diff index 73abaa1f3d2..1f41f482c4c 100644 --- a/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt.diff @@ -2,15 +2,18 @@ +++ new.noAssertForUnparseableTypedefs.errors.txt @@= skipped -0, +0 lines =@@ +bug26693.js(1,15): error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. ++bug26693.js(1,21): error TS1003: Identifier expected. +bug26693.js(1,21): error TS1005: '}' expected. bug26693.js(2,22): error TS2307: Cannot find module 'nope' or its corresponding type declarations. -==== bug26693.js (1 errors) ==== -+==== bug26693.js (3 errors) ==== ++==== bug26693.js (4 errors) ==== /** @typedef {module:locale} hi */ + ~~~~~~ +!!! error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. ++ ++!!! error TS1003: Identifier expected. + ~ +!!! error TS1005: '}' expected. import { nope } from 'nope'; diff --git a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt index 5a5e1fedf51..f5e324f1f94 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt @@ -1,15 +1,18 @@ bug25104.js(1,7): error TS2300: Duplicate identifier 'C'. +bug25104.js(3,19): error TS1003: Identifier expected. bug25104.js(3,19): error TS1005: '}' expected. bug25104.js(4,26): error TS2300: Duplicate identifier 'C'. bug25104.js(6,18): error TS1005: '}' expected. -==== bug25104.js (4 errors) ==== +==== bug25104.js (5 errors) ==== class C { ~ !!! error TS2300: Duplicate identifier 'C'. /** * @typedef {C~A} C~B + +!!! error TS1003: Identifier expected. ~ !!! error TS1005: '}' expected. * @typedef {object} C~A diff --git a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt.diff index db8d8129a0f..8459baf9319 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt.diff @@ -2,18 +2,23 @@ +++ new.typedefInnerNamepaths.errors.txt @@= skipped -0, +0 lines =@@ bug25104.js(1,7): error TS2300: Duplicate identifier 'C'. ++bug25104.js(3,19): error TS1003: Identifier expected. bug25104.js(3,19): error TS1005: '}' expected. bug25104.js(4,26): error TS2300: Duplicate identifier 'C'. -bug25104.js(6,18): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. bug25104.js(6,18): error TS1005: '}' expected. --==== bug25104.js (5 errors) ==== -+==== bug25104.js (4 errors) ==== - class C { - ~ +@@= skipped -10, +10 lines =@@ !!! error TS2300: Duplicate identifier 'C'. -@@= skipped -17, +16 lines =@@ + /** + * @typedef {C~A} C~B ++ ++!!! error TS1003: Identifier expected. + ~ + !!! error TS1005: '}' expected. + * @typedef {object} C~A +@@= skipped -7, +9 lines =@@ !!! error TS2300: Duplicate identifier 'C'. */ /** @param {C~A} o */ diff --git a/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt b/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt index 0239a154bc7..83c1223b141 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt @@ -1,9 +1,12 @@ +mod1.js(2,22): error TS1003: Identifier expected. mod1.js(2,22): error TS2300: Duplicate identifier '(Missing)'. mod1.js(2,22): error TS1005: '}' expected. mod1.js(9,12): error TS2552: Cannot find name 'Type1'. Did you mean 'Type2'? +mod3.js(4,22): error TS1003: Identifier expected. mod3.js(4,22): error TS2300: Duplicate identifier '(Missing)'. mod3.js(4,22): error TS1005: '}' expected. mod3.js(10,12): error TS2304: Cannot find name 'StringOrNumber1'. +mod4.js(4,22): error TS1003: Identifier expected. mod4.js(4,22): error TS2300: Duplicate identifier '(Missing)'. mod4.js(4,22): error TS1005: '}' expected. mod4.js(11,12): error TS2304: Cannot find name 'StringOrNumber2'. @@ -11,10 +14,12 @@ mod7.js(5,7): error TS1110: Type expected. mod7.js(8,4): error TS1110: Type expected. -==== mod1.js (3 errors) ==== +==== mod1.js (4 errors) ==== /** * @typedef {function(string): boolean} +!!! error TS1003: Identifier expected. + !!! error TS2300: Duplicate identifier '(Missing)'. !!! related TS6203 mod3.js:4:22: '(Missing)' was also declared here. !!! related TS6203 mod4.js:4:22: '(Missing)' was also declared here. @@ -54,12 +59,14 @@ mod7.js(8,4): error TS1110: Type expected. return obj.boo ? obj.num : obj.str; } -==== mod3.js (3 errors) ==== +==== mod3.js (4 errors) ==== /** * A function whose signature is very long. * * @typedef {function(boolean, string, number): +!!! error TS1003: Identifier expected. + !!! error TS2300: Duplicate identifier '(Missing)'. !!! related TS6203 mod1.js:2:22: '(Missing)' was also declared here. ~ @@ -81,12 +88,14 @@ mod7.js(8,4): error TS1110: Type expected. return func(bool, str, num) } -==== mod4.js (3 errors) ==== +==== mod4.js (4 errors) ==== /** * A function whose signature is very long. * * @typedef {function(boolean, string, +!!! error TS1003: Identifier expected. + !!! error TS2300: Duplicate identifier '(Missing)'. !!! related TS6203 mod1.js:2:22: '(Missing)' was also declared here. ~ diff --git a/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt.diff index 5a85165b86d..29cc2949751 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt.diff @@ -1,12 +1,15 @@ --- old.typedefTagWrapping.errors.txt +++ new.typedefTagWrapping.errors.txt @@= skipped -0, +0 lines =@@ ++mod1.js(2,22): error TS1003: Identifier expected. +mod1.js(2,22): error TS2300: Duplicate identifier '(Missing)'. +mod1.js(2,22): error TS1005: '}' expected. +mod1.js(9,12): error TS2552: Cannot find name 'Type1'. Did you mean 'Type2'? ++mod3.js(4,22): error TS1003: Identifier expected. +mod3.js(4,22): error TS2300: Duplicate identifier '(Missing)'. +mod3.js(4,22): error TS1005: '}' expected. +mod3.js(10,12): error TS2304: Cannot find name 'StringOrNumber1'. ++mod4.js(4,22): error TS1003: Identifier expected. +mod4.js(4,22): error TS2300: Duplicate identifier '(Missing)'. +mod4.js(4,22): error TS1005: '}' expected. +mod4.js(11,12): error TS2304: Cannot find name 'StringOrNumber2'. @@ -15,10 +18,12 @@ -==== mod1.js (0 errors) ==== -+==== mod1.js (3 errors) ==== ++==== mod1.js (4 errors) ==== /** * @typedef {function(string): boolean} + ++!!! error TS1003: Identifier expected. ++ +!!! error TS2300: Duplicate identifier '(Missing)'. +!!! related TS6203 mod3.js:4:22: '(Missing)' was also declared here. +!!! related TS6203 mod4.js:4:22: '(Missing)' was also declared here. @@ -27,7 +32,7 @@ * Type1 */ -@@= skipped -11, +26 lines =@@ +@@= skipped -11, +31 lines =@@ * Tries to use a type whose name is on a different * line than the typedef tag. * @param {Type1} func The function to call. @@ -41,12 +46,14 @@ } -==== mod3.js (0 errors) ==== -+==== mod3.js (3 errors) ==== ++==== mod3.js (4 errors) ==== /** * A function whose signature is very long. * * @typedef {function(boolean, string, number): + ++!!! error TS1003: Identifier expected. ++ +!!! error TS2300: Duplicate identifier '(Missing)'. +!!! related TS6203 mod1.js:2:22: '(Missing)' was also declared here. + ~ @@ -62,17 +69,19 @@ * @param {boolean} bool The condition. * @param {string} str The string. * @param {number} num The number. -@@= skipped -20, +27 lines =@@ +@@= skipped -20, +29 lines =@@ return func(bool, str, num) } -==== mod4.js (0 errors) ==== -+==== mod4.js (3 errors) ==== ++==== mod4.js (4 errors) ==== /** * A function whose signature is very long. * * @typedef {function(boolean, string, + ++!!! error TS1003: Identifier expected. ++ +!!! error TS2300: Duplicate identifier '(Missing)'. +!!! related TS6203 mod1.js:2:22: '(Missing)' was also declared here. + ~ @@ -80,7 +89,7 @@ * number): * (string|number)} StringOrNumber2 */ -@@= skipped -12, +17 lines =@@ +@@= skipped -12, +19 lines =@@ /** * Makes use of a function type with a long signature. * @param {StringOrNumber2} func The function. diff --git a/testdata/tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts b/testdata/tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts new file mode 100644 index 00000000000..0e773820f9b --- /dev/null +++ b/testdata/tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts @@ -0,0 +1,25 @@ +// @checkJs: true +// @allowJs: true +// @declaration: true +// @emitDeclarationOnly: true +// @filename: index.js +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ + +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export function Button(props) { + return { ...props } +} + +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ From 2b8ab4cf90177a9435d3c6392db54926d943fe1a Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 26 May 2026 15:08:17 -0700 Subject: [PATCH 2/5] Fix lint --- internal/parser/reparser.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/parser/reparser.go b/internal/parser/reparser.go index 8fb804f9b97..1e9fcf5800b 100644 --- a/internal/parser/reparser.go +++ b/internal/parser/reparser.go @@ -31,9 +31,11 @@ func (p *Parser) addDeepCloneReparse(node *ast.Node) *ast.Node { return clone } -func (p *Parser) addTransformedReparse(new *ast.Node, old *ast.Node) *ast.Node { - p.reparsedClones = append(p.reparsedClones, new) - return new +func (p *Parser) addTransformedReparse(newNode *ast.Node, old *ast.Node) *ast.Node { + p.reparsedClones = append(p.reparsedClones, newNode) + // TODO: reference `old` with `.original` lookups? Can't set `.Loc` as it would set text source + // Don't need to set `.Parent` as `finishReparsedNode` will handle that + return newNode } func (p *Parser) checkNonIdentifierName(name *ast.Node) *ast.Node { From d3765faff4f204d657ad8c0ef97a8f86f7e6feab Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 27 May 2026 10:16:14 -0700 Subject: [PATCH 3/5] Set .Loc on node-kind-swapped reparser nodes, but forbid literal text lookup on them --- internal/ast/nodeflags.go | 1 + internal/parser/reparser.go | 4 ++-- internal/scanner/utilities.go | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/internal/ast/nodeflags.go b/internal/ast/nodeflags.go index 6d3963d9fb8..5d91cd76551 100644 --- a/internal/ast/nodeflags.go +++ b/internal/ast/nodeflags.go @@ -44,6 +44,7 @@ const ( NodeFlagsJsonFile NodeFlags = 1 << 25 // If node was parsed in a Json NodeFlagsPossiblyContainsDeprecatedTag NodeFlags = 1 << 26 // Set during parse if comment text contains '@deprecated'; must confirm via JSDoc lookup NodeFlagsUnreachable NodeFlags = 1 << 27 // If node is unreachable according to the binder + NodeFlagsReparserTransformedLiteral NodeFlags = 1 << 28 // If node was transformed during parsing, making its' naive text source not match the AST NodeFlagsBlockScoped = NodeFlagsLet | NodeFlagsConst | NodeFlagsUsing NodeFlagsConstant = NodeFlagsConst | NodeFlagsUsing diff --git a/internal/parser/reparser.go b/internal/parser/reparser.go index 1e9fcf5800b..6c394716c37 100644 --- a/internal/parser/reparser.go +++ b/internal/parser/reparser.go @@ -32,9 +32,9 @@ func (p *Parser) addDeepCloneReparse(node *ast.Node) *ast.Node { } func (p *Parser) addTransformedReparse(newNode *ast.Node, old *ast.Node) *ast.Node { + p.finishReparsedNode(newNode, old) + newNode.Flags |= ast.NodeFlagsReparserTransformedLiteral p.reparsedClones = append(p.reparsedClones, newNode) - // TODO: reference `old` with `.original` lookups? Can't set `.Loc` as it would set text source - // Don't need to set `.Parent` as `finishReparsedNode` will handle that return newNode } diff --git a/internal/scanner/utilities.go b/internal/scanner/utilities.go index a5bb3008e58..f890a5fbbfc 100644 --- a/internal/scanner/utilities.go +++ b/internal/scanner/utilities.go @@ -6,6 +6,7 @@ import ( "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/debug" ) const ( @@ -70,6 +71,21 @@ func GetTextOfNodeFromSourceText(sourceText string, node *ast.Node, includeTrivi pos = SkipTrivia(sourceText, pos) } text := sourceText[pos:node.End()] + if node.Flags&ast.NodeFlagsReparserTransformedLiteral != 0 { + // This is similar to `getLiteralTextOfNode` in the printer, but without the context of an `emitContext` to provide overrides + if ast.IsStringLiteral(node) { + if node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote != 0 { + return "'"+text+"'" + } + return "\""+text+"\"" + } else if ast.IsIdentifier(node) { + return node.Text() + } + // Only the above node kinds are currently transformed into one another by the reparser, requiring the textual remapping. + // (Any reamppings done by emit transforms are handled by `getLiteralTextOfNode` in the printer) + // Fail on any other kinds. + debug.FailBadSyntaxKind(node, "Unexpected reparser-transformed node kind") + } // if (isJSDocTypeExpressionOrChild(node)) { // // strip space + asterisk at line start // text = text.split(/\r\n|\n|\r/).map(line => line.replace(/^\s*\*/, "").trimStart()).join("\n"); From 6b7e68958cda2cd51fcc15235c1479e47cce7ab0 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 28 May 2026 11:17:21 -0700 Subject: [PATCH 4/5] Format & gen --- internal/ast/nodeflags.go | 2 +- internal/scanner/utilities.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/ast/nodeflags.go b/internal/ast/nodeflags.go index 5d91cd76551..1b020bab095 100644 --- a/internal/ast/nodeflags.go +++ b/internal/ast/nodeflags.go @@ -44,7 +44,7 @@ const ( NodeFlagsJsonFile NodeFlags = 1 << 25 // If node was parsed in a Json NodeFlagsPossiblyContainsDeprecatedTag NodeFlags = 1 << 26 // Set during parse if comment text contains '@deprecated'; must confirm via JSDoc lookup NodeFlagsUnreachable NodeFlags = 1 << 27 // If node is unreachable according to the binder - NodeFlagsReparserTransformedLiteral NodeFlags = 1 << 28 // If node was transformed during parsing, making its' naive text source not match the AST + NodeFlagsReparserTransformedLiteral NodeFlags = 1 << 28 // If node was transformed during parsing, making its' naive text source not match the AST NodeFlagsBlockScoped = NodeFlagsLet | NodeFlagsConst | NodeFlagsUsing NodeFlagsConstant = NodeFlagsConst | NodeFlagsUsing diff --git a/internal/scanner/utilities.go b/internal/scanner/utilities.go index f890a5fbbfc..d7ef83de75f 100644 --- a/internal/scanner/utilities.go +++ b/internal/scanner/utilities.go @@ -75,9 +75,9 @@ func GetTextOfNodeFromSourceText(sourceText string, node *ast.Node, includeTrivi // This is similar to `getLiteralTextOfNode` in the printer, but without the context of an `emitContext` to provide overrides if ast.IsStringLiteral(node) { if node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote != 0 { - return "'"+text+"'" + return "'" + text + "'" } - return "\""+text+"\"" + return "\"" + text + "\"" } else if ast.IsIdentifier(node) { return node.Text() } From 77080b0e6b3c86329974e5802cd004daebbd9aef Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 28 May 2026 11:49:27 -0700 Subject: [PATCH 5/5] regen api enums --- _packages/native-preview/src/enums/nodeFlags.enum.ts | 1 + _packages/native-preview/src/enums/nodeFlags.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/_packages/native-preview/src/enums/nodeFlags.enum.ts b/_packages/native-preview/src/enums/nodeFlags.enum.ts index 16df62b0345..97df0fb637d 100644 --- a/_packages/native-preview/src/enums/nodeFlags.enum.ts +++ b/_packages/native-preview/src/enums/nodeFlags.enum.ts @@ -30,6 +30,7 @@ export enum NodeFlags { JsonFile = 1 << 25, PossiblyContainsDeprecatedTag = 1 << 26, Unreachable = 1 << 27, + ReparserTransformedLiteral = 1 << 28, BlockScoped = Let | Const | Using, Constant = Const | Using, AwaitUsing = Const | Using, diff --git a/_packages/native-preview/src/enums/nodeFlags.ts b/_packages/native-preview/src/enums/nodeFlags.ts index 154525c5f41..82335a8e501 100644 --- a/_packages/native-preview/src/enums/nodeFlags.ts +++ b/_packages/native-preview/src/enums/nodeFlags.ts @@ -30,6 +30,7 @@ export var NodeFlags: any; NodeFlags[NodeFlags["JsonFile"] = 33554432] = "JsonFile"; NodeFlags[NodeFlags["PossiblyContainsDeprecatedTag"] = 67108864] = "PossiblyContainsDeprecatedTag"; NodeFlags[NodeFlags["Unreachable"] = 134217728] = "Unreachable"; + NodeFlags[NodeFlags["ReparserTransformedLiteral"] = 268435456] = "ReparserTransformedLiteral"; NodeFlags[NodeFlags["BlockScoped"] = 7] = "BlockScoped"; NodeFlags[NodeFlags["Constant"] = 6] = "Constant"; NodeFlags[NodeFlags["AwaitUsing"] = 6] = "AwaitUsing";