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.
Files changed (45) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +290 -0
  3. package/bin/prettier-wolfram.js +55 -0
  4. package/package.json +58 -0
  5. package/src/index.js +80 -0
  6. package/src/options.js +206 -0
  7. package/src/parser/adapter.js +690 -0
  8. package/src/parser/cstEqual.js +18 -0
  9. package/src/parser/index.js +29 -0
  10. package/src/parser/operators.js +35 -0
  11. package/src/parser/position.js +62 -0
  12. package/src/parser/tree-sitter-wolfram.wasm +0 -0
  13. package/src/range.js +98 -0
  14. package/src/rules/index.js +57 -0
  15. package/src/rules/line-width.js +129 -0
  16. package/src/rules/newlines-between-definitions.js +103 -0
  17. package/src/rules/no-bare-symbol-set.js +19 -0
  18. package/src/rules/no-dynamic-module-leak.js +74 -0
  19. package/src/rules/no-general-infix-function.js +52 -0
  20. package/src/rules/no-shadowed-pattern.js +71 -0
  21. package/src/rules/no-unused-module-var.js +84 -0
  22. package/src/rules/prefer-rule-delayed.js +59 -0
  23. package/src/rules/spacing-commas.js +64 -0
  24. package/src/rules/spacing-operators.js +87 -0
  25. package/src/translator/commentSpacing.js +51 -0
  26. package/src/translator/docComments.js +89 -0
  27. package/src/translator/index.js +98 -0
  28. package/src/translator/nodes/binary.js +205 -0
  29. package/src/translator/nodes/call.js +254 -0
  30. package/src/translator/nodes/compound.js +117 -0
  31. package/src/translator/nodes/container.js +194 -0
  32. package/src/translator/nodes/group.js +159 -0
  33. package/src/translator/nodes/infix.js +408 -0
  34. package/src/translator/nodes/leaf.js +605 -0
  35. package/src/translator/nodes/postfix.js +29 -0
  36. package/src/translator/nodes/prefix.js +27 -0
  37. package/src/translator/nodes/ternary.js +82 -0
  38. package/src/translator/ruleAlignment.js +133 -0
  39. package/src/translator/sourceLines.js +49 -0
  40. package/src/translator/sourcePreservation.js +22 -0
  41. package/src/translator/specialForms.js +665 -0
  42. package/src/utils/codeSpacing.js +420 -0
  43. package/src/utils/cstErrors.js +36 -0
  44. package/src/utils/offsets.js +132 -0
  45. package/src/utils/operatorSpacing.js +49 -0
