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,408 @@
|
|
|
1
|
+
// src/translator/nodes/infix.js
|
|
2
|
+
import { doc } from "prettier";
|
|
3
|
+
const { builders } = doc;
|
|
4
|
+
import { isTrivia, isComment } from "./leaf.js";
|
|
5
|
+
import {
|
|
6
|
+
documentationCommentColumn,
|
|
7
|
+
joinCommentDocs,
|
|
8
|
+
withAlignedTrailingComment,
|
|
9
|
+
} from "../docComments.js";
|
|
10
|
+
import { commentBoundarySeparator } from "../commentSpacing.js";
|
|
11
|
+
import { wantsSpacesAroundOperator } from "../../utils/operatorSpacing.js";
|
|
12
|
+
import {
|
|
13
|
+
hasImmediateComment,
|
|
14
|
+
printOriginalSource,
|
|
15
|
+
} from "../sourcePreservation.js";
|
|
16
|
+
import {
|
|
17
|
+
nodeEndLine,
|
|
18
|
+
nodeStartLine,
|
|
19
|
+
} from "../sourceLines.js";
|
|
20
|
+
import { normalizeWolframOptions } from "../../options.js";
|
|
21
|
+
const { group, indent, line, hardline, join, fill } = builders;
|
|
22
|
+
|
|
23
|
+
// Map WL op names to their display strings
|
|
24
|
+
const OP_DISPLAY = {
|
|
25
|
+
Plus: "+",
|
|
26
|
+
Times: "*",
|
|
27
|
+
Power: "^",
|
|
28
|
+
Equal: "==",
|
|
29
|
+
Unequal: "!=",
|
|
30
|
+
Greater: ">",
|
|
31
|
+
Less: "<",
|
|
32
|
+
GreaterEqual: ">=",
|
|
33
|
+
LessEqual: "<=",
|
|
34
|
+
And: "&&",
|
|
35
|
+
Or: "||",
|
|
36
|
+
StringJoin: "<>",
|
|
37
|
+
Dot: ".",
|
|
38
|
+
Alternatives: "|",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function isSemanticTokenLeaf(node) {
|
|
42
|
+
return (
|
|
43
|
+
node?.type === "LeafNode" &&
|
|
44
|
+
[
|
|
45
|
+
"Token`Hash",
|
|
46
|
+
"Token`HashHash",
|
|
47
|
+
"Token`Under",
|
|
48
|
+
"Token`UnderUnder",
|
|
49
|
+
"Token`UnderUnderUnder",
|
|
50
|
+
].includes(node.kind)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isCommaToken(node) {
|
|
55
|
+
return node?.type === "LeafNode" && node.kind === "Token`Comma";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isSemicolonToken(node) {
|
|
59
|
+
return (
|
|
60
|
+
node?.type === "LeafNode" &&
|
|
61
|
+
(node.kind === "Token`Semi" || node.kind === "Token`Semicolon")
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isNewlineTrivia(node) {
|
|
66
|
+
return (
|
|
67
|
+
node?.type === "LeafNode" &&
|
|
68
|
+
(node.kind === "Token`Newline" ||
|
|
69
|
+
node.kind === "Newline" ||
|
|
70
|
+
(typeof node.value === "string" && node.value.includes("\n")))
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function commentBoundary(leftNode, rightNode, options, fallback = line) {
|
|
75
|
+
return commentBoundarySeparator(leftNode, rightNode, options, fallback);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hasCommentBoundary(leftNode, rightNode) {
|
|
79
|
+
return isComment(leftNode) || isComment(rightNode);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function nextContentNode(entries, startIndex) {
|
|
83
|
+
for (let i = startIndex + 1; i < entries.length; i++) {
|
|
84
|
+
if (!isCommaToken(entries[i])) return entries[i];
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Extract semantic operands from InfixNode children (skip trivia + operator tokens). */
|
|
90
|
+
function operands(node) {
|
|
91
|
+
// InfixNode children alternate: operand, ws, op-token, ws, operand, ...
|
|
92
|
+
// Keep only non-trivia, non-operator-token children.
|
|
93
|
+
return node.children.filter((c) => {
|
|
94
|
+
if (isTrivia(c)) return false;
|
|
95
|
+
if (
|
|
96
|
+
c.type === "LeafNode" &&
|
|
97
|
+
c.kind.startsWith("Token`") &&
|
|
98
|
+
!isSemanticTokenLeaf(c)
|
|
99
|
+
)
|
|
100
|
+
return false;
|
|
101
|
+
return true;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function printMessageNameOperand(node, print) {
|
|
106
|
+
if (
|
|
107
|
+
node?.type === "LeafNode" &&
|
|
108
|
+
node.kind === "String" &&
|
|
109
|
+
!String(node.value ?? "").startsWith('"')
|
|
110
|
+
) {
|
|
111
|
+
return String(node.value ?? "");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return print(node);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function printInfix(node, options, print) {
|
|
118
|
+
options = normalizeWolframOptions(options);
|
|
119
|
+
if (node.op === "CompoundExpression") {
|
|
120
|
+
const entries = [];
|
|
121
|
+
let leadingComments = [];
|
|
122
|
+
let previousEntry = null;
|
|
123
|
+
let pendingLineBreakBeforeNextEntry = false;
|
|
124
|
+
|
|
125
|
+
for (const child of node.children) {
|
|
126
|
+
if (isTrivia(child)) {
|
|
127
|
+
if (previousEntry && isNewlineTrivia(child)) {
|
|
128
|
+
pendingLineBreakBeforeNextEntry = true;
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (isSemicolonToken(child)) {
|
|
134
|
+
if (previousEntry) previousEntry.hasSemicolon = true;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (isComment(child)) {
|
|
139
|
+
const previousLine = previousEntry?.endLine;
|
|
140
|
+
const commentLine = nodeStartLine(child, options);
|
|
141
|
+
if (
|
|
142
|
+
previousEntry?.hasSemicolon &&
|
|
143
|
+
(!previousLine ||
|
|
144
|
+
!commentLine ||
|
|
145
|
+
previousLine === commentLine)
|
|
146
|
+
) {
|
|
147
|
+
previousEntry.trailingComments.push({
|
|
148
|
+
node: child,
|
|
149
|
+
doc: print(child),
|
|
150
|
+
});
|
|
151
|
+
previousEntry.endLine =
|
|
152
|
+
nodeEndLine(child, options) ?? previousEntry.endLine;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
leadingComments.push({ node: child, doc: print(child) });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const startLine = nodeStartLine(child, options);
|
|
161
|
+
const previousEndLine = previousEntry?.endLine;
|
|
162
|
+
const entry = {
|
|
163
|
+
node: child,
|
|
164
|
+
doc: print(child),
|
|
165
|
+
leadingComments,
|
|
166
|
+
trailingComments: [],
|
|
167
|
+
hasSemicolon: false,
|
|
168
|
+
breakBefore:
|
|
169
|
+
entries.length > 0 &&
|
|
170
|
+
(pendingLineBreakBeforeNextEntry ||
|
|
171
|
+
(previousEndLine &&
|
|
172
|
+
startLine &&
|
|
173
|
+
startLine > previousEndLine)),
|
|
174
|
+
endLine: nodeEndLine(child, options) ?? startLine,
|
|
175
|
+
};
|
|
176
|
+
entries.push(entry);
|
|
177
|
+
previousEntry = entry;
|
|
178
|
+
leadingComments = [];
|
|
179
|
+
pendingLineBreakBeforeNextEntry = false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
entry.trailingCommentDoc = joinCommentDocs(
|
|
184
|
+
entry.trailingComments,
|
|
185
|
+
options,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const suffixForEntry = (entry) => (entry.hasSemicolon ? ";" : "");
|
|
190
|
+
const trailingEntries = entries.filter(
|
|
191
|
+
(entry) => entry.trailingCommentDoc,
|
|
192
|
+
);
|
|
193
|
+
const alignTrailingComments =
|
|
194
|
+
(options.wolframDocumentationCommentColumn ?? 0) > 0 ||
|
|
195
|
+
trailingEntries.length > 1;
|
|
196
|
+
const trailingColumn =
|
|
197
|
+
alignTrailingComments && trailingEntries.length > 0
|
|
198
|
+
? documentationCommentColumn(
|
|
199
|
+
trailingEntries,
|
|
200
|
+
options,
|
|
201
|
+
suffixForEntry,
|
|
202
|
+
)
|
|
203
|
+
: null;
|
|
204
|
+
|
|
205
|
+
const docs = [];
|
|
206
|
+
let hasHardSeparator = false;
|
|
207
|
+
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (docs.length > 0) {
|
|
210
|
+
const separator = entry.breakBefore ? hardline : line;
|
|
211
|
+
if (entry.breakBefore) hasHardSeparator = true;
|
|
212
|
+
docs.push(separator);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (entry.leadingComments.length > 0) {
|
|
216
|
+
for (let i = 0; i < entry.leadingComments.length; i++) {
|
|
217
|
+
const comment = entry.leadingComments[i];
|
|
218
|
+
const followingNode =
|
|
219
|
+
entry.leadingComments[i + 1]?.node ?? entry.node;
|
|
220
|
+
docs.push(
|
|
221
|
+
comment.doc,
|
|
222
|
+
commentBoundary(
|
|
223
|
+
comment.node,
|
|
224
|
+
followingNode,
|
|
225
|
+
options,
|
|
226
|
+
line,
|
|
227
|
+
),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!entry.trailingCommentDoc) {
|
|
233
|
+
docs.push([entry.doc, suffixForEntry(entry)]);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (trailingColumn == null) {
|
|
238
|
+
docs.push([
|
|
239
|
+
entry.doc,
|
|
240
|
+
suffixForEntry(entry),
|
|
241
|
+
" ",
|
|
242
|
+
entry.trailingCommentDoc,
|
|
243
|
+
]);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
docs.push(
|
|
248
|
+
withAlignedTrailingComment(
|
|
249
|
+
entry,
|
|
250
|
+
options,
|
|
251
|
+
trailingColumn,
|
|
252
|
+
suffixForEntry(entry),
|
|
253
|
+
),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (leadingComments.length > 0) {
|
|
258
|
+
if (docs.length > 0) {
|
|
259
|
+
const separator = pendingLineBreakBeforeNextEntry
|
|
260
|
+
? hardline
|
|
261
|
+
: line;
|
|
262
|
+
if (pendingLineBreakBeforeNextEntry) hasHardSeparator = true;
|
|
263
|
+
docs.push(separator);
|
|
264
|
+
}
|
|
265
|
+
for (let i = 0; i < leadingComments.length; i++) {
|
|
266
|
+
const comment = leadingComments[i];
|
|
267
|
+
const followingNode = leadingComments[i + 1]?.node;
|
|
268
|
+
docs.push(comment.doc);
|
|
269
|
+
if (followingNode) {
|
|
270
|
+
docs.push(
|
|
271
|
+
commentBoundary(
|
|
272
|
+
comment.node,
|
|
273
|
+
followingNode,
|
|
274
|
+
options,
|
|
275
|
+
line,
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return hasHardSeparator ? fill(docs) : group(docs);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (node.op === "Comma") {
|
|
286
|
+
const docs = [];
|
|
287
|
+
const commaGap = options.wolframSpaceAfterComma
|
|
288
|
+
? line
|
|
289
|
+
: doc.builders.softline;
|
|
290
|
+
let previousKind = null;
|
|
291
|
+
let previousNode = null;
|
|
292
|
+
const entries = node.children.filter((child) => !isTrivia(child));
|
|
293
|
+
|
|
294
|
+
for (let i = 0; i < entries.length; i++) {
|
|
295
|
+
const child = entries[i];
|
|
296
|
+
if (isCommaToken(child)) {
|
|
297
|
+
if (previousKind === null || previousKind === "comma") continue;
|
|
298
|
+
const followingEntry = nextContentNode(entries, i);
|
|
299
|
+
const separator =
|
|
300
|
+
followingEntry &&
|
|
301
|
+
hasCommentBoundary(previousNode, followingEntry)
|
|
302
|
+
? commentBoundary(
|
|
303
|
+
previousNode,
|
|
304
|
+
followingEntry,
|
|
305
|
+
options,
|
|
306
|
+
commaGap,
|
|
307
|
+
)
|
|
308
|
+
: commaGap;
|
|
309
|
+
docs.push(",", separator);
|
|
310
|
+
previousKind = "comma";
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (previousKind !== null && previousKind !== "comma") {
|
|
315
|
+
docs.push(
|
|
316
|
+
hasCommentBoundary(previousNode, child)
|
|
317
|
+
? commentBoundary(previousNode, child, options, line)
|
|
318
|
+
: line,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
docs.push(print(child));
|
|
323
|
+
previousKind = isComment(child) ? "comment" : "item";
|
|
324
|
+
previousNode = child;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return group(docs);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (hasImmediateComment(node)) {
|
|
331
|
+
return printOriginalSource(node, options);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (node.op === "MessageName") {
|
|
335
|
+
const parts = operands(node);
|
|
336
|
+
return group(
|
|
337
|
+
join(
|
|
338
|
+
["::"],
|
|
339
|
+
parts.map((part, index) =>
|
|
340
|
+
index === 0
|
|
341
|
+
? print(part)
|
|
342
|
+
: printMessageNameOperand(part, print),
|
|
343
|
+
),
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (node.op === "InfixInequality") {
|
|
349
|
+
const semantic = node.children.filter((c) => !isTrivia(c));
|
|
350
|
+
if (semantic.length === 3 && semantic[1]?.type === "LeafNode") {
|
|
351
|
+
const opStr = semantic[1].value;
|
|
352
|
+
const space = wantsSpacesAroundOperator(node, options, semantic[1]);
|
|
353
|
+
const gap = space ? " " : "";
|
|
354
|
+
return group([
|
|
355
|
+
print(semantic[0]),
|
|
356
|
+
`${gap}${opStr}`,
|
|
357
|
+
space ? line : "",
|
|
358
|
+
print(semantic[2]),
|
|
359
|
+
]);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const semantic = node.children.filter((c) => {
|
|
364
|
+
if (isTrivia(c)) return false;
|
|
365
|
+
if (
|
|
366
|
+
c.type === "LeafNode" &&
|
|
367
|
+
c.kind.startsWith("Token`") &&
|
|
368
|
+
!isSemanticTokenLeaf(c)
|
|
369
|
+
)
|
|
370
|
+
return false;
|
|
371
|
+
return true;
|
|
372
|
+
});
|
|
373
|
+
const tokens = node.children.filter(
|
|
374
|
+
(c) =>
|
|
375
|
+
!isTrivia(c) &&
|
|
376
|
+
c.type === "LeafNode" &&
|
|
377
|
+
c.kind.startsWith("Token`") &&
|
|
378
|
+
!isSemanticTokenLeaf(c),
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
if (semantic.length >= 2 && tokens.length === semantic.length - 1) {
|
|
382
|
+
const parts = [print(semantic[0])];
|
|
383
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
384
|
+
const space = wantsSpacesAroundOperator(node, options, tokens[i]);
|
|
385
|
+
const gap = space ? " " : "";
|
|
386
|
+
if (space) {
|
|
387
|
+
parts.push(
|
|
388
|
+
`${gap}${tokens[i].value}`,
|
|
389
|
+
line,
|
|
390
|
+
print(semantic[i + 1]),
|
|
391
|
+
);
|
|
392
|
+
} else {
|
|
393
|
+
parts.push(tokens[i].value, print(semantic[i + 1]));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return group(parts);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const opStr = OP_DISPLAY[node.op] ?? node.op;
|
|
400
|
+
const space = wantsSpacesAroundOperator(node, options);
|
|
401
|
+
const sep = space ? [" ", opStr, line] : [opStr];
|
|
402
|
+
return group(
|
|
403
|
+
join(
|
|
404
|
+
sep,
|
|
405
|
+
operands(node).map((o) => print(o)),
|
|
406
|
+
),
|
|
407
|
+
);
|
|
408
|
+
}
|