Skip to content

IRRI & IRRef

IRRef pairs IR with its AST node. IRRI collects all IRRefs for batch transformations.

Each IR has a corresponding AST node. IRRef<T, TNode> holds both:

export class IRRef<T extends IR, TNode extends Node> {
constructor(
readonly ir: T, // Immutable IR
readonly node: TNode, // AST node to transform
) {
inject(IRRI).register(this) // Auto-register
}
}

When you create an IRRef, it auto-registers with IRRI and becomes discoverable.

IRRI collects all IRRef instances during compilation:

export class IRRI {
// Query all IRs of a kind
allByKind<K extends IRKind>(kind: K): IRRef<KnownIRs[K], Node>[]
// Find single IRRef with filter
firstIrr<K extends IRKind>(kind: K, filterFn: (item: KnownIRs[K]) => boolean): IRRef<KnownIRs[K], Node> | undefined
// Get pure IR without AST (for generators)
implode<T extends IR>(irrs: IRRef<T, Node>[]): ImplodeIRRef<T>[]
}

Process all IRs of a kind at once:

const components = irri.allByKind('Component')
for (const ref of components) {
componentTransformer.transform(ref) // Has both IR and AST
}
const myButton = irri.firstIrr('Component', (comp) => comp.tag === 'my-button')
if (myButton) {
updateDecorator(myButton.node, myButton.ir)
}

For generators that don’t need AST:

const components = irri.allByKind('Component')
const pureIR = irri.implode(components) // ComponentIR[]
generateIRJSON(pureIR)

Convenience – IR and AST are together; no separate lookup needed.

Efficiency – Batch by kind enables efficient transformation: process all components once.

Clarity – Fresh IR each compilation; IRRef is purely organizational.