Skip to content

Deep clone descendants of reparsed nodes #966

New issue

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

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

Already on GitHub? Sign in to your account

Closed
Closed
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
1 change: 0 additions & 1 deletion internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -8825,7 +8825,6 @@ func (node *JSDocLinkCode) Name() *DeclarationName {

type JSDocTypeExpression struct {
TypeNodeBase
Host *Node
Type *TypeNode
}

Expand Down
1 change: 1 addition & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,7 @@ func isDeclarationStatementKind(kind Kind) bool {
KindExportDeclaration,
KindExportAssignment,
KindJSExportAssignment,
KindCommonJSExport,
KindNamespaceExportDeclaration:
return true
}
Expand Down
8 changes: 5 additions & 3 deletions internal/binder/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,11 @@ func (b *Binder) bind(node *ast.Node) bool {
if node == nil {
return false
}
node.Parent = b.parent
if node.Parent == nil {
node.Parent = b.parent
} else if node.Parent != b.parent {
panic("parent pointer already set, but doesn't match - collectModuleReferences probably out of sync with binder")
}
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 Expand Up @@ -1646,8 +1650,6 @@ func (b *Binder) bindChildren(node *ast.Node) {
case ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression, ast.KindPropertyAssignment, ast.KindSpreadElement:
b.inAssignmentPattern = saveInAssignmentPattern
b.bindEachChild(node)
case ast.KindJSExportAssignment, ast.KindCommonJSExport:
return // Reparsed nodes do not double-bind children, which are not reparsed
default:
b.bindEachChild(node)
}
Expand Down
10 changes: 3 additions & 7 deletions internal/binder/nameresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,13 +288,9 @@ loop:
if isSelfReferenceLocation(location, lastLocation) {
lastSelfReferenceLocation = location
}
if location.Kind == ast.KindJSDocTypeExpression && location.AsJSDocTypeExpression().Host != nil {
lastLocation = location.AsJSDocTypeExpression().Host.Type()
location = location.AsJSDocTypeExpression().Host
} else {
lastLocation = location
location = location.Parent
}

lastLocation = location
location = location.Parent
}
// We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`.
// If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself.
Expand Down
6 changes: 0 additions & 6 deletions internal/parser/jsdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,6 @@ func (p *Parser) parseJSDocTypeExpression(mayOmitBraces bool) *ast.Node {
}

result := p.factory.NewJSDocTypeExpression(t)
// normally parent references are set during binding. However, for clients that only need
// a syntax tree, and no semantic features, then the binding process is an unnecessary
// overhead. This functions allows us to set all the parents, without all the expense of
// binding.
ast.SetParentInChildren(result)
p.finishNode(result, pos)
return result
}
Expand All @@ -105,7 +100,6 @@ func (p *Parser) parseJSDocNameReference() *ast.Node {
}

