Skip to content
Draft
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
3 changes: 1 addition & 2 deletions internal/ast/deepclone.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ func (f *NodeFactory) DeepCloneNode(node *Node) *Node {
func (f *NodeFactory) DeepCloneReparse(node *Node) *Node {
if node != nil {
node = getDeepCloneVisitor(f, false /*syntheticLocation*/).VisitNode(node)
SetParentInChildren(node)
node.Flags |= NodeFlagsReparsed
setParentAndReparsedInChildren(node)
}
return node
}
Expand Down
21 changes: 21 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,27 @@ func SetParentInChildren(node *Node) {
fn(node)
}

// setParentAndReparsedInChildren is like SetParentInChildren but also marks
// all nodes (including the root) with NodeFlagsReparsed. Used by
// DeepCloneReparse to ensure all cloned nodes from JSDoc are properly
// flagged as reparsed, so traversal code (e.g. FindPrecedingToken)
// correctly skips them.
func setParentAndReparsedInChildren(node *Node) {
node.Flags |= NodeFlagsReparsed
var visit func(child *Node, parent *Node) bool
visit = func(child *Node, parent *Node) bool {
child.Parent = parent
child.Flags |= NodeFlagsReparsed
child.ForEachChild(func(grandchild *Node) bool {
return visit(grandchild, child)
})
return false
}
node.ForEachChild(func(child *Node) bool {
return visit(child, node)
})
}
Comment on lines +942 to +953
Copy link
Copy Markdown
Member

@jakebailey jakebailey Mar 27, 2026

Choose a reason for hiding this comment

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

Using closures for this is slow/allocate-y, which is why the above code for the normal SetParentInChildren is so goofy. If this fix is right, we should make it work like SetParentInChildren


// This should never be called outside the parser
func SetImportsOfSourceFile(node *SourceFile, imports []*LiteralLikeNode) {
node.imports = imports
Expand Down
46 changes: 46 additions & 0 deletions internal/fourslash/tests/signatureHelpJSDocReparsed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestSignatureHelpJSDocReparsed(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @allowJs: true
// @checkJs: true
// @Filename: test.js
/**
* @param {string} x
*/
function foo(x) {
foo(/*1*/);
}
/**
* @this {string}
*/
function bar() {
bar(/*2*/);
}
/**
* @type {function(string): void}
*/
function qux(x) {
qux(/*3*/);
}
/**
* @template T
* @param {T} x
* @returns {T}
*/
function identity(x) {
identity(/*4*/);
}
`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.VerifyBaselineSignatureHelp(t)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// === SignatureHelp ===
=== /test.js ===
// /**
// * @param {string} x
// */
// function foo(x) {
// foo();
// ^
// | ----------------------------------------------------------------------
// | foo(**x: string**): void
// | ----------------------------------------------------------------------
// }
// /**
// * @this {string}
// */
// function bar() {
// bar();
// ^
// | ----------------------------------------------------------------------
// | bar(): void
// | ----------------------------------------------------------------------
// }
// /**
// * @type {function(string): void}
// */
// function qux(x) {
// qux();
// ^
// | ----------------------------------------------------------------------
// | qux(**x: any**): void
// | ----------------------------------------------------------------------
// }
// /**
// * @template T
// * @param {T} x
// * @returns {T}
// */
// function identity(x) {
// identity();
// ^
// | ----------------------------------------------------------------------
// | identity(**x: unknown**): unknown
// | ----------------------------------------------------------------------
// }
//
[
{
"marker": {
"Position": 55,
"LSPosition": {
"line": 4,
"character": 8
},
"Name": "1",
"Data": {}
},
"item": {
"signatures": [
{
"label": "foo(x: string): void",
"parameters": [
{
"label": "x: string"
}
],
"activeParameter": 0
}
],
"activeSignature": 0
}
},
{
"marker": {
"Position": 111,
"LSPosition": {
"line": 10,
"character": 8
},
"Name": "2",
"Data": {}
},
"item": {
"signatures": [
{
"label": "bar(): void",
"parameters": []
}
],
"activeSignature": 0
}
},
{
"marker": {
"Position": 184,
"LSPosition": {
"line": 16,
"character": 8
},
"Name": "3",
"Data": {}
},
"item": {
"signatures": [
{
"label": "qux(x: any): void",
"parameters": [
{
"label": "x: any"
}
],
"activeParameter": 0
}
],
"activeSignature": 0
}
},
{
"marker": {
"Position": 280,
"LSPosition": {
"line": 24,
"character": 13
},
"Name": "4",
"Data": {}
},
"item": {
"signatures": [
{
"label": "identity(x: unknown): unknown",
"parameters": [
{
"label": "x: unknown"
}
],
"activeParameter": 0
}
],
"activeSignature": 0
}
}
]
Loading