AST reference
Every AST kind, every field. This page exists so a tool author never has to open ast.ts. It is the authoritative shape reference for the tree that parse returns and that resolve and expandtransform.
The model
Nodes are discriminated unions keyed on kind. Every node extends HasLoc ({ loc: Loc }). There are three top-level
unions — Block, Inline, and DataValue — plus
the helpers Param, AccessPath, and Condition. AstNode is the umbrella union of all of them.
Top level
Document—children: Block[]. The rootparsereturns. (After expansion the root is a distinctExpandedDocument— see Resolve & expand.)
The Block union
Block = Paragraph | Comment | NodeUse | NodeDef | DataDef | ReferenceDirective | IfStatement | EachStatement | ScriptBlock
Paragraph
children: Inline[] → <p>. A run of prose.
Comment
text: string · inline: boolean. A ~ comment.
Dropped by renderers unless WIT_DEBUG_COMMENTS=1.
NodeUse — the central node
name: string— the node name.access?: string[]— segments of a dotted use (@a.b.c→['b','c']after the head).params: Param[]— the arguments (seeParam).paramsSource—'parens' | 'pipes' | 'mixed' | 'record' | 'form-fill' | 'none'.body—(Block | Inline)[] | null.null= self-closing;[]= empty body.inline: boolean— inline in prose vs. its own block.closeStyle—'named' | 'parens' | 'bare'.raw?: boolean—@@name; body is one verbatimText.frozen?: boolean—@@@name; verbatim andno{{ }}interpolation.
NodeDef
name: string— the name authors invoke with@name.captures: string[]— the named holes a caller fills.shape—'block' | 'single-line' | 'value-block'.body—(Block | Inline | Record | Collection)[].additive: boolean—truefor a+#namepartial.
A single-line definition whose whole value is a literal collapses: its body becomes [Record] or [Collection] even though those kinds are not in the Block or Inlineunions. That collapse case is the one time body holds a DataValue.
DataDef
name: string · value: DataValue. A #name: … data definition. @load nodes become DataDefs during loadExternalData.
ReferenceDirective
path: string. A reference ./other.wit line.
IfStatement
cond: Condition · then: Block[] · else?: Block[].
Evaluated to one branch by expand.
EachStatement
collection: AccessPath · itemName: string · body: Block[]. Unrolled by expand.
ScriptBlock
content: string (the JS text) · inline: boolean.
The Inline union
Inline = Text | Italic | Bold | Interpolation | BodySlot | NodeUse | ScriptCall | ScriptBlock | Comment
Text—value: string.Italic—children: Inline[]→<em>.Bold—children: Inline[]→<strong>.Interpolation—name: string. The::name::capture hole.BodySlot— no fields. Theslot in a definition body.ScriptCall—fnName: string·args: string[]. Args are raw source tokens — a quoted arg keeps its quotes (see Scripting).
The DataValue union
DataValue = StringValue | NumberValue | BooleanValue | NullValue | Record | Collection
StringValue—value: string.NumberValue—value: number.BooleanValue—value: boolean.NullValue— no fields.Record—fields: { key: string; value: DataValue }[]. An ordered array, not a JS object.Collection—items: DataValue[].
Helpers
Param—name: string | null(null= positional) ·value: string·typedValue?: DataValue.typedValueis shape-probed: it is populated when the captured text looks like[...],{...}, or a scalar literal — this is what lets@table |rows @sales|consume a collection.valuealways exists;typedValueis additive.AccessPath—segments: string[].Condition— eitherExistenceCondition { path: AccessPath }orComparisonCondition { left: AccessPath; op: 'is' | 'equals'; right: DataValue }.Loc—{ file, line, col, offset, length }·HasLoc—{ loc: Loc }.
Worked shapes
Each below is a one-line source and the trimmed JSON it parses to, lifted from the fixture that verifies the shape.
DataDef + Record
#author: { name - Mara Finch }{ "kind": "dataDef", "name": "author",
"value": { "kind": "record",
"fields": [ { "key": "name",
"value": { "kind": "stringValue", "value": "Mara Finch" } } ] } }Fixture 09-records.
Collection
#tags: [ a, b ]{ "kind": "dataDef", "name": "tags",
"value": { "kind": "collection", "items": [
{ "kind": "stringValue", "value": "a" },
{ "kind": "stringValue", "value": "b" } ] } }Fixture 10-collections.
EachStatement
(each @team as p) @li @p.name li@ (end)
{ "kind": "eachStatement", "itemName": "p",
"collection": { "kind": "accessPath", "segments": ["team"] },
"body": [ { "kind": "nodeUse", "name": "li", … } ] }Fixtures 13-iteration and 12-conditionals (for IfStatement).
Edges
NodeUse.body === null(self-closing) is distinct from[](empty body). Branch on both.Param.name === nullmarks a positional parameter.Record.fieldsorder is preserved; do not reorder it.typedValueis additive —param.valueis always the raw string,typedValuethe structured view when one applies.- A
NodeDef.bodymay hold aRecordorCollection(the collapse case) even though those are notBlockorInline.
Common mistakes
- Treating
Recordas a JS object — it is an ordered{ key, value }array. - Treating
body: nullandbody: []as the same. - Forgetting
CommentandNodeUselive in boththeBlockandInlineunions. - Reaching for
contenton a normal node — onlyScriptBlockhas it.
See also
Parsing → the AST· Resolve & expand (what is gone post-expand) · Build your own renderer.