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
40 changes: 40 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,46 @@ func SetParentInChildren(node *Node) {
fn(node)
}

var setParentAndReparsedInChildrenPool = sync.Pool{
New: func() any {
return newParentAndReparsedInChildrenSetter()
},
}

func newParentAndReparsedInChildrenSetter() func(node *Node) bool {
// Consolidate state into one allocation.
// Similar to https://go.dev/cl/552375.
var state struct {
parent *Node
visit func(*Node) bool
}

state.visit = func(node *Node) bool {
if state.parent != nil {
node.Parent = state.parent
}
node.Flags |= NodeFlagsReparsed
saveParent := state.parent
state.parent = node
node.ForEachChild(state.visit)
state.parent = saveParent
return false
}

return state.visit
}

// 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) {
fn := setParentAndReparsedInChildrenPool.Get().(func(node *Node) bool)
defer setParentAndReparsedInChildrenPool.Put(fn)
fn(node)
}

// 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