Skip to content

Ensure nodes have real parents, fix parent race #970

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 4 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -9643,9 +9643,10 @@ func (node *JSDocThisTag) Clone(f NodeFactoryCoercible) *Node {
// JSDocImportTag
type JSDocImportTag struct {
JSDocTagBase
ImportClause *Declaration
ModuleSpecifier *Expression
Attributes *Node
JSImportDeclaration *ImportDeclaration
ImportClause *Declaration
ModuleSpecifier *Expression
Attributes *Node
}

func (f *NodeFactory) NewJSDocImportTag(tagName *IdentifierNode, importClause *Declaration, moduleSpecifier *Node, attributes *Node, comment *NodeList) *Node {
Expand Down
4 changes: 3 additions & 1 deletion internal/binder/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,9 @@ func (b *Binder) bind(node *ast.Node) bool {
if node == nil {
return false
}
node.Parent = b.parent
if node.Parent == nil || node.Parent.Flags&ast.NodeFlagsReparsed != 0 {
node.Parent = b.parent
}
saveInStrictMode := b.inStrictMode
// Even though in the AST the jsdoc @typedef node belongs to the current node,
// its symbol might be in the same scope with the current node's symbol. Consider:
Expand Down
5 changes: 5 additions & 0 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -13848,6 +13848,9 @@ func (c *Checker) getTargetOfImportSpecifier(node *ast.Node, dontResolveAlias bo
}
}
root := node.Parent.Parent.Parent // ImportDeclaration
if root.Kind == ast.KindJSDocImportTag {
root = root.AsJSDocImportTag().JSImportDeclaration.AsNode()
}
if ast.IsBindingElement(node) {
root = ast.GetRootDeclaration(node)
}
Expand Down Expand Up @@ -14219,6 +14222,8 @@ func (c *Checker) getModuleSpecifierForImportOrExport(node *ast.Node) *ast.Node

func getModuleSpecifierFromNode(node *ast.Node) *ast.Node {
switch node.Kind {
case ast.KindJSDocImportTag:
return node.AsJSDocImportTag().JSImportDeclaration.ModuleSpecifier
case ast.KindImportDeclaration, ast.KindJSImportDeclaration:
return node.AsImportDeclaration().ModuleSpecifier
case ast.KindExportDeclaration:
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/grammarchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2117,7 +2117,7 @@ func (c *Checker) checkGrammarBigIntLiteral(node *ast.BigIntLiteral) bool {
}

func (c *Checker) checkGrammarImportClause(node *ast.ImportClause) bool {
if node.IsTypeOnly && node.Name() != nil && node.NamedBindings != nil {
if node.Flags&ast.NodeFlagsJSDoc == 0 && node.IsTypeOnly && node.Name() != nil && node.NamedBindings != nil {
return c.grammarErrorOnNode(&node.Node, diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both)
}
if node.IsTypeOnly && node.NamedBindings != nil && node.NamedBindings.Kind == ast.KindNamedImports {
Expand Down
13 changes: 9 additions & 4 deletions internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1918,18 +1918,23 @@ func containsNonMissingUndefinedType(c *Checker, t *Type) bool {
}

func getAnyImportSyntax(node *ast.Node) *ast.Node {
var importNode *ast.Node
switch node.Kind {
case ast.KindImportEqualsDeclaration:
return node
importNode = node
case ast.KindImportClause:
return node.Parent
importNode = node.Parent
case ast.KindNamespaceImport:
return node.Parent.Parent
importNode = node.Parent.Parent
case ast.KindImportSpecifier:
return node.Parent.Parent.Parent
importNode = node.Parent.Parent.Parent
default:
return nil
}
if importNode.Kind == ast.KindJSDocImportTag {
return importNode.AsJSDocImportTag().JSImportDeclaration.AsNode()
}
return importNode
}

// A reserved member name consists of the byte 0xFE (which is an invalid UTF-8 encoding) followed by one or more
Expand Down
1 change: 1 addition & 0 deletions internal/parser/reparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
importDeclaration.Loc = core.NewTextRange(tag.Pos(), tag.End())
importDeclaration.Flags = p.contextFlags | ast.NodeFlagsReparsed
p.reparseList = append(p.reparseList, importDeclaration)
importTag.JSImportDeclaration = importDeclaration.AsImportDeclaration()
// !!! @overload and other unattached tags (@callback et al) support goes here
}
if !isLast {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
index.js(2,28): error TS2304: Cannot find name 'B'.
index.js(2,42): error TS2304: Cannot find name 'A'.
index.js(2,48): error TS2304: Cannot find name 'B'.
index.js(2,67): error TS2304: Cannot find name 'B'.


==== index.js (4 errors) ====
/**
* @typedef {{ [K in keyof B]: { fn: (a: A, b: B) => void; thing: B[K]; } }} Funcs
~
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my best guess is that the something set the type expression parents to the synthetic type alias, inconsistent with other type expressions, and the binder change now makes it consistent with the others. I have a branch that sets Host on typedef children which should fix this.

Edit:actually it may be that all type aliases were working by mistake?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually it may be that all typedefs were setting their type's parent to the synthetic node. Not in the reparser, though, so through the binder's existing code.

!!! error TS2304: Cannot find name 'B'.
~
!!! error TS2304: Cannot find name 'A'.
~
!!! error TS2304: Cannot find name 'B'.
~
!!! error TS2304: Cannot find name 'B'.
* @template A
* @template {Record<string, unknown>} B
*/

/**
* @template A
* @template {Record<string, unknown>} B
* @param {Funcs<A, B>} fns
* @returns {[A, B]}
*/
function foo(fns) {
return /** @type {any} */ (null);
}

const result = foo({
bar: {
fn:
/** @param {string} a */
(a) => {},
thing: "asd",
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@
* @returns {[A, B]}
*/
function foo(fns) {
>foo : <A, B extends Record<string, unknown>>(fns: Funcs<A, B>) => [A, B]
>fns : Funcs<A, B>
>foo : <A, B extends Record<string, unknown>>(fns: { [x: string]: { fn: (a: A, b: B) => void; thing: B; }; }) => [A, B]
>fns : { [x: string]: { fn: (a: A, b: B) => void; thing: B; }; }

return /** @type {any} */ (null);
>(null) : any
>null : any
}

const result = foo({
>result : [string, { bar: string; }]
>foo({ bar: { fn: /** @param {string} a */ (a) => {}, thing: "asd", },}) : [string, { bar: string; }]
>foo : <A, B extends Record<string, unknown>>(fns: Funcs<A, B>) => [A, B]
>result : [unknown, Record<string, unknown>]
>foo({ bar: { fn: /** @param {string} a */ (a) => {}, thing: "asd", },}) : [unknown, Record<string, unknown>]
>foo : <A, B extends Record<string, unknown>>(fns: { [x: string]: { fn: (a: A, b: B) => void; thing: B; }; }) => [A, B]
>{ bar: { fn: /** @param {string} a */ (a) => {}, thing: "asd", },} : { bar: { fn: (a: string) => void; thing: string; }; }

bar: {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,22 @@ module.exports = Timer;
* @param {HookHandler} handle
*/
function Hook(handle) {
>Hook : (handle: HookHandler) => void
>handle : HookHandler
>Hook : (handle: (arg: any) => void) => void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the main different effect of this alternate-parent technique is that symbols no longer print as aliases. I'll need to figure out why when I start burning down the jsdoc diffs.

>handle : (arg: any) => void

this.handle = handle;
>this.handle = handle : HookHandler
>this.handle = handle : (arg: any) => void
>this.handle : any
>this : any
>handle : any
>handle : HookHandler
>handle : (arg: any) => void
}
module.exports = Hook;
>module.exports = Hook : (handle: HookHandler) => void
>module.exports : (handle: HookHandler) => void
>module : { Hook(handle: HookHandler): void; }
>exports : (handle: HookHandler) => void
>Hook : (handle: HookHandler) => void
>module.exports = Hook : (handle: (arg: any) => void) => void
>module.exports : (handle: (arg: any) => void) => void
>module : { Hook(handle: (arg: any) => void): void; }
>exports : (handle: (arg: any) => void) => void
>Hook : (handle: (arg: any) => void) => void

=== context.js ===
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export {}; // flag file as module
* @returns {SomeType}
*/
function doTheThing(x) {
>doTheThing : (x: number) => SomeType
>doTheThing : (x: number) => number | ExportedThing | LocalThing | { x: string; }
>x : number

return {x: ""+x};
Expand All @@ -56,14 +56,14 @@ class ExportedThing {
>"ok" : "ok"
}
module.exports = {
>module.exports = { doTheThing, ExportedThing,} : { doTheThing: (x: number) => SomeType; ExportedThing: typeof ExportedThing; }
>module.exports : { doTheThing: (x: number) => SomeType; ExportedThing: typeof ExportedThing; }
>module : { "export=": { doTheThing: (x: number) => SomeType; ExportedThing: typeof ExportedThing; }; }
>exports : { doTheThing: (x: number) => SomeType; ExportedThing: typeof ExportedThing; }
>{ doTheThing, ExportedThing,} : { doTheThing: (x: number) => SomeType; ExportedThing: typeof ExportedThing; }
>module.exports = { doTheThing, ExportedThing,} : { doTheThing: (x: number) => number | ExportedThing | LocalThing | { x: string; }; ExportedThing: typeof ExportedThing; }
>module.exports : { doTheThing: (x: number) => number | ExportedThing | LocalThing | { x: string; }; ExportedThing: typeof ExportedThing; }
>module : { "export=": { doTheThing: (x: number) => number | ExportedThing | LocalThing | { x: string; }; ExportedThing: typeof ExportedThing; }; }
>exports : { doTheThing: (x: number) => number | ExportedThing | LocalThing | { x: string; }; ExportedThing: typeof ExportedThing; }
>{ doTheThing, ExportedThing,} : { doTheThing: (x: number) => number | ExportedThing | LocalThing | { x: string; }; ExportedThing: typeof ExportedThing; }

doTheThing,
>doTheThing : (x: number) => SomeType
>doTheThing : (x: number) => number | ExportedThing | LocalThing | { x: string; }

ExportedThing,
>ExportedThing : typeof ExportedThing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
templateTagOnConstructorFunctions.js(3,18): error TS2304: Cannot find name 'U'.
templateTagOnConstructorFunctions.js(3,24): error TS2304: Cannot find name 'U'.


==== templateTagOnConstructorFunctions.js (2 errors) ====
/**
* @template U
* @typedef {(u: U) => U} Id
~
!!! error TS2304: Cannot find name 'U'.
~
!!! error TS2304: Cannot find name 'U'.
*/
/**
* @param {T} t
* @template T
*/
function Zet(t) {
/** @type {T} */
this.u
this.t = t
}
/**
* @param {T} v
* @param {Id<T>} id
*/
Zet.prototype.add = function(v, id) {
this.u = v || this.t
return id(this.u)
}
var z = new Zet(1)
z.t = 2
z.u = false

Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
file.js(3,15): error TS2304: Cannot find name 'T'.
file.js(17,17): error TS2304: Cannot find name 'T'.
file.js(18,15): error TS2304: Cannot find name 'T'.
file.js(18,18): error TS2304: Cannot find name 'U'.
file.js(23,15): error TS2304: Cannot find name 'T'.
file.js(28,15): error TS2304: Cannot find name 'T'.
file.js(33,14): error TS2706: Required type parameters may not follow optional type parameters.
file.js(38,17): error TS2744: Type parameter defaults can only reference previously declared type parameters.
file.js(34,15): error TS2304: Cannot find name 'T'.
file.js(34,18): error TS2304: Cannot find name 'U'.
file.js(38,17): error TS2304: Cannot find name 'U'.
file.js(39,17): error TS2304: Cannot find name 'T'.
file.js(40,15): error TS2304: Cannot find name 'T'.
file.js(40,18): error TS2304: Cannot find name 'U'.
file.js(45,17): error TS2304: Cannot find name 'T'.
file.js(53,14): error TS2706: Required type parameters may not follow optional type parameters.
file.js(60,17): error TS2304: Cannot find name 'U'.
file.js(61,17): error TS2304: Cannot find name 'T'.


==== file.js (7 errors) ====
==== file.js (17 errors) ====
/**
* @template {string | number} [T=string] - ok: defaults are permitted
* @typedef {[T]} A
Expand All @@ -27,17 +37,27 @@ file.js(61,17): error TS2304: Cannot find name 'T'.
/**
* @template T
* @template [U=T] - ok: default can reference earlier type parameter
~
!!! error TS2304: Cannot find name 'T'.
* @typedef {[T, U]} B
~
!!! error TS2304: Cannot find name 'T'.
~
!!! error TS2304: Cannot find name 'U'.
*/

/**
* @template {string | number} [T] - error: default requires an `=type`
* @typedef {[T]} C
~
!!! error TS2304: Cannot find name 'T'.
*/

/**
* @template {string | number} [T=] - error: default requires a `type`
* @typedef {[T]} D
~
!!! error TS2304: Cannot find name 'T'.
*/

/**
Expand All @@ -46,14 +66,24 @@ file.js(61,17): error TS2304: Cannot find name 'T'.
~
!!! error TS2706: Required type parameters may not follow optional type parameters.
* @typedef {[T, U]} E
~
!!! error TS2304: Cannot find name 'T'.
~
!!! error TS2304: Cannot find name 'U'.
*/

/**
* @template [T=U] - error: Type parameter defaults can only reference previously declared type parameters.
~
!!! error TS2744: Type parameter defaults can only reference previously declared type parameters.
!!! error TS2304: Cannot find name 'U'.
* @template [U=T]
~
!!! error TS2304: Cannot find name 'T'.
* @typedef {[T, U]} G
~
!!! error TS2304: Cannot find name 'T'.
~
!!! error TS2304: Cannot find name 'U'.
*/

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ inJs(1); // lints error. Why?

/**@type {IFn}*/
const inJsArrow = (j) => {
>inJsArrow : IFn
>inJsArrow : <T>(m: T) => T
>(j) => { return j;} : <T>(j: T) => T
>j : T

Expand All @@ -29,6 +29,6 @@ const inJsArrow = (j) => {
}
inJsArrow(2); // no error gets linted as expected
>inJsArrow(2) : 2
>inJsArrow : IFn
>inJsArrow : <T>(m: T) => T
>2 : 2

Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ var both1 = { type: 'a', x: 1 };

/** @type {import('./mod2').Both} */
var both2 = both1;
>both2 : import("./mod2").Both
>both2 : { type: "a"; x: 1; } | { type: "b"; y: 1; }
>both1 : any

/** @type {import('./mod3').Both} */
var both3 = both2;
>both3 : { type: "a"; x: 1; } | { type: "b"; y: 1; }
>both2 : import("./mod2").Both
>both2 : { type: "a"; x: 1; } | { type: "b"; y: 1; }



Loading