Wit

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

  • Documentchildren: Block[]. The root parse returns. (After expansion the root is a distinct ExpandedDocument — 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 (see Param).
  • 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 verbatim Text.
  • 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: booleantrue for a +#name partial.

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

  • Textvalue: string.
  • Italicchildren: Inline[]<em>.
  • Boldchildren: Inline[]<strong>.
  • Interpolationname: string. The ::name:: capture hole.
  • BodySlot — no fields. The slot in a definition body.
  • ScriptCallfnName: 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

  • StringValuevalue: string.
  • NumberValuevalue: number.
  • BooleanValuevalue: boolean.
  • NullValue — no fields.
  • Recordfields: { key: string; value: DataValue }[]. An ordered array, not a JS object.
  • Collectionitems: DataValue[].

Helpers

  • Paramname: string | null (null = positional) · value: string · typedValue?: DataValue. typedValue is 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. value always exists; typedValue is additive.
  • AccessPathsegments: string[].
  • Condition — either ExistenceCondition { path: AccessPath } or ComparisonCondition { 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 === null marks a positional parameter.
  • Record.fields order is preserved; do not reorder it.
  • typedValue is additive — param.value is always the raw string, typedValue the structured view when one applies.
  • A NodeDef.body may hold a Record or Collection (the collapse case) even though those are not Block or Inline.

Common mistakes

  • Treating Record as a JS object — it is an ordered { key, value } array.
  • Treating body: null and body: [] as the same.
  • Forgetting Comment and NodeUse live in boththe Block and Inline unions.
  • Reaching for content on a normal node — only ScriptBlockhas it.

See also

Parsing → the AST

· Resolve & expand (what is gone post-expand) · Build your own renderer.