mimo-lang 1.1.1 → 2.0.6
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/.gitattributes +24 -0
- package/LICENSE +21 -0
- package/README.md +71 -39
- package/adapters/browserAdapter.js +86 -0
- package/adapters/nodeAdapter.js +101 -0
- package/bin/cli.js +80 -0
- package/bin/commands/convert.js +27 -0
- package/bin/commands/doctor.js +139 -0
- package/bin/commands/eval.js +39 -0
- package/bin/commands/fmt.js +109 -0
- package/bin/commands/help.js +72 -0
- package/bin/commands/lint.js +117 -0
- package/bin/commands/repl.js +24 -0
- package/bin/commands/run.js +64 -0
- package/bin/commands/test.js +126 -0
- package/bin/utils/colors.js +38 -0
- package/bin/utils/formatError.js +47 -0
- package/bin/utils/fs.js +57 -0
- package/bin/utils/version.js +8 -0
- package/build.js +18 -0
- package/bun.lock +74 -0
- package/index.js +48 -77
- package/index.web.js +364 -0
- package/interpreter/BuiltinFunction.js +32 -0
- package/interpreter/ErrorHandler.js +120 -0
- package/interpreter/ExpressionEvaluator.js +106 -0
- package/interpreter/Interpreter.js +172 -0
- package/interpreter/MimoError.js +112 -0
- package/interpreter/ModuleLoader.js +236 -0
- package/interpreter/StatementExecutor.js +107 -0
- package/interpreter/Utils.js +82 -0
- package/interpreter/Values.js +87 -0
- package/interpreter/coreBuiltins.js +490 -0
- package/interpreter/environment.js +99 -0
- package/interpreter/evaluators/binaryExpressionEvaluator.js +111 -0
- package/interpreter/evaluators/collectionEvaluator.js +151 -0
- package/interpreter/evaluators/functionCallEvaluator.js +76 -0
- package/interpreter/evaluators/literalEvaluator.js +27 -0
- package/interpreter/evaluators/moduleAccessEvaluator.js +25 -0
- package/interpreter/evaluators/templateLiteralEvaluator.js +20 -0
- package/interpreter/executors/BaseExecutor.js +37 -0
- package/interpreter/executors/ControlFlowExecutor.js +206 -0
- package/interpreter/executors/FunctionExecutor.js +126 -0
- package/interpreter/executors/PatternMatchExecutor.js +93 -0
- package/interpreter/executors/VariableExecutor.js +144 -0
- package/interpreter/index.js +8 -0
- package/interpreter/stdlib/array/accessFunctions.js +61 -0
- package/interpreter/stdlib/array/arrayUtils.js +36 -0
- package/interpreter/stdlib/array/higherOrderFunctions.js +285 -0
- package/interpreter/stdlib/array/searchFunctions.js +77 -0
- package/interpreter/stdlib/array/setFunctions.js +49 -0
- package/interpreter/stdlib/array/transformationFunctions.js +68 -0
- package/interpreter/stdlib/array.js +85 -0
- package/interpreter/stdlib/assert.js +143 -0
- package/interpreter/stdlib/datetime.js +170 -0
- package/interpreter/stdlib/env.js +54 -0
- package/interpreter/stdlib/fs.js +161 -0
- package/interpreter/stdlib/http.js +92 -0
- package/interpreter/stdlib/json.js +70 -0
- package/interpreter/stdlib/math.js +309 -0
- package/interpreter/stdlib/object.js +142 -0
- package/interpreter/stdlib/path.js +69 -0
- package/interpreter/stdlib/regex.js +134 -0
- package/interpreter/stdlib/string.js +260 -0
- package/interpreter/suggestions.js +46 -0
- package/lexer/Lexer.js +245 -0
- package/lexer/TokenTypes.js +131 -0
- package/lexer/createToken.js +11 -0
- package/lexer/tokenizers/commentTokenizer.js +45 -0
- package/lexer/tokenizers/literalTokenizer.js +163 -0
- package/lexer/tokenizers/symbolTokenizer.js +69 -0
- package/lexer/tokenizers/whitespaceTokenizer.js +36 -0
- package/package.json +29 -13
- package/parser/ASTNodes.js +448 -0
- package/parser/Parser.js +188 -0
- package/parser/expressions/atomicExpressions.js +165 -0
- package/parser/expressions/conditionalExpressions.js +0 -0
- package/parser/expressions/operatorExpressions.js +79 -0
- package/parser/expressions/primaryExpressions.js +77 -0
- package/parser/parseStatement.js +184 -0
- package/parser/parserExpressions.js +115 -0
- package/parser/parserUtils.js +19 -0
- package/parser/statements/controlFlowParsers.js +106 -0
- package/parser/statements/functionParsers.js +314 -0
- package/parser/statements/moduleParsers.js +57 -0
- package/parser/statements/patternMatchParsers.js +124 -0
- package/parser/statements/variableParsers.js +155 -0
- package/repl.js +325 -0
- package/test.js +47 -0
- package/tools/PrettyPrinter.js +3 -0
- package/tools/convert/Args.js +46 -0
- package/tools/convert/Registry.js +91 -0
- package/tools/convert/Transpiler.js +78 -0
- package/tools/convert/plugins/README.md +66 -0
- package/tools/convert/plugins/alya/index.js +10 -0
- package/tools/convert/plugins/alya/to_alya.js +289 -0
- package/tools/convert/plugins/alya/visitors/expressions.js +257 -0
- package/tools/convert/plugins/alya/visitors/statements.js +403 -0
- package/tools/convert/plugins/base_converter.js +228 -0
- package/tools/convert/plugins/javascript/index.js +10 -0
- package/tools/convert/plugins/javascript/mimo_runtime.js +265 -0
- package/tools/convert/plugins/javascript/to_js.js +155 -0
- package/tools/convert/plugins/javascript/visitors/expressions.js +197 -0
- package/tools/convert/plugins/javascript/visitors/patterns.js +102 -0
- package/tools/convert/plugins/javascript/visitors/statements.js +236 -0
- package/tools/convert/plugins/python/index.js +10 -0
- package/tools/convert/plugins/python/mimo_runtime.py +811 -0
- package/tools/convert/plugins/python/to_py.js +329 -0
- package/tools/convert/plugins/python/visitors/expressions.js +272 -0
- package/tools/convert/plugins/python/visitors/patterns.js +100 -0
- package/tools/convert/plugins/python/visitors/statements.js +257 -0
- package/tools/convert.js +102 -0
- package/tools/format/CommentAttacher.js +190 -0
- package/tools/format/CommentLexer.js +152 -0
- package/tools/format/Printer.js +849 -0
- package/tools/format/config.js +107 -0
- package/tools/formatter.js +169 -0
- package/tools/lint/Linter.js +391 -0
- package/tools/lint/config.js +114 -0
- package/tools/lint/rules/consistent-return.js +62 -0
- package/tools/lint/rules/max-depth.js +56 -0
- package/tools/lint/rules/no-empty-function.js +45 -0
- package/tools/lint/rules/no-magic-numbers.js +46 -0
- package/tools/lint/rules/no-shadow.js +113 -0
- package/tools/lint/rules/no-unused-vars.js +26 -0
- package/tools/lint/rules/prefer-const.js +19 -0
- package/tools/linter.js +261 -0
- package/tools/replFormatter.js +93 -0
- package/tools/stamp-version.js +32 -0
- package/web/index.js +9 -0
- package/bun.lockb +0 -0
- package/cli.js +0 -84
- package/compiler/execute/interpreter.js +0 -68
- package/compiler/execute/interpreters/binary.js +0 -12
- package/compiler/execute/interpreters/call.js +0 -10
- package/compiler/execute/interpreters/if.js +0 -10
- package/compiler/execute/interpreters/try-catch.js +0 -10
- package/compiler/execute/interpreters/while.js +0 -8
- package/compiler/execute/utils/createfunction.js +0 -11
- package/compiler/execute/utils/evaluate.js +0 -20
- package/compiler/execute/utils/operate.js +0 -23
- package/compiler/lexer/processToken.js +0 -40
- package/compiler/lexer/tokenTypes.js +0 -4
- package/compiler/lexer/tokenizer.js +0 -74
- package/compiler/parser/expression/comparison.js +0 -18
- package/compiler/parser/expression/identifier.js +0 -29
- package/compiler/parser/expression/number.js +0 -10
- package/compiler/parser/expression/operator.js +0 -21
- package/compiler/parser/expression/punctuation.js +0 -31
- package/compiler/parser/expression/string.js +0 -6
- package/compiler/parser/parseExpression.js +0 -27
- package/compiler/parser/parseStatement.js +0 -34
- package/compiler/parser/parser.js +0 -45
- package/compiler/parser/statement/call.js +0 -26
- package/compiler/parser/statement/function.js +0 -29
- package/compiler/parser/statement/if.js +0 -34
- package/compiler/parser/statement/return.js +0 -10
- package/compiler/parser/statement/set.js +0 -11
- package/compiler/parser/statement/show.js +0 -10
- package/compiler/parser/statement/try-catch.js +0 -25
- package/compiler/parser/statement/while.js +0 -22
- package/converter/go/convert.js +0 -110
- package/converter/js/convert.js +0 -107
- package/jsconfig.json +0 -27
- package/vite.config.js +0 -17
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
// tools/format/Printer.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PrettyPrinter — AST-to-source formatter for Mimo.
|
|
5
|
+
*
|
|
6
|
+
* Improvements over the original PrettyPrinter.js:
|
|
7
|
+
* - A1: Comment preservation (leadingComments / trailingComments on AST nodes)
|
|
8
|
+
* - A2: Blank-line logic applied inside blocks, not just at the top level
|
|
9
|
+
* - A3: Full estimateNodeLength coverage (no more 24-fallback for known types)
|
|
10
|
+
* - A4: State reset on every format() call — safe to reuse across files
|
|
11
|
+
* - A5: Decorator indent no longer leaks / zeroes global currentIndent
|
|
12
|
+
* - A6: Configurable indentSize, useTabs, quoteStyle via constructor options
|
|
13
|
+
* - A7: Dead code removed (pipe InlineIfExpression branch, rest-param ternary)
|
|
14
|
+
* - A8: Trailing newline always normalised to exactly one '\n'
|
|
15
|
+
*/
|
|
16
|
+
export class PrettyPrinter {
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} options
|
|
19
|
+
* @param {number} [options.indentSize=4] Spaces per indent level (ignored when useTabs=true)
|
|
20
|
+
* @param {boolean} [options.useTabs=false] Use a tab character instead of spaces
|
|
21
|
+
* @param {'double'|'single'} [options.quoteStyle='double'] Quote character for string literals
|
|
22
|
+
* @param {number} [options.maxInlineArrayLength=100] Max estimated length before array goes multiline
|
|
23
|
+
* @param {number} [options.maxInlineObjectLength=80] Max estimated length before object goes multiline
|
|
24
|
+
*/
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this._indentUnit = options.useTabs
|
|
27
|
+
? '\t'
|
|
28
|
+
: ' '.repeat(options.indentSize ?? 4);
|
|
29
|
+
this._quote = options.quoteStyle === 'single' ? "'" : '"';
|
|
30
|
+
this.maxInlineArrayLength = options.maxInlineArrayLength ?? 100;
|
|
31
|
+
this.maxInlineObjectLength = options.maxInlineObjectLength ?? 80;
|
|
32
|
+
|
|
33
|
+
// Mutable state — reset on every format() call (A4)
|
|
34
|
+
this.currentIndent = '';
|
|
35
|
+
this.output = '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Format a parsed AST and return the canonical source string.
|
|
42
|
+
* Safe to call multiple times on the same instance.
|
|
43
|
+
* @param {object} ast Program node
|
|
44
|
+
* @returns {string} Formatted source, ending with exactly one newline (A8)
|
|
45
|
+
*/
|
|
46
|
+
format(ast) {
|
|
47
|
+
// A4: reset state so the same instance can be reused
|
|
48
|
+
this.output = '';
|
|
49
|
+
this.currentIndent = '';
|
|
50
|
+
this.visitNode(ast);
|
|
51
|
+
// A8: normalise trailing whitespace to exactly one newline
|
|
52
|
+
return this.output.trimEnd() + '\n';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Indent helpers ────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
indent() {
|
|
58
|
+
this.currentIndent += this._indentUnit;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
dedent() {
|
|
62
|
+
this.currentIndent = this.currentIndent.slice(0, -this._indentUnit.length);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
write(text) {
|
|
66
|
+
this.output += text;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
writeLine(line = '') {
|
|
70
|
+
this.output += this.currentIndent + line + '\n';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Main dispatcher ───────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
visitNode(node) {
|
|
76
|
+
if (!node || !node.type) return;
|
|
77
|
+
const visitor = this[`visit${node.type}`];
|
|
78
|
+
if (visitor) {
|
|
79
|
+
visitor.call(this, node);
|
|
80
|
+
} else {
|
|
81
|
+
console.warn(`[PrettyPrinter] No visitor for AST node type: ${node.type}`);
|
|
82
|
+
this.writeLine(`// UNKNOWN NODE: ${JSON.stringify(node)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Block helper ──────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Emit a list of statements with one extra indent level.
|
|
90
|
+
* A2: applies blank-line logic between siblings (same rules as top-level).
|
|
91
|
+
*/
|
|
92
|
+
visitBlock(statements) {
|
|
93
|
+
this.indent();
|
|
94
|
+
for (let i = 0; i < statements.length; i++) {
|
|
95
|
+
const current = statements[i];
|
|
96
|
+
const next = statements[i + 1];
|
|
97
|
+
// A1: leading comments before this statement
|
|
98
|
+
this._emitLeadingComments(current);
|
|
99
|
+
this.visitNode(current);
|
|
100
|
+
// A1: trailing inline comment after this statement
|
|
101
|
+
this._emitTrailingComment(current);
|
|
102
|
+
// A2: blank lines inside blocks
|
|
103
|
+
if (next && this.shouldInsertBlankLine(current, next)) {
|
|
104
|
+
this.write('\n');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.dedent();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Comment helpers (A1) ──────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
_emitLeadingComments(node) {
|
|
113
|
+
if (!node?.leadingComments?.length) return;
|
|
114
|
+
for (const c of node.leadingComments) {
|
|
115
|
+
if (c.kind === 'block') {
|
|
116
|
+
this.writeLine(`/* ${c.value} */`);
|
|
117
|
+
} else {
|
|
118
|
+
this.writeLine(`// ${c.value}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_emitTrailingComment(node) {
|
|
124
|
+
if (!node?.trailingComment) return;
|
|
125
|
+
const c = node.trailingComment;
|
|
126
|
+
// trailing comment goes on the same line — we need to back up the '\n'
|
|
127
|
+
// that the statement visitor already wrote, then re-add it after the comment.
|
|
128
|
+
if (this.output.endsWith('\n')) {
|
|
129
|
+
this.output = this.output.slice(0, -1);
|
|
130
|
+
}
|
|
131
|
+
if (c.kind === 'block') {
|
|
132
|
+
this.write(` /* ${c.value} */`);
|
|
133
|
+
} else {
|
|
134
|
+
this.write(` // ${c.value}`);
|
|
135
|
+
}
|
|
136
|
+
this.write('\n');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Blank-line logic ──────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Decide whether to emit an extra blank line between two adjacent statements.
|
|
143
|
+
*
|
|
144
|
+
* @param {object} current AST node for the statement just emitted
|
|
145
|
+
* @param {object} next AST node for the upcoming statement
|
|
146
|
+
* @param {number} [currentEndLine] Last source line occupied by `current`
|
|
147
|
+
* (used to detect author-inserted blank lines). When not supplied
|
|
148
|
+
* we fall back to `current.line` (conservative — always safe).
|
|
149
|
+
*/
|
|
150
|
+
shouldInsertBlankLine(current, next, currentEndLine) {
|
|
151
|
+
if (!current || !next) return false;
|
|
152
|
+
|
|
153
|
+
// Keep import blocks tight; single blank line at the boundary.
|
|
154
|
+
if (current.type === 'ImportStatement' && next.type === 'ImportStatement') return false;
|
|
155
|
+
if (current.type === 'ImportStatement' || next.type === 'ImportStatement') return true;
|
|
156
|
+
|
|
157
|
+
// ── Preserve author intent ────────────────────────────────────────────
|
|
158
|
+
// If the original source had at least one blank line between the end of
|
|
159
|
+
// `current` and the start of `next`, honour it unconditionally.
|
|
160
|
+
const endLine = currentEndLine ?? current.line;
|
|
161
|
+
if (next.line - endLine >= 2) return true;
|
|
162
|
+
|
|
163
|
+
// Honour explicit empty-show visual separators.
|
|
164
|
+
if (this.isEmptyShowSeparator(current)) return true;
|
|
165
|
+
|
|
166
|
+
// Surround block-like constructs with breathing room.
|
|
167
|
+
if (this.isBlockLike(current) || this.isBlockLike(next)) return true;
|
|
168
|
+
|
|
169
|
+
// Consecutive simple statements with no blank line in source → keep tight.
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
isBlockLike(node) {
|
|
174
|
+
return [
|
|
175
|
+
'FunctionDeclaration',
|
|
176
|
+
'IfStatement',
|
|
177
|
+
'GuardStatement',
|
|
178
|
+
'WhileStatement',
|
|
179
|
+
'ForStatement',
|
|
180
|
+
'LoopStatement',
|
|
181
|
+
'TryStatement',
|
|
182
|
+
'MatchStatement',
|
|
183
|
+
'LabeledStatement',
|
|
184
|
+
].includes(node?.type);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
isEmptyShowSeparator(node) {
|
|
188
|
+
return (
|
|
189
|
+
node?.type === 'ShowStatement' &&
|
|
190
|
+
node.expression?.type === 'Literal' &&
|
|
191
|
+
node.expression.value === ''
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Length estimator (A3 — complete coverage) ─────────────────────────────
|
|
196
|
+
|
|
197
|
+
estimateNodeLength(node) {
|
|
198
|
+
if (!node) return 0;
|
|
199
|
+
switch (node.type) {
|
|
200
|
+
case 'Identifier':
|
|
201
|
+
return node.name.length;
|
|
202
|
+
case 'Literal':
|
|
203
|
+
return typeof node.value === 'string'
|
|
204
|
+
? node.value.length + 2
|
|
205
|
+
: String(node.value).length;
|
|
206
|
+
case 'PropertyAccess':
|
|
207
|
+
case 'SafePropertyAccess':
|
|
208
|
+
return this.estimateNodeLength(node.object) + 1 + String(node.property ?? '').length;
|
|
209
|
+
case 'ArrayAccess':
|
|
210
|
+
case 'SafeArrayAccess':
|
|
211
|
+
return this.estimateNodeLength(node.object) + this.estimateNodeLength(node.index) + 2;
|
|
212
|
+
case 'ModuleAccess':
|
|
213
|
+
return String(node.module ?? '').length + 1 + String(node.property ?? '').length;
|
|
214
|
+
case 'CallExpression':
|
|
215
|
+
case 'SafeCallExpression':
|
|
216
|
+
return 7
|
|
217
|
+
+ this.estimateNodeLength(node.callee)
|
|
218
|
+
+ node.arguments.reduce((n, arg) => n + this.estimateNodeLength(arg) + 2, 0);
|
|
219
|
+
case 'BinaryExpression':
|
|
220
|
+
return String(node.operator ?? '').length + 2
|
|
221
|
+
+ this.estimateNodeLength(node.left)
|
|
222
|
+
+ this.estimateNodeLength(node.right);
|
|
223
|
+
case 'UnaryExpression':
|
|
224
|
+
return String(node.operator ?? '').length + 1 + this.estimateNodeLength(node.argument);
|
|
225
|
+
case 'ArrayLiteral':
|
|
226
|
+
return 2 + node.elements.reduce((n, el) => n + this.estimateNodeLength(el) + 2, 0);
|
|
227
|
+
case 'ObjectLiteral':
|
|
228
|
+
return 4 + node.properties.reduce((n, prop) => {
|
|
229
|
+
if (!prop) return n;
|
|
230
|
+
if (prop.type === 'SpreadElement') return n + 3 + this.estimateNodeLength(prop.argument);
|
|
231
|
+
return n + String(prop.key ?? '').length + 2 + this.estimateNodeLength(prop.value) + 2;
|
|
232
|
+
}, 0);
|
|
233
|
+
case 'AnonymousFunction':
|
|
234
|
+
return 20 + (node.params?.length ?? 0) * 4;
|
|
235
|
+
// A3: previously fell through to the 24 default ↓
|
|
236
|
+
case 'SpreadElement':
|
|
237
|
+
return 3 + this.estimateNodeLength(node.argument);
|
|
238
|
+
case 'RangeLiteral':
|
|
239
|
+
return this.estimateNodeLength(node.start) + 4 + this.estimateNodeLength(node.end);
|
|
240
|
+
case 'InlineIfExpression':
|
|
241
|
+
return 10
|
|
242
|
+
+ this.estimateNodeLength(node.condition)
|
|
243
|
+
+ this.estimateNodeLength(node.consequent)
|
|
244
|
+
+ this.estimateNodeLength(node.alternate);
|
|
245
|
+
case 'TemplateLiteral':
|
|
246
|
+
return 2 + (node.parts ?? []).reduce((n, p) => {
|
|
247
|
+
if (p.type === 'Literal') return n + String(p.value).length;
|
|
248
|
+
return n + this.estimateNodeLength(p) + 3; // ${ }
|
|
249
|
+
}, 0);
|
|
250
|
+
case 'PipeExpression': {
|
|
251
|
+
// flatten the chain and sum stage lengths
|
|
252
|
+
let total = 0;
|
|
253
|
+
let cur = node;
|
|
254
|
+
while (cur?.type === 'PipeExpression') {
|
|
255
|
+
total += this.estimateNodeLength(cur.callee ?? cur.right) + 4;
|
|
256
|
+
cur = cur.left;
|
|
257
|
+
}
|
|
258
|
+
return total + this.estimateNodeLength(cur);
|
|
259
|
+
}
|
|
260
|
+
default:
|
|
261
|
+
return 24;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ── Statement visitors ────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
visitProgram(node) {
|
|
268
|
+
// Program-level leading comments (e.g. file header comments not
|
|
269
|
+
// attached to any statement, collected by CommentAttacher)
|
|
270
|
+
if (node.leadingComments?.length) {
|
|
271
|
+
for (const c of node.leadingComments) {
|
|
272
|
+
if (c.kind === 'block') {
|
|
273
|
+
this.writeLine(`/* ${c.value} */`);
|
|
274
|
+
} else {
|
|
275
|
+
this.writeLine(`// ${c.value}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (node.body.length) this.write('\n');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (let i = 0; i < node.body.length; i++) {
|
|
282
|
+
const current = node.body[i];
|
|
283
|
+
const next = node.body[i + 1];
|
|
284
|
+
this._emitLeadingComments(current);
|
|
285
|
+
this.visitNode(current);
|
|
286
|
+
this._emitTrailingComment(current);
|
|
287
|
+
if (next && this.shouldInsertBlankLine(current, next)) {
|
|
288
|
+
this.write('\n');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
visitVariableDeclaration(node) {
|
|
294
|
+
this.write(this.currentIndent);
|
|
295
|
+
if (node.isExported) this.write('export ');
|
|
296
|
+
this.write(`${node.kind} ${node.identifier} `);
|
|
297
|
+
this.visitNode(node.value);
|
|
298
|
+
this.write('\n');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
visitDestructuringAssignment(node) {
|
|
302
|
+
this.write(this.currentIndent + 'destructure ');
|
|
303
|
+
this.visitNode(node.pattern);
|
|
304
|
+
this.write(' from ');
|
|
305
|
+
this.visitNode(node.expression);
|
|
306
|
+
this.write('\n');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
visitPropertyAssignment(node) {
|
|
310
|
+
this.write(this.currentIndent + 'set ');
|
|
311
|
+
this.visitNode(node.object);
|
|
312
|
+
this.write(`.${node.property} `);
|
|
313
|
+
this.visitNode(node.value);
|
|
314
|
+
this.write('\n');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
visitBracketAssignment(node) {
|
|
318
|
+
this.write(this.currentIndent + 'set ');
|
|
319
|
+
this.visitNode(node.object);
|
|
320
|
+
this.write('[');
|
|
321
|
+
this.visitNode(node.index);
|
|
322
|
+
this.write('] ');
|
|
323
|
+
this.visitNode(node.value);
|
|
324
|
+
this.write('\n');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
visitFunctionDeclaration(node) {
|
|
328
|
+
if (node.decorators?.length > 0) {
|
|
329
|
+
for (const decorator of node.decorators) {
|
|
330
|
+
this.visitNode(decorator);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (node.isExported) {
|
|
334
|
+
this.write(this.currentIndent + 'export ');
|
|
335
|
+
} else {
|
|
336
|
+
this.write(this.currentIndent);
|
|
337
|
+
}
|
|
338
|
+
this.write(`function ${node.name}(`);
|
|
339
|
+
const params = this._formatParams(node.params, node.defaults, node.restParam);
|
|
340
|
+
this.write(params.join(', '));
|
|
341
|
+
this.write(')\n');
|
|
342
|
+
this.visitBlock(node.body);
|
|
343
|
+
this.writeLine('end');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
visitDecorator(node) {
|
|
347
|
+
// A5: write the full line directly — no currentIndent manipulation needed.
|
|
348
|
+
// visitNode(node.name) for an Identifier just calls write(node.name), which is safe.
|
|
349
|
+
this.write(this.currentIndent + '@');
|
|
350
|
+
this.write(typeof node.name === 'string' ? node.name : node.name.name ?? '');
|
|
351
|
+
if (node.arguments?.length) {
|
|
352
|
+
this.write('(');
|
|
353
|
+
node.arguments.forEach((arg, i) => {
|
|
354
|
+
this.visitNode(arg);
|
|
355
|
+
if (i < node.arguments.length - 1) this.write(', ');
|
|
356
|
+
});
|
|
357
|
+
this.write(')');
|
|
358
|
+
}
|
|
359
|
+
this.write('\n');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
visitIfStatement(node) {
|
|
363
|
+
this.write(this.currentIndent + 'if ');
|
|
364
|
+
this._visitIfBody(node);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Emit the condition, consequent, and optional alternate for an if statement.
|
|
369
|
+
* Called after the caller has already written the leading `if `.
|
|
370
|
+
* currentIndent must be at the correct outer level throughout — never zeroed.
|
|
371
|
+
*
|
|
372
|
+
* NOTE: We never emit `else if` as a single construct. The parser always
|
|
373
|
+
* requires two `end`s for a chained else-if (the inner if consumes its own
|
|
374
|
+
* `end`, then the outer if expects another). So we always emit the fully
|
|
375
|
+
* explicit nested form:
|
|
376
|
+
*
|
|
377
|
+
* else
|
|
378
|
+
* if <condition>
|
|
379
|
+
* ...
|
|
380
|
+
* end
|
|
381
|
+
* end
|
|
382
|
+
*/
|
|
383
|
+
_visitIfBody(node) {
|
|
384
|
+
this.visitNode(node.condition);
|
|
385
|
+
this.write('\n');
|
|
386
|
+
this.visitBlock(node.consequent);
|
|
387
|
+
if (node.alternate) {
|
|
388
|
+
if (node.alternate.type === 'IfStatement') {
|
|
389
|
+
// Nested else-if: emit as explicit else + indented if block.
|
|
390
|
+
this.writeLine('else');
|
|
391
|
+
this.visitBlock([node.alternate]);
|
|
392
|
+
} else {
|
|
393
|
+
this.writeLine('else');
|
|
394
|
+
this.visitBlock(node.alternate);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
this.writeLine('end');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
visitGuardStatement(node) {
|
|
401
|
+
this.write(this.currentIndent + 'guard ');
|
|
402
|
+
this.visitNode(node.condition);
|
|
403
|
+
this.write('\n');
|
|
404
|
+
this.writeLine('else');
|
|
405
|
+
this.visitBlock(node.alternate);
|
|
406
|
+
this.writeLine('end');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
visitWhileStatement(node) {
|
|
410
|
+
this.write(this.currentIndent + 'while ');
|
|
411
|
+
this.visitNode(node.condition);
|
|
412
|
+
this.write('\n');
|
|
413
|
+
this.visitBlock(node.body);
|
|
414
|
+
this.writeLine('end');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
visitForStatement(node) {
|
|
418
|
+
this.write(this.currentIndent + 'for ');
|
|
419
|
+
this.visitNode(node.variable);
|
|
420
|
+
this.write(' in ');
|
|
421
|
+
this.visitNode(node.iterable);
|
|
422
|
+
this.write('\n');
|
|
423
|
+
this.visitBlock(node.body);
|
|
424
|
+
this.writeLine('end');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
visitLoopStatement(node) {
|
|
428
|
+
if (node.label) this.writeLine(`${node.label}:`);
|
|
429
|
+
this.writeLine('loop');
|
|
430
|
+
this.visitBlock(node.body);
|
|
431
|
+
this.writeLine('end');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
visitLabeledStatement(node) {
|
|
435
|
+
this.writeLine(`${node.label}:`);
|
|
436
|
+
this.visitNode(node.statement);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
visitTryStatement(node) {
|
|
440
|
+
this.writeLine('try');
|
|
441
|
+
this.visitBlock(node.tryBlock);
|
|
442
|
+
if (node.catchBlock?.length > 0) {
|
|
443
|
+
this.write(this.currentIndent + 'catch');
|
|
444
|
+
if (node.catchVar) {
|
|
445
|
+
this.write(' ');
|
|
446
|
+
this.visitNode(node.catchVar);
|
|
447
|
+
}
|
|
448
|
+
this.write('\n');
|
|
449
|
+
this.visitBlock(node.catchBlock);
|
|
450
|
+
}
|
|
451
|
+
this.writeLine('end');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
visitThrowStatement(node) {
|
|
455
|
+
this.write(this.currentIndent + 'throw ');
|
|
456
|
+
this.visitNode(node.argument);
|
|
457
|
+
this.write('\n');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
visitCallStatement(node) {
|
|
461
|
+
this.write(this.currentIndent + 'call ');
|
|
462
|
+
this.visitNode(node.callee);
|
|
463
|
+
this.write('(');
|
|
464
|
+
node.arguments.forEach((arg, i) => {
|
|
465
|
+
this.visitNode(arg);
|
|
466
|
+
if (i < node.arguments.length - 1) this.write(', ');
|
|
467
|
+
});
|
|
468
|
+
this.write(')');
|
|
469
|
+
if (node.destination) {
|
|
470
|
+
this.write(' -> ');
|
|
471
|
+
this.visitNode(node.destination);
|
|
472
|
+
}
|
|
473
|
+
this.write('\n');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
visitShowStatement(node) {
|
|
477
|
+
this.write(this.currentIndent + 'show ');
|
|
478
|
+
this.visitNode(node.expression);
|
|
479
|
+
this.write('\n');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
visitReturnStatement(node) {
|
|
483
|
+
this.write(this.currentIndent + 'return');
|
|
484
|
+
if (node.argument) {
|
|
485
|
+
this.write(' ');
|
|
486
|
+
this.visitNode(node.argument);
|
|
487
|
+
}
|
|
488
|
+
this.write('\n');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
visitBreakStatement(node) {
|
|
492
|
+
this.write(this.currentIndent + 'break');
|
|
493
|
+
if (node.label) this.write(` ${node.label}`);
|
|
494
|
+
this.write('\n');
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
visitContinueStatement(node) {
|
|
498
|
+
this.write(this.currentIndent + 'continue');
|
|
499
|
+
if (node.label) this.write(` ${node.label}`);
|
|
500
|
+
this.write('\n');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
visitImportStatement(node) {
|
|
504
|
+
this.writeLine(`import ${node.alias} from "${node.path}"`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
visitMatchStatement(node) {
|
|
508
|
+
this.write(this.currentIndent + 'match ');
|
|
509
|
+
this.visitNode(node.discriminant);
|
|
510
|
+
this.write('\n');
|
|
511
|
+
for (const caseClause of node.cases) {
|
|
512
|
+
this.visitNode(caseClause);
|
|
513
|
+
}
|
|
514
|
+
this.writeLine('end');
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
visitCaseClause(node) {
|
|
518
|
+
this.indent();
|
|
519
|
+
this.write(this.currentIndent);
|
|
520
|
+
if (node.pattern) {
|
|
521
|
+
this.write('case ');
|
|
522
|
+
this.visitNode(node.pattern);
|
|
523
|
+
if (node.guard) {
|
|
524
|
+
this.write(' when ');
|
|
525
|
+
this.visitNode(node.guard);
|
|
526
|
+
}
|
|
527
|
+
} else {
|
|
528
|
+
this.write('default');
|
|
529
|
+
}
|
|
530
|
+
this.write(':\n');
|
|
531
|
+
this.visitBlock(node.consequent);
|
|
532
|
+
this.dedent();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ── Expression visitors ───────────────────────────────────────────────────
|
|
536
|
+
|
|
537
|
+
visitBinaryExpression(node) {
|
|
538
|
+
this.write(`${node.operator} `);
|
|
539
|
+
this.visitNode(node.left);
|
|
540
|
+
this.write(' ');
|
|
541
|
+
this.visitNode(node.right);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
visitUnaryExpression(node) {
|
|
545
|
+
this.write(`${node.operator} `);
|
|
546
|
+
this.visitNode(node.argument);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
visitIdentifier(node) {
|
|
550
|
+
this.write(node.name);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
visitLiteral(node) {
|
|
554
|
+
if (typeof node.value === 'string') {
|
|
555
|
+
const q = this._quote;
|
|
556
|
+
// Escape the chosen quote character within the string value
|
|
557
|
+
const escaped = node.value.replace(new RegExp(q, 'g'), `\\${q}`);
|
|
558
|
+
this.write(`${q}${escaped}${q}`);
|
|
559
|
+
} else {
|
|
560
|
+
this.write(String(node.value));
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
visitArrayLiteral(node) {
|
|
565
|
+
this.write('[');
|
|
566
|
+
const hasComplexElements = node.elements.some((el) =>
|
|
567
|
+
['ObjectLiteral', 'ArrayLiteral', 'AnonymousFunction', 'CallExpression'].includes(el?.type) ||
|
|
568
|
+
(el?.type === 'SpreadElement' && ['ObjectLiteral', 'ArrayLiteral'].includes(el.argument?.type))
|
|
569
|
+
);
|
|
570
|
+
const shouldMultiline =
|
|
571
|
+
node.elements.length > 4 ||
|
|
572
|
+
(hasComplexElements && node.elements.length > 1) ||
|
|
573
|
+
this.estimateNodeLength(node) > this.maxInlineArrayLength;
|
|
574
|
+
|
|
575
|
+
if (!shouldMultiline || node.elements.length === 0) {
|
|
576
|
+
node.elements.forEach((el, i) => {
|
|
577
|
+
if (el.type === 'SpreadElement') {
|
|
578
|
+
this.write('...');
|
|
579
|
+
this.visitNode(el.argument);
|
|
580
|
+
} else {
|
|
581
|
+
this.visitNode(el);
|
|
582
|
+
}
|
|
583
|
+
if (i < node.elements.length - 1) this.write(', ');
|
|
584
|
+
});
|
|
585
|
+
this.write(']');
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
this.write('\n');
|
|
590
|
+
this.indent();
|
|
591
|
+
node.elements.forEach((el, i) => {
|
|
592
|
+
this.write(this.currentIndent);
|
|
593
|
+
if (el.type === 'SpreadElement') {
|
|
594
|
+
this.write('...');
|
|
595
|
+
this.visitNode(el.argument);
|
|
596
|
+
} else {
|
|
597
|
+
this.visitNode(el);
|
|
598
|
+
}
|
|
599
|
+
if (i < node.elements.length - 1) this.write(',');
|
|
600
|
+
this.write('\n');
|
|
601
|
+
});
|
|
602
|
+
this.dedent();
|
|
603
|
+
this.write(this.currentIndent + ']');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
visitObjectLiteral(node) {
|
|
607
|
+
if (node.properties.length === 0) {
|
|
608
|
+
this.write('{}');
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const hasComplexValues = node.properties.some((prop) =>
|
|
612
|
+
prop?.type === 'SpreadElement' ||
|
|
613
|
+
['ObjectLiteral', 'ArrayLiteral', 'AnonymousFunction', 'CallExpression'].includes(prop?.value?.type)
|
|
614
|
+
);
|
|
615
|
+
const shouldMultiline =
|
|
616
|
+
node.properties.length > 4 ||
|
|
617
|
+
hasComplexValues ||
|
|
618
|
+
this.estimateNodeLength(node) > this.maxInlineObjectLength;
|
|
619
|
+
|
|
620
|
+
if (!shouldMultiline) {
|
|
621
|
+
this.write('{ ');
|
|
622
|
+
node.properties.forEach((prop, i) => {
|
|
623
|
+
if (prop?.type === 'SpreadElement') {
|
|
624
|
+
this.write('...');
|
|
625
|
+
this.visitNode(prop.argument);
|
|
626
|
+
} else {
|
|
627
|
+
this.write(`${prop.key}: `);
|
|
628
|
+
this.visitNode(prop.value);
|
|
629
|
+
}
|
|
630
|
+
if (i < node.properties.length - 1) this.write(', ');
|
|
631
|
+
});
|
|
632
|
+
this.write(' }');
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
this.write('{\n');
|
|
637
|
+
this.indent();
|
|
638
|
+
node.properties.forEach((prop, i) => {
|
|
639
|
+
this.write(this.currentIndent);
|
|
640
|
+
if (prop?.type === 'SpreadElement') {
|
|
641
|
+
this.write('...');
|
|
642
|
+
this.visitNode(prop.argument);
|
|
643
|
+
} else {
|
|
644
|
+
this.write(`${prop.key}: `);
|
|
645
|
+
this.visitNode(prop.value);
|
|
646
|
+
}
|
|
647
|
+
if (i < node.properties.length - 1) this.write(',');
|
|
648
|
+
this.write('\n');
|
|
649
|
+
});
|
|
650
|
+
this.dedent();
|
|
651
|
+
this.write(this.currentIndent + '}');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
visitArrayPattern(node) {
|
|
655
|
+
this.write('[');
|
|
656
|
+
node.elements.forEach((v, i) => {
|
|
657
|
+
this.visitNode(v);
|
|
658
|
+
if (i < node.elements.length - 1) this.write(', ');
|
|
659
|
+
});
|
|
660
|
+
this.write(']');
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
visitObjectPattern(node) {
|
|
664
|
+
this.write('{ ');
|
|
665
|
+
node.properties.forEach((p, i) => {
|
|
666
|
+
this.visitNode(p);
|
|
667
|
+
if (i < node.properties.length - 1) this.write(', ');
|
|
668
|
+
});
|
|
669
|
+
this.write(' }');
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
visitSpreadElement(node) {
|
|
673
|
+
this.write('...');
|
|
674
|
+
this.visitNode(node.argument);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
visitModuleAccess(node) {
|
|
678
|
+
this.write(`${node.module}.${node.property}`);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
visitPropertyAccess(node) {
|
|
682
|
+
this.visitNode(node.object);
|
|
683
|
+
this.write(`.${node.property}`);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
visitSafePropertyAccess(node) {
|
|
687
|
+
this.visitNode(node.object);
|
|
688
|
+
this.write(`?.${node.property}`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
visitArrayAccess(node) {
|
|
692
|
+
this.visitNode(node.object);
|
|
693
|
+
this.write('[');
|
|
694
|
+
this.visitNode(node.index);
|
|
695
|
+
this.write(']');
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
visitSafeArrayAccess(node) {
|
|
699
|
+
this.visitNode(node.object);
|
|
700
|
+
this.write('?.[');
|
|
701
|
+
this.visitNode(node.index);
|
|
702
|
+
this.write(']');
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
visitCallExpression(node) {
|
|
706
|
+
this.write('call ');
|
|
707
|
+
this.visitNode(node.callee);
|
|
708
|
+
this.write('(');
|
|
709
|
+
node.arguments.forEach((arg, i) => {
|
|
710
|
+
this.visitNode(arg);
|
|
711
|
+
if (i < node.arguments.length - 1) this.write(', ');
|
|
712
|
+
});
|
|
713
|
+
this.write(')');
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
visitSafeCallExpression(node) {
|
|
717
|
+
this.visitNode(node.callee);
|
|
718
|
+
this.write('?.(');
|
|
719
|
+
node.arguments.forEach((arg, i) => {
|
|
720
|
+
this.visitNode(arg);
|
|
721
|
+
if (i < node.arguments.length - 1) this.write(', ');
|
|
722
|
+
});
|
|
723
|
+
this.write(')');
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
visitAnonymousFunction(node) {
|
|
727
|
+
const canUseFnShorthand =
|
|
728
|
+
node.isFn === true &&
|
|
729
|
+
node.body.length === 1 &&
|
|
730
|
+
node.body[0]?.type === 'ReturnStatement' &&
|
|
731
|
+
node.body[0]?.argument;
|
|
732
|
+
|
|
733
|
+
if (canUseFnShorthand) {
|
|
734
|
+
this.write('(fn');
|
|
735
|
+
const params = this._formatFnParams(node.params, node.defaults, node.restParam);
|
|
736
|
+
if (params.length > 0) {
|
|
737
|
+
// A7: removed dead ternary — always a space before params
|
|
738
|
+
this.write(' ' + params.join(' '));
|
|
739
|
+
}
|
|
740
|
+
this.write(' -> ');
|
|
741
|
+
this.visitNode(node.body[0].argument);
|
|
742
|
+
this.write(')');
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
this.write('(function(');
|
|
747
|
+
const params = this._formatParams(node.params, node.defaults, node.restParam);
|
|
748
|
+
this.write(params.join(', '));
|
|
749
|
+
this.write(')\n');
|
|
750
|
+
this.visitBlock(node.body);
|
|
751
|
+
this.write(this.currentIndent + 'end)');
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
visitRangeLiteral(node) {
|
|
755
|
+
this.visitNode(node.start);
|
|
756
|
+
this.write(' .. ');
|
|
757
|
+
this.visitNode(node.end);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
visitInlineIfExpression(node) {
|
|
761
|
+
this.write('if ');
|
|
762
|
+
this.visitNode(node.condition);
|
|
763
|
+
this.write(' then ');
|
|
764
|
+
this.visitNode(node.consequent);
|
|
765
|
+
this.write(' else ');
|
|
766
|
+
this.visitNode(node.alternate);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
visitPipeExpression(node) {
|
|
770
|
+
// Flatten the right-deep chain
|
|
771
|
+
const stages = [];
|
|
772
|
+
let current = node;
|
|
773
|
+
while (current?.type === 'PipeExpression') {
|
|
774
|
+
stages.unshift(current);
|
|
775
|
+
current = current.left;
|
|
776
|
+
}
|
|
777
|
+
const initial = current;
|
|
778
|
+
|
|
779
|
+
this.visitNode(initial);
|
|
780
|
+
this.write('\n');
|
|
781
|
+
|
|
782
|
+
const stageIndent = this.currentIndent + this._indentUnit;
|
|
783
|
+
for (let i = 0; i < stages.length; i++) {
|
|
784
|
+
const stage = stages[i];
|
|
785
|
+
this.write(stageIndent + '|> ');
|
|
786
|
+
// A7: dead InlineIfExpression branch removed — unified path
|
|
787
|
+
this.visitNode(stage.callee);
|
|
788
|
+
if (stage.args?.length > 0) {
|
|
789
|
+
this.write(' ');
|
|
790
|
+
stage.args.forEach((arg, idx) => {
|
|
791
|
+
this.visitNode(arg);
|
|
792
|
+
if (idx < stage.args.length - 1) this.write(' ');
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
if (i < stages.length - 1) this.write('\n');
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
visitTemplateLiteral(node) {
|
|
800
|
+
this.write('`');
|
|
801
|
+
for (const part of node.parts) {
|
|
802
|
+
if (part.type === 'Literal') {
|
|
803
|
+
this.write(part.value);
|
|
804
|
+
} else {
|
|
805
|
+
this.write('${');
|
|
806
|
+
this.visitNode(part);
|
|
807
|
+
this.write('}');
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
this.write('`');
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// ── Private helpers ───────────────────────────────────────────────────────
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Format params for named functions and full anonymous functions (comma-separated).
|
|
817
|
+
* Uses a fresh Printer with same options so defaults are rendered consistently.
|
|
818
|
+
*/
|
|
819
|
+
_formatParams(params, defaults, restParam) {
|
|
820
|
+
const out = params.map((p) => {
|
|
821
|
+
const name = typeof p === 'string' ? p : p.name;
|
|
822
|
+
const defaultNode = defaults?.[name];
|
|
823
|
+
if (defaultNode) {
|
|
824
|
+
const tmp = new PrettyPrinter({
|
|
825
|
+
indentSize: this._indentUnit.length,
|
|
826
|
+
useTabs: this._indentUnit === '\t',
|
|
827
|
+
quoteStyle: this._quote === "'" ? 'single' : 'double',
|
|
828
|
+
maxInlineArrayLength: this.maxInlineArrayLength,
|
|
829
|
+
maxInlineObjectLength: this.maxInlineObjectLength,
|
|
830
|
+
});
|
|
831
|
+
tmp.visitNode(defaultNode);
|
|
832
|
+
return `${name}: ${tmp.output.trimEnd()}`;
|
|
833
|
+
}
|
|
834
|
+
return name;
|
|
835
|
+
});
|
|
836
|
+
if (restParam) {
|
|
837
|
+
const restName = typeof restParam === 'string' ? restParam : restParam.name;
|
|
838
|
+
out.push(`...${restName}`);
|
|
839
|
+
}
|
|
840
|
+
return out;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Format params for `fn` shorthand (space-separated, no commas).
|
|
845
|
+
*/
|
|
846
|
+
_formatFnParams(params, defaults, restParam) {
|
|
847
|
+
return this._formatParams(params, defaults, restParam);
|
|
848
|
+
}
|
|
849
|
+
}
|