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,205 @@
|
|
|
1
|
+
// src/translator/nodes/binary.js
|
|
2
|
+
import { doc } from "prettier";
|
|
3
|
+
const { builders } = doc;
|
|
4
|
+
import { isTrivia } from "./leaf.js";
|
|
5
|
+
import { argPathEntries } from "./call.js";
|
|
6
|
+
import { wantsSpacesAroundOperator } from "../../utils/operatorSpacing.js";
|
|
7
|
+
import {
|
|
8
|
+
hasImmediateComment,
|
|
9
|
+
printOriginalSource,
|
|
10
|
+
} from "../sourcePreservation.js";
|
|
11
|
+
const { group, indent, line, softline } = builders;
|
|
12
|
+
const { willBreak } = doc.utils;
|
|
13
|
+
|
|
14
|
+
const OP_DISPLAY = {
|
|
15
|
+
Set: "=",
|
|
16
|
+
SetDelayed: ":=",
|
|
17
|
+
Power: "^",
|
|
18
|
+
ReplaceAll: "/.",
|
|
19
|
+
Divide: "/",
|
|
20
|
+
Map: "/@",
|
|
21
|
+
Apply: "@@",
|
|
22
|
+
MapApply: "@@@",
|
|
23
|
+
MapAll: "//@",
|
|
24
|
+
BinaryAt: "@",
|
|
25
|
+
BinarySlashSlash: "//",
|
|
26
|
+
Rule: "->",
|
|
27
|
+
RuleDelayed: ":>",
|
|
28
|
+
TagSet: "/: =",
|
|
29
|
+
TagSetDelayed: "/: :=",
|
|
30
|
+
UpSet: "^=",
|
|
31
|
+
UpSetDelayed: "^:=",
|
|
32
|
+
PatternTest: "?",
|
|
33
|
+
Condition: "/;",
|
|
34
|
+
MessageName: "::",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function isSemanticTokenLeaf(node) {
|
|
38
|
+
return (
|
|
39
|
+
node?.type === "LeafNode" &&
|
|
40
|
+
[
|
|
41
|
+
"Token`Hash",
|
|
42
|
+
"Token`HashHash",
|
|
43
|
+
"Token`Under",
|
|
44
|
+
"Token`UnderUnder",
|
|
45
|
+
"Token`UnderUnderUnder",
|
|
46
|
+
].includes(node.kind)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isFakeTokenLeaf(node) {
|
|
51
|
+
return node?.type === "LeafNode" && node.kind.startsWith("Token`Fake`");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isOperatorTokenLeaf(node) {
|
|
55
|
+
return (
|
|
56
|
+
node?.type === "LeafNode" &&
|
|
57
|
+
node.kind.startsWith("Token`") &&
|
|
58
|
+
!isSemanticTokenLeaf(node) &&
|
|
59
|
+
!isFakeTokenLeaf(node)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function lhsRhs(node) {
|
|
64
|
+
const semantic = node.children.filter(
|
|
65
|
+
(c) =>
|
|
66
|
+
!isTrivia(c) &&
|
|
67
|
+
!(
|
|
68
|
+
c.type === "LeafNode" &&
|
|
69
|
+
c.kind.startsWith("Token`") &&
|
|
70
|
+
!isSemanticTokenLeaf(c)
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
return [semantic[0], semantic[1]];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function operatorToken(node) {
|
|
77
|
+
const tokens = node.children.filter(
|
|
78
|
+
(c) => !isTrivia(c) && isOperatorTokenLeaf(c),
|
|
79
|
+
);
|
|
80
|
+
return tokens.length === 1 ? tokens[0] : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function printSpan(node, print) {
|
|
84
|
+
const semantic = node.children.filter((c) => !isTrivia(c));
|
|
85
|
+
const tokenIndex = semantic.findIndex(
|
|
86
|
+
(c) => isOperatorTokenLeaf(c) && c.value === ";;",
|
|
87
|
+
);
|
|
88
|
+
if (tokenIndex === -1) return null;
|
|
89
|
+
const lhs = semantic
|
|
90
|
+
.slice(0, tokenIndex)
|
|
91
|
+
.find((c) => !isOperatorTokenLeaf(c));
|
|
92
|
+
const rhs = semantic
|
|
93
|
+
.slice(tokenIndex + 1)
|
|
94
|
+
.find((c) => !isOperatorTokenLeaf(c));
|
|
95
|
+
|
|
96
|
+
return group([
|
|
97
|
+
lhs && !isFakeTokenLeaf(lhs) ? print(lhs) : "",
|
|
98
|
+
";;",
|
|
99
|
+
rhs && !isFakeTokenLeaf(rhs) ? print(rhs) : "",
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isMultilineStringJoin(node) {
|
|
104
|
+
if (node?.type === "InfixNode" && node.op === "StringJoin") {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
node?.type !== "CallNode" ||
|
|
110
|
+
node.head?.type !== "LeafNode" ||
|
|
111
|
+
node.head.kind !== "Symbol" ||
|
|
112
|
+
node.head.value !== "StringJoin"
|
|
113
|
+
) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
argPathEntries(node).filter(
|
|
119
|
+
(entry) =>
|
|
120
|
+
!(
|
|
121
|
+
entry.node?.type === "LeafNode" &&
|
|
122
|
+
entry.node.kind === "Token`Comma"
|
|
123
|
+
),
|
|
124
|
+
).length > 1
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isMultilineStringLeaf(node, printedDoc) {
|
|
129
|
+
return (
|
|
130
|
+
node?.type === "LeafNode" &&
|
|
131
|
+
node.kind === "String" &&
|
|
132
|
+
willBreak(printedDoc)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** A right-hand side that delimits itself with brackets (a call like f[...] or
|
|
137
|
+
* a group like {...}, <|...|>, (...)) already dedents its own closing token,
|
|
138
|
+
* so its broken layout is correct as-is. Other values (infix chains, etc.)
|
|
139
|
+
* need their continuation lines indented one level under the operator. */
|
|
140
|
+
function rhsManagesOwnIndent(node) {
|
|
141
|
+
return node?.type === "CallNode" || node?.type === "GroupNode";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function printBinary(node, options, print) {
|
|
145
|
+
if (hasImmediateComment(node)) {
|
|
146
|
+
return printOriginalSource(node, options);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (node.op === "Span") {
|
|
150
|
+
return printSpan(node, print) ?? printOriginalSource(node, options);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const token = operatorToken(node);
|
|
154
|
+
const opStr = token?.value ?? OP_DISPLAY[node.op] ?? node.op;
|
|
155
|
+
const space = wantsSpacesAroundOperator(node, options, token);
|
|
156
|
+
const [lhs, rhs] = lhsRhs(node);
|
|
157
|
+
if (!lhs || !rhs) {
|
|
158
|
+
return printOriginalSource(node, options);
|
|
159
|
+
}
|
|
160
|
+
const gap = space ? " " : "";
|
|
161
|
+
const lhsDoc = print(lhs);
|
|
162
|
+
const rhsDoc = print(rhs);
|
|
163
|
+
const rhsWillBreak =
|
|
164
|
+
isMultilineStringLeaf(rhs, rhsDoc) || isMultilineStringJoin(rhs);
|
|
165
|
+
|
|
166
|
+
if (node.op === "BinaryAt" || node.op === "BinarySlashSlash") {
|
|
167
|
+
return group([
|
|
168
|
+
lhsDoc,
|
|
169
|
+
`${gap}${opStr}`,
|
|
170
|
+
space ? line : softline,
|
|
171
|
+
rhsDoc,
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!space) {
|
|
176
|
+
if (rhsWillBreak) {
|
|
177
|
+
return group([lhsDoc, opStr, indent([line, rhsDoc])]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return group([lhsDoc, opStr, rhsDoc]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
[
|
|
185
|
+
"Power",
|
|
186
|
+
"Divide",
|
|
187
|
+
"ReplaceAll",
|
|
188
|
+
"Rule",
|
|
189
|
+
"RuleDelayed",
|
|
190
|
+
"Map",
|
|
191
|
+
"Apply",
|
|
192
|
+
"MapApply",
|
|
193
|
+
"MapAll",
|
|
194
|
+
].includes(node.op)
|
|
195
|
+
) {
|
|
196
|
+
if (rhsWillBreak) {
|
|
197
|
+
return group([lhsDoc, `${gap}${opStr}`, indent([line, rhsDoc])]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const value = rhsManagesOwnIndent(rhs) ? rhsDoc : indent(rhsDoc);
|
|
201
|
+
return group([lhsDoc, `${gap}${opStr}${gap}`, value]);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return group([lhsDoc, `${gap}${opStr}`, indent([line, rhsDoc])]);
|
|
205
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// src/translator/nodes/call.js
|
|
2
|
+
import { doc } from "prettier";
|
|
3
|
+
const { builders } = doc;
|
|
4
|
+
import { isComment, isTrivia } from "./leaf.js";
|
|
5
|
+
import { alignedRuleDoc, withAlignedRuleValues } from "../ruleAlignment.js";
|
|
6
|
+
import { commentBoundarySeparator } from "../commentSpacing.js";
|
|
7
|
+
import { normalizeWolframOptions } from "../../options.js";
|
|
8
|
+
const { group, indent, softline, line } = builders;
|
|
9
|
+
|
|
10
|
+
const BRACKET_KINDS = new Set(["Token`OpenSquare", "Token`CloseSquare"]);
|
|
11
|
+
|
|
12
|
+
function isBracketToken(node) {
|
|
13
|
+
return node.type === "LeafNode" && BRACKET_KINDS.has(node.kind);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isCommaToken(node) {
|
|
17
|
+
return node.type === "LeafNode" && node.kind === "Token`Comma";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function nextContentEntry(entries, startIndex) {
|
|
21
|
+
for (let i = startIndex + 1; i < entries.length; i++) {
|
|
22
|
+
if (!isCommaToken(entries[i].node)) return entries[i];
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function previousContentEntry(entries, startIndex) {
|
|
28
|
+
for (let i = startIndex - 1; i >= 0; i--) {
|
|
29
|
+
if (!isCommaToken(entries[i].node)) return entries[i];
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function hasCommentBoundary(leftEntry, rightEntry) {
|
|
35
|
+
return isComment(leftEntry?.node) || isComment(rightEntry?.node);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function commentBoundary(leftEntry, rightEntry, options, fallback = line) {
|
|
39
|
+
if (!leftEntry || !rightEntry) return fallback;
|
|
40
|
+
return commentBoundarySeparator(
|
|
41
|
+
leftEntry.node,
|
|
42
|
+
rightEntry.node,
|
|
43
|
+
options,
|
|
44
|
+
fallback,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Find index of the InfixNode[Comma] wrapper in node.children, or -1. */
|
|
49
|
+
function commaWrapperIndex(node) {
|
|
50
|
+
return node.children.findIndex(
|
|
51
|
+
(c) => c.type === "InfixNode" && c.op === "Comma",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function rawArgEntries(node) {
|
|
56
|
+
const wrapperIdx = commaWrapperIndex(node);
|
|
57
|
+
|
|
58
|
+
return node.children.reduce((entries, child, idx) => {
|
|
59
|
+
if (isTrivia(child) || isBracketToken(child)) return entries;
|
|
60
|
+
|
|
61
|
+
if (idx === wrapperIdx) {
|
|
62
|
+
child.children.forEach((wrappedChild, wrappedIdx) => {
|
|
63
|
+
if (isTrivia(wrappedChild)) return;
|
|
64
|
+
entries.push({
|
|
65
|
+
node: wrappedChild,
|
|
66
|
+
path: ["children", idx, "children", wrappedIdx],
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
return entries;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
entries.push({
|
|
73
|
+
node: child,
|
|
74
|
+
path: ["children", idx],
|
|
75
|
+
});
|
|
76
|
+
return entries;
|
|
77
|
+
}, []);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function argPathEntries(node) {
|
|
81
|
+
return rawArgEntries(node);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function hasDirectCommentArg(node) {
|
|
85
|
+
return argPathEntries(node).some((entry) => isComment(entry.node));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function printedArgEntries(path, options, print, node) {
|
|
89
|
+
return argPathEntries(node).map((entry) => ({
|
|
90
|
+
node: entry.node,
|
|
91
|
+
doc: path.call(print, ...entry.path),
|
|
92
|
+
path: entry.path,
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Extract semantic argument nodes from a CallNode, and return them along
|
|
97
|
+
* with a path-aware print function for each.
|
|
98
|
+
*
|
|
99
|
+
* path/print are prettier primitives so we can descend correctly into nested
|
|
100
|
+
* nodes (avoiding indexOf returning -1 for nodes that aren't direct children).
|
|
101
|
+
*/
|
|
102
|
+
export function printedArgs(path, options, print, node) {
|
|
103
|
+
return printedArgEntries(path, options, print, node)
|
|
104
|
+
.filter((entry) => !isCommaToken(entry.node))
|
|
105
|
+
.map((entry) => entry.doc);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function partGroupEntry(node) {
|
|
109
|
+
const entries = rawArgEntries(node).filter(
|
|
110
|
+
(entry) => !isCommaToken(entry.node),
|
|
111
|
+
);
|
|
112
|
+
if (entries.length !== 1) return null;
|
|
113
|
+
const [entry] = entries;
|
|
114
|
+
return entry.node.type === "GroupNode" && entry.node.kind === "GroupSquare"
|
|
115
|
+
? entry
|
|
116
|
+
: null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function groupPathEntries(groupNode, groupPath) {
|
|
120
|
+
const contents = groupNode.children.filter(
|
|
121
|
+
(child) => !isTrivia(child) && !isBracketToken(child),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (
|
|
125
|
+
contents.length === 1 &&
|
|
126
|
+
contents[0].type === "InfixNode" &&
|
|
127
|
+
contents[0].op === "Comma"
|
|
128
|
+
) {
|
|
129
|
+
const wrapperIdx = groupNode.children.indexOf(contents[0]);
|
|
130
|
+
return contents[0].children.reduce((entries, child, idx) => {
|
|
131
|
+
if (isTrivia(child)) return entries;
|
|
132
|
+
entries.push({
|
|
133
|
+
node: child,
|
|
134
|
+
path: [...groupPath, "children", wrapperIdx, "children", idx],
|
|
135
|
+
});
|
|
136
|
+
return entries;
|
|
137
|
+
}, []);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return groupNode.children.reduce((entries, child, idx) => {
|
|
141
|
+
if (isTrivia(child) || isBracketToken(child)) return entries;
|
|
142
|
+
entries.push({
|
|
143
|
+
node: child,
|
|
144
|
+
path: [...groupPath, "children", idx],
|
|
145
|
+
});
|
|
146
|
+
return entries;
|
|
147
|
+
}, []);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function printedEntries(path, print, entries) {
|
|
151
|
+
return entries.map((entry) => ({
|
|
152
|
+
node: entry.node,
|
|
153
|
+
doc: path.call(print, ...entry.path),
|
|
154
|
+
path: entry.path,
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function sequenceDocs(entries, options, itemKind) {
|
|
159
|
+
const docs = [];
|
|
160
|
+
const commaGap = options.wolframSpaceAfterComma ? line : softline;
|
|
161
|
+
const alignmentGroupId = entries.some((entry) => entry.alignedRuleDoc)
|
|
162
|
+
? Symbol("wolfram-align-rule-values")
|
|
163
|
+
: null;
|
|
164
|
+
let previousKind = null;
|
|
165
|
+
|
|
166
|
+
for (let i = 0; i < entries.length; i++) {
|
|
167
|
+
const entry = entries[i];
|
|
168
|
+
if (isCommaToken(entry.node)) {
|
|
169
|
+
if (previousKind === null || previousKind === "comma") continue;
|
|
170
|
+
const previousEntry = previousContentEntry(entries, i);
|
|
171
|
+
const followingEntry = nextContentEntry(entries, i);
|
|
172
|
+
const separator =
|
|
173
|
+
followingEntry &&
|
|
174
|
+
hasCommentBoundary(previousEntry, followingEntry)
|
|
175
|
+
? commentBoundary(
|
|
176
|
+
previousEntry,
|
|
177
|
+
followingEntry,
|
|
178
|
+
options,
|
|
179
|
+
commaGap,
|
|
180
|
+
)
|
|
181
|
+
: commaGap;
|
|
182
|
+
docs.push(",", separator);
|
|
183
|
+
previousKind = "comma";
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (previousKind !== null && previousKind !== "comma") {
|
|
188
|
+
const previousEntry = previousContentEntry(entries, i);
|
|
189
|
+
docs.push(
|
|
190
|
+
hasCommentBoundary(previousEntry, entry)
|
|
191
|
+
? commentBoundary(previousEntry, entry, options, line)
|
|
192
|
+
: line,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
docs.push(alignedRuleDoc(entry, alignmentGroupId));
|
|
197
|
+
previousKind = isComment(entry.node) ? "comment" : itemKind;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { docs, alignmentGroupId };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function grouped(contents, alignmentGroupId) {
|
|
204
|
+
return alignmentGroupId
|
|
205
|
+
? group(contents, { id: alignmentGroupId })
|
|
206
|
+
: group(contents);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function printPartCall(path, options, print, node, head) {
|
|
210
|
+
const partEntry = partGroupEntry(node);
|
|
211
|
+
if (!partEntry) return null;
|
|
212
|
+
|
|
213
|
+
const entries = withAlignedRuleValues(
|
|
214
|
+
printedEntries(
|
|
215
|
+
path,
|
|
216
|
+
print,
|
|
217
|
+
groupPathEntries(partEntry.node, partEntry.path),
|
|
218
|
+
),
|
|
219
|
+
path,
|
|
220
|
+
options,
|
|
221
|
+
print,
|
|
222
|
+
);
|
|
223
|
+
const args = entries.filter((entry) => !isCommaToken(entry.node));
|
|
224
|
+
|
|
225
|
+
if (args.length === 0) return [head, "[[]]"];
|
|
226
|
+
|
|
227
|
+
const { docs, alignmentGroupId } = sequenceDocs(entries, options, "part");
|
|
228
|
+
return grouped(
|
|
229
|
+
[head, "[[", indent([softline, ...docs]), softline, "]]"],
|
|
230
|
+
alignmentGroupId,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function printCall(path, options, print, node) {
|
|
235
|
+
options = normalizeWolframOptions(options);
|
|
236
|
+
const head = path.call(print, "head");
|
|
237
|
+
const partCall = printPartCall(path, options, print, node, head);
|
|
238
|
+
if (partCall) return partCall;
|
|
239
|
+
|
|
240
|
+
const entries = withAlignedRuleValues(
|
|
241
|
+
printedArgEntries(path, options, print, node),
|
|
242
|
+
path,
|
|
243
|
+
options,
|
|
244
|
+
print,
|
|
245
|
+
);
|
|
246
|
+
const args = entries.filter((entry) => !isCommaToken(entry.node));
|
|
247
|
+
|
|
248
|
+
if (args.length === 0) return [head, "[]"];
|
|
249
|
+
|
|
250
|
+
const { docs, alignmentGroupId } = sequenceDocs(entries, options, "arg");
|
|
251
|
+
const contents = [head, "[", indent([softline, ...docs]), softline, "]"];
|
|
252
|
+
|
|
253
|
+
return grouped(contents, alignmentGroupId);
|
|
254
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/translator/nodes/compound.js
|
|
2
|
+
import { doc } from "prettier";
|
|
3
|
+
const { builders } = doc;
|
|
4
|
+
import { isTrivia, isComment } from "./leaf.js";
|
|
5
|
+
import { joinCommentDocs } from "../docComments.js";
|
|
6
|
+
import {
|
|
7
|
+
blankLinesForCodeGap,
|
|
8
|
+
observedBlankLinesBetween,
|
|
9
|
+
} from "../../utils/codeSpacing.js";
|
|
10
|
+
const { group, hardline } = builders;
|
|
11
|
+
|
|
12
|
+
const PATTERN_BLANK_OPS = new Set([
|
|
13
|
+
"PatternBlank",
|
|
14
|
+
"PatternBlankSequence",
|
|
15
|
+
"PatternBlankNullSequence",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const BLANK_OPS = new Set(["Blank", "BlankSequence", "BlankNullSequence"]);
|
|
19
|
+
|
|
20
|
+
function isSemicolonToken(node) {
|
|
21
|
+
return (
|
|
22
|
+
node?.type === "LeafNode" &&
|
|
23
|
+
(node.kind === "Token`Semi" || node.kind === "Token`Semicolon")
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hasTrailingSemicolon(children) {
|
|
28
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
29
|
+
const child = children[i];
|
|
30
|
+
if (isTrivia(child) || isComment(child)) continue;
|
|
31
|
+
return isSemicolonToken(child);
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function printBlankCompound(node, print) {
|
|
37
|
+
const semantic = node.children.filter((c) => !isTrivia(c));
|
|
38
|
+
if (semantic.length === 0) return "";
|
|
39
|
+
return group(semantic.map((child) => print(child)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function printCompound(node, options, print) {
|
|
43
|
+
if (PATTERN_BLANK_OPS.has(node.op)) {
|
|
44
|
+
return printBlankCompound(node, print);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (BLANK_OPS.has(node.op)) {
|
|
48
|
+
return printBlankCompound(node, print);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (node.op === "Pattern") {
|
|
52
|
+
const semantic = node.children.filter((c) => !isTrivia(c));
|
|
53
|
+
if (semantic.length === 2)
|
|
54
|
+
return [print(semantic[0]), ":", print(semantic[1])];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (node.op === "Slot" || node.op === "SlotSequence") {
|
|
58
|
+
return node.children.filter((c) => !isTrivia(c)).map((c) => print(c));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// CompoundNode[Semicolon, {a, ws, ;, ws, b, ...}]
|
|
62
|
+
const stmts = node.children.filter(
|
|
63
|
+
(c) => !isTrivia(c) && !isComment(c) && !isSemicolonToken(c),
|
|
64
|
+
);
|
|
65
|
+
const trailingComments = node.children.filter((c) => isComment(c));
|
|
66
|
+
const trailingSemicolon = hasTrailingSemicolon(node.children);
|
|
67
|
+
|
|
68
|
+
if (stmts.length === 1) {
|
|
69
|
+
return trailingComments.length === 0
|
|
70
|
+
? [print(stmts[0]), ";"]
|
|
71
|
+
: [
|
|
72
|
+
print(stmts[0]),
|
|
73
|
+
"; ",
|
|
74
|
+
joinCommentDocs(
|
|
75
|
+
trailingComments.map((node) => ({
|
|
76
|
+
node,
|
|
77
|
+
doc: print(node),
|
|
78
|
+
})),
|
|
79
|
+
options,
|
|
80
|
+
),
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const body = [];
|
|
85
|
+
let previousStmt = null;
|
|
86
|
+
|
|
87
|
+
for (const stmt of stmts) {
|
|
88
|
+
if (previousStmt) {
|
|
89
|
+
const observedBlankLines = observedBlankLinesBetween(
|
|
90
|
+
previousStmt.source?.[1]?.[0],
|
|
91
|
+
stmt.source?.[0]?.[0],
|
|
92
|
+
);
|
|
93
|
+
const blankLines = blankLinesForCodeGap(
|
|
94
|
+
previousStmt,
|
|
95
|
+
stmt,
|
|
96
|
+
observedBlankLines,
|
|
97
|
+
options,
|
|
98
|
+
);
|
|
99
|
+
body.push(";", hardline, ...Array(blankLines).fill(hardline));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
body.push(print(stmt));
|
|
103
|
+
previousStmt = stmt;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (trailingSemicolon) body.push(";");
|
|
107
|
+
|
|
108
|
+
if (trailingComments.length === 0) return body;
|
|
109
|
+
return [
|
|
110
|
+
body,
|
|
111
|
+
" ",
|
|
112
|
+
joinCommentDocs(
|
|
113
|
+
trailingComments.map((node) => ({ node, doc: print(node) })),
|
|
114
|
+
options,
|
|
115
|
+
),
|
|
116
|
+
];
|
|
117
|
+
}
|