@@ -0,0 +1,420 @@
1
+ import { normalizeWolframOptions } from "../options.js";
2
+
3
+ const DEFAULT_MAX_BLANK_LINES_BETWEEN_CODE = 1;
4
+ const DEFAULT_BLANK_LINES_BETWEEN_DEFINITIONS = 1;
5
+ const DEFAULT_BLANK_LINES_BETWEEN_SAME_NAME_DEFINITIONS = 0;
6
+
7
+ const SET_DECLARATION_OPS = new Set(["Set", "TagSet", "UpSet"]);
8
+ const SET_DELAYED_DECLARATION_OPS = new Set([
9
+ "SetDelayed",
10
+ "TagSetDelayed",
11
+ "UpSetDelayed",
12
+ ]);
13
+ const DECLARATION_OPS = new Set([
14
+ ...SET_DECLARATION_OPS,
15
+ ...SET_DELAYED_DECLARATION_OPS,
16
+ ]);
17
+
18
+ const TRIVIA_KINDS = new Set([
19
+ "Token`Whitespace",
20
+ "Whitespace",
21
+ "Token`Newline",
22
+ "Newline",
23
+ "Token`LineContinuation",
24
+ "LineContinuation",
25
+ "Token`Fake`ImplicitNull",
26
+ ]);
27
+
28
+ const SEMICOLON_KINDS = new Set(["Token`Semi", "Token`Semicolon"]);
29
+ const SAME_DEFINITION_TARGET_HEADS = new Set(["Options", "Attributes"]);
30
+ const ATTRIBUTE_SETTER_HEADS = new Set(["SetAttributes"]);
31
+
32
+ export function nonNegativeIntegerOption(value, fallback) {
33
+ const numeric = Number(value);
34
+ if (!Number.isFinite(numeric)) return fallback;
35
+ return Math.max(0, Math.floor(numeric));
36
+ }
37
+
38
+ export function maxBlankLinesBetweenCode(options = {}) {
39
+ options = normalizeWolframOptions(options);
40
+ return nonNegativeIntegerOption(
41
+ options.wolframMaxBlankLinesBetweenCode,
42
+ DEFAULT_MAX_BLANK_LINES_BETWEEN_CODE,
43
+ );
44
+ }
45
+
46
+ export function blankLinesBetweenDefinitions(options = {}) {
47
+ options = normalizeWolframOptions(options);
48
+ return nonNegativeIntegerOption(
49
+ options.wolframNewlinesBetweenDefinitions,
50
+ DEFAULT_BLANK_LINES_BETWEEN_DEFINITIONS,
51
+ );
52
+ }
53
+
54
+ function optionalBlankLinesOption(value, fallback) {
55
+ if (value == null) return fallback;
56
+ return nonNegativeIntegerOption(value, fallback);
57
+ }
58
+
59
+ export function blankLinesBetweenSetDefinitions(options = {}) {
60
+ options = normalizeWolframOptions(options);
61
+ return optionalBlankLinesOption(
62
+ options.wolframNewlinesBetweenSetDefinitions,
63
+ blankLinesBetweenDefinitions(options),
64
+ );
65
+ }
66
+
67
+ export function blankLinesBetweenSetDelayedDefinitions(options = {}) {
68
+ options = normalizeWolframOptions(options);
69
+ return optionalBlankLinesOption(
70
+ options.wolframNewlinesBetweenSetDelayedDefinitions,
71
+ blankLinesBetweenDefinitions(options),
72
+ );
73
+ }
74
+
75
+ export function blankLinesBetweenSetAndSetDelayedDefinitions(options = {}) {
76
+ options = normalizeWolframOptions(options);
77
+ return optionalBlankLinesOption(
78
+ options.wolframNewlinesBetweenSetAndSetDelayedDefinitions,
79
+ blankLinesBetweenDefinitions(options),
80
+ );
81
+ }
82
+
83
+ export function blankLinesBetweenSameNameDefinitions(options = {}) {
84
+ options = normalizeWolframOptions(options);
85
+ return nonNegativeIntegerOption(
86
+ options.wolframNewlinesBetweenSameNameDefinitions,
87
+ DEFAULT_BLANK_LINES_BETWEEN_SAME_NAME_DEFINITIONS,
88
+ );
89
+ }
90
+
91
+ function isTrivia(node) {
92
+ return node?.type === "LeafNode" && TRIVIA_KINDS.has(node.kind);
93
+ }
94
+
95
+ function isSemicolonToken(node) {
96
+ return node?.type === "LeafNode" && SEMICOLON_KINDS.has(node.kind);
97
+ }
98
+
99
+ function isComment(node) {
100
+ return node?.type === "LeafNode" && node.kind === "Token`Comment";
101
+ }
102
+
103
+ function isToken(node) {
104
+ return node?.type === "LeafNode" && node.kind?.startsWith("Token`");
105
+ }
106
+
107
+ function semanticChildren(node) {
108
+ return (node?.children ?? []).filter(
109
+ (child) => !isTrivia(child) && !isComment(child) && !isToken(child),
110
+ );
111
+ }
112
+
113
+ function isSingleStatementCompound(node) {
114
+ return (
115
+ (node?.type === "InfixNode" && node.op === "CompoundExpression") ||
116
+ (node?.type === "CompoundNode" &&
117
+ (node.op === "CompoundExpression" || node.op === "Semicolon"))
118
+ );
119
+ }
120
+
121
+ export function unwrapSingleStatementNode(node) {
122
+ if (!isSingleStatementCompound(node)) return node;
123
+
124
+ const statements = (node.children ?? []).filter(
125
+ (child) =>
126
+ !isTrivia(child) && !isSemicolonToken(child) && !isComment(child),
127
+ );
128
+
129
+ if (statements.length !== 1) return node;
130
+ return unwrapSingleStatementNode(statements[0]);
131
+ }
132
+
133
+ export function isDeclarationNode(node) {
134
+ const statement = unwrapSingleStatementNode(node);
135
+ return (
136
+ statement?.type === "BinaryNode" && DECLARATION_OPS.has(statement.op)
137
+ );
138
+ }
139
+
140
+ function declarationSpacingKind(node) {
141
+ const statement = unwrapSingleStatementNode(node);
142
+ if (statement?.type !== "BinaryNode") return null;
143
+ if (SET_DECLARATION_OPS.has(statement.op)) return "set";
144
+ if (SET_DELAYED_DECLARATION_OPS.has(statement.op)) return "setDelayed";
145
+ return null;
146
+ }
147
+
148
+ function blankLinesBetweenDeclarationKinds(
149
+ prevKind,
150
+ nextKind,
151
+ options,
152
+ { requireSpecificOption = false } = {},
153
+ ) {
154
+ if (prevKind === "set" && nextKind === "set") {
155
+ if (
156
+ requireSpecificOption &&
157
+ options.wolframNewlinesBetweenSetDefinitions == null
158
+ ) {
159
+ return null;
160
+ }
161
+ return blankLinesBetweenSetDefinitions(options);
162
+ }
163
+
164
+ if (prevKind === "setDelayed" && nextKind === "setDelayed") {
165
+ if (
166
+ requireSpecificOption &&
167
+ options.wolframNewlinesBetweenSetDelayedDefinitions == null
168
+ ) {
169
+ return null;
170
+ }
171
+ return blankLinesBetweenSetDelayedDefinitions(options);
172
+ }
173
+
174
+ if (
175
+ (prevKind === "set" && nextKind === "setDelayed") ||
176
+ (prevKind === "setDelayed" && nextKind === "set")
177
+ ) {
178
+ if (
179
+ requireSpecificOption &&
180
+ options.wolframNewlinesBetweenSetAndSetDelayedDefinitions == null
181
+ ) {
182
+ return null;
183
+ }
184
+ return blankLinesBetweenSetAndSetDelayedDefinitions(options);
185
+ }
186
+
187
+ return null;
188
+ }
189
+
190
+ function blankLinesBetweenDeclarationNodes(
191
+ prevNode,
192
+ nextNode,
193
+ options,
194
+ requireSpecificOption = false,
195
+ ) {
196
+ return blankLinesBetweenDeclarationKinds(
197
+ declarationSpacingKind(prevNode),
198
+ declarationSpacingKind(nextNode),
199
+ options,
200
+ { requireSpecificOption },
201
+ );
202
+ }
203
+
204
+ function firstSemanticChild(node) {
205
+ return semanticChildren(node)[0] ?? null;
206
+ }
207
+
208
+ function symbolName(node) {
209
+ if (node?.type === "LeafNode" && node.kind === "Symbol") {
210
+ return String(node.value ?? "");
211
+ }
212
+
213
+ return null;
214
+ }
215
+
216
+ function callHeadName(node) {
217
+ return symbolName(node?.head);
218
+ }
219
+
220
+ function callArgumentNodes(node) {
221
+ if (node?.type !== "CallNode") return [];
222
+
223
+ const commaWrapper = (node.children ?? []).find(
224
+ (child) => child.type === "InfixNode" && child.op === "Comma",
225
+ );
226
+ if (commaWrapper) {
227
+ return semanticChildren(commaWrapper);
228
+ }
229
+
230
+ return semanticChildren(node);
231
+ }
232
+
233
+ function subjectNamesFromExpression(node) {
234
+ const name = symbolName(node);
235
+ if (name) return new Set([name]);
236
+
237
+ if (
238
+ node?.type === "GroupNode" ||
239
+ node?.type === "InfixNode" ||
240
+ node?.type === "CompoundNode"
241
+ ) {
242
+ const names = new Set();
243
+ for (const child of semanticChildren(node)) {
244
+ for (const childName of subjectNamesFromExpression(child)) {
245
+ names.add(childName);
246
+ }
247
+ }
248
+ return names;
249
+ }
250
+
251
+ return new Set();
252
+ }
253
+
254
+ function definitionTargetNamesFromCall(node, allowedHeads) {
255
+ if (node?.type !== "CallNode" || !allowedHeads.has(callHeadName(node))) {
256
+ return new Set();
257
+ }
258
+
259
+ return subjectNamesFromExpression(callArgumentNodes(node)[0]);
260
+ }
261
+
262
+ function definitionTargetNamesFromShorthand(node, allowedHeads) {
263
+ if (node?.type !== "BinaryNode") return new Set();
264
+
265
+ const [lhs, rhs] = semanticChildren(node);
266
+ if (node.op === "BinaryAt" && allowedHeads.has(symbolName(lhs))) {
267
+ return subjectNamesFromExpression(rhs);
268
+ }
269
+
270
+ if (node.op === "BinarySlashSlash" && allowedHeads.has(symbolName(rhs))) {
271
+ return subjectNamesFromExpression(lhs);
272
+ }
273
+
274
+ return new Set();
275
+ }
276
+
277
+ function definitionTargetNames(node, allowedHeads) {
278
+ const callNames = definitionTargetNamesFromCall(node, allowedHeads);
279
+ if (callNames.size > 0) return callNames;
280
+
281
+ return definitionTargetNamesFromShorthand(node, allowedHeads);
282
+ }
283
+
284
+ function functionNameFromCallHead(node) {
285
+ const name = symbolName(node);
286
+ if (name) return name;
287
+
288
+ if (node?.type === "CallNode") {
289
+ return functionNameFromCallHead(node.head);
290
+ }
291
+
292
+ if (node?.type === "BinaryNode" || node?.type === "CompoundNode") {
293
+ return functionNameFromCallHead(firstSemanticChild(node));
294
+ }
295
+
296
+ return null;
297
+ }
298
+
299
+ function functionDefinitionSubjectNamesFromLhs(
300
+ node,
301
+ { allowBareSymbol = false } = {},
302
+ ) {
303
+ const bareName = symbolName(node);
304
+ if (allowBareSymbol && bareName) return new Set([bareName]);
305
+
306
+ if (node?.type === "InfixNode" && node.op === "MessageName") {
307
+ return subjectNamesFromExpression(firstSemanticChild(node));
308
+ }
309
+
310
+ const targetNames = definitionTargetNames(
311
+ node,
312
+ SAME_DEFINITION_TARGET_HEADS,
313
+ );
314
+ if (targetNames.size > 0) return targetNames;
315
+
316
+ if (node?.type === "BinaryNode") {
317
+ const [lhs, rhs] = semanticChildren(node);
318
+ if (node.op === "BinaryAt") {
319
+ const name = functionNameFromCallHead(lhs);
320
+ return name ? new Set([name]) : new Set();
321
+ }
322
+
323
+ if (node.op === "BinarySlashSlash") {
324
+ const name = functionNameFromCallHead(rhs);
325
+ return name ? new Set([name]) : new Set();
326
+ }
327
+ }
328
+
329
+ if (node?.type === "CallNode") {
330
+ const name = functionNameFromCallHead(node.head);
331
+ return name ? new Set([name]) : new Set();
332
+ }
333
+
334
+ if (node?.type === "BinaryNode" || node?.type === "CompoundNode") {
335
+ return functionDefinitionSubjectNamesFromLhs(firstSemanticChild(node), {
336
+ allowBareSymbol,
337
+ });
338
+ }
339
+
340
+ return new Set();
341
+ }
342
+
343
+ function binaryLhs(node) {
344
+ return firstSemanticChild(node);
345
+ }
346
+
347
+ function definitionSubjectNames(node) {
348
+ const statement = unwrapSingleStatementNode(node);
349
+ const attributeNames = definitionTargetNames(
350
+ statement,
351
+ ATTRIBUTE_SETTER_HEADS,
352
+ );
353
+ if (attributeNames.size > 0) return attributeNames;
354
+
355
+ if (
356
+ statement?.type !== "BinaryNode" ||
357
+ !DECLARATION_OPS.has(statement.op)
358
+ ) {
359
+ return new Set();
360
+ }
361
+
362
+ return functionDefinitionSubjectNamesFromLhs(binaryLhs(statement), {
363
+ allowBareSymbol: ["TagSet", "TagSetDelayed"].includes(statement.op),
364
+ });
365
+ }
366
+
367
+ function hasSharedDefinitionSubject(prevNode, nextNode) {
368
+ const prevNames = definitionSubjectNames(prevNode);
369
+ if (prevNames.size === 0) return false;
370
+
371
+ const nextNames = definitionSubjectNames(nextNode);
372
+ for (const name of prevNames) {
373
+ if (nextNames.has(name)) return true;
374
+ }
375
+
376
+ return false;
377
+ }
378
+
379
+ export function observedBlankLinesBetween(prevEndLine, nextStartLine) {
380
+ if (!Number.isFinite(prevEndLine) || !Number.isFinite(nextStartLine)) {
381
+ return 0;
382
+ }
383
+
384
+ return Math.max(0, nextStartLine - prevEndLine - 1);
385
+ }
386
+
387
+ export function blankLinesForCodeGap(
388
+ prevNode,
389
+ nextNode,
390
+ observedBlankLines,
391
+ options = {},
392
+ { topLevel = false } = {},
393
+ ) {
394
+ options = normalizeWolframOptions(options);
395
+ const mode = options.wolframTopLevelSpacingMode ?? "declarations";
396
+ if (topLevel && mode === "none") return 0;
397
+
398
+ if (hasSharedDefinitionSubject(prevNode, nextNode)) {
399
+ return blankLinesBetweenSameNameDefinitions(options);
400
+ }
401
+
402
+ if (isDeclarationNode(prevNode) && isDeclarationNode(nextNode)) {
403
+ return (
404
+ blankLinesBetweenDeclarationNodes(prevNode, nextNode, options) ??
405
+ blankLinesBetweenDefinitions(options)
406
+ );
407
+ }
408
+
409
+ const maxBlankLines = maxBlankLinesBetweenCode(options);
410
+ const cappedObserved = Math.min(
411
+ maxBlankLines,
412
+ nonNegativeIntegerOption(observedBlankLines, 0),
413
+ );
414
+
415
+ if (topLevel && mode === "all") {
416
+ return Math.min(maxBlankLines, Math.max(1, cappedObserved));
417
+ }
418
+
419
+ return cappedObserved;
420
+ }
@@ -0,0 +1,36 @@
1
+ const CST_ERROR_SYMBOL_PATTERN =
2
+ /(?:^|`)(?:AbstractSyntaxErrorNode|SyntaxErrorNode|\w*ErrorNode|\w*Missing\w*Node|\w*Unterminated\w*Node|\w*Incomplete\w*Node|\w*Issue)\[/;
3
+
4
+ function looksLikeCstErrorSymbol(value) {
5
+ return typeof value === "string" && CST_ERROR_SYMBOL_PATTERN.test(value);
6
+ }
7
+
8
+ export function isCstErrorNode(node) {
9
+ if (!node || typeof node !== "object" || node.type !== "Unknown") {
10
+ return false;
11
+ }
12
+
13
+ return (
14
+ looksLikeCstErrorSymbol(node.kind) || looksLikeCstErrorSymbol(node.wl)
15
+ );
16
+ }
17
+
18
+ export function containsCstErrors(node) {
19
+ if (!node || typeof node !== "object") return false;
20
+ if (isCstErrorNode(node)) return true;
21
+ if (containsCstErrors(node.head)) return true;
22
+
23
+ return (
24
+ Array.isArray(node.children) && node.children.some(containsCstErrors)
25
+ );
26
+ }
27
+
28
+ export function createUnformattableNode(sourceText = "") {
29
+ return {
30
+ type: "UnformattableNode",
31
+ children: [],
32
+ locStart: 0,
33
+ locEnd: sourceText.length,
34
+ wl: sourceText,
35
+ };
36
+ }
@@ -0,0 +1,132 @@
1
+ // src/utils/offsets.js
2
+
3
+ /** Build a table mapping line index (0-based) → character offset of line start */
4
+ export function buildOffsetTable(source, tabWidth = 2) {
5
+ const table = [0];
6
+ for (let i = 0; i < source.length; i++) {
7
+ if (source[i] === "\n") table.push(i + 1);
8
+ }
9
+ Object.defineProperties(table, {
10
+ sourceText: {
11
+ value: source,
12
+ enumerable: false,
13
+ },
14
+ tabWidth: {
15
+ value: tabWidth,
16
+ enumerable: false,
17
+ },
18
+ });
19
+ return table;
20
+ }
21
+
22
+ /** Convert 1-based line/col to 0-based character offset */
23
+ export function lineColToOffset(table, line, col) {
24
+ const lineStart = table[line - 1];
25
+ if (typeof lineStart !== "number") return 0;
26
+
27
+ const sourceText =
28
+ table &&
29
+ typeof table === "object" &&
30
+ typeof table.sourceText === "string"
31
+ ? table.sourceText
32
+ : null;
33
+ if (!sourceText) return lineStart + (col - 1);
34
+
35
+ const width =
36
+ table && typeof table === "object" && typeof table.tabWidth === "number"
37
+ ? table.tabWidth
38
+ : 2;
39
+ let offset = lineStart;
40
+ let visualCol = 1;
41
+
42
+ while (
43
+ offset < sourceText.length &&
44
+ sourceText[offset] !== "\n" &&
45
+ visualCol < col
46
+ ) {
47
+ if (sourceText[offset] === "\t") {
48
+ visualCol += width - ((visualCol - 1) % width);
49
+ } else {
50
+ visualCol += 1;
51
+ }
52
+ offset += 1;
53
+ }
54
+
55
+ return offset;
56
+ }
57
+
58
+ function sourceToOffsets(source, table) {
59
+ if (!Array.isArray(source) || source.length !== 2) return null;
60
+ const [start, end] = source;
61
+ if (!Array.isArray(start) || !Array.isArray(end)) return null;
62
+ const [startLine, startCol] = start;
63
+ const [endLine, endCol] = end;
64
+ if (
65
+ [startLine, startCol, endLine, endCol].some(
66
+ (value) => typeof value !== "number" || Number.isNaN(value),
67
+ )
68
+ ) {
69
+ return null;
70
+ }
71
+
72
+ return {
73
+ locStart: lineColToOffset(table, startLine, startCol),
74
+ locEnd: lineColToOffset(table, endLine, endCol),
75
+ };
76
+ }
77
+
78
+ function nodeOffsets(node) {
79
+ if (
80
+ !node ||
81
+ typeof node !== "object" ||
82
+ typeof node.locStart !== "number" ||
83
+ typeof node.locEnd !== "number"
84
+ ) {
85
+ return null;
86
+ }
87
+
88
+ return { locStart: node.locStart, locEnd: node.locEnd };
89
+ }
90
+
91
+ function combinedOffsets(nodes) {
92
+ const ranges = nodes.map(nodeOffsets).filter(Boolean);
93
+ if (ranges.length === 0) return null;
94
+
95
+ return {
96
+ locStart: Math.min(...ranges.map((range) => range.locStart)),
97
+ locEnd: Math.max(...ranges.map((range) => range.locEnd)),
98
+ };
99
+ }
100
+
101
+ /** Recursively attach locStart/locEnd to every node in the CST tree */
102
+ export function addOffsets(node, table, fallbackOffset = null) {
103
+ if (!node || typeof node !== "object") return node;
104
+
105
+ const ownOffsets = sourceToOffsets(node.source, table);
106
+ const childFallback =
107
+ ownOffsets?.locStart ??
108
+ (typeof fallbackOffset === "number" ? fallbackOffset : null);
109
+
110
+ if (node.children) {
111
+ node.children.forEach((child) =>
112
+ addOffsets(child, table, childFallback),
113
+ );
114
+ }
115
+ if (node.head) {
116
+ addOffsets(node.head, table, childFallback);
117
+ }
118
+
119
+ const derivedOffsets =
120
+ ownOffsets ??
121
+ combinedOffsets([node.head, ...(node.children ?? [])]) ??
122
+ (typeof childFallback === "number"
123
+ ? { locStart: childFallback, locEnd: childFallback }
124
+ : null);
125
+
126
+ if (derivedOffsets) {
127
+ node.locStart = derivedOffsets.locStart;
128
+ node.locEnd = derivedOffsets.locEnd;
129
+ }
130
+
131
+ return node;
132
+ }
@@ -0,0 +1,49 @@
1
+ import { normalizeWolframOptions } from "../options.js";
2
+
3
+ const TIGHT_INFIX_OPERATORS = new Set(["MessageName"]);
4
+
5
+ const TIGHT_BINARY_OPERATORS = new Set(["PatternTest", "Span"]);
6
+
7
+ const TIGHT_TERNARY_OPERATORS = new Set(["Span"]);
8
+
9
+ const TIGHT_OPERATOR_TOKENS = new Set([
10
+ "Token`ColonColon",
11
+ "Token`Question",
12
+ "Token`SemiSemi",
13
+ ]);
14
+
15
+ const TIGHT_OPERATOR_VALUES = new Set(["::", "?", ";;"]);
16
+
17
+ export function prefersNoSpacesAroundOperator(node, operatorToken = null) {
18
+ if (
19
+ operatorToken &&
20
+ (TIGHT_OPERATOR_TOKENS.has(operatorToken.kind) ||
21
+ TIGHT_OPERATOR_VALUES.has(operatorToken.value))
22
+ ) {
23
+ return true;
24
+ }
25
+
26
+ if (node?.type === "InfixNode") return TIGHT_INFIX_OPERATORS.has(node.op);
27
+ if (node?.type === "BinaryNode") return TIGHT_BINARY_OPERATORS.has(node.op);
28
+ if (node?.type === "TernaryNode")
29
+ return TIGHT_TERNARY_OPERATORS.has(node.op);
30
+ return false;
31
+ }
32
+
33
+ export function wantsSpacesAroundOperator(
34
+ node,
35
+ options = {},
36
+ operatorToken = null,
37
+ ) {
38
+ if (prefersNoSpacesAroundOperator(node, operatorToken)) return false;
39
+ options = normalizeWolframOptions(options);
40
+ return options?.wolframSpaceAroundOperators ?? true;
41
+ }
42
+
43
+ export {
44
+ TIGHT_BINARY_OPERATORS,
45
+ TIGHT_INFIX_OPERATORS,
46
+ TIGHT_OPERATOR_TOKENS,
47
+ TIGHT_OPERATOR_VALUES,
48
+ TIGHT_TERNARY_OPERATORS,
49
+ };