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
26 changes: 25 additions & 1 deletion _packages/api/src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,29 @@ export class RemoteNode extends RemoteNodeBase implements Node {
return this.decoder.decode(text);
}

private getChildAtNodeIndex(parent: number, index: number): RemoteNode | RemoteNodeList | undefined {
let next = parent + 1;
let count = 0;
while (next !== 0) {
let child = new RemoteNode(this.view, this.decoder, next, this);

if (child.parent.index !== parent) {
return undefined;
}

if (count === index) {
if (child.kind == KIND_NODE_LIST) {
return new RemoteNodeList(this.view, this.decoder, child.index, this);
} else {
return child;
}
}

count += 1;
next = child.next;
}
}
Comment on lines +393 to +414
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

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

The new getChildAtNodeIndex method is missing documentation. Add a JSDoc comment explaining its purpose, parameters (parent - the parent node index, index - the child's ordinal position), return value, and how it differs from getOrCreateChildAtNodeIndex (specifically that it doesn't cache children).

Copilot uses AI. Check for mistakes.

private getOrCreateChildAtNodeIndex(index: number): RemoteNode | RemoteNodeList {
const pos = this.view.getUint32(this.offsetNodes + index * NODE_LEN + NODE_OFFSET_POS, true);
let child = (this._children ??= new Map()).get(pos);
Expand Down Expand Up @@ -487,7 +510,8 @@ export class RemoteNode extends RemoteNodeBase implements Node {
// were present, we would have `parameters = children[5]`, but since `postfixToken` and `astersiskToken` are
// missing, we have `parameters = children[5 - 2]`.
const propertyIndex = order - popcount8[~(mask | ((0xff << order) & 0xff)) & 0xff];
return this.getOrCreateChildAtNodeIndex(this.index + 1 + propertyIndex);
return this.getChildAtNodeIndex(this.index, propertyIndex);
// return this.getOrCreateChildAtNodeIndex(this.index + 1 + propertyIndex);
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

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

Commented-out code should be removed. If this line is being kept temporarily for reference during development, it should be removed before merging to keep the codebase clean.

Suggested change
// return this.getOrCreateChildAtNodeIndex(this.index + 1 + propertyIndex);

Copilot uses AI. Check for mistakes.
}

__print(): string {
Expand Down
45 changes: 45 additions & 0 deletions _packages/api/test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import {
isTemplateHead,
isTemplateMiddle,
isTemplateTail,
isFunctionDeclaration,
isExpressionStatement,
isCallExpression,
isPropertyAccessExpression,
isIdentifier,
isStringLiteral,
} from "@typescript/ast";
import assert from "node:assert";
import {
Expand Down Expand Up @@ -151,6 +157,45 @@ test("Dispose", () => {
});
});

test("Function", () => {
const currentFiles = {
"/tsconfig.json": "{}",
"/src/index.ts": `function foo() {
console.log("hello", "world")
}`,
};

let api = spawnAPI(currentFiles);
const project = api.loadProject("/tsconfig.json");
const sourceFile = project.getSourceFile("/src/index.ts");

let func = sourceFile?.statements[0]!;
assert.ok(isFunctionDeclaration(func));

let body = func.body!;
let expr = body.statements[0]!;
assert.ok(isExpressionStatement(expr));
let call_expr = expr.expression;
assert.ok(isCallExpression(call_expr));

let callee = call_expr.expression;
assert.ok(isPropertyAccessExpression(callee));
let left = callee.expression;
let right = callee.name;
assert.ok(isIdentifier(left));
assert.ok(isIdentifier(right));
assert.equal(left.text, "console");
assert.equal(right.text, "log");

let args = call_expr.arguments;
let arg0 = args[0];
let arg1 = args[1];
assert.ok(isStringLiteral(arg0));
assert.ok(isStringLiteral(arg1));
assert.equal(arg0.text, "hello");
assert.equal(arg1.text, "world");
});

test("Benchmarks", async () => {
await runBenchmarks(/*singleIteration*/ true);
});
Expand Down
2 changes: 1 addition & 1 deletion internal/api/encoder/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ func getChildrenPropertyMask(node *ast.Node) uint8 {
return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.PropertyName != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Initializer != nil) << 3)
case ast.KindFunctionDeclaration:
n := node.AsFunctionDeclaration()
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5) | (boolToByte(n.Body != nil) << 6)
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil && len(n.Parameters.Nodes) > 0) << 4) | (boolToByte(n.Type != nil) << 5) | (boolToByte(n.Body != nil) << 6)
Copy link

Copilot AI Nov 3, 2025

Choose a reason for hiding this comment

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

The FunctionDeclaration case now checks for both non-nil and non-empty parameters, but other similar function-like constructs (lines 485, 488, 491, 494, 497, 500, 503, 506, 521, 524, 596, 599, 689) only check for != nil. This inconsistency could lead to similar bugs with CallSignature, ConstructSignature, Constructor, GetAccessor, SetAccessor, IndexSignature, MethodSignature, MethodDeclaration, ArrowFunction, FunctionExpression, and other function-like nodes. Consider applying the same empty check (len(n.Parameters.Nodes) > 0) to all node types with Parameters properties for consistency.

Copilot uses AI. Check for mistakes.
case ast.KindInterfaceDeclaration:
n := node.AsInterfaceDeclaration()
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.HeritageClauses != nil) << 3) | (boolToByte(n.Members != nil) << 4)
Expand Down