Skip to content

API: Small bug fixes#3085

Open
navya9singh wants to merge 7 commits intomicrosoft:mainfrom
navya9singh:navyasingh/apiBugFix
Open

API: Small bug fixes#3085
navya9singh wants to merge 7 commits intomicrosoft:mainfrom
navya9singh:navyasingh/apiBugFix

Conversation

@navya9singh
Copy link
Member

Discovered 3 bugs while testing that copilot fixed:

  1. Empty array truthy check in transformFunctionInitializerAndType
    The Corsa API returns empty arrays [] for properties like signature.typeParameters where strada returned undefined. In JavaScript, !![] evaluates to true, so the
    check !!functionSignature.typeParameters at line 1915 of extractSymbol.ts was incorrectly truthy for non-generic functions. This caused an early return that left
    the FunctionType variableType set, which then sent an ArrayType node with a nil elementType to the Go printer — resulting in a nil pointer dereference panic. Fixed
    by changing to functionSignature.typeParameters && functionSignature.typeParameters.length > 0.
  2. RemoteNode.getNamedChild crashes on single-child nodes with zero encoded children
    In @typescript/api's RemoteNode class, the getNamedChild method's single-child path blindly read the node at index + 1 without verifying it was actually a child of
    the current node. When the Go encoder skips encoding empty NodeLists (e.g., JsxAttributes with no attributes), the node has zero children — but getNamedChild would
    read whatever unrelated node happened to be at index + 1, then throw "Expected only one child" because that node's next pointer was non-zero. Fixed by adding a
    hasChildren() guard that checks the parent pointer before accessing index + 1, returning undefined when the node has no children.
  3. Nil pointer dereference in canEmitSimpleArrowHead when printing ArrowFunction with nil Parameters
    When the JS encoder sends a synthetic ArrowFunction node (e.g., inside an object literal PropertyAssignment) to the Go printer, the Parameters field can be nil
    because the Go encoder skips encoding empty NodeLists. The printer's canEmitSimpleArrowHead function at printer.go:1513 accessed parameters.Nodes without a nil
    check, causing a panic. Fixed by adding parameters == nil guard to the condition, returning false (i.e., not a simple arrow head) when parameters is nil.

Copilot AI review requested due to automatic review settings March 13, 2026 07:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses several crashers/incorrect assumptions in the Go printer / Go decoder and the TypeScript @typescript/api RemoteNode implementation, primarily around empty/omitted child NodeLists in the binary AST protocol.

Changes:

  • Add nil-guard in Go printer’s canEmitSimpleArrowHead and a regression test for ArrowFunctions whose Parameters list is omitted/nil.
  • Update Go decoder to synthesize an empty ModuleBlock.Statements NodeList when omitted, and decode JSX fragment tokens as dedicated node types.
  • Fix RemoteNode.getNamedChild for single-child nodes with zero encoded children, add a JS regression test, and add several missing child-property accessors.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/printer/printer.go Avoid nil deref in canEmitSimpleArrowHead when parameters is nil.
internal/printer/printer_test.go Adds regression test ensuring printing an ArrowFunction with nil Parameters doesn’t panic.
internal/api/encoder/decoder.go Synthesizes empty statements for ModuleBlock; decodes JSX fragment tokens as their own node types.
internal/api/encoder/decoder_test.go Adds decode regression test for an empty namespace body (ModuleBlock with empty statements).
_packages/api/src/node/node.ts Prevent getNamedChild from reading a non-child at index+1 when there are zero children; adds some missing getters.
_packages/api/test/encoder.test.ts Adds regression test for JsxAttributes with empty properties NodeList; imports RemoteNodeList for traversal.

You can also share your feedback on Copilot code review. Take the survey.

}
return d.factory.NewModuleBlock(stmts), nil
case ast.KindCaseBlock:
return d.factory.NewCaseBlock(d.singleNodeListChild(childIndices)), nil
Copy link
Member

Choose a reason for hiding this comment

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

This is definitely plausible, but I'd want to know what the parser does 🥴

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, the parser always produces a non-nil NodeList for CaseBlock.Clause. Have also fixed other similar cases

Comment on lines +1345 to +1350
// JSX fragment tokens (must be their own types, not Token, for the printer)
case ast.KindJsxOpeningFragment:
return d.factory.NewJsxOpeningFragment(), nil
case ast.KindJsxClosingFragment:
return d.factory.NewJsxClosingFragment(), nil

Comment on lines +205 to +206
const declarations = declList.declarations! as unknown as RemoteNodeList;
const varDecl = declarations.at(0)!;
Copy link
Member

Choose a reason for hiding this comment

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

This is right, seems like it was a mistake in #3029

Copy link
Member

Choose a reason for hiding this comment

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

@navya9singh you need to fix this return type at the source in node.ts get declarations()

@navya9singh
Copy link
Member Author

@copilot format

func canEmitSimpleArrowHead(parentNode *ast.Node, parameters *ast.ParameterList) bool {
// only arrow functions with a single parameter may have simple arrow head
if !ast.IsArrowFunction(parentNode) || len(parameters.Nodes) != 1 {
if parameters == nil || !ast.IsArrowFunction(parentNode) || len(parameters.Nodes) != 1 {
Copy link
Member

Choose a reason for hiding this comment

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

One nil NodeList printer crash was fixed in the decoder; the other here. We want the decoder to produce ASTs that are as similar as possible to the parser. The fact that this check wasn't necessary before suggests that the decoder is doing something wrong. I am too scared to ask whether the parser itself is internally consistent about whether to use nil or empty NodeLists, but the least we can do is match whatever the parser is already doing. This should be handled in the decoder instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

The decoder now produces a non-nil empty NodeList instead of nil, which matches the parser's nil vs non-nil semantics.

emittestutil.CheckEmit(t, nil, file.AsSourceFile(), "() => ({}.a);")
}

func TestArrowFunctionNilParameters(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

This test should be deleted per my other comment. An API test that constructs something like this and prints it would be better.

virtulis added a commit to virtulis/typescript-go that referenced this pull request Mar 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants