Skip to content

Teach Oxc::Node to read and print itself - #6

Merged
marcoroth merged 1 commit into
node-field-accessfrom
node-introspection
Aug 26, 2026
Merged

Teach Oxc::Node to read and print itself#6
marcoroth merged 1 commit into
node-field-accessfrom
node-introspection

Conversation

@marcoroth

@marcoroth marcoroth commented Aug 26, 2026

Copy link
Copy Markdown
Owner

This pull request gives a node a way to answer questions about itself. Reading its source needed the source passed back in, there was no way to get the ESTree back out, and inspect printed almost none of what a node carries.

The source comes with the tree

Oxc.parse already holds the source, so Oxc::ParseResult keeps it and hands it down to every node it builds.

parsed = Oxc.parse(source)

parsed.source
#=> "let count = 0\nfunction bump() { count += 1 }"

parsed.root.every("FunctionDeclaration").first.slice
#=> "function bump() { count += 1 }"

slice still takes an argument, for a node assembled by hand or read against a different string.

Getting at the ESTree

keys answers the field names a node carries and fields answers those fields and their values without the span. to_h and to_json answer the ESTree itself, and to_json nests, so { node: node }.to_json works.

Nodes also pattern match, and patterns nest, since a field holding a node comes back as one. Either spelling of a field name works.

node => { type: "VariableDeclarator", id: { name: }, init: { value: } }
name   #=> "count"
value  #=> 0

root.select { |node| node in { type: "FunctionDeclaration", id: { name: /^handle/ } } }

There is no deconstruct. each yields every descendant, so an array pattern over direct children would disagree with to_a.

Printing every field

#<Oxc::Node VariableDeclaration range=[0, 13] kind="let" declarations=[... 1 item]>
#<Oxc::Node VariableDeclarator range=[4, 13] id=#<Oxc::Node Identifier> init=#<Oxc::Node Literal>>
#<Oxc::Node Identifier range=[4, 9] name="count">

A field holding a node prints as that node's type, one holding a list prints how many, and one holding nothing prints the nil, false or [] it holds. Nothing is left out, so inspect and keys can never disagree.

Leaving out the empty fields was tried first and cost more than it saved. It hid nine of a TypeScript PropertyDefinition's twelve fields, and the field cap it bought room for was spending that room on computed=false static=false override=false. Both the filter and the cap are gone.

Spans print as range=[start, end], matching Herb::Token. Oxc::ParseResult and Oxc::Label print theirs the same way.

@marcoroth
marcoroth marked this pull request as ready for review August 26, 2026 04:53
@marcoroth
marcoroth merged commit 7dbd2bb into main Aug 26, 2026
11 checks passed
@marcoroth
marcoroth deleted the node-introspection branch August 26, 2026 04:57
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.

1 participant