Skip to content
Open
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
8 changes: 4 additions & 4 deletions packages/typescript/src/api/node/node.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,8 +665,8 @@ export class RemoteNode extends RemoteNodeBase implements Node {
get checkType(): RemoteNode | undefined {
return this.getNamedChild("checkType") as RemoteNode;
}
get children(): RemoteNode | RemoteNodeList | undefined {
return this.getNamedChild("children") as RemoteNode | RemoteNodeList;
get children(): RemoteNodeList | undefined {
return this.getNamedChild("children") as RemoteNodeList;
}
get className(): RemoteNode | undefined {
return this.getNamedChild("className") as RemoteNode;
Expand Down Expand Up @@ -761,8 +761,8 @@ export class RemoteNode extends RemoteNodeBase implements Node {
get initializer(): RemoteNode | undefined {
return this.getNamedChild("initializer") as RemoteNode;
}
get jsdocPropertyTags(): RemoteNode | undefined {
return this.getNamedChild("jsdocPropertyTags") as RemoteNode;
get jsdocPropertyTags(): RemoteNodeList | undefined {
return this.getNamedChild("jsdocPropertyTags") as RemoteNodeList;
}
get label(): RemoteNode | undefined {
return this.getNamedChild("label") as RemoteNode;
Expand Down
40 changes: 40 additions & 0 deletions packages/typescript/test/sync/ast.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import type {
Identifier,
InterfaceDeclaration,
JSDoc,
JSDocPropertyTag,
JSDocTypeLiteral,
JSDocTypedefTag,
MethodDeclaration,
Node,
NodeArray,
Expand Down Expand Up @@ -1359,6 +1362,43 @@ describe("RemoteNode + child/token getters", { concurrency }, () => {
});
});

test("jsdocPropertyTags exposes every raw list element", () => {
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { allowJs: true, noLib: true } }),
"/src/children.js": [
"/**",
" * @typedef {Object} Events",
" * @property {string} a",
" * @property {string} b",
" * @property {string} c",
" */",
"export const x = 1;",
].join("\n"),
});
try {
const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/children.js");
const typedefTag = sf.statements[0].jsDoc?.[0]?.tags?.[0] as JSDocTypedefTag | undefined;
assert.ok(typedefTag, "expected a JSDoc typedef tag");
const typeLiteral = typedefTag.typeExpression as JSDocTypeLiteral | undefined;
assert.ok(typeLiteral?.kind === SyntaxKind.JSDocTypeLiteral, "expected a JSDoc type literal");
const propertyTags = typeLiteral.jsdocPropertyTags;
assert.ok(Array.isArray(propertyTags), "jsdocPropertyTags should be a node list");
assert.deepStrictEqual(propertyTags.map(tag => (tag as JSDocPropertyTag).name.getText(sf)), ["a", "b", "c"]);

const visitedPropertyTagNames: string[] = [];
typeLiteral.forEachChild(child => {
if (child.kind === SyntaxKind.JSDocPropertyTag) {
visitedPropertyTagNames.push((child as JSDocPropertyTag).name.getText(sf));
}
return undefined;
});
assert.deepStrictEqual(visitedPropertyTagNames, ["a", "b", "c"]);
}
finally {
api.close();
}
});

test("getChildren throws on a synthesized node without a real position", () => {
const synthesized = createBlock([]); // a non-token node with pos/end === -1
assert.throws(() => synthesized.getChildren(), /real position/);
Expand Down
2 changes: 1 addition & 1 deletion tools/scripts/tsc/generate-encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1293,7 +1293,7 @@ function generateTSNodeGenerated(): string {
for (const m of info.childProps) {
const propName = api.uncapitalize(m.name);
const ct = childType(m);
const isListType = ct === "nodeList" || ct === "modifierList";
const isListType = ct === "nodeList" || ct === "rawNodeList" || ct === "modifierList";
const existing = childGetters.get(propName);
if (!existing) {
childGetters.set(propName, isListType ? "nodeList" : "node");
Expand Down
38 changes: 34 additions & 4 deletions tsc/internal/api/encoder/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,36 @@ type NodeIndexTable struct {

var nodeIndexTableKey = ast.NewSourceFileDataKey[*NodeIndexTable]()

// rawNodeList returns the child list stored directly on raw-list AST members.
// The encoded tree represents lists with a synthetic NodeList node, so callers
// must visit this list through the same hook as ordinary NodeList members.
func rawNodeList(node *ast.Node) *ast.NodeList {
switch node.Kind {
case ast.KindSyntaxList:
children := node.AsSyntaxList().Children
if len(children) == 0 {
return nil
}
return &ast.NodeList{Loc: core.NewTextRange(node.Pos(), node.End()), Nodes: children}
case ast.KindJSDocTypeLiteral:
tags := node.AsJSDocTypeLiteral().JSDocPropertyTags
if len(tags) == 0 {
return nil
}
return &ast.NodeList{Loc: core.NewTextRange(tags[0].Pos(), tags[len(tags)-1].End()), Nodes: tags}
default:
return nil
}
}

func visitEncodedChildren(node *ast.Node, visitor *ast.NodeVisitor) {
if list := rawNodeList(node); list != nil {
visitor.Hooks.VisitNodes(list, visitor)
Comment on lines +361 to +362
return
}
visitor.VisitEachChild(node)
}

// GetIndex returns the encoder index for the given node.
// On the first call the sortedIdx array is built (O(n log n) sort on a flat []uint32),
// then subsequent calls use binary search (O(log n)). This turns out to be much faster than
Expand Down Expand Up @@ -393,7 +423,7 @@ func BuildNodeIndexTable(sourceFile *ast.SourceFile) *NodeIndexTable {
visitor.Visit = func(node *ast.Node) *ast.Node {
nodeCount++
nodeTable = append(nodeTable, node)
visitor.VisitEachChild(node)
visitEncodedChildren(node, visitor)
for _, jsdoc := range node.JSDoc(sourceFile) {
visitor.Visit(jsdoc)
}
Expand All @@ -405,7 +435,7 @@ func BuildNodeIndexTable(sourceFile *ast.SourceFile) *NodeIndexTable {
nodeCount++
nodeTable = append(nodeTable, rootNode)

visitor.VisitEachChild(rootNode)
visitEncodedChildren(rootNode, visitor)
for _, jsdoc := range rootNode.JSDoc(sourceFile) {
visitor.Visit(jsdoc)
}
Expand Down Expand Up @@ -567,7 +597,7 @@ func encodeTree(rootNode *ast.Node, sourceFile *ast.SourceFile) ([]byte, *NodeIn
currentIndex := nodeCount
prevIndex = 0
parentIndex = currentIndex
visitor.VisitEachChild(node)
visitEncodedChildren(node, visitor)
if sourceFile != nil {
for _, jsdoc := range node.JSDoc(sourceFile) {
visitor.Visit(jsdoc)
Expand All @@ -587,7 +617,7 @@ func encodeTree(rootNode *ast.Node, sourceFile *ast.SourceFile) ([]byte, *NodeIn
sfExtendedDataOffset = len(extendedData)
nodes = appendUint32s(nodes, uint32(rootNode.Kind), utf16(rootNode.Pos()), utf16(rootNode.End()), 0, 0, getNodeData(rootNode, strs, positionMap, &extendedData, &structuredData), uint32(rootNode.Flags))

visitor.VisitEachChild(rootNode)
visitEncodedChildren(rootNode, visitor)
if sourceFile != nil {
for _, jsdoc := range rootNode.JSDoc(sourceFile) {
visitor.Visit(jsdoc)
Expand Down