Skip to content

Commit 3460a26

Browse files
authored
feat: Generic pretty-printer implementation (#39)
1 parent fabf4d6 commit 3460a26

17 files changed

Lines changed: 2378 additions & 1 deletion

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@elastic/esql",
33
"version": "0.0.1",
44
"author": "Kibana ES|QL team",
5-
"description": "A set of ts tools to parse, build and transform ES|QL queries programatically.",
5+
"description": "A set of ts tools to parse, build and transform ES|QL queries programmatically.",
66
"packageManager": "yarn@1.22.22",
77
"main": "./lib/index.js",
88
"types": "./lib/index.d.ts",

src/printer/README.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Printer — Wadler-Lindig Pretty-Printer
2+
3+
A pretty-printing library based on the Wadler-Lindig document algebra
4+
(Philip Wadler, *"A prettier printer"*, 2003; Christian Lindig's strict
5+
formulation). The implementation draws heavily from Prettier's internal
6+
`doc-printer`.
7+
8+
## How it works
9+
10+
Pretty-printing is a two-phase process:
11+
12+
1. **Build** a *document tree* (`Doc`) using combinators (see below). The tree
13+
describes the *structure* of possible layouts — it contains no concrete line
14+
breaks or indentation yet.
15+
2. **Layout** the tree into a concrete string. The layout engine fits as much
16+
content on each line as possible (up to `printWidth`), breaking `group`
17+
nodes when the flat representation would exceed the available width.
18+
19+
Example:
20+
21+
```ts
22+
import { text, group, indent, line, softline, join, layout } from './printer';
23+
24+
const doc = group([
25+
text('SELECT'),
26+
indent([line, join(
27+
[text(','), line],
28+
[text('a'), text('b'), text('c')])]
29+
),
30+
line,
31+
text('FROM t'),
32+
]);
33+
34+
layout(doc, { printWidth: 40 });
35+
// SELECT
36+
// a,
37+
// b,
38+
// c
39+
// FROM t
40+
41+
layout(doc, { printWidth: 80 });
42+
// SELECT a, b, c FROM t
43+
```
44+
45+
## `layout(tree, opts?)`
46+
47+
The main entry point. Takes a *print tree* and returns the formatted string.
48+
49+
Options:
50+
51+
| Option | Type | Default | Description |
52+
|--------------|----------------------------------------|---------|------------------------------------------|
53+
| `printWidth` | `number` | `80` | Target maximum line width. |
54+
| `tabWidth` | `number` | `2` | Spaces per indentation level. |
55+
| `useTabs` | `boolean` | `false` | Indent with tabs instead of spaces. |
56+
| `endOfLine` | `'\n'` \| `'\r\n'` \| `'\r'` | `'\n'` | Line-ending character(s). |
57+
58+
59+
## Combinators / Commands
60+
61+
The printer provides a set of combinators (or "commands") for building print
62+
trees. These are the building blocks for describing the structure of the output.
63+
64+
### Primitives
65+
66+
- `text(string)` — Literal text, printed as-is. Must **not** contain
67+
newline characters.
68+
- `Doc[]` (array) — Concatenation. An array of docs is rendered left to
69+
right.
70+
71+
### Line breaks
72+
73+
- `line` — Space `" "` in flat mode; *newline + indent* in break mode.
74+
- `softline` — Nothing `""` in flat mode; *newline + indent* in break mode.
75+
- `hardline` — Always a *newline + indent*. Forces the enclosing group to
76+
break.
77+
- `hardlineWithoutBreakParent` — Like `hardline` but does **not** force
78+
the parent group to break.
79+
- `literalline` — Hard line break that indents to the *root* position
80+
(set by `markAsRoot`) instead of the current nesting level. Preserves
81+
trailing whitespace. Forces enclosing groups to break.
82+
- `literallineWithoutBreakParent` — Like `literalline` but does **not**
83+
force the parent group to break.
84+
85+
### Grouping
86+
87+
- `group(tree, opts?)` — The core break-decision unit. Tries to render
88+
print tree node flat (single line). If it exceeds the remaining width, switches
89+
to break mode. Options: `shouldBreak` (force break), `id` (symbol for
90+
cross-referencing with `ifBreak`/`indentIfBreak`).
91+
- `conditionalGroup(states, opts?)` — Multi-state group. Provide
92+
alternative layouts from most compact to most expanded. The engine tries each
93+
in order and uses the first one that fits; if none fits, the last state is
94+
rendered in break mode.
95+
- `fill(parts)` — Wrapping layout. `parts` alternate between content and
96+
line-break docs: `[item, line, item, line, ...]`. Fills each line with as
97+
many items as fit before breaking — unlike `group`, which is all-or-nothing.
98+
99+
### Indentation
100+
101+
- `indent(tree)` — Increase indentation by one tab level.
102+
- `align(n, tree)` — Increase indentation by exactly `n` spaces (not a
103+
tab level). Special values: `-1` (dedent one level),
104+
`Number.NEGATIVE_INFINITY` (reset to root), `{ type: 'root' }` (mark current
105+
indent as root).
106+
- `dedent(tree)` — Decrease indentation by one level. Shorthand for
107+
`align(-1, tree)`.
108+
- `dedentToRoot(tree)` — Reset indentation to root (column 0 or the
109+
position set by `markAsRoot`).
110+
- `markAsRoot(tree)` — Mark the current indentation as the "root"
111+
position for `literalline`.
112+
113+
### Conditional content
114+
115+
- `ifBreak(breakDoc, flatDoc?, opts?)` — Emit `breakDoc` when the
116+
enclosing group (or a group referenced by `groupId`) is in break mode;
117+
otherwise emit `flatDoc`.
118+
- `indentIfBreak(tree, opts)` — Conditionally indent `doc` based on a
119+
referenced group's mode. Optimized form of
120+
`ifBreak(indent(tree), doc, { groupId })`. Set `negate: true` to reverse.
121+
- `breakParent` — Force all ancestor groups to break. Propagated during
122+
a pre-pass (`propagateBreaks`).
123+
124+
### Comments & line suffixes
125+
126+
- `lineSuffix(tree)` — Buffer `doc` and emit it at the end of the current
127+
line, just before the next line break. Used for trailing comments.
128+
- `lineSuffixBoundary` — Force-flush any pending `lineSuffix` content.
129+
Prevents trailing comments from leaking past syntactic boundaries.
130+
131+
### Miscellaneous
132+
133+
- `label(lbl, tree)` — Annotate a doc with an opaque label for
134+
language-specific heuristic introspection. No effect on layout.
135+
- `trim` — Remove all trailing whitespace from the output at the current
136+
position.
137+
- `cursor` — Placeholder for cursor-position tracking. The layout engine
138+
emits a sentinel; callers can locate it to preserve cursor position after
139+
formatting.
140+
- `join(separator, trees)` — Join an array of docs with `separator`
141+
between each pair.
142+
- `bracketedList(open, close, sep, items)` — Convenience for a grouped,
143+
indented, bracketed list. Flat: `(a, b, c)`. Broken:
144+
```
145+
(
146+
a,
147+
b,
148+
c
149+
)
150+
```
151+
- `addAlignmentToDoc(doc, size, tabWidth)` — Convert an absolute
152+
indentation size into nested `indent`/`align` wrappers inside `dedentToRoot`.
153+
Used for embedded content needing a specific indentation level.
154+
155+
## Traversal
156+
157+
- `traverse(doc, { onEnter?, onExit? })` — Generic depth-first traversal
158+
of a doc tree using an explicit stack (no recursion). Return `false` from
159+
`onEnter` to skip a node's children.

0 commit comments

Comments
 (0)