diff --git a/internal/ast/deepclone.go b/internal/ast/deepclone.go index 48e9b4893d..c7d88032e3 100644 --- a/internal/ast/deepclone.go +++ b/internal/ast/deepclone.go @@ -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 } diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 686d093210..8df011de7a 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -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) + }) +} + // This should never be called outside the parser func SetImportsOfSourceFile(node *SourceFile, imports []*LiteralLikeNode) { node.imports = imports diff --git a/internal/fourslash/tests/signatureHelpJSDocReparsed_test.go b/internal/fourslash/tests/signatureHelpJSDocReparsed_test.go new file mode 100644 index 0000000000..d607868875 --- /dev/null +++ b/internal/fourslash/tests/signatureHelpJSDocReparsed_test.go @@ -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) +} diff --git a/testdata/baselines/reference/fourslash/signatureHelp/signatureHelpJSDocReparsed.baseline b/testdata/baselines/reference/fourslash/signatureHelp/signatureHelpJSDocReparsed.baseline new file mode 100644 index 0000000000..54c37caedb --- /dev/null +++ b/testdata/baselines/reference/fourslash/signatureHelp/signatureHelpJSDocReparsed.baseline @@ -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 + } + } +] \ No newline at end of file