prettier-plugin-wolfram 0.7.0
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/LICENSE +7 -0
- package/README.md +290 -0
- package/bin/prettier-wolfram.js +55 -0
- package/package.json +58 -0
- package/src/index.js +80 -0
- package/src/options.js +206 -0
- package/src/parser/adapter.js +690 -0
- package/src/parser/cstEqual.js +18 -0
- package/src/parser/index.js +29 -0
- package/src/parser/operators.js +35 -0
- package/src/parser/position.js +62 -0
- package/src/parser/tree-sitter-wolfram.wasm +0 -0
- package/src/range.js +98 -0
- package/src/rules/index.js +57 -0
- package/src/rules/line-width.js +129 -0
- package/src/rules/newlines-between-definitions.js +103 -0
- package/src/rules/no-bare-symbol-set.js +19 -0
- package/src/rules/no-dynamic-module-leak.js +74 -0
- package/src/rules/no-general-infix-function.js +52 -0
- package/src/rules/no-shadowed-pattern.js +71 -0
- package/src/rules/no-unused-module-var.js +84 -0
- package/src/rules/prefer-rule-delayed.js +59 -0
- package/src/rules/spacing-commas.js +64 -0
- package/src/rules/spacing-operators.js +87 -0
- package/src/translator/commentSpacing.js +51 -0
- package/src/translator/docComments.js +89 -0
- package/src/translator/index.js +98 -0
- package/src/translator/nodes/binary.js +205 -0
- package/src/translator/nodes/call.js +254 -0
- package/src/translator/nodes/compound.js +117 -0
- package/src/translator/nodes/container.js +194 -0
- package/src/translator/nodes/group.js +159 -0
- package/src/translator/nodes/infix.js +408 -0
- package/src/translator/nodes/leaf.js +605 -0
- package/src/translator/nodes/postfix.js +29 -0
- package/src/translator/nodes/prefix.js +27 -0
- package/src/translator/nodes/ternary.js +82 -0
- package/src/translator/ruleAlignment.js +133 -0
- package/src/translator/sourceLines.js +49 -0
- package/src/translator/sourcePreservation.js +22 -0
- package/src/translator/specialForms.js +665 -0
- package/src/utils/codeSpacing.js +420 -0
- package/src/utils/cstErrors.js +36 -0
- package/src/utils/offsets.js +132 -0
- package/src/utils/operatorSpacing.js +49 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
// src/translator/nodes/leaf.js
|
|
2
|
+
|
|
3
|
+
import { doc } from "prettier";
|
|
4
|
+
const { builders } = doc;
|
|
5
|
+
const { group, indent, line, join, hardline } = builders;
|
|
6
|
+
|
|
7
|
+
const DISCARD_KINDS = new Set([
|
|
8
|
+
"Token`Whitespace",
|
|
9
|
+
"Whitespace",
|
|
10
|
+
"Token`Newline",
|
|
11
|
+
"Newline",
|
|
12
|
+
"Token`LineContinuation",
|
|
13
|
+
"LineContinuation",
|
|
14
|
+
"Token`Fake`ImplicitNull",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function repairMojibake(value) {
|
|
18
|
+
if (typeof value !== "string") return value;
|
|
19
|
+
// Common UTF-8 bytes decoded as Latin-1 by WL JSON export/import path.
|
|
20
|
+
return value
|
|
21
|
+
.replace(/âÂÂ/g, "—")
|
|
22
|
+
.replace(/â/g, "—")
|
|
23
|
+
.replace(/âÂÂ/g, "’")
|
|
24
|
+
.replace(/â/g, "’")
|
|
25
|
+
.replace(/âÂÂ/g, "“")
|
|
26
|
+
.replace(/âÂÂ/g, "”")
|
|
27
|
+
.replace(/â/g, "“")
|
|
28
|
+
.replace(/â/g, "”");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function quotedStringLiteral(value) {
|
|
32
|
+
return typeof value === "string" &&
|
|
33
|
+
value.startsWith('"') &&
|
|
34
|
+
value.endsWith('"')
|
|
35
|
+
? value
|
|
36
|
+
: JSON.stringify(String(value ?? ""));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function rawQuotedStringLiteral(value) {
|
|
40
|
+
return `"${value}"`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isQuotedStringLiteral(value) {
|
|
44
|
+
return (
|
|
45
|
+
typeof value === "string" &&
|
|
46
|
+
value.startsWith('"') &&
|
|
47
|
+
value.endsWith('"')
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function lineWidth(options) {
|
|
52
|
+
return options?.printWidth ?? 80;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function tabWidth(options) {
|
|
56
|
+
return options?.tabWidth ?? 2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const INLINE_RHS_BINARY_OPS = new Set([
|
|
60
|
+
"Power",
|
|
61
|
+
"Divide",
|
|
62
|
+
"ReplaceAll",
|
|
63
|
+
"Rule",
|
|
64
|
+
"RuleDelayed",
|
|
65
|
+
"Map",
|
|
66
|
+
"Apply",
|
|
67
|
+
"MapApply",
|
|
68
|
+
"MapAll",
|
|
69
|
+
"BinaryAt",
|
|
70
|
+
"BinarySlashSlash",
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
function isOperatorLikeLeaf(node) {
|
|
74
|
+
return (
|
|
75
|
+
node?.type === "LeafNode" &&
|
|
76
|
+
node.kind?.startsWith("Token`") &&
|
|
77
|
+
![
|
|
78
|
+
"Token`Hash",
|
|
79
|
+
"Token`HashHash",
|
|
80
|
+
"Token`Under",
|
|
81
|
+
"Token`UnderUnder",
|
|
82
|
+
"Token`UnderUnderUnder",
|
|
83
|
+
].includes(node.kind) &&
|
|
84
|
+
!node.kind.startsWith("Token`Fake`")
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function binarySemanticChildren(node) {
|
|
89
|
+
return (node?.children ?? []).filter(
|
|
90
|
+
(child) =>
|
|
91
|
+
!DISCARD_KINDS.has(child?.kind) && !isOperatorLikeLeaf(child),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isStringJoinNode(node) {
|
|
96
|
+
if (node?.type === "InfixNode" && node.op === "StringJoin") return true;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
node?.type === "CallNode" &&
|
|
100
|
+
node.head?.type === "LeafNode" &&
|
|
101
|
+
node.head.kind === "Symbol" &&
|
|
102
|
+
node.head.value === "StringJoin"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isDirectMultilineStringRhs(node, child) {
|
|
107
|
+
const semantic = binarySemanticChildren(node);
|
|
108
|
+
return (
|
|
109
|
+
semantic[1] === child &&
|
|
110
|
+
((child?.type === "LeafNode" && child.kind === "String") ||
|
|
111
|
+
isStringJoinNode(child))
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function binaryAddsVisibleIndent(node, child) {
|
|
116
|
+
if (!INLINE_RHS_BINARY_OPS.has(node?.op))
|
|
117
|
+
return binarySemanticChildren(node)[1] === child;
|
|
118
|
+
return isDirectMultilineStringRhs(node, child);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function stringLineIndentDepth(path) {
|
|
122
|
+
let depth = 1;
|
|
123
|
+
let child = path?.getValue?.();
|
|
124
|
+
|
|
125
|
+
for (const ancestor of path?.ancestors ?? []) {
|
|
126
|
+
if (ancestor?.type === "CallNode" || ancestor?.type === "GroupNode") {
|
|
127
|
+
depth++;
|
|
128
|
+
child = ancestor;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (
|
|
133
|
+
ancestor?.type === "BinaryNode" &&
|
|
134
|
+
binaryAddsVisibleIndent(ancestor, child)
|
|
135
|
+
) {
|
|
136
|
+
depth++;
|
|
137
|
+
}
|
|
138
|
+
child = ancestor;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return depth;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function inlineStringLiteralWidth(options, indentDepth) {
|
|
145
|
+
return Math.max(
|
|
146
|
+
3,
|
|
147
|
+
lineWidth(options) - Math.max(0, indentDepth - 1) * tabWidth(options),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const STRING_JOIN_LINE_OVERHEAD = 3; // opening quote, closing quote, and a trailing comma.
|
|
152
|
+
|
|
153
|
+
function stringJoinContentWidth(options, indentDepth) {
|
|
154
|
+
return Math.max(
|
|
155
|
+
1,
|
|
156
|
+
lineWidth(options) -
|
|
157
|
+
indentDepth * tabWidth(options) -
|
|
158
|
+
STRING_JOIN_LINE_OVERHEAD,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function indentColumns(text, options) {
|
|
163
|
+
let columns = 0;
|
|
164
|
+
const width = tabWidth(options);
|
|
165
|
+
|
|
166
|
+
for (const char of text) {
|
|
167
|
+
if (char === " ") {
|
|
168
|
+
columns += 1;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (char === "\t") {
|
|
172
|
+
columns += width - (columns % width);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return columns;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function stripIndentColumns(line, targetColumns, options) {
|
|
182
|
+
let consumedChars = 0;
|
|
183
|
+
let consumedColumns = 0;
|
|
184
|
+
const width = tabWidth(options);
|
|
185
|
+
|
|
186
|
+
while (consumedChars < line.length && consumedColumns < targetColumns) {
|
|
187
|
+
const char = line[consumedChars];
|
|
188
|
+
|
|
189
|
+
if (char === " ") {
|
|
190
|
+
consumedChars += 1;
|
|
191
|
+
consumedColumns += 1;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (char === "\t") {
|
|
196
|
+
const nextColumns =
|
|
197
|
+
consumedColumns + (width - (consumedColumns % width));
|
|
198
|
+
if (nextColumns > targetColumns) break;
|
|
199
|
+
consumedChars += 1;
|
|
200
|
+
consumedColumns = nextColumns;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return line.slice(consumedChars);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function commentIndentPrefix(node, options) {
|
|
211
|
+
if (
|
|
212
|
+
typeof options?.originalText !== "string" ||
|
|
213
|
+
typeof node?.locStart !== "number" ||
|
|
214
|
+
node.locStart < 0
|
|
215
|
+
) {
|
|
216
|
+
return "";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const lineStart =
|
|
220
|
+
options.originalText.lastIndexOf("\n", Math.max(0, node.locStart - 1)) +
|
|
221
|
+
1;
|
|
222
|
+
return options.originalText.slice(lineStart, node.locStart);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function sourceTextForNode(node, options) {
|
|
226
|
+
if (
|
|
227
|
+
typeof options?.originalText !== "string" ||
|
|
228
|
+
typeof node?.locStart !== "number" ||
|
|
229
|
+
typeof node?.locEnd !== "number" ||
|
|
230
|
+
node.locStart < 0 ||
|
|
231
|
+
node.locEnd < node.locStart
|
|
232
|
+
) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return options.originalText.slice(node.locStart, node.locEnd);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function normalizeCommentLines(node, options) {
|
|
240
|
+
const value = repairMojibake(
|
|
241
|
+
sourceTextForNode(node, options) ?? node.value,
|
|
242
|
+
);
|
|
243
|
+
const lines = String(value).split("\n");
|
|
244
|
+
if (lines.length <= 1) return lines;
|
|
245
|
+
|
|
246
|
+
const basePrefix = commentIndentPrefix(node, options);
|
|
247
|
+
const baseColumns = indentColumns(basePrefix, options);
|
|
248
|
+
|
|
249
|
+
return [
|
|
250
|
+
lines[0],
|
|
251
|
+
...lines.slice(1).map((line) => {
|
|
252
|
+
if (basePrefix && line.startsWith(basePrefix)) {
|
|
253
|
+
return line.slice(basePrefix.length);
|
|
254
|
+
}
|
|
255
|
+
return stripIndentColumns(line, baseColumns, options);
|
|
256
|
+
}),
|
|
257
|
+
];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function multilineCommentDoc(node, options) {
|
|
261
|
+
const lines = normalizeCommentLines(node, options);
|
|
262
|
+
if (lines.length <= 1) return lines[0] ?? "";
|
|
263
|
+
|
|
264
|
+
const docs = [lines[0]];
|
|
265
|
+
for (let i = 1; i < lines.length; i++) {
|
|
266
|
+
docs.push(hardline, lines[i]);
|
|
267
|
+
}
|
|
268
|
+
return docs;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function stringTextUnits(text, preserveEscapes) {
|
|
272
|
+
if (!preserveEscapes) return Array.from(text);
|
|
273
|
+
|
|
274
|
+
const units = [];
|
|
275
|
+
for (let i = 0; i < text.length; i++) {
|
|
276
|
+
if (text[i] !== "\\" || i === text.length - 1) {
|
|
277
|
+
units.push(text[i]);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (text[i + 1] === "u") {
|
|
282
|
+
const hex = text.slice(i + 2, i + 6);
|
|
283
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
284
|
+
units.push(text.slice(i, i + 6));
|
|
285
|
+
i += 5;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
units.push(text.slice(i, i + 2));
|
|
291
|
+
i++;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return units;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function makeTextToken(units) {
|
|
298
|
+
const text = units.join("");
|
|
299
|
+
let length = 0;
|
|
300
|
+
for (const unit of units) length += unit.length;
|
|
301
|
+
return { units, text, length };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function tokenizeStringUnits(units) {
|
|
305
|
+
const tokens = [];
|
|
306
|
+
for (let i = 0; i < units.length; ) {
|
|
307
|
+
const token = [];
|
|
308
|
+
if (/\s/u.test(units[i])) {
|
|
309
|
+
while (i < units.length && /\s/u.test(units[i])) {
|
|
310
|
+
token.push(units[i]);
|
|
311
|
+
i++;
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
while (i < units.length && !/\s/u.test(units[i])) {
|
|
315
|
+
token.push(units[i]);
|
|
316
|
+
i++;
|
|
317
|
+
}
|
|
318
|
+
while (i < units.length && /\s/u.test(units[i])) {
|
|
319
|
+
token.push(units[i]);
|
|
320
|
+
i++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
tokens.push(makeTextToken(token));
|
|
324
|
+
}
|
|
325
|
+
return tokens;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function shouldKeepOversizedTokenTogether(token) {
|
|
329
|
+
// Prefer an over-width chunk to slicing through a word or URL-like token.
|
|
330
|
+
return /\p{L}/u.test(token.text);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function splitOversizedTokens(tokens, maxChunkLength) {
|
|
334
|
+
const normalized = [];
|
|
335
|
+
|
|
336
|
+
for (const token of tokens) {
|
|
337
|
+
if (
|
|
338
|
+
token.length <= maxChunkLength ||
|
|
339
|
+
shouldKeepOversizedTokenTogether(token)
|
|
340
|
+
) {
|
|
341
|
+
normalized.push(token);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
normalized.push(...splitOversizedToken(token, maxChunkLength));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return normalized;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function splitOversizedToken(token, maxChunkLength) {
|
|
352
|
+
const chunks = [];
|
|
353
|
+
let current = [];
|
|
354
|
+
let currentLength = 0;
|
|
355
|
+
|
|
356
|
+
for (const unit of token.units) {
|
|
357
|
+
const unitLength = unit.length;
|
|
358
|
+
|
|
359
|
+
if (currentLength > 0 && currentLength + unitLength > maxChunkLength) {
|
|
360
|
+
chunks.push(makeTextToken(current));
|
|
361
|
+
current = [];
|
|
362
|
+
currentLength = 0;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
current.push(unit);
|
|
366
|
+
currentLength += unitLength;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (currentLength > 0) chunks.push(makeTextToken(current));
|
|
370
|
+
return chunks;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function greedyTokenWrap(tokens, maxChunkLength) {
|
|
374
|
+
const chunks = [];
|
|
375
|
+
let current = [];
|
|
376
|
+
let currentLength = 0;
|
|
377
|
+
|
|
378
|
+
for (const token of tokens) {
|
|
379
|
+
if (
|
|
380
|
+
currentLength > 0 &&
|
|
381
|
+
currentLength + token.length > maxChunkLength
|
|
382
|
+
) {
|
|
383
|
+
chunks.push(current.map((entry) => entry.text).join(""));
|
|
384
|
+
current = [];
|
|
385
|
+
currentLength = 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
current.push(token);
|
|
389
|
+
currentLength += token.length;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (currentLength > 0) {
|
|
393
|
+
chunks.push(current.map((entry) => entry.text).join(""));
|
|
394
|
+
}
|
|
395
|
+
return chunks;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function splitStringContent(content, maxChunkLength, preserveEscapes = false) {
|
|
399
|
+
const units = stringTextUnits(content, preserveEscapes);
|
|
400
|
+
if (content.length <= maxChunkLength) return [content];
|
|
401
|
+
|
|
402
|
+
return greedyTokenWrap(
|
|
403
|
+
splitOversizedTokens(tokenizeStringUnits(units), maxChunkLength),
|
|
404
|
+
maxChunkLength,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function multilineStringJoin(chunks) {
|
|
409
|
+
return group(
|
|
410
|
+
["StringJoin[", indent([line, join([",", line], chunks)]), line, "]"],
|
|
411
|
+
{ shouldBreak: true },
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function stringLiteralInfo(value) {
|
|
416
|
+
if (!isQuotedStringLiteral(value)) {
|
|
417
|
+
const content = String(value ?? "");
|
|
418
|
+
return {
|
|
419
|
+
literal: quotedStringLiteral(content),
|
|
420
|
+
content,
|
|
421
|
+
splitSafe: true,
|
|
422
|
+
preserveEscapes: false,
|
|
423
|
+
wrapChunk: quotedStringLiteral,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
literal: value,
|
|
429
|
+
content: value.slice(1, -1),
|
|
430
|
+
splitSafe: true,
|
|
431
|
+
preserveEscapes: true,
|
|
432
|
+
wrapChunk: rawQuotedStringLiteral,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function stringLiteralData(node) {
|
|
437
|
+
if (node?.type !== "LeafNode" || node.kind !== "String") return null;
|
|
438
|
+
|
|
439
|
+
const value = repairMojibake(node.value);
|
|
440
|
+
return stringLiteralInfo(value);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function docsForStringLiteralInfo(info, options, indentDepth, mode) {
|
|
444
|
+
const literalWidth =
|
|
445
|
+
mode === "stringJoinArg"
|
|
446
|
+
? stringJoinContentWidth(options, indentDepth) + 2
|
|
447
|
+
: inlineStringLiteralWidth(options, indentDepth);
|
|
448
|
+
|
|
449
|
+
if (info.splitSafe && info.literal.length > literalWidth) {
|
|
450
|
+
const chunks = splitStringContent(
|
|
451
|
+
info.content,
|
|
452
|
+
stringJoinContentWidth(options, indentDepth),
|
|
453
|
+
info.preserveEscapes,
|
|
454
|
+
);
|
|
455
|
+
if (chunks.length > 1) return chunks.map(info.wrapChunk);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return [info.literal];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function hasOddTrailingBackslashes(text) {
|
|
462
|
+
let count = 0;
|
|
463
|
+
for (let i = text.length - 1; i >= 0 && text[i] === "\\"; i--) {
|
|
464
|
+
count++;
|
|
465
|
+
}
|
|
466
|
+
return count % 2 === 1;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function hasOpenNamedCharacterEscape(text) {
|
|
470
|
+
const openIndex = text.lastIndexOf("\\[");
|
|
471
|
+
if (openIndex === -1) return false;
|
|
472
|
+
return text.indexOf("]", openIndex + 2) === -1;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function hasOpenHexCharacterEscape(text) {
|
|
476
|
+
return /\\(?:\.[0-9a-fA-F]{0,2}|:[0-9a-fA-F]{0,4})$/u.test(text);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function canJoinStringLiteralData(previous, next) {
|
|
480
|
+
if (previous.preserveEscapes !== next.preserveEscapes) return false;
|
|
481
|
+
if (previous.wrapChunk !== next.wrapChunk) return false;
|
|
482
|
+
if (!previous.preserveEscapes) return true;
|
|
483
|
+
|
|
484
|
+
return (
|
|
485
|
+
!hasOddTrailingBackslashes(previous.content) &&
|
|
486
|
+
!hasOpenNamedCharacterEscape(previous.content) &&
|
|
487
|
+
!hasOpenHexCharacterEscape(previous.content)
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function combineStringLiteralData(run) {
|
|
492
|
+
const content = run.map((entry) => entry.content).join("");
|
|
493
|
+
const first = run[0];
|
|
494
|
+
|
|
495
|
+
return {
|
|
496
|
+
literal: first.wrapChunk(content),
|
|
497
|
+
content,
|
|
498
|
+
splitSafe: run.every((entry) => entry.splitSafe),
|
|
499
|
+
preserveEscapes: first.preserveEscapes,
|
|
500
|
+
wrapChunk: first.wrapChunk,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function stringLiteralDocs(node, options, context = {}) {
|
|
505
|
+
const info = stringLiteralData(node);
|
|
506
|
+
if (!info) return null;
|
|
507
|
+
|
|
508
|
+
const indentDepth =
|
|
509
|
+
context.indentDepth ?? stringLineIndentDepth(context.path);
|
|
510
|
+
const mode = context.mode ?? "inline";
|
|
511
|
+
|
|
512
|
+
return docsForStringLiteralInfo(info, options, indentDepth, mode);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function stringLiteralRunDocs(nodes, options, context = {}) {
|
|
516
|
+
const indentDepth =
|
|
517
|
+
context.indentDepth ?? stringLineIndentDepth(context.path);
|
|
518
|
+
const mode = context.mode ?? "inline";
|
|
519
|
+
const docs = [];
|
|
520
|
+
let run = [];
|
|
521
|
+
|
|
522
|
+
function flushRun() {
|
|
523
|
+
if (run.length === 0) return;
|
|
524
|
+
docs.push(
|
|
525
|
+
...docsForStringLiteralInfo(
|
|
526
|
+
combineStringLiteralData(run),
|
|
527
|
+
options,
|
|
528
|
+
indentDepth,
|
|
529
|
+
mode,
|
|
530
|
+
),
|
|
531
|
+
);
|
|
532
|
+
run = [];
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
for (const node of nodes) {
|
|
536
|
+
const data = stringLiteralData(node);
|
|
537
|
+
if (!data) {
|
|
538
|
+
flushRun();
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (run.length > 0 && !canJoinStringLiteralData(run.at(-1), data)) {
|
|
543
|
+
flushRun();
|
|
544
|
+
}
|
|
545
|
+
run.push(data);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
flushRun();
|
|
549
|
+
return docs;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function singleLineStringLiteralRunDoc(nodes, options, context = {}) {
|
|
553
|
+
const indentDepth =
|
|
554
|
+
context.indentDepth ?? stringLineIndentDepth(context.path);
|
|
555
|
+
const widthOffset = Math.max(0, context.widthOffset ?? 0);
|
|
556
|
+
const run = [];
|
|
557
|
+
|
|
558
|
+
for (const node of nodes) {
|
|
559
|
+
const data = stringLiteralData(node);
|
|
560
|
+
if (!data) return null;
|
|
561
|
+
if (run.length > 0 && !canJoinStringLiteralData(run.at(-1), data)) {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
run.push(data);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (run.length === 0) return null;
|
|
568
|
+
|
|
569
|
+
const info = combineStringLiteralData(run);
|
|
570
|
+
if (/[\r\n]/u.test(info.literal)) return null;
|
|
571
|
+
if (
|
|
572
|
+
info.literal.length >
|
|
573
|
+
inlineStringLiteralWidth(options, indentDepth) - widthOffset
|
|
574
|
+
) {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return info.literal;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** Returns the text representation of a leaf node, or '' for trivia. */
|
|
582
|
+
export function printLeaf(node, options, context = {}) {
|
|
583
|
+
if (DISCARD_KINDS.has(node.kind)) return "";
|
|
584
|
+
if (node.kind === "String") {
|
|
585
|
+
const docs = stringLiteralDocs(node, options, {
|
|
586
|
+
...context,
|
|
587
|
+
mode: "inline",
|
|
588
|
+
});
|
|
589
|
+
if (!docs) return "";
|
|
590
|
+
return docs.length > 1 ? multilineStringJoin(docs) : docs[0];
|
|
591
|
+
}
|
|
592
|
+
if (node.kind === "Token`Comment") {
|
|
593
|
+
return multilineCommentDoc(node, options);
|
|
594
|
+
}
|
|
595
|
+
const value = repairMojibake(node.value);
|
|
596
|
+
return String(value);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export function isTrivia(node) {
|
|
600
|
+
return node?.type === "LeafNode" && DISCARD_KINDS.has(node.kind);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export function isComment(node) {
|
|
604
|
+
return node?.type === "LeafNode" && node.kind === "Token`Comment";
|
|
605
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/translator/nodes/postfix.js
|
|
2
|
+
import { isTrivia } from "./leaf.js";
|
|
3
|
+
import {
|
|
4
|
+
hasImmediateComment,
|
|
5
|
+
printOriginalSource,
|
|
6
|
+
} from "../sourcePreservation.js";
|
|
7
|
+
|
|
8
|
+
const OP_DISPLAY = {
|
|
9
|
+
Function: "&",
|
|
10
|
+
Increment: "++",
|
|
11
|
+
Decrement: "--",
|
|
12
|
+
Factorial: "!",
|
|
13
|
+
Factorial2: "!!",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function printPostfix(node, options, print) {
|
|
17
|
+
if (hasImmediateComment(node)) {
|
|
18
|
+
return printOriginalSource(node, options);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const semantic = node.children.filter((c) => !isTrivia(c));
|
|
22
|
+
if (semantic.length < 2) {
|
|
23
|
+
return printOriginalSource(node, options);
|
|
24
|
+
}
|
|
25
|
+
const opStr =
|
|
26
|
+
semantic[semantic.length - 1]?.value ?? OP_DISPLAY[node.op] ?? node.op;
|
|
27
|
+
const operand = semantic[0];
|
|
28
|
+
return [print(operand), opStr];
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/translator/nodes/prefix.js
|
|
2
|
+
import { isTrivia } from "./leaf.js";
|
|
3
|
+
import {
|
|
4
|
+
hasImmediateComment,
|
|
5
|
+
printOriginalSource,
|
|
6
|
+
} from "../sourcePreservation.js";
|
|
7
|
+
|
|
8
|
+
const OP_DISPLAY = {
|
|
9
|
+
Minus: "-",
|
|
10
|
+
Not: "!",
|
|
11
|
+
PreIncrement: "++",
|
|
12
|
+
PreDecrement: "--",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function printPrefix(node, options, print) {
|
|
16
|
+
if (hasImmediateComment(node)) {
|
|
17
|
+
return printOriginalSource(node, options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const semantic = node.children.filter((c) => !isTrivia(c));
|
|
21
|
+
if (semantic.length < 2) {
|
|
22
|
+
return printOriginalSource(node, options);
|
|
23
|
+
}
|
|
24
|
+
const opStr = semantic[0]?.value ?? OP_DISPLAY[node.op] ?? node.op;
|
|
25
|
+
const operand = semantic[semantic.length - 1];
|
|
26
|
+
return [opStr, print(operand)];
|
|
27
|
+
}
|