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,82 @@
|
|
|
1
|
+
// src/translator/nodes/ternary.js
|
|
2
|
+
import { doc } from "prettier";
|
|
3
|
+
const { builders } = doc;
|
|
4
|
+
import { isTrivia } from "./leaf.js";
|
|
5
|
+
import {
|
|
6
|
+
hasImmediateComment,
|
|
7
|
+
printOriginalSource,
|
|
8
|
+
} from "../sourcePreservation.js";
|
|
9
|
+
import { normalizeWolframOptions } from "../../options.js";
|
|
10
|
+
const { group, indent, line, softline } = builders;
|
|
11
|
+
|
|
12
|
+
function preservedTildeFunctions(options) {
|
|
13
|
+
options = normalizeWolframOptions(options);
|
|
14
|
+
return new Set(
|
|
15
|
+
String(options.wolframPreserveTildeInfixFunctions ?? "")
|
|
16
|
+
.split(",")
|
|
17
|
+
.map((s) => s.trim())
|
|
18
|
+
.filter(Boolean),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isFakeTokenLeaf(node) {
|
|
23
|
+
return node?.type === "LeafNode" && node.kind.startsWith("Token`Fake`");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isSpanToken(node) {
|
|
27
|
+
return node?.type === "LeafNode" && node.kind === "Token`SemiSemi";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function tildeGap(token, options) {
|
|
31
|
+
options = normalizeWolframOptions(options);
|
|
32
|
+
if (options.wolframSpaceAroundOperators ?? true) {
|
|
33
|
+
return [` ${token}`, line];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return [token, softline];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function printTernary(node, options, print) {
|
|
40
|
+
if (hasImmediateComment(node)) {
|
|
41
|
+
return printOriginalSource(node, options);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const semantic = node.children.filter((c) => !isTrivia(c));
|
|
45
|
+
|
|
46
|
+
if (node.op === "Span") {
|
|
47
|
+
return group(
|
|
48
|
+
semantic.map((child) => {
|
|
49
|
+
if (isSpanToken(child)) return ";;";
|
|
50
|
+
if (isFakeTokenLeaf(child)) return "";
|
|
51
|
+
return print(child);
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (node.op === "TernaryTilde" && semantic.length === 5) {
|
|
57
|
+
const [lhs, firstTilde, fn, secondTilde, rhs] = semantic;
|
|
58
|
+
if (
|
|
59
|
+
fn.type === "LeafNode" &&
|
|
60
|
+
fn.kind === "Symbol" &&
|
|
61
|
+
preservedTildeFunctions(options).has(fn.value)
|
|
62
|
+
) {
|
|
63
|
+
return group([
|
|
64
|
+
print(lhs),
|
|
65
|
+
...tildeGap(firstTilde.value, options),
|
|
66
|
+
print(fn),
|
|
67
|
+
...tildeGap(secondTilde.value, options),
|
|
68
|
+
print(rhs),
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return group([
|
|
73
|
+
print(fn),
|
|
74
|
+
"[",
|
|
75
|
+
indent([softline, print(lhs), ",", line, print(rhs)]),
|
|
76
|
+
softline,
|
|
77
|
+
"]",
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return printOriginalSource(node, options);
|
|
82
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { doc } from "prettier";
|
|
2
|
+
import { renderFlatDoc } from "./docComments.js";
|
|
3
|
+
import { isComment, isTrivia } from "./nodes/leaf.js";
|
|
4
|
+
import { wantsSpacesAroundOperator } from "../utils/operatorSpacing.js";
|
|
5
|
+
import { normalizeWolframOptions } from "../options.js";
|
|
6
|
+
|
|
7
|
+
const { ifBreak } = doc.builders;
|
|
8
|
+
|
|
9
|
+
const RULE_OPS = new Set(["Rule", "RuleDelayed"]);
|
|
10
|
+
const RULE_DISPLAY = {
|
|
11
|
+
Rule: "->",
|
|
12
|
+
RuleDelayed: ":>",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function isSemanticTokenLeaf(node) {
|
|
16
|
+
return (
|
|
17
|
+
node?.type === "LeafNode" &&
|
|
18
|
+
[
|
|
19
|
+
"Token`Hash",
|
|
20
|
+
"Token`HashHash",
|
|
21
|
+
"Token`Under",
|
|
22
|
+
"Token`UnderUnder",
|
|
23
|
+
"Token`UnderUnderUnder",
|
|
24
|
+
].includes(node.kind)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isFakeTokenLeaf(node) {
|
|
29
|
+
return node?.type === "LeafNode" && node.kind.startsWith("Token`Fake`");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isOperatorTokenLeaf(node) {
|
|
33
|
+
return (
|
|
34
|
+
node?.type === "LeafNode" &&
|
|
35
|
+
node.kind.startsWith("Token`") &&
|
|
36
|
+
!isSemanticTokenLeaf(node) &&
|
|
37
|
+
!isFakeTokenLeaf(node)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ruleParts(node) {
|
|
42
|
+
if (node?.type !== "BinaryNode" || !RULE_OPS.has(node.op)) return null;
|
|
43
|
+
if ((node.children ?? []).some((child) => isComment(child))) return null;
|
|
44
|
+
|
|
45
|
+
const semantic = node.children.filter(
|
|
46
|
+
(child) =>
|
|
47
|
+
!isTrivia(child) &&
|
|
48
|
+
!(
|
|
49
|
+
child.type === "LeafNode" &&
|
|
50
|
+
child.kind.startsWith("Token`") &&
|
|
51
|
+
!isSemanticTokenLeaf(child)
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
const [lhs, rhs] = semantic;
|
|
55
|
+
if (!lhs || !rhs) return null;
|
|
56
|
+
|
|
57
|
+
const token = node.children.find(
|
|
58
|
+
(child) => !isTrivia(child) && isOperatorTokenLeaf(child),
|
|
59
|
+
);
|
|
60
|
+
return {
|
|
61
|
+
lhs,
|
|
62
|
+
rhs,
|
|
63
|
+
token,
|
|
64
|
+
op: token?.value ?? RULE_DISPLAY[node.op],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function childDoc(path, print, entry, child) {
|
|
69
|
+
const childIndex = entry.node.children.indexOf(child);
|
|
70
|
+
if (childIndex === -1 || !entry.path) return null;
|
|
71
|
+
return path.call(print, ...entry.path, "children", childIndex);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function alignmentCandidate(path, options, print, entry) {
|
|
75
|
+
const parts = ruleParts(entry.node);
|
|
76
|
+
if (!parts) return null;
|
|
77
|
+
|
|
78
|
+
const lhsDoc = childDoc(path, print, entry, parts.lhs);
|
|
79
|
+
const rhsDoc = childDoc(path, print, entry, parts.rhs);
|
|
80
|
+
if (!lhsDoc || !rhsDoc) return null;
|
|
81
|
+
|
|
82
|
+
const lhsText = renderFlatDoc(lhsDoc, options);
|
|
83
|
+
if (lhsText.includes("\n")) return null;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
entry,
|
|
87
|
+
lhsText,
|
|
88
|
+
lhsWidth: lhsText.length,
|
|
89
|
+
rhsDoc,
|
|
90
|
+
op: parts.op,
|
|
91
|
+
token: parts.token,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function withAlignedRuleValues(entries, path, options, print) {
|
|
96
|
+
options = normalizeWolframOptions(options);
|
|
97
|
+
if (!options.wolframAlignRuleValues) return entries;
|
|
98
|
+
|
|
99
|
+
const candidates = entries
|
|
100
|
+
.map((entry) => alignmentCandidate(path, options, print, entry))
|
|
101
|
+
.filter(Boolean);
|
|
102
|
+
|
|
103
|
+
if (candidates.length < 2) return entries;
|
|
104
|
+
|
|
105
|
+
const maxLhsWidth = Math.max(
|
|
106
|
+
...candidates.map((candidate) => candidate.lhsWidth),
|
|
107
|
+
);
|
|
108
|
+
for (const candidate of candidates) {
|
|
109
|
+
const hasOperatorSpace = wantsSpacesAroundOperator(
|
|
110
|
+
candidate.entry.node,
|
|
111
|
+
options,
|
|
112
|
+
candidate.token,
|
|
113
|
+
);
|
|
114
|
+
const beforeOperator =
|
|
115
|
+
maxLhsWidth - candidate.lhsWidth + (hasOperatorSpace ? 1 : 0);
|
|
116
|
+
const afterOperator = hasOperatorSpace ? " " : "";
|
|
117
|
+
candidate.entry.alignedRuleDoc = [
|
|
118
|
+
candidate.lhsText,
|
|
119
|
+
" ".repeat(beforeOperator),
|
|
120
|
+
candidate.op,
|
|
121
|
+
afterOperator,
|
|
122
|
+
candidate.rhsDoc,
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return entries;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function alignedRuleDoc(entry, groupId) {
|
|
130
|
+
return entry.alignedRuleDoc
|
|
131
|
+
? ifBreak(entry.alignedRuleDoc, entry.doc, { groupId })
|
|
132
|
+
: entry.doc;
|
|
133
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
function lineNumberAtOffset(text, offset) {
|
|
2
|
+
if (typeof text !== "string" || typeof offset !== "number" || offset < 0) {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const limit = Math.min(offset, text.length);
|
|
7
|
+
let line = 1;
|
|
8
|
+
let searchFrom = 0;
|
|
9
|
+
|
|
10
|
+
while (searchFrom < limit) {
|
|
11
|
+
const newlineOffset = text.indexOf("\n", searchFrom);
|
|
12
|
+
if (newlineOffset === -1 || newlineOffset >= limit) break;
|
|
13
|
+
line++;
|
|
14
|
+
searchFrom = newlineOffset + 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return line;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function nodeStartLine(node, options) {
|
|
21
|
+
const sourceStartLine = node?.source?.[0]?.[0];
|
|
22
|
+
if (Number.isFinite(sourceStartLine)) return sourceStartLine;
|
|
23
|
+
return lineNumberAtOffset(options?.originalText, node?.locStart);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function nodeEndLine(node, options) {
|
|
27
|
+
const sourceEndLine = node?.source?.[1]?.[0];
|
|
28
|
+
if (Number.isFinite(sourceEndLine)) return sourceEndLine;
|
|
29
|
+
|
|
30
|
+
if (typeof node?.locEnd === "number") {
|
|
31
|
+
const lastIncludedOffset =
|
|
32
|
+
typeof node?.locStart === "number" && node.locEnd > node.locStart
|
|
33
|
+
? node.locEnd - 1
|
|
34
|
+
: node.locEnd;
|
|
35
|
+
return lineNumberAtOffset(options?.originalText, lastIncludedOffset);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return nodeStartLine(node, options);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function sourceLineGap(leftNode, rightNode, options) {
|
|
42
|
+
const leftEndLine = nodeEndLine(leftNode, options);
|
|
43
|
+
const rightStartLine = nodeStartLine(rightNode, options);
|
|
44
|
+
if (!Number.isFinite(leftEndLine) || !Number.isFinite(rightStartLine)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return rightStartLine - leftEndLine;
|
|
49
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isComment } from "./nodes/leaf.js";
|
|
2
|
+
|
|
3
|
+
export function printOriginalSource(node, options) {
|
|
4
|
+
if (
|
|
5
|
+
typeof options?.originalText === "string" &&
|
|
6
|
+
typeof node?.locStart === "number" &&
|
|
7
|
+
typeof node?.locEnd === "number" &&
|
|
8
|
+
node.locEnd >= node.locStart
|
|
9
|
+
) {
|
|
10
|
+
const source = options.originalText.slice(node.locStart, node.locEnd);
|
|
11
|
+
if (source.length > 0) return source;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return node?.wl ?? "";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function hasImmediateComment(node) {
|
|
18
|
+
if (!node || typeof node !== "object") return false;
|
|
19
|
+
if (isComment(node)) return true;
|
|
20
|
+
if (isComment(node.head)) return true;
|
|
21
|
+
return (node.children ?? []).some((child) => isComment(child));
|
|
22
|
+
}
|