result := p.factory.NewJSDocNameReference(entityName)
ast.SetParentInChildren(result)
p.finishNode(result, pos)
return result
}
Expand Down
78 changes: 42 additions & 36 deletions internal/parser/reparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ func (p *Parser) reparseCommonJS(node *ast.Node) {
var export *ast.Node
switch kind {
case ast.JSDeclarationKindModuleExports:
export = p.factory.NewJSExportAssignment(bin.Right)
export = p.factory.NewJSExportAssignment(p.factory.DeepCloneNode(bin.Right))
case ast.JSDeclarationKindExportsProperty:
nodes := p.nodeSlicePool.NewSlice(1)
nodes[0] = p.factory.NewModifier(ast.KindExportKeyword)
nodes[0].Flags = ast.NodeFlagsReparsed
nodes[0].Loc = bin.Loc
// TODO: Name can sometimes be a string literal, so downstream code needs to handle this
export = p.factory.NewCommonJSExport(p.newModifierList(bin.Loc, nodes), ast.GetElementOrPropertyAccessName(bin.Left), bin.Right)
export = p.factory.NewCommonJSExport(
p.newModifierList(bin.Loc, nodes),
p.factory.DeepCloneNode(ast.GetElementOrPropertyAccessName(bin.Left)),
p.factory.DeepCloneNode(bin.Right),
)
}
if export != nil {
export.Flags = ast.NodeFlagsReparsed
Expand Down Expand Up @@ -63,7 +67,7 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
var t *ast.Node
switch typeExpression.Kind {
case ast.KindJSDocTypeExpression:
t = typeExpression.Type()
t = p.factory.DeepCloneNode(typeExpression.Type())
Copy link
Member

Choose a reason for hiding this comment

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

do you need to clone every child added to a synthetic node? There are a few up in reparseCommonJS too (eg bin.Left)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, good catch.

case ast.KindJSDocTypeLiteral:
members := p.nodeSlicePool.NewSlice(0)
for _, member := range typeExpression.AsJSDocTypeLiteral().JSDocPropertyTags {
Expand All @@ -74,7 +78,7 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
questionToken.Loc = core.NewTextRange(member.Pos(), member.End())
questionToken.Flags = p.contextFlags | ast.NodeFlagsReparsed
}
prop := p.factory.NewPropertySignatureDeclaration(nil, member.Name(), questionToken, member.Type(), nil /*initializer*/)
prop := p.factory.NewPropertySignatureDeclaration(nil, p.factory.DeepCloneNode(member.Name()), questionToken, p.factory.DeepCloneNode(member.Type()), nil /*initializer*/)
prop.Loc = member.Loc
prop.Flags = p.contextFlags | ast.NodeFlagsReparsed
members = append(members, prop)
Expand All @@ -85,16 +89,25 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
default:
panic("typedef tag type expression should be a name reference or a type expression" + typeExpression.Kind.String())
}
typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, tag.AsJSDocTypedefTag().Name(), typeParameters, t)
typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.factory.DeepCloneNode(tag.AsJSDocTypedefTag().Name()), typeParameters, t)
typeAlias.Loc = core.NewTextRange(tag.Pos(), tag.End())
typeAlias.Flags = p.contextFlags | ast.NodeFlagsReparsed
ast.SetParentInChildren(typeAlias)
p.reparseList = append(p.reparseList, typeAlias)
case ast.KindJSDocImportTag:
importTag := tag.AsJSDocImportTag()
importClause := importTag.ImportClause.Clone(&p.factory)
importClause := p.factory.DeepCloneNode(importTag.ImportClause)
importClause.Flags |= ast.NodeFlagsReparsed
var modifiers *ast.ModifierList
if importTag.Modifiers() != nil {
modifiers = importTag.Modifiers().Clone(&p.factory)
for i, m := range modifiers.Nodes {
modifiers.Nodes[i] = p.factory.DeepCloneNode(m)
modifiers.Nodes[i].Flags |= ast.NodeFlagsReparsed
}
}
importClause.AsImportClause().IsTypeOnly = true
importDeclaration := p.factory.NewJSImportDeclaration(importTag.Modifiers(), importClause, importTag.ModuleSpecifier, importTag.Attributes)
importDeclaration := p.factory.NewJSImportDeclaration(modifiers, importClause, p.factory.DeepCloneNode(importTag.ModuleSpecifier), p.factory.DeepCloneNode(importTag.Attributes))
importDeclaration.Loc = core.NewTextRange(tag.Pos(), tag.End())
importDeclaration.Flags = p.contextFlags | ast.NodeFlagsReparsed
p.reparseList = append(p.reparseList, importDeclaration)
Expand All @@ -108,36 +121,36 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
if parent.Kind == ast.KindVariableStatement && parent.AsVariableStatement().DeclarationList != nil {
for _, declaration := range parent.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
if declaration.AsVariableDeclaration().Type == nil {
declaration.AsVariableDeclaration().Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, declaration)
declaration.AsVariableDeclaration().Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression)
break
}
}
} else if parent.Kind == ast.KindVariableDeclaration {
if parent.AsVariableDeclaration().Type == nil {
parent.AsVariableDeclaration().Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, parent)
parent.AsVariableDeclaration().Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression)
}
} else if parent.Kind == ast.KindPropertyDeclaration {
declaration := parent.AsPropertyDeclaration()
if declaration.Type == nil {
declaration.Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, parent)
declaration.Type = p.makeNewType(tag.AsJSDocTypeTag().TypeExpression)
}
} else if parent.Kind == ast.KindPropertyAssignment {
prop := parent.AsPropertyAssignment()
prop.Initializer = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), prop.Initializer)
prop.Initializer = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression), prop.Initializer)
} else if parent.Kind == ast.KindExportAssignment {
export := parent.AsExportAssignment()
export.Expression = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), export.Expression)
export.Expression = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression), export.Expression)
} else if parent.Kind == ast.KindReturnStatement {
ret := parent.AsReturnStatement()
ret.Expression = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), ret.Expression)
ret.Expression = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression), ret.Expression)
} else if parent.Kind == ast.KindParenthesizedExpression {
paren := parent.AsParenthesizedExpression()
paren.Expression = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), paren.Expression)
paren.Expression = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression), paren.Expression)
} else if parent.Kind == ast.KindExpressionStatement &&
parent.AsExpressionStatement().Expression.Kind == ast.KindBinaryExpression {
bin := parent.AsExpressionStatement().Expression.AsBinaryExpression()
if ast.GetAssignmentDeclarationKind(bin) != ast.JSDeclarationKindNone {
bin.Right = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression, nil), bin.Right)
bin.Right = p.makeNewTypeAssertion(p.makeNewType(tag.AsJSDocTypeTag().TypeExpression), bin.Right)
}
}
case ast.KindJSDocTemplateTag:
Expand All @@ -161,7 +174,7 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
jsparam := tag.AsJSDocParameterTag()
if param, ok := findMatchingParameter(fun, jsparam); ok {
if param.Type() == nil {
param.AsParameterDeclaration().Type = p.makeNewType(jsparam.TypeExpression, param)
param.AsParameterDeclaration().Type = p.makeNewType(jsparam.TypeExpression)
if param.AsParameterDeclaration().QuestionToken == nil &&
param.AsParameterDeclaration().Initializer == nil &&
(jsparam.IsBracketed || jsparam.TypeExpression != nil && jsparam.TypeExpression.Type().Kind == ast.KindJSDocOptionalType) {
Expand All @@ -175,7 +188,7 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
case ast.KindJSDocReturnTag:
if fun, ok := getFunctionLikeHost(parent); ok {
if fun.Type() == nil {
fun.FunctionLikeData().Type = p.makeNewType(tag.AsJSDocReturnTag().TypeExpression, fun)
fun.FunctionLikeData().Type = p.makeNewType(tag.AsJSDocReturnTag().TypeExpression)
}
}
}
Expand Down Expand Up @@ -209,14 +222,12 @@ func (p *Parser) gatherTypeParameters(j *ast.Node) *ast.NodeList {
constraint := tag.AsJSDocTemplateTag().Constraint
for _, tp := range tag.AsJSDocTemplateTag().TypeParameters().Nodes {
typeParameter := tp.AsTypeParameter()
var reparse *ast.Node
if constraint == nil {
reparse = typeParameter.Clone(&p.factory)
} else {
clone := constraint.Type().Clone(&p.factory)
clone.Flags |= ast.NodeFlagsReparsed
reparse = p.factory.NewTypeParameterDeclaration(typeParameter.Modifiers(), typeParameter.Name(), clone, typeParameter.DefaultType)
reparse.Loc = typeParameter.Loc
reparse := p.factory.DeepCloneNode(tp)
reparse.Loc = typeParameter.Loc
if constraint != nil {
constraintClone := p.factory.DeepCloneNode(constraint.Type())
constraintClone.Flags |= ast.NodeFlagsReparsed
reparse.AsTypeParameter().Constraint = constraintClone
}
reparse.Flags |= ast.NodeFlagsReparsed
typeParameters = append(typeParameters, reparse)
Expand Down Expand Up @@ -255,25 +266,20 @@ func getFunctionLikeHost(host *ast.Node) (*ast.Node, bool) {
}

func (p *Parser) makeNewTypeAssertion(t *ast.TypeNode, e *ast.Node) *ast.Node {
assert := p.factory.NewTypeAssertion(t, e)
if t.Flags&ast.NodeFlagsReparsed == 0 {
panic("should only pass reparsed type nodes to makeNewTypeAssertion")
}
assert := p.factory.NewTypeAssertion(t, p.factory.DeepCloneNode(e))
assert.Flags = p.contextFlags | ast.NodeFlagsReparsed
assert.Loc = core.NewTextRange(e.Pos(), e.End())
return assert
}

func (p *Parser) makeNewType(typeExpression *ast.TypeNode, host *ast.Node) *ast.Node {
func (p *Parser) makeNewType(typeExpression *ast.TypeNode) *ast.Node {
if typeExpression == nil || typeExpression.Type() == nil {
return nil
}
if typeExpression.AsJSDocTypeExpression().Host == nil {
typeExpression.AsJSDocTypeExpression().Host = host
} else {
panic("JSDoc type expression already has a host: " + typeExpression.AsJSDocTypeExpression().Host.Kind.String())
}
t := typeExpression.Type().Clone(&p.factory)
t := p.factory.DeepCloneNode(typeExpression.Type())
t.Flags |= ast.NodeFlagsReparsed
if host != nil {
t.Parent = host
}
return t
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export = Foo;
/** @typedef {(foo: Foo) => string} FooFun */

module.exports = /** @type {FooFun} */(void 0);
>module.exports = /** @type {FooFun} */(void 0) : (foo: typeof Foo) => string
>module.exports : (foo: typeof Foo) => string
>module : { export=: (foo: typeof Foo) => string; }
>exports : (foo: typeof Foo) => string
>(void 0) : (foo: typeof Foo) => string
>void 0 : (foo: typeof Foo) => string
>module.exports = /** @type {FooFun} */(void 0) : FooFun
>module.exports : FooFun
>module : { export=: FooFun; }
>exports : FooFun
>(void 0) : FooFun
>void 0 : FooFun
>void 0 : undefined
>0 : 0

Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ exports.x;
// 'exports' does not provide a contextual type to a function-class
exports.Cls = function() {
>exports.Cls = function() { this.x = 0; } : () => void
>exports.Cls : any
>exports.Cls : () => void
>exports : typeof import("/a")
>Cls : any
>Cls : () => void
>function() { this.x = 0; } : () => void

this.x = 0;
Expand All @@ -35,7 +35,7 @@ exports.x;
const instance = new exports.Cls();
>instance : any
>new exports.Cls() : any
>exports.Cls : any
>exports.Cls : () => void
>exports : typeof import("/a")
>Cls : any
>Cls : () => void

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function acceptNum(num) {}

/** @type {Fn} */
const fn1 =
>fn1 : (a: string, b: number) => void
>fn1 : Fn

/**
* @param [b]
Expand Down Expand Up @@ -46,7 +46,7 @@ const fn1 =

/** @type {Fn} */
const fn2 =
>fn2 : (a: string, b: number) => void
>fn2 : Fn

/**
* @param {number} [b]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
input.js(5,48): error TS2304: Cannot find name 'P'.
input.js(48,1): error TS2309: An export assignment cannot be used in a module with other exported elements.


==== input.js (2 errors) ====
==== input.js (1 errors) ====
/** @typedef {{ color: "red" | "blue" }} MyComponentProps */

/**
* @template P
* @typedef {{ (): any; defaultProps?: Partial<P> }} StatelessComponent */
~
!!! error TS2304: Cannot find name 'P'.

/**
* @type {StatelessComponent<MyComponentProps>}
Expand Down
Loading