Skip to main content

@abapify/abap-ast

Zero-dependency, strictly-typed AST nodes and a deterministic pretty-printer for ABAP. Think of it as a small ts.factory for ABAP: every node is built through a validating factory, every field is readonly, and print() turns a tree into source code that is stable byte-for-byte across runs.

No parser (yet) — emission only. The package is the foundation for deterministic code generators such as @abapify/openai-codegen.

Install

bun add @abapify/abap-ast

Public API

export {
// Printer
print,
printInlineType,
printExpression,
Writer,
type PrintOptions,
// Node factories (selection)
classDef,
localClassDef,
section,
typeDef,
builtinType,
namedTypeRef,
tableType,
structureType,
enumType,
methodDef,
methodImpl,
methodParam,
attributeDef,
constantDecl,
eventDef,
interfaceDef,
// Error
AbapAstError,
} from '@abapify/abap-ast';

Quick start

import {
print,
classDef,
section,
typeDef,
builtinType,
methodDef,
methodParam,
} from '@abapify/abap-ast';

const cls = classDef({
name: 'zcl_hello',
sections: [
section({
visibility: 'public',
members: [
typeDef({ name: 'ty_id', type: builtinType({ name: 'string' }) }),
methodDef({
name: 'greet',
visibility: 'public',
params: [
methodParam({
paramKind: 'importing',
name: 'iv_name',
typeRef: builtinType({ name: 'string' }),
}),
methodParam({
paramKind: 'returning',
name: 'rv_text',
typeRef: builtinType({ name: 'string' }),
}),
],
}),
],
}),
],
});

console.log(print(cls));

produces:

CLASS zcl_hello DEFINITION PUBLIC CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_id TYPE string.
METHODS greet
IMPORTING iv_name TYPE string
RETURNING VALUE(rv_text) TYPE string.
ENDCLASS.

CLASS zcl_hello IMPLEMENTATION.
ENDCLASS.

Node families

AST node definitions are grouped by concern under src/nodes/:

FileNodes
base.tsAbapNode, Identifier, Visibility
types.tsBuiltinType, NamedTypeRef, TableType, StructureType, EnumType, TypeDef
data.tsDataDecl, FieldSymbolDecl, ConstantDecl
expressions.tsLiteral, Identifier, MethodCall, operator expressions
statements.tsControl flow, assignments, comments
members.tsMethodParam, MethodDef, MethodImpl, AttributeDef, EventDef
class.tsClassDef, LocalClassDef, Section
interface.tsInterfaceDef
errors.tsAbapAstError (thrown by every factory on invalid input)

Every factory validates its inputs (e.g. a method with a RETURNING parameter cannot also declare EXPORTING/CHANGING, a class cannot be both FINAL and ABSTRACT, a section's members must match its visibility), throws AbapAstError, and freezes the resulting node. Arrays are frozen too — the AST is effectively immutable.

ABAPDoc comments

Declaration nodes (TypeDef, MethodDef, AttributeDef, InterfaceDef, ClassDef) accept an optional abapDoc: readonly string[] field. The printer emits each entry as a "! <line> comment at the declaration's own indentation, immediately above the declaration. Consumers use this to attach structured, round-trip metadata (for example the @openapi-operation, @openapi-path, @openapi-schema, and @openapi-ref markers emitted by @abapify/openai-codegen).

import { print, methodDef, methodParam, builtinType } from '@abapify/abap-ast';

const m = methodDef({
name: 'get_foo',
visibility: 'public',
abapDoc: ['@openapi-operation getFoo', 'Return a foo.'],
params: [
methodParam({
paramKind: 'importing',
name: 'id',
typeRef: builtinType({ name: 'string' }),
}),
],
});

console.log(print(m));

emits:

"! @openapi-operation getFoo
"! Return a foo.
METHODS get_foo
IMPORTING id TYPE string.

Lines are written verbatim — no wrapping, no escaping, no tag rewriting.

print(node: AbapNode, options?: PrintOptions): string

interface PrintOptions {
indent?: number; // spaces per level, default 2
keywordCase?: 'upper' | 'lower'; // default 'upper'
eol?: string; // default '\n'
}

print() accepts top-level nodes: ClassDef, LocalClassDef, InterfaceDef, TypeDef, member-level nodes (AttributeDef, MethodDef, MethodImpl, EventDef, ConstantDecl), statements and expressions. Inline type references and expressions are also exported as printInlineType() and printExpression() for embedding into other generators.

Determinism guarantee

print() is a pure function of its inputs — no file I/O, no timestamps, no hashing of unrelated data. Given the same AST and the same options it returns the same string on every invocation, on every platform. Ordering is preserved exactly as declared: sections appear in the order they were passed, members in the order they were added to the section, parameters in the order given to methodDef. Downstream code generators rely on this property to produce diff-stable output.

Design note: ts.factory for ABAP

The shape of the API intentionally mirrors TypeScript's compiler factory (ts.factory.createClassDeclaration, etc.) — a set of small, typed, validating builders that compose into a tree, and a single printer that turns the tree back into source. This pattern makes code generation mechanical and testable: generators build trees, tests compare strings.

A future .aclass Chevrotain parser (tracked as a follow-up) can target the same AST definitions without any change to the printer or the factories. That symmetry — parse to AST, transform AST, print AST — is what the node shapes were designed for.

Consumers

  • @abapify/openai-codegen — OpenAPI → ABAP class code generator. Builds a ClassDef (plus any auxiliary LocalClassDef for exception types) and calls print().

See also

  • acds — ABAP CDS parser (also uses Chevrotain; will share conventions with the future .aclass parser).
  • adt-codegen — hook-based generator for ADT artefacts (TypeScript output, unrelated pipeline).