@malloydata/malloy 0.0.385 → 0.0.387
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +3 -1
- package/dist/lang/prettify/binary-chain.d.ts +3 -0
- package/dist/lang/prettify/binary-chain.js +65 -0
- package/dist/lang/prettify/block-body.d.ts +4 -0
- package/dist/lang/prettify/block-body.js +87 -0
- package/dist/lang/prettify/error-listener.d.ts +6 -0
- package/dist/lang/prettify/error-listener.js +19 -0
- package/dist/lang/prettify/field-properties.d.ts +3 -0
- package/dist/lang/prettify/field-properties.js +57 -0
- package/dist/lang/prettify/formatter.d.ts +17 -0
- package/dist/lang/prettify/formatter.js +150 -0
- package/dist/lang/prettify/import-select.d.ts +3 -0
- package/dist/lang/prettify/import-select.js +88 -0
- package/dist/lang/prettify/index.d.ts +19 -0
- package/dist/lang/prettify/index.js +163 -0
- package/dist/lang/prettify/inline-renderer.d.ts +3 -0
- package/dist/lang/prettify/inline-renderer.js +101 -0
- package/dist/lang/prettify/leaf.d.ts +9 -0
- package/dist/lang/prettify/leaf.js +340 -0
- package/dist/lang/prettify/out.d.ts +12 -0
- package/dist/lang/prettify/out.js +74 -0
- package/dist/lang/prettify/pick-case.d.ts +5 -0
- package/dist/lang/prettify/pick-case.js +222 -0
- package/dist/lang/prettify/rules.d.ts +13 -0
- package/dist/lang/prettify/rules.js +111 -0
- package/dist/lang/prettify/sections.d.ts +4 -0
- package/dist/lang/prettify/sections.js +380 -0
- package/dist/lang/prettify/tokens.d.ts +13 -0
- package/dist/lang/prettify/tokens.js +185 -0
- package/dist/lang/prettify/types.d.ts +9 -0
- package/dist/lang/prettify/types.js +7 -0
- package/dist/lang/run-malloy-parser.d.ts +23 -0
- package/dist/lang/run-malloy-parser.js +32 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* ============================================================================
|
|
7
|
+
* Malloy pretty-printer (experimental, /internal export — no stability promise)
|
|
8
|
+
* ============================================================================
|
|
9
|
+
*
|
|
10
|
+
* Architecture
|
|
11
|
+
* ------------
|
|
12
|
+
* 1. Lex + parse the input.
|
|
13
|
+
* 2. Walk the parse tree from `Formatter.format(node)`. The dispatcher routes
|
|
14
|
+
* each rule context to a per-rule free function in a sibling module;
|
|
15
|
+
* everything unhandled recurses to children, eventually reaching terminal
|
|
16
|
+
* tokens that emit through `emitVisibleToken` (the leaf).
|
|
17
|
+
* 3. The leaf walker handles per-token spacing/indentation/comments. It is
|
|
18
|
+
* v1's behaviour and is also the fallback when parsing fails.
|
|
19
|
+
*
|
|
20
|
+
* File layout
|
|
21
|
+
* -----------
|
|
22
|
+
* - ./out — Out buffer (indent, newlines, single-space coalescing).
|
|
23
|
+
* - ./tokens — LINE_BUDGET, INDENT_STR; classification sets
|
|
24
|
+
* (SECTION_TOKENS, BINARY_OPS, CALL_HUG_AFTER, etc.);
|
|
25
|
+
* findMatching, endLineOf.
|
|
26
|
+
* - ./rules — SECTION_STATEMENT_RULES + STATEMENT_KIND_BY_CTX.
|
|
27
|
+
* A maintainer adding a new section keyword lands here.
|
|
28
|
+
* - ./error-listener — CollectingErrorListener.
|
|
29
|
+
* - ./types — PrettifyError, PrettifyResult.
|
|
30
|
+
* - ./formatter — Formatter class (state + format() dispatcher).
|
|
31
|
+
* - ./leaf — emitVisibleToken + per-token state mutators
|
|
32
|
+
* (note, flushHiddenBefore, startStatementLine, …)
|
|
33
|
+
* and the small read-only helpers used by rule
|
|
34
|
+
* formatters (approxInlineSpan, hasCommentsInRange,
|
|
35
|
+
* formatTokenRange).
|
|
36
|
+
* - ./inline-renderer — renderItemInline (flat-string form for budget
|
|
37
|
+
* measurement and column alignment).
|
|
38
|
+
* - ./block-body — formatBlockBody, formatTopLevel.
|
|
39
|
+
* - ./sections — formatSectionStatement / formatSectionList.
|
|
40
|
+
* - ./field-properties — formatFieldProperties (postfix `{…}`).
|
|
41
|
+
* - ./pick-case — formatPickStatement, formatCaseStatement.
|
|
42
|
+
* - ./binary-chain — formatBinaryChain.
|
|
43
|
+
* - ./index (this) — prettify() entry point + type re-exports.
|
|
44
|
+
*
|
|
45
|
+
* Decisions worth knowing
|
|
46
|
+
* -----------------------
|
|
47
|
+
* - Comparison operators (`=`, `!=`, `<`, `>`, …) are kept glued to their
|
|
48
|
+
* operands. We only break chains at and/or/??/+/-. Justification: LHS/RHS
|
|
49
|
+
* of a comparison reads as one unit; breaking inside is more confusing
|
|
50
|
+
* than the line being long.
|
|
51
|
+
* - SQL strings (`"""…"""`, including `%{…}` malloy interpolations) and
|
|
52
|
+
* block annotations (`#" … "`) are emitted verbatim from source. We don't
|
|
53
|
+
* own a SQL formatter; annotation indentation is significant.
|
|
54
|
+
* - `;` is the compact-inline statement separator. Wrapped form drops it
|
|
55
|
+
* (newlines do the job); inline form keeps it.
|
|
56
|
+
* - `,` in section-list bare flow: intra-line yes, end-of-line never.
|
|
57
|
+
* - Single-arg function calls don't wrap (no point — nowhere useful to break).
|
|
58
|
+
* - `(` hugs only after a known-callable token (CALL_HUG_AFTER); after `is`,
|
|
59
|
+
* `as`, `extend`, `on`, `when`, etc. the `(` is grouping and gets a space.
|
|
60
|
+
* CALL_HUG_AFTER includes the keyword-named built-ins that are commonly
|
|
61
|
+
* used as functions: ALL, EXCLUDE, the timeframe truncation keywords
|
|
62
|
+
* (YEAR/MONTH/DAY/…), and the cast-target type names
|
|
63
|
+
* (TIMESTAMP/DATE/NUMBER/STRING/BOOLEAN/JSON).
|
|
64
|
+
* - `!` is the cast operator (`epoch_ms!timestamp(x)`); it glues to both
|
|
65
|
+
* sides like `.` does.
|
|
66
|
+
* - Empty `{}` collapses inline (`extend {}`, not `extend {\n}`).
|
|
67
|
+
* - `import {a, b, c} from 'x'` formats as a flat list when it fits on the
|
|
68
|
+
* line; otherwise one item per line at +1 indent.
|
|
69
|
+
* - `view:` definitions in a block body always have a blank line before
|
|
70
|
+
* each one (after the first), even when no blank was in the source. The
|
|
71
|
+
* same-kind-no-blank rule still applies to other statement kinds.
|
|
72
|
+
* - `{...}` bodies that contain more than one section statement never
|
|
73
|
+
* collapse onto a single line, even when they would fit. Reading two
|
|
74
|
+
* `group_by:` clauses jammed on one line is hostile.
|
|
75
|
+
* - Single is-item section lists keep the keyword and item on the same
|
|
76
|
+
* line (`nest: name is { … }`), so the body wraps naturally instead of
|
|
77
|
+
* forcing a `nest:\n name is {` opener split.
|
|
78
|
+
* - join_one / join_many / join_cross multi-item lists always wrap one
|
|
79
|
+
* item per line — items use `with`/`on` instead of `is` but are
|
|
80
|
+
* structurally is-like.
|
|
81
|
+
* - Trailing comments between the last item of a section list and the
|
|
82
|
+
* enclosing `}` are emitted at the section's inner indent, so they
|
|
83
|
+
* stay associated with the section the user wrote them in.
|
|
84
|
+
*
|
|
85
|
+
* Adding a new section-statement
|
|
86
|
+
* ------------------------------
|
|
87
|
+
* Add a row to SECTION_STATEMENT_RULES in ./rules with the rule's context
|
|
88
|
+
* class, the keyword token type(s), the list-context accessor, and the
|
|
89
|
+
* item-kind tag. Add a corresponding entry to listItems() in ./sections.
|
|
90
|
+
*
|
|
91
|
+
* Note: section keywords NOT in the table fall through to the leaf walker
|
|
92
|
+
* (which produces correct-but-plain output). Add a row only when the default
|
|
93
|
+
* isn't good enough — flow-fill, alignment, or annotation handling.
|
|
94
|
+
*
|
|
95
|
+
* Inter-token spacing
|
|
96
|
+
* -------------------
|
|
97
|
+
* The classifier `leadingAction(prev, next)` in ./tokens is the single
|
|
98
|
+
* source of truth for what separator (glue, hug, or coalescing space) goes
|
|
99
|
+
* between two adjacent tokens. Both walkers — emitVisibleToken in ./leaf
|
|
100
|
+
* and renderItemInline in ./inline-renderer — consult it, so a change to
|
|
101
|
+
* `leadingAction` (e.g. adding a token type that hugs `(`) takes effect in
|
|
102
|
+
* both paths automatically. Walker-specific concerns (newlines, indent,
|
|
103
|
+
* paren-wrap decisions, compact-inline `, ` / `; `) live in the walker.
|
|
104
|
+
*/
|
|
105
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
106
|
+
exports.prettify = prettify;
|
|
107
|
+
const antlr4ts_1 = require("antlr4ts");
|
|
108
|
+
const run_malloy_parser_1 = require("../run-malloy-parser");
|
|
109
|
+
const error_listener_1 = require("./error-listener");
|
|
110
|
+
const formatter_1 = require("./formatter");
|
|
111
|
+
const leaf_1 = require("./leaf");
|
|
112
|
+
/**
|
|
113
|
+
* Pretty-print a Malloy source string.
|
|
114
|
+
*
|
|
115
|
+
* **Experimental — this API may vanish or change at any time without notice.**
|
|
116
|
+
* It is exposed only via `@malloydata/malloy/internal` and is not covered by
|
|
117
|
+
* any compatibility commitment. Do not depend on it from anything you can't
|
|
118
|
+
* fix in a single PR.
|
|
119
|
+
*
|
|
120
|
+
* Parses the input, walks the parse tree, and emits a reformatted string.
|
|
121
|
+
*
|
|
122
|
+
* `errors` surfaces parse errors only (lexer + parser). Semantic / compile
|
|
123
|
+
* errors aren't checked here. If `errors.length > 0` you have a bigger problem
|
|
124
|
+
* than formatting — output is best-effort and not guaranteed to round-trip.
|
|
125
|
+
*
|
|
126
|
+
* @experimental
|
|
127
|
+
*/
|
|
128
|
+
function prettify(src) {
|
|
129
|
+
const lexerErrors = new error_listener_1.CollectingErrorListener();
|
|
130
|
+
const parserErrors = new error_listener_1.CollectingErrorListener();
|
|
131
|
+
const { tokenStream, parser: malloyParser } = (0, run_malloy_parser_1.makeMalloyParser)(src, {
|
|
132
|
+
lexerErrorListener: lexerErrors,
|
|
133
|
+
parserErrorListener: parserErrors,
|
|
134
|
+
});
|
|
135
|
+
tokenStream.fill();
|
|
136
|
+
const tokens = tokenStream.getTokens();
|
|
137
|
+
let root = null;
|
|
138
|
+
try {
|
|
139
|
+
root = malloyParser.malloyDocument();
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
root = null;
|
|
143
|
+
}
|
|
144
|
+
const f = new formatter_1.Formatter(src, tokens);
|
|
145
|
+
if (root) {
|
|
146
|
+
f.format(root);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Parse failed. Fall back to leaf-only emission so we still produce
|
|
150
|
+
// something reasonable.
|
|
151
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
152
|
+
const t = tokens[i];
|
|
153
|
+
if (t.channel !== antlr4ts_1.Token.HIDDEN_CHANNEL && t.type !== antlr4ts_1.Token.EOF) {
|
|
154
|
+
(0, leaf_1.emitVisibleToken)(f, t, i);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
result: f.o.toString(),
|
|
160
|
+
errors: [...lexerErrors.errors, ...parserErrors.errors],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* renderItemInline — flat-string form of a parse-rule's token range.
|
|
7
|
+
*
|
|
8
|
+
* Used by:
|
|
9
|
+
* - section-list inline measurement and bare-item flow-fill
|
|
10
|
+
* - postfix `{…}` inline form
|
|
11
|
+
* - pick / case alignment (rendering values and conditions to strings)
|
|
12
|
+
*
|
|
13
|
+
* Inter-token spacing comes from `leadingAction` in ./tokens, the same
|
|
14
|
+
* classifier the leaf walker (./leaf) consults. Both walkers therefore agree
|
|
15
|
+
* on what spacing goes between any two adjacent tokens; inline measurement
|
|
16
|
+
* predicts actual emission.
|
|
17
|
+
*
|
|
18
|
+
* Walker-specific divergence:
|
|
19
|
+
* - This produces a flat string (no newlines, no indentation).
|
|
20
|
+
* - SEMI emits `; ` (compact-inline form), not a newline.
|
|
21
|
+
* - COMMA emits `, ` (intra-line form), not a newline.
|
|
22
|
+
* - The space-coalescing skip-list omits `\n` (we never emit one) but is
|
|
23
|
+
* otherwise the same as Out.space.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.renderItemInline = renderItemInline;
|
|
27
|
+
const antlr4ts_1 = require("antlr4ts");
|
|
28
|
+
const tokens_1 = require("./tokens");
|
|
29
|
+
function renderItemInline(f, ctx) {
|
|
30
|
+
var _a;
|
|
31
|
+
let buf = '';
|
|
32
|
+
let lastType = null;
|
|
33
|
+
// Coalescing space: skip if buffer is empty or last char already provides
|
|
34
|
+
// separation. Mirrors Out.space() (./out) minus the newline check, since
|
|
35
|
+
// this renderer never emits a newline.
|
|
36
|
+
const space = () => {
|
|
37
|
+
if (buf.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
const last = buf[buf.length - 1];
|
|
40
|
+
if (last === ' ' || last === '(' || last === '[' || last === '.')
|
|
41
|
+
return;
|
|
42
|
+
buf += ' ';
|
|
43
|
+
};
|
|
44
|
+
const trim = () => {
|
|
45
|
+
buf = buf.replace(/ +$/, '');
|
|
46
|
+
};
|
|
47
|
+
for (let i = ctx._start.tokenIndex; i <= ctx._stop.tokenIndex; i++) {
|
|
48
|
+
const t = f.tokens[i];
|
|
49
|
+
if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
|
|
50
|
+
continue;
|
|
51
|
+
const text = (_a = t.text) !== null && _a !== void 0 ? _a : '';
|
|
52
|
+
if (t.type === tokens_1.L.SQL_BEGIN) {
|
|
53
|
+
const endIdx = (0, tokens_1.findMatching)(f.tokens, i, tokens_1.L.SQL_BEGIN, tokens_1.L.SQL_END);
|
|
54
|
+
const stop = f.tokens[endIdx].stopIndex;
|
|
55
|
+
space();
|
|
56
|
+
buf += f.src.substring(t.startIndex, stop + 1);
|
|
57
|
+
i = endIdx;
|
|
58
|
+
lastType = tokens_1.L.SQL_END;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (t.type === tokens_1.L.CCURLY) {
|
|
62
|
+
// `{}` empty: no inner space. `{ x }`: both inner spaces.
|
|
63
|
+
if (buf.length > 0 && !buf.endsWith('{') && !buf.endsWith(' '))
|
|
64
|
+
buf += ' ';
|
|
65
|
+
else
|
|
66
|
+
trim();
|
|
67
|
+
buf += '}';
|
|
68
|
+
lastType = t.type;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Walker-specific divergence: COMMA and SEMI use compact-inline form
|
|
72
|
+
// (`, ` and `; `). The leaf walker (./leaf) emits newlines for these in
|
|
73
|
+
// wrapped contexts.
|
|
74
|
+
if (t.type === tokens_1.L.COMMA) {
|
|
75
|
+
trim();
|
|
76
|
+
buf += ', ';
|
|
77
|
+
lastType = t.type;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (t.type === tokens_1.L.SEMI) {
|
|
81
|
+
trim();
|
|
82
|
+
buf += '; ';
|
|
83
|
+
lastType = t.type;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// Everything else: classifier-driven leading separator + text + (if a
|
|
87
|
+
// binary op) trailing space. Same shape as the leaf walker's default.
|
|
88
|
+
const action = (0, tokens_1.leadingAction)(lastType, t.type);
|
|
89
|
+
if (action === 'glue')
|
|
90
|
+
trim();
|
|
91
|
+
else if (action === 'space')
|
|
92
|
+
space();
|
|
93
|
+
// 'hug' — emit nothing before
|
|
94
|
+
buf += text;
|
|
95
|
+
if (tokens_1.BINARY_OPS.has(t.type))
|
|
96
|
+
buf += ' ';
|
|
97
|
+
lastType = t.type;
|
|
98
|
+
}
|
|
99
|
+
return buf;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=inline-renderer.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Token } from 'antlr4ts';
|
|
2
|
+
import type { Formatter } from './formatter';
|
|
3
|
+
export declare function note(f: Formatter, tokenType: number, idx: number, endTok: Token): void;
|
|
4
|
+
export declare function flushHiddenBefore(f: Formatter, idx: number): void;
|
|
5
|
+
export declare function startStatementLine(f: Formatter): void;
|
|
6
|
+
export declare function approxInlineSpan(f: Formatter, fromIdx: number, toIdx: number): number;
|
|
7
|
+
export declare function hasCommentsInRange(f: Formatter, fromIdx: number, toIdx: number): boolean;
|
|
8
|
+
export declare function emitVisibleToken(f: Formatter, t: Token, idx: number): void;
|
|
9
|
+
export declare function formatTokenRange(f: Formatter, fromIdx: number, toIdx: number): void;
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright Contributors to the Malloy project
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* Leaf walker: per-token spacing, structural punctuation, comment placement,
|
|
7
|
+
* and the small helpers the rule formatters use to look at adjacent tokens.
|
|
8
|
+
*
|
|
9
|
+
* Per-rule formatters call into here via:
|
|
10
|
+
* - emitVisibleToken — the leaf
|
|
11
|
+
* - flushHiddenBefore — emit pending comments before a target index
|
|
12
|
+
* - note — atomic per-token state update
|
|
13
|
+
* - startStatementLine — newline (consuming `needBlank`) for stmt start
|
|
14
|
+
* - approxInlineSpan — line-budget overestimate for a token range
|
|
15
|
+
* - hasCommentsInRange — comment-presence check (gates inline form)
|
|
16
|
+
* - formatTokenRange — emit a span via the leaf walker
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.note = note;
|
|
20
|
+
exports.flushHiddenBefore = flushHiddenBefore;
|
|
21
|
+
exports.startStatementLine = startStatementLine;
|
|
22
|
+
exports.approxInlineSpan = approxInlineSpan;
|
|
23
|
+
exports.hasCommentsInRange = hasCommentsInRange;
|
|
24
|
+
exports.emitVisibleToken = emitVisibleToken;
|
|
25
|
+
exports.formatTokenRange = formatTokenRange;
|
|
26
|
+
const antlr4ts_1 = require("antlr4ts");
|
|
27
|
+
const tokens_1 = require("./tokens");
|
|
28
|
+
// Update per-token state after emitting a token (or a token-like span).
|
|
29
|
+
function note(f, tokenType, idx, endTok) {
|
|
30
|
+
f.lastEmittedType = tokenType;
|
|
31
|
+
f.prevTokenEndLine = (0, tokens_1.endLineOf)(endTok);
|
|
32
|
+
f.lastEmittedIdx = idx;
|
|
33
|
+
}
|
|
34
|
+
// Emit any hidden-channel tokens (comments) sitting between f.lastEmittedIdx
|
|
35
|
+
// and idx-1, advancing f.lastEmittedIdx so they are not re-emitted by a later
|
|
36
|
+
// call. Visible tokens in the same range (e.g. commas the section-list rule
|
|
37
|
+
// chose to drop) are skipped.
|
|
38
|
+
function flushHiddenBefore(f, idx) {
|
|
39
|
+
if (idx <= f.lastEmittedIdx + 1)
|
|
40
|
+
return;
|
|
41
|
+
for (let j = f.lastEmittedIdx + 1; j < idx; j++) {
|
|
42
|
+
const t = f.tokens[j];
|
|
43
|
+
if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL) {
|
|
44
|
+
emitHiddenToken(f, t);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
f.lastEmittedIdx = idx - 1;
|
|
48
|
+
}
|
|
49
|
+
// Hidden-channel tokens (comments). Trailing comments (same line as previous
|
|
50
|
+
// token) attach with a space; leading comments (own line) start a fresh line.
|
|
51
|
+
function emitHiddenToken(f, t) {
|
|
52
|
+
var _a;
|
|
53
|
+
const text = (_a = t.text) !== null && _a !== void 0 ? _a : '';
|
|
54
|
+
const sameLine = f.prevTokenEndLine !== 0 && t.line === f.prevTokenEndLine;
|
|
55
|
+
if (t.type === tokens_1.L.COMMENT_TO_EOL) {
|
|
56
|
+
if (sameLine) {
|
|
57
|
+
f.o.space();
|
|
58
|
+
f.o.text(text.replace(/\s+$/, ''));
|
|
59
|
+
f.o.nl();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
if (f.o.indent === 0)
|
|
63
|
+
startStatementLine(f);
|
|
64
|
+
else
|
|
65
|
+
f.o.nl();
|
|
66
|
+
f.o.text(text.replace(/\s+$/, ''));
|
|
67
|
+
f.o.nl();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (t.type === tokens_1.L.BLOCK_COMMENT) {
|
|
71
|
+
if (sameLine) {
|
|
72
|
+
f.o.space();
|
|
73
|
+
f.o.text(text);
|
|
74
|
+
f.o.space();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
if (f.o.indent === 0)
|
|
78
|
+
startStatementLine(f);
|
|
79
|
+
else
|
|
80
|
+
f.o.nl();
|
|
81
|
+
f.o.text(text);
|
|
82
|
+
f.o.nl();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
f.prevTokenEndLine = (0, tokens_1.endLineOf)(t);
|
|
86
|
+
}
|
|
87
|
+
// Either consume `needBlank` (emit blank line) or just newline.
|
|
88
|
+
function startStatementLine(f) {
|
|
89
|
+
if (f.needBlank) {
|
|
90
|
+
f.o.blank();
|
|
91
|
+
f.needBlank = false;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
f.o.nl();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Approximate length of the inline form of tokens [fromIdx, toIdx]: sum of
|
|
98
|
+
// visible token text + 1 char between adjacent tokens. Used for budget checks
|
|
99
|
+
// (paren wrap, pickStatement wrap, etc.). It's an overestimate for dotted-paths
|
|
100
|
+
// and similar (`a.b` is 3 chars, our estimate is 5) but the direction is
|
|
101
|
+
// conservative — we wrap a touch sooner than strictly needed.
|
|
102
|
+
function approxInlineSpan(f, fromIdx, toIdx) {
|
|
103
|
+
var _a;
|
|
104
|
+
let len = 0;
|
|
105
|
+
let prev = -1;
|
|
106
|
+
for (let i = fromIdx; i <= toIdx; i++) {
|
|
107
|
+
const t = f.tokens[i];
|
|
108
|
+
if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
|
|
109
|
+
continue;
|
|
110
|
+
if (t.type === antlr4ts_1.Token.EOF)
|
|
111
|
+
continue;
|
|
112
|
+
if (prev >= 0)
|
|
113
|
+
len += 1;
|
|
114
|
+
len += ((_a = t.text) !== null && _a !== void 0 ? _a : '').length;
|
|
115
|
+
prev = i;
|
|
116
|
+
}
|
|
117
|
+
return len;
|
|
118
|
+
}
|
|
119
|
+
// Are there any hidden-channel tokens (comments) in [fromIdx, toIdx]? Used to
|
|
120
|
+
// gate inline-form candidates: any path that goes through `renderItemInline`
|
|
121
|
+
// strips comments, so candidates with comments must fall back to wrapped or
|
|
122
|
+
// broken form where the leaf walker preserves them.
|
|
123
|
+
function hasCommentsInRange(f, fromIdx, toIdx) {
|
|
124
|
+
for (let i = fromIdx; i <= toIdx; i++) {
|
|
125
|
+
if (f.tokens[i].channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
// Index of the next non-hidden, non-EOF token strictly after `idx`, or -1.
|
|
131
|
+
function nextVisibleAfter(f, idx) {
|
|
132
|
+
for (let j = idx + 1; j < f.tokens.length; j++) {
|
|
133
|
+
const t = f.tokens[j];
|
|
134
|
+
if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
|
|
135
|
+
continue;
|
|
136
|
+
if (t.type === antlr4ts_1.Token.EOF)
|
|
137
|
+
return -1;
|
|
138
|
+
return j;
|
|
139
|
+
}
|
|
140
|
+
return -1;
|
|
141
|
+
}
|
|
142
|
+
// Does the paren-pair at [openIdx, closeIdx] have any COMMA at its own depth?
|
|
143
|
+
// (Used to distinguish "function call with multiple args" from "single-arg
|
|
144
|
+
// call" / "empty parens".)
|
|
145
|
+
function hasCommaAtDepth1(f, openIdx, closeIdx) {
|
|
146
|
+
let depth = 0;
|
|
147
|
+
for (let i = openIdx + 1; i < closeIdx; i++) {
|
|
148
|
+
const t = f.tokens[i];
|
|
149
|
+
if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
|
|
150
|
+
continue;
|
|
151
|
+
if (t.type === tokens_1.L.OPAREN || t.type === tokens_1.L.OBRACK || t.type === tokens_1.L.OCURLY)
|
|
152
|
+
depth++;
|
|
153
|
+
else if (t.type === tokens_1.L.CPAREN || t.type === tokens_1.L.CBRACK || t.type === tokens_1.L.CCURLY)
|
|
154
|
+
depth--;
|
|
155
|
+
else if (t.type === tokens_1.L.COMMA && depth === 0)
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
// The big switch. Each branch ends with `note(...)`.
|
|
161
|
+
function emitVisibleToken(f, t, idx) {
|
|
162
|
+
var _a, _b;
|
|
163
|
+
if (idx <= f.lastEmittedIdx)
|
|
164
|
+
return; // already emitted (e.g. SQL block range)
|
|
165
|
+
flushHiddenBefore(f, idx);
|
|
166
|
+
const text = (_a = t.text) !== null && _a !== void 0 ? _a : '';
|
|
167
|
+
// ---- Verbatim regions: SQL strings and block annotations ----
|
|
168
|
+
// We don't own a SQL formatter. Annotation indentation is significant.
|
|
169
|
+
if (t.type === tokens_1.L.SQL_BEGIN) {
|
|
170
|
+
const endIdx = (0, tokens_1.findMatching)(f.tokens, idx, tokens_1.L.SQL_BEGIN, tokens_1.L.SQL_END);
|
|
171
|
+
const stop = f.tokens[endIdx].stopIndex;
|
|
172
|
+
f.o.space();
|
|
173
|
+
f.o.text(f.src.substring(t.startIndex, stop + 1));
|
|
174
|
+
note(f, tokens_1.L.SQL_END, endIdx, f.tokens[endIdx]);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (t.type === tokens_1.L.BLOCK_ANNOTATION_BEGIN ||
|
|
178
|
+
t.type === tokens_1.L.DOC_BLOCK_ANNOTATION_BEGIN) {
|
|
179
|
+
const endIdx = (0, tokens_1.findMatching)(f.tokens, idx, t.type, tokens_1.L.BLOCK_ANNOTATION_END);
|
|
180
|
+
const stop = f.tokens[endIdx].stopIndex;
|
|
181
|
+
if (f.o.indent === 0)
|
|
182
|
+
startStatementLine(f);
|
|
183
|
+
else
|
|
184
|
+
f.o.nl();
|
|
185
|
+
f.o.text(f.src.substring(t.startIndex, stop + 1));
|
|
186
|
+
f.o.nl();
|
|
187
|
+
note(f, tokens_1.L.BLOCK_ANNOTATION_END, endIdx, f.tokens[endIdx]);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// ---- Single-line annotations on their own line ----
|
|
191
|
+
if (t.type === tokens_1.L.ANNOTATION || t.type === tokens_1.L.DOC_ANNOTATION) {
|
|
192
|
+
if (f.o.indent === 0)
|
|
193
|
+
startStatementLine(f);
|
|
194
|
+
else
|
|
195
|
+
f.o.nl();
|
|
196
|
+
f.o.text(text.replace(/\s+$/, ''));
|
|
197
|
+
f.o.nl();
|
|
198
|
+
note(f, t.type, idx, t);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// ---- Curly braces: indent in/out around block bodies ----
|
|
202
|
+
if (t.type === tokens_1.L.OCURLY) {
|
|
203
|
+
f.o.space();
|
|
204
|
+
f.o.text('{');
|
|
205
|
+
// Empty `{}`: peek the next visible token. If it's the matching close
|
|
206
|
+
// AND nothing hidden sits between them (no comments to preserve), emit
|
|
207
|
+
// inline so we get `extend {}` not `extend {\n}`. With a comment in the
|
|
208
|
+
// gap (`extend { /* keep */ }`), fall through to the wrapping form so
|
|
209
|
+
// the leaf walker's comment placement runs.
|
|
210
|
+
const nextVisible = nextVisibleAfter(f, idx);
|
|
211
|
+
if (nextVisible !== -1 &&
|
|
212
|
+
f.tokens[nextVisible].type === tokens_1.L.CCURLY &&
|
|
213
|
+
!hasCommentsInRange(f, idx + 1, nextVisible - 1)) {
|
|
214
|
+
f.o.text('}');
|
|
215
|
+
if (f.o.indent === 0)
|
|
216
|
+
f.needBlank = true;
|
|
217
|
+
note(f, tokens_1.L.CCURLY, nextVisible, f.tokens[nextVisible]);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
f.o.indent++;
|
|
221
|
+
f.o.nl();
|
|
222
|
+
note(f, t.type, idx, t);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (t.type === tokens_1.L.CCURLY) {
|
|
226
|
+
f.o.indent = Math.max(0, f.o.indent - 1);
|
|
227
|
+
f.o.nl();
|
|
228
|
+
f.o.text('}');
|
|
229
|
+
if (f.o.indent === 0)
|
|
230
|
+
f.needBlank = true;
|
|
231
|
+
note(f, t.type, idx, t);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// ---- Statement separators ----
|
|
235
|
+
// `;` in wrapped form is dropped — newlines do the job. (Inline `;` appears
|
|
236
|
+
// via renderItemInline, not here.)
|
|
237
|
+
if (t.type === tokens_1.L.SEMI) {
|
|
238
|
+
f.o.trimTrailingSpace();
|
|
239
|
+
f.o.nl();
|
|
240
|
+
if (f.o.indent === 0)
|
|
241
|
+
f.needBlank = true;
|
|
242
|
+
note(f, t.type, idx, t);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// ---- Commas ----
|
|
246
|
+
// At top level (parenDepth==0) → newline. Inside parens that the wrap logic
|
|
247
|
+
// flagged as multi-line → newline. Otherwise inline (just space).
|
|
248
|
+
if (t.type === tokens_1.L.COMMA) {
|
|
249
|
+
f.o.trimTrailingSpace();
|
|
250
|
+
f.o.text(',');
|
|
251
|
+
const innerBreaks = f.parenBreaks.length > 0 && f.parenBreaks[f.parenBreaks.length - 1];
|
|
252
|
+
if (f.parenDepth === 0 || innerBreaks)
|
|
253
|
+
f.o.nl();
|
|
254
|
+
note(f, t.type, idx, t);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// ---- Open paren / bracket: decide call-hug vs grouping, decide wrap ----
|
|
258
|
+
if (t.type === tokens_1.L.OPAREN || t.type === tokens_1.L.OBRACK) {
|
|
259
|
+
const action = (0, tokens_1.leadingAction)(f.lastEmittedType, t.type);
|
|
260
|
+
if (action === 'space')
|
|
261
|
+
f.o.space();
|
|
262
|
+
f.o.text(text);
|
|
263
|
+
// Decide whether the contents will exceed the line budget when laid out
|
|
264
|
+
// inline. Break only if there's somewhere useful to break:
|
|
265
|
+
// - call/subscript parens (action='hug'): must have ≥ 2 args (commas at
|
|
266
|
+
// this depth);
|
|
267
|
+
// - grouping parens (action='space'): any overflow — content's own
|
|
268
|
+
// rules will wrap.
|
|
269
|
+
const closeType = t.type === tokens_1.L.OPAREN ? tokens_1.L.CPAREN : tokens_1.L.CBRACK;
|
|
270
|
+
const matchIdx = (0, tokens_1.findMatching)(f.tokens, idx, t.type, closeType);
|
|
271
|
+
const inlineLen = approxInlineSpan(f, idx, matchIdx);
|
|
272
|
+
const wouldOverflow = f.o.lineLengthSoFar() + inlineLen > tokens_1.LINE_BUDGET;
|
|
273
|
+
const hasArgCommas = hasCommaAtDepth1(f, idx, matchIdx);
|
|
274
|
+
const isCall = action === 'hug';
|
|
275
|
+
const willBreak = wouldOverflow && (hasArgCommas || !isCall);
|
|
276
|
+
f.parenBreaks.push(willBreak);
|
|
277
|
+
f.parenDepth++;
|
|
278
|
+
if (willBreak) {
|
|
279
|
+
f.o.indent++;
|
|
280
|
+
f.o.nl();
|
|
281
|
+
}
|
|
282
|
+
note(f, t.type, idx, t);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (t.type === tokens_1.L.CPAREN || t.type === tokens_1.L.CBRACK) {
|
|
286
|
+
const wasBreak = (_b = f.parenBreaks.pop()) !== null && _b !== void 0 ? _b : false;
|
|
287
|
+
if (wasBreak) {
|
|
288
|
+
f.o.indent = Math.max(0, f.o.indent - 1);
|
|
289
|
+
f.o.nl();
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
f.o.trimTrailingSpace();
|
|
293
|
+
}
|
|
294
|
+
f.o.text(text);
|
|
295
|
+
f.parenDepth = Math.max(0, f.parenDepth - 1);
|
|
296
|
+
note(f, t.type, idx, t);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// ---- Section keyword fallback (no explicit handler took it) ----
|
|
300
|
+
// Inside a brace block, force a fresh line. Keeps v1-style readable output
|
|
301
|
+
// even for sections we don't yet handle.
|
|
302
|
+
if (tokens_1.SECTION_TOKENS.has(t.type) && f.o.indent > 0) {
|
|
303
|
+
f.o.nl();
|
|
304
|
+
f.o.text(text.replace(/\s+/g, ''));
|
|
305
|
+
note(f, t.type, idx, t);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// ---- Top-level statement starter ----
|
|
309
|
+
if (tokens_1.TOP_LEVEL_STARTERS.has(t.type) &&
|
|
310
|
+
f.o.indent === 0 &&
|
|
311
|
+
f.parenDepth === 0) {
|
|
312
|
+
startStatementLine(f);
|
|
313
|
+
f.o.text(text.replace(/\s+/g, ''));
|
|
314
|
+
note(f, t.type, idx, t);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// ---- Default: identifier / literal / keyword / DOT / COLON / TRIPLECOLON
|
|
318
|
+
// / binary op. Leading separator from the classifier; binary ops also
|
|
319
|
+
// get a trailing space.
|
|
320
|
+
const action = (0, tokens_1.leadingAction)(f.lastEmittedType, t.type);
|
|
321
|
+
if (action === 'glue')
|
|
322
|
+
f.o.trimTrailingSpace();
|
|
323
|
+
else if (action === 'space')
|
|
324
|
+
f.o.space();
|
|
325
|
+
// 'hug' — emit nothing before
|
|
326
|
+
f.o.text(text);
|
|
327
|
+
if (tokens_1.BINARY_OPS.has(t.type))
|
|
328
|
+
f.o.space();
|
|
329
|
+
note(f, t.type, idx, t);
|
|
330
|
+
}
|
|
331
|
+
// Emit each visible token in [fromIdx, toIdx] via the leaf walker.
|
|
332
|
+
function formatTokenRange(f, fromIdx, toIdx) {
|
|
333
|
+
for (let i = fromIdx; i <= toIdx; i++) {
|
|
334
|
+
const t = f.tokens[i];
|
|
335
|
+
if (t.channel !== antlr4ts_1.Token.HIDDEN_CHANNEL && t.type !== antlr4ts_1.Token.EOF) {
|
|
336
|
+
emitVisibleToken(f, t, i);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=leaf.js.map
|