@jpeakai/mermaidjs-lint 0.7.1 → 0.7.2
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/README.md +20 -21
- package/dist/bin.js +691 -578
- package/dist/index.js +691 -578
- package/dist/testing.js +471 -411
- package/dist/types/parse/contrast.d.ts +6 -1
- package/dist/types/parse/graph.d.ts +16 -0
- package/dist/types/rules/shared.d.ts +5 -1
- package/package.json +1 -1
- package/src/check.ts +13 -11
- package/src/cli.ts +153 -90
- package/src/files.ts +23 -18
- package/src/fix/engine.ts +82 -57
- package/src/fix/verify.ts +13 -8
- package/src/model.ts +1 -1
- package/src/parse/color.ts +1 -1
- package/src/parse/contrast.ts +308 -228
- package/src/parse/graph.ts +98 -0
- package/src/parse/metrics.ts +1 -1
- package/src/parse/stats.ts +111 -82
- package/src/plugins/discover.ts +13 -15
- package/src/plugins/index.ts +66 -37
- package/src/plugins/load.ts +94 -61
- package/src/rules/complexity.ts +1 -1
- package/src/rules/contrast-text-below-aa.ts +34 -21
- package/src/rules/parse-diagram-unparseable.ts +1 -1
- package/src/rules/parse-fence-unterminated.ts +5 -7
- package/src/rules/registry.ts +38 -16
- package/src/rules/shared.ts +8 -3
package/dist/bin.js
CHANGED
|
@@ -9,7 +9,7 @@ import { parseArgs } from "node:util";
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "@jpeakai/mermaidjs-lint",
|
|
12
|
-
version: "0.7.
|
|
12
|
+
version: "0.7.2",
|
|
13
13
|
type: "module",
|
|
14
14
|
description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
|
|
15
15
|
license: "MIT",
|
|
@@ -329,7 +329,7 @@ var parseProperties = (raw, rawStart, line) => {
|
|
|
329
329
|
const key = pair.slice(0, colon).trim().toLowerCase();
|
|
330
330
|
const valueRaw = pair.slice(colon + 1);
|
|
331
331
|
const value = valueRaw.trim();
|
|
332
|
-
if (!key
|
|
332
|
+
if (!(key && value))
|
|
333
333
|
continue;
|
|
334
334
|
properties[key] = value;
|
|
335
335
|
const lead = valueRaw.length - valueRaw.trimStart().length;
|
|
@@ -484,7 +484,7 @@ var parserReason = (err) => {
|
|
|
484
484
|
`);
|
|
485
485
|
if (message.startsWith("No diagram type detected"))
|
|
486
486
|
return (lines[0] ?? "").trim();
|
|
487
|
-
return lines.filter((line, i) => !POINTER.test(line.trim())
|
|
487
|
+
return lines.filter((line, i) => !(POINTER.test(line.trim()) || POINTER.test((lines[i + 1] ?? "").trim()))).join(" ").replace(/\s+/g, " ").trim();
|
|
488
488
|
};
|
|
489
489
|
async function langiumParseAny(type, content) {
|
|
490
490
|
switch (type) {
|
|
@@ -515,80 +515,7 @@ async function extractLangiumStats(content, keyword) {
|
|
|
515
515
|
if (!mapped)
|
|
516
516
|
throw new Error(`not a langium keyword: ${keyword}`);
|
|
517
517
|
const ast = await langiumParseAny(mapped, content);
|
|
518
|
-
const nodeIds = [];
|
|
519
|
-
const subgraphNames = [];
|
|
520
|
-
let edges = 0;
|
|
521
|
-
let maxDepth = 0;
|
|
522
|
-
let treeDepth = 0;
|
|
523
|
-
switch (mapped) {
|
|
524
|
-
case "architecture": {
|
|
525
|
-
const services = ast.services ?? [];
|
|
526
|
-
const groups = ast.groups ?? [];
|
|
527
|
-
const edgeArr = ast.edges ?? [];
|
|
528
|
-
nodeIds.push(...services.map((s) => s.id ?? ""));
|
|
529
|
-
subgraphNames.push(...groups.map((g) => g.title ?? g.id ?? ""));
|
|
530
|
-
maxDepth = architectureDepth(groups);
|
|
531
|
-
edges = edgeArr.length;
|
|
532
|
-
break;
|
|
533
|
-
}
|
|
534
|
-
case "pie": {
|
|
535
|
-
const sections = ast.sections ?? [];
|
|
536
|
-
nodeIds.push(...sections.map((s, i) => s.label ?? `slice_${i}`));
|
|
537
|
-
break;
|
|
538
|
-
}
|
|
539
|
-
case "packet": {
|
|
540
|
-
const blocks = ast.blocks ?? [];
|
|
541
|
-
nodeIds.push(...blocks.map((_, i) => `block_${i}`));
|
|
542
|
-
break;
|
|
543
|
-
}
|
|
544
|
-
case "gitGraph": {
|
|
545
|
-
const statements = ast.statements ?? [];
|
|
546
|
-
for (const s of statements) {
|
|
547
|
-
if (s.$type === "Commit")
|
|
548
|
-
nodeIds.push(s.id ?? `commit_${nodeIds.length}`);
|
|
549
|
-
else if (s.$type === "Branch")
|
|
550
|
-
subgraphNames.push(s.name ?? `branch_${subgraphNames.length}`);
|
|
551
|
-
else if (s.$type === "Merge")
|
|
552
|
-
edges++;
|
|
553
|
-
}
|
|
554
|
-
break;
|
|
555
|
-
}
|
|
556
|
-
case "radar": {
|
|
557
|
-
const axes = ast.axes ?? [];
|
|
558
|
-
const curves = ast.curves ?? [];
|
|
559
|
-
nodeIds.push(...axes.map((a, i) => a.name ?? `axis_${i}`));
|
|
560
|
-
nodeIds.push(...curves.map((c, i) => c.name ?? `curve_${i}`));
|
|
561
|
-
edges = axes.length * curves.length;
|
|
562
|
-
break;
|
|
563
|
-
}
|
|
564
|
-
case "treeView": {
|
|
565
|
-
const nodes = ast.nodes ?? [];
|
|
566
|
-
const stats = indentTreeStats(nodes.map((n, i) => ({ label: n.name ?? `node_${i}`, indent: n.indent ?? 0 })));
|
|
567
|
-
nodeIds.push(...stats.labels);
|
|
568
|
-
edges = stats.edges;
|
|
569
|
-
treeDepth = stats.depth;
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
case "treemap": {
|
|
573
|
-
const rows = ast.TreemapRows ?? [];
|
|
574
|
-
const stats = indentTreeStats(rows.map((r, i) => ({ label: r.item?.name ?? `row_${i}`, indent: r.indent ?? 0 })));
|
|
575
|
-
nodeIds.push(...stats.labels);
|
|
576
|
-
edges = stats.edges;
|
|
577
|
-
treeDepth = stats.depth;
|
|
578
|
-
break;
|
|
579
|
-
}
|
|
580
|
-
case "wardley": {
|
|
581
|
-
const components = ast.components ?? [];
|
|
582
|
-
const anchors = ast.anchors ?? [];
|
|
583
|
-
const links = ast.links ?? [];
|
|
584
|
-
nodeIds.push(...anchors.map((a, i) => a.name ?? `anchor_${i}`));
|
|
585
|
-
nodeIds.push(...components.map((c, i) => c.name ?? `comp_${i}`));
|
|
586
|
-
edges = links.length;
|
|
587
|
-
break;
|
|
588
|
-
}
|
|
589
|
-
case "info":
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
518
|
+
const { nodeIds, edges, subgraphNames, maxDepth, treeDepth = 0 } = LANGIUM_ADAPTERS[mapped](ast);
|
|
592
519
|
return {
|
|
593
520
|
nodes: new Set(nodeIds.filter(Boolean)).size,
|
|
594
521
|
edges,
|
|
@@ -602,6 +529,85 @@ async function extractLangiumStats(content, keyword) {
|
|
|
602
529
|
error: null
|
|
603
530
|
};
|
|
604
531
|
}
|
|
532
|
+
var LANGIUM_ADAPTERS = {
|
|
533
|
+
architecture: adaptLangiumArchitecture,
|
|
534
|
+
info: adaptLangiumInfo,
|
|
535
|
+
pie: adaptLangiumPie,
|
|
536
|
+
gitGraph: adaptLangiumGitGraph,
|
|
537
|
+
packet: adaptLangiumPacket,
|
|
538
|
+
radar: adaptLangiumRadar,
|
|
539
|
+
treemap: adaptLangiumTreemap,
|
|
540
|
+
treeView: adaptLangiumTreeView,
|
|
541
|
+
wardley: adaptLangiumWardley
|
|
542
|
+
};
|
|
543
|
+
function adaptLangiumArchitecture(ast) {
|
|
544
|
+
const services = ast.services ?? [];
|
|
545
|
+
const groups = ast.groups ?? [];
|
|
546
|
+
const edgeArr = ast.edges ?? [];
|
|
547
|
+
return {
|
|
548
|
+
nodeIds: services.map((s) => s.id ?? ""),
|
|
549
|
+
edges: edgeArr.length,
|
|
550
|
+
subgraphNames: groups.map((g) => g.title ?? g.id ?? ""),
|
|
551
|
+
maxDepth: architectureDepth(groups)
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function adaptLangiumInfo() {
|
|
555
|
+
return { nodeIds: [], edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
556
|
+
}
|
|
557
|
+
function adaptLangiumPie(ast) {
|
|
558
|
+
const sections = ast.sections ?? [];
|
|
559
|
+
return { nodeIds: sections.map((s, i) => s.label ?? `slice_${i}`), edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
560
|
+
}
|
|
561
|
+
function adaptLangiumPacket(ast) {
|
|
562
|
+
const blocks = ast.blocks ?? [];
|
|
563
|
+
return { nodeIds: blocks.map((_, i) => `block_${i}`), edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
564
|
+
}
|
|
565
|
+
function adaptLangiumGitGraph(ast) {
|
|
566
|
+
const statements = ast.statements ?? [];
|
|
567
|
+
const nodeIds = [];
|
|
568
|
+
const subgraphNames = [];
|
|
569
|
+
let edges = 0;
|
|
570
|
+
for (const s of statements) {
|
|
571
|
+
if (s.$type === "Commit")
|
|
572
|
+
nodeIds.push(s.id ?? `commit_${nodeIds.length}`);
|
|
573
|
+
else if (s.$type === "Branch")
|
|
574
|
+
subgraphNames.push(s.name ?? `branch_${subgraphNames.length}`);
|
|
575
|
+
else if (s.$type === "Merge")
|
|
576
|
+
edges++;
|
|
577
|
+
}
|
|
578
|
+
return { nodeIds, edges, subgraphNames, maxDepth: 0 };
|
|
579
|
+
}
|
|
580
|
+
function adaptLangiumRadar(ast) {
|
|
581
|
+
const axes = ast.axes ?? [];
|
|
582
|
+
const curves = ast.curves ?? [];
|
|
583
|
+
return {
|
|
584
|
+
nodeIds: [...axes.map((a, i) => a.name ?? `axis_${i}`), ...curves.map((c, i) => c.name ?? `curve_${i}`)],
|
|
585
|
+
edges: axes.length * curves.length,
|
|
586
|
+
subgraphNames: [],
|
|
587
|
+
maxDepth: 0
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function adaptLangiumTreeView(ast) {
|
|
591
|
+
const nodes = ast.nodes ?? [];
|
|
592
|
+
const stats = indentTreeStats(nodes.map((n, i) => ({ label: n.name ?? `node_${i}`, indent: n.indent ?? 0 })));
|
|
593
|
+
return { nodeIds: stats.labels, edges: stats.edges, subgraphNames: [], maxDepth: 0, treeDepth: stats.depth };
|
|
594
|
+
}
|
|
595
|
+
function adaptLangiumTreemap(ast) {
|
|
596
|
+
const rows = ast.TreemapRows ?? [];
|
|
597
|
+
const stats = indentTreeStats(rows.map((r, i) => ({ label: r.item?.name ?? `row_${i}`, indent: r.indent ?? 0 })));
|
|
598
|
+
return { nodeIds: stats.labels, edges: stats.edges, subgraphNames: [], maxDepth: 0, treeDepth: stats.depth };
|
|
599
|
+
}
|
|
600
|
+
function adaptLangiumWardley(ast) {
|
|
601
|
+
const components = ast.components ?? [];
|
|
602
|
+
const anchors = ast.anchors ?? [];
|
|
603
|
+
const links = ast.links ?? [];
|
|
604
|
+
return {
|
|
605
|
+
nodeIds: [...anchors.map((a, i) => a.name ?? `anchor_${i}`), ...components.map((c, i) => c.name ?? `comp_${i}`)],
|
|
606
|
+
edges: links.length,
|
|
607
|
+
subgraphNames: [],
|
|
608
|
+
maxDepth: 0
|
|
609
|
+
};
|
|
610
|
+
}
|
|
605
611
|
function architectureDepth(groups) {
|
|
606
612
|
const parentOf = new Map;
|
|
607
613
|
for (const g of groups)
|
|
@@ -1025,7 +1031,7 @@ var buildDocModel = (src, kind = "markdown") => {
|
|
|
1025
1031
|
collectMermaid(tree, fences);
|
|
1026
1032
|
const diagrams = fences.flatMap((node, index) => {
|
|
1027
1033
|
const { start, end } = node.position ?? {};
|
|
1028
|
-
if (!start
|
|
1034
|
+
if (!(start && end) || start.offset === undefined || end.offset === undefined)
|
|
1029
1035
|
return [];
|
|
1030
1036
|
return [
|
|
1031
1037
|
makeDiagram(src, lineStarts, {
|
|
@@ -1092,72 +1098,71 @@ var scorePair = (kind, d, fg, bg) => {
|
|
|
1092
1098
|
};
|
|
1093
1099
|
};
|
|
1094
1100
|
var message = (err) => err instanceof Error ? err.message : String(err);
|
|
1095
|
-
var
|
|
1101
|
+
var skip = (out, d, reason, blocking) => {
|
|
1102
|
+
out.skipped.push({ selector: d.selector, line: d.line, reason, ...blocking && { blocking } });
|
|
1103
|
+
};
|
|
1104
|
+
var scoreEdgeLabel = (d, canvas, out) => {
|
|
1096
1105
|
const { color } = d.properties;
|
|
1097
1106
|
if (!color)
|
|
1098
1107
|
return;
|
|
1099
1108
|
try {
|
|
1100
|
-
pairs.push(...THEMES.map((t) => scoreHostPair("text", d, t, color, edgeLabelBackground(t, canvas))));
|
|
1109
|
+
out.pairs.push(...THEMES.map((t) => scoreHostPair("text", d, t, { fg: color, bg: edgeLabelBackground(t, canvas) })));
|
|
1101
1110
|
} catch (err) {
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1111
|
+
skip(out, d, `edge label text unparseable: ${message(err)}`, true);
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
var githubBoxes = (fill, alpha, canvas) => alpha < 1 ? THEMES.map((t) => [compositeOver(fill, canvas[t]), t]) : [[fill, undefined]];
|
|
1115
|
+
var scoreGithubPair = (out, d, kind, { fg, bg, theme }) => {
|
|
1116
|
+
try {
|
|
1117
|
+
out.pairs.push({ ...scorePair(kind, d, fg, bg), ...theme && { theme } });
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
skip(out, d, `${kind} pair unparseable: ${message(err)}`, true);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
var fillAlpha = (out, d, fill) => {
|
|
1123
|
+
try {
|
|
1124
|
+
return alphaOf(fill);
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
skip(out, d, `fill unparseable: ${message(err)}`, true);
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
var scoreGithubStyle = (d, canvas, out) => {
|
|
1131
|
+
const { fill, color, stroke } = d.properties;
|
|
1132
|
+
if (!fill) {
|
|
1133
|
+
const reason = color || stroke ? "no fill declared, so text and border contrast have no anchor" : "no color properties";
|
|
1134
|
+
skip(out, d, reason);
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
const alpha = fillAlpha(out, d, fill);
|
|
1138
|
+
if (alpha === null)
|
|
1139
|
+
return;
|
|
1140
|
+
if (alpha === 0) {
|
|
1141
|
+
skip(out, d, "fill is fully transparent, so there is no box to anchor contrast");
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const boxes = githubBoxes(fill, alpha, canvas);
|
|
1145
|
+
if (!color) {
|
|
1146
|
+
skip(out, d, "no color: declared beside fill:, and the github profile requires one " + "(if the host forces label text, use the mkdocs-material profile)", true);
|
|
1147
|
+
}
|
|
1148
|
+
for (const [bg, theme] of boxes) {
|
|
1149
|
+
if (color)
|
|
1150
|
+
scoreGithubPair(out, d, "text", { fg: color, bg, theme });
|
|
1151
|
+
if (stroke)
|
|
1152
|
+
scoreGithubPair(out, d, "border", { fg: stroke, bg, theme });
|
|
1108
1153
|
}
|
|
1109
1154
|
};
|
|
1110
1155
|
var scoreGithub = (directives, canvas = GITHUB_CANVAS) => {
|
|
1111
|
-
const
|
|
1112
|
-
const skipped = [];
|
|
1156
|
+
const out = { pairs: [], skipped: [] };
|
|
1113
1157
|
for (const d of directives) {
|
|
1114
1158
|
if (d.kind === "linkStyle")
|
|
1115
|
-
scoreEdgeLabel(d, canvas,
|
|
1116
|
-
if (d.kind
|
|
1117
|
-
|
|
1118
|
-
const { fill, color, stroke } = d.properties;
|
|
1119
|
-
const note = (reason, blocking) => {
|
|
1120
|
-
skipped.push({ selector: d.selector, line: d.line, reason, ...blocking && { blocking } });
|
|
1121
|
-
};
|
|
1122
|
-
if (!fill) {
|
|
1123
|
-
note(color || stroke ? "no fill declared, so text and border contrast have no anchor" : "no color properties");
|
|
1124
|
-
continue;
|
|
1125
|
-
}
|
|
1126
|
-
let alpha;
|
|
1127
|
-
try {
|
|
1128
|
-
alpha = alphaOf(fill);
|
|
1129
|
-
} catch (err) {
|
|
1130
|
-
note(`fill unparseable: ${message(err)}`, true);
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
if (alpha === 0) {
|
|
1134
|
-
note("fill is fully transparent, so there is no box to anchor contrast");
|
|
1135
|
-
continue;
|
|
1136
|
-
}
|
|
1137
|
-
const boxes = alpha < 1 ? THEMES.map((t) => [compositeOver(fill, canvas[t]), t]) : [[fill, undefined]];
|
|
1138
|
-
if (!color) {
|
|
1139
|
-
note("no color: declared beside fill:, and the github profile requires one " + "(if the host forces label text, use the mkdocs-material profile)", true);
|
|
1140
|
-
}
|
|
1141
|
-
for (const [box, theme] of boxes) {
|
|
1142
|
-
if (color) {
|
|
1143
|
-
try {
|
|
1144
|
-
pairs.push({ ...scorePair("text", d, color, box), ...theme && { theme } });
|
|
1145
|
-
} catch (err) {
|
|
1146
|
-
note(`text pair unparseable: ${message(err)}`, true);
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
if (stroke) {
|
|
1150
|
-
try {
|
|
1151
|
-
pairs.push({ ...scorePair("border", d, stroke, box), ...theme && { theme } });
|
|
1152
|
-
} catch (err) {
|
|
1153
|
-
note(`border pair unparseable: ${message(err)}`, true);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1159
|
+
scoreEdgeLabel(d, canvas, out);
|
|
1160
|
+
if (d.kind === "classDef" || d.kind === "style")
|
|
1161
|
+
scoreGithubStyle(d, canvas, out);
|
|
1157
1162
|
}
|
|
1158
|
-
return
|
|
1163
|
+
return out;
|
|
1159
1164
|
};
|
|
1160
|
-
var scoreHostPair = (kind, d, theme, fgRaw, box) => {
|
|
1165
|
+
var scoreHostPair = (kind, d, theme, { fg: fgRaw, bg: box }) => {
|
|
1161
1166
|
const fg = compositeOver(fgRaw, box);
|
|
1162
1167
|
const assessment = wcagAssess(fg, box);
|
|
1163
1168
|
return {
|
|
@@ -1173,99 +1178,83 @@ var scoreHostPair = (kind, d, theme, fgRaw, box) => {
|
|
|
1173
1178
|
advisory: kind === "border"
|
|
1174
1179
|
};
|
|
1175
1180
|
};
|
|
1176
|
-
var
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
skipped.push({ selector: d.selector, line: d.line, reason });
|
|
1192
|
-
};
|
|
1193
|
-
if (!fill) {
|
|
1194
|
-
note("no fill declared, and the host theme controls the text");
|
|
1195
|
-
continue;
|
|
1181
|
+
var scoreMkdocsStyle = (d, out) => {
|
|
1182
|
+
const { fill, stroke, color } = d.properties;
|
|
1183
|
+
if (!fill) {
|
|
1184
|
+
skip(out, d, "no fill declared, and the host theme controls the text");
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (color)
|
|
1188
|
+
skip(out, d, "color: ignored, because the host theme forces label text");
|
|
1189
|
+
for (const theme of THEMES) {
|
|
1190
|
+
let box;
|
|
1191
|
+
try {
|
|
1192
|
+
box = compositeOver(fill, MKDOCS_MATERIAL[theme].bg);
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
skip(out, d, `fill unparseable: ${message(err)}`, true);
|
|
1195
|
+
break;
|
|
1196
1196
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
for (const theme of THEMES) {
|
|
1200
|
-
let box;
|
|
1197
|
+
out.pairs.push(scoreHostPair("text", d, theme, { fg: MKDOCS_MATERIAL[theme].text, bg: box }));
|
|
1198
|
+
if (stroke) {
|
|
1201
1199
|
try {
|
|
1202
|
-
|
|
1200
|
+
out.pairs.push(scoreHostPair("border", d, theme, { fg: stroke, bg: box }));
|
|
1203
1201
|
} catch (err) {
|
|
1204
|
-
|
|
1205
|
-
selector: d.selector,
|
|
1206
|
-
line: d.line,
|
|
1207
|
-
reason: `fill unparseable: ${message(err)}`,
|
|
1208
|
-
blocking: true
|
|
1209
|
-
});
|
|
1210
|
-
break;
|
|
1211
|
-
}
|
|
1212
|
-
pairs.push(scoreHostPair("text", d, theme, MKDOCS_MATERIAL[theme].text, box));
|
|
1213
|
-
if (stroke) {
|
|
1214
|
-
try {
|
|
1215
|
-
pairs.push(scoreHostPair("border", d, theme, stroke, box));
|
|
1216
|
-
} catch (err) {
|
|
1217
|
-
skipped.push({
|
|
1218
|
-
selector: d.selector,
|
|
1219
|
-
line: d.line,
|
|
1220
|
-
reason: `border pair unparseable: ${message(err)}`,
|
|
1221
|
-
blocking: true
|
|
1222
|
-
});
|
|
1223
|
-
}
|
|
1202
|
+
skip(out, d, `border pair unparseable: ${message(err)}`, true);
|
|
1224
1203
|
}
|
|
1225
1204
|
}
|
|
1226
1205
|
}
|
|
1227
|
-
|
|
1206
|
+
};
|
|
1207
|
+
var scoreMkdocs = (directives) => {
|
|
1208
|
+
const out = { pairs: [], skipped: [] };
|
|
1209
|
+
for (const d of directives) {
|
|
1210
|
+
if (d.kind === "linkStyle" && d.properties.color) {
|
|
1211
|
+
skip(out, d, "color: ignored, because the host theme forces edge label text");
|
|
1212
|
+
}
|
|
1213
|
+
if (d.kind === "classDef" || d.kind === "style")
|
|
1214
|
+
scoreMkdocsStyle(d, out);
|
|
1215
|
+
}
|
|
1216
|
+
return out;
|
|
1217
|
+
};
|
|
1218
|
+
var erRows = (theme, fill) => {
|
|
1219
|
+
const anchor = MERMAID_ER[theme];
|
|
1220
|
+
return [
|
|
1221
|
+
["odd", anchor.odd],
|
|
1222
|
+
["even", fill ? compositeOver(fill, anchor.even) : anchor.even]
|
|
1223
|
+
];
|
|
1224
|
+
};
|
|
1225
|
+
var erPairs = (d) => {
|
|
1226
|
+
const { fill, color, stroke } = d.properties;
|
|
1227
|
+
const scored = [];
|
|
1228
|
+
const score = (kind, theme, row, colours) => {
|
|
1229
|
+
scored.push({ ...scoreHostPair(kind, d, theme, colours), row, advisory: kind === "border" });
|
|
1230
|
+
};
|
|
1231
|
+
for (const theme of THEMES) {
|
|
1232
|
+
for (const [row, bg] of erRows(theme, fill)) {
|
|
1233
|
+
if (color || row === "even" && fill)
|
|
1234
|
+
score("text", theme, row, { fg: color ?? MERMAID_ER[theme].label, bg });
|
|
1235
|
+
if (stroke)
|
|
1236
|
+
score("border", theme, row, { fg: stroke, bg });
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return scored;
|
|
1228
1240
|
};
|
|
1229
1241
|
var scoreEr = (directives) => {
|
|
1230
|
-
const
|
|
1231
|
-
const skipped = [];
|
|
1242
|
+
const out = { pairs: [], skipped: [] };
|
|
1232
1243
|
for (const d of directives) {
|
|
1233
1244
|
if (d.kind !== "classDef" && d.kind !== "style")
|
|
1234
1245
|
continue;
|
|
1235
1246
|
const { fill, color, stroke } = d.properties;
|
|
1236
|
-
if (!fill
|
|
1237
|
-
|
|
1247
|
+
if (!(fill || color || stroke)) {
|
|
1248
|
+
skip(out, d, "no color properties");
|
|
1238
1249
|
continue;
|
|
1239
1250
|
}
|
|
1240
|
-
const scored = [];
|
|
1241
|
-
const score = (kind, theme, row, fg, bg) => {
|
|
1242
|
-
scored.push({ ...scoreHostPair(kind, d, theme, fg, bg), row, advisory: kind === "border" });
|
|
1243
|
-
};
|
|
1244
1251
|
try {
|
|
1245
|
-
|
|
1246
|
-
const anchor = MERMAID_ER[theme];
|
|
1247
|
-
const rows = [
|
|
1248
|
-
["odd", anchor.odd],
|
|
1249
|
-
["even", fill ? compositeOver(fill, anchor.even) : anchor.even]
|
|
1250
|
-
];
|
|
1251
|
-
for (const [row, bg] of rows) {
|
|
1252
|
-
if (color || row === "even" && fill)
|
|
1253
|
-
score("text", theme, row, color ?? anchor.label, bg);
|
|
1254
|
-
if (stroke)
|
|
1255
|
-
score("border", theme, row, stroke, bg);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
pairs.push(...scored);
|
|
1252
|
+
out.pairs.push(...erPairs(d));
|
|
1259
1253
|
} catch (err) {
|
|
1260
|
-
|
|
1261
|
-
selector: d.selector,
|
|
1262
|
-
line: d.line,
|
|
1263
|
-
reason: `erDiagram pair unparseable: ${message(err)}`,
|
|
1264
|
-
blocking: true
|
|
1265
|
-
});
|
|
1254
|
+
skip(out, d, `erDiagram pair unparseable: ${message(err)}`, true);
|
|
1266
1255
|
}
|
|
1267
1256
|
}
|
|
1268
|
-
return
|
|
1257
|
+
return out;
|
|
1269
1258
|
};
|
|
1270
1259
|
var scoreForProfile = (directives, profile, keyword, canvas = GITHUB_CANVAS) => {
|
|
1271
1260
|
if (profile === "mkdocs-material")
|
|
@@ -1307,9 +1296,7 @@ var themeVariable = (selector, line) => ({
|
|
|
1307
1296
|
spans: {},
|
|
1308
1297
|
line
|
|
1309
1298
|
});
|
|
1310
|
-
var
|
|
1311
|
-
const pairs = [];
|
|
1312
|
-
const skipped = [];
|
|
1299
|
+
var sequenceLines = (source) => {
|
|
1313
1300
|
const lines = source.split(`
|
|
1314
1301
|
`);
|
|
1315
1302
|
const keywordLine = lines.findIndex((l) => /^\s*sequenceDiagram\b/.test(l)) + 1;
|
|
@@ -1317,94 +1304,125 @@ var scoreSequence = async (source, profile, canvas = GITHUB_CANVAS) => {
|
|
|
1317
1304
|
const found = lines.findLastIndex((l) => new RegExp(`\\b${key}["']?\\s*:`).test(l));
|
|
1318
1305
|
return found < 0 ? keywordLine : found + 1;
|
|
1319
1306
|
};
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
let db;
|
|
1307
|
+
return { lineOf, rectLines: lines.flatMap((l, i) => /^\s*rect\s/.test(l) ? [i + 1] : []) };
|
|
1308
|
+
};
|
|
1309
|
+
var parseSequence = async (mermaid, source) => {
|
|
1324
1310
|
try {
|
|
1325
|
-
config =
|
|
1326
|
-
db =
|
|
1311
|
+
const { config } = await mermaid.parse(source);
|
|
1312
|
+
const { db } = await mermaid.mermaidAPI.getDiagramFromText(source);
|
|
1313
|
+
return { config, db };
|
|
1327
1314
|
} catch {
|
|
1328
|
-
return
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
var unmodelled = (config) => {
|
|
1319
|
+
if (config.themeCSS !== undefined) {
|
|
1320
|
+
return { selector: "themeCSS", reason: "themeCSS restyles the diagram with CSS the audit cannot model" };
|
|
1329
1321
|
}
|
|
1330
|
-
const block = (selector, reason) => {
|
|
1331
|
-
skipped.push({ selector, line: lineOf(selector), reason, blocking: true });
|
|
1332
|
-
return { pairs, skipped };
|
|
1333
|
-
};
|
|
1334
|
-
if (config.themeCSS !== undefined)
|
|
1335
|
-
return block("themeCSS", "themeCSS restyles the diagram with CSS the audit cannot model");
|
|
1336
1322
|
const pinned = config.theme;
|
|
1337
1323
|
if (pinned !== undefined && !SEQUENCE_THEMES.includes(String(pinned))) {
|
|
1338
|
-
return
|
|
1324
|
+
return {
|
|
1325
|
+
selector: "theme",
|
|
1326
|
+
reason: `the ${String(pinned)} theme is not modelled, only ${SEQUENCE_THEMES.join(", ")}`
|
|
1327
|
+
};
|
|
1339
1328
|
}
|
|
1329
|
+
return null;
|
|
1330
|
+
};
|
|
1331
|
+
var sequenceThemes = (pinned, host) => {
|
|
1332
|
+
const theme = (fallback) => pinned === undefined ? fallback : String(pinned);
|
|
1333
|
+
return { light: theme("default"), dark: theme(host ? "default" : "dark") };
|
|
1334
|
+
};
|
|
1335
|
+
var sequencePair = ({ theme: t, page, vars, host, lineOf, out }) => (kind, fgKey, bg, { suffix = "", at } = {}) => {
|
|
1336
|
+
const color = (key) => {
|
|
1337
|
+
const value = vars[key];
|
|
1338
|
+
if (typeof value !== "string")
|
|
1339
|
+
throw new Error(`${key} is not a resolved colour`);
|
|
1340
|
+
return value;
|
|
1341
|
+
};
|
|
1342
|
+
const selector = fgKey + suffix;
|
|
1343
|
+
const line = at ?? lineOf(fgKey);
|
|
1344
|
+
try {
|
|
1345
|
+
const box = compositeOver("key" in bg ? color(bg.key) : bg.colour, page);
|
|
1346
|
+
out.pairs.push({
|
|
1347
|
+
...scoreHostPair(kind, themeVariable(selector, line), t, { fg: color(fgKey), bg: box }),
|
|
1348
|
+
advisory: host
|
|
1349
|
+
});
|
|
1350
|
+
} catch (err) {
|
|
1351
|
+
out.skipped.push({ selector, line, reason: `${t} sequence pair unparseable: ${message(err)}`, blocking: true });
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
var scoreHostSequence = (pair, page, features) => {
|
|
1355
|
+
const onPage = { colour: page };
|
|
1356
|
+
pair("border", "actorLineColor", onPage);
|
|
1357
|
+
pair("border", "signalColor", onPage, { suffix: " (arrowhead)" });
|
|
1358
|
+
if (features.activation)
|
|
1359
|
+
pair("border", "activationBorderColor", { key: "activationBkgColor" });
|
|
1360
|
+
};
|
|
1361
|
+
var scoreAuthorSequence = (pair, page, features, rectLines) => {
|
|
1362
|
+
const onPage = { colour: page };
|
|
1363
|
+
pair("text", "actorTextColor", { key: "actorBkg" });
|
|
1364
|
+
pair("border", "actorBorder", { key: "actorBkg" });
|
|
1365
|
+
if (features.humanActor) {
|
|
1366
|
+
pair("text", "actorTextColor", onPage, { suffix: " (human actor label)" });
|
|
1367
|
+
pair("border", "actorBorder", onPage, { suffix: " (human actor glyph)" });
|
|
1368
|
+
}
|
|
1369
|
+
pair("border", "actorLineColor", onPage);
|
|
1370
|
+
pair("text", "signalTextColor", onPage);
|
|
1371
|
+
pair("border", "signalColor", onPage);
|
|
1372
|
+
if (features.note)
|
|
1373
|
+
pair("text", "noteTextColor", { key: "noteBkgColor" });
|
|
1374
|
+
if (features.frame) {
|
|
1375
|
+
pair("text", "labelTextColor", { key: "labelBoxBkgColor" });
|
|
1376
|
+
pair("text", "loopTextColor", onPage);
|
|
1377
|
+
pair("border", "labelBoxBorderColor", onPage, { suffix: " (loop frame)" });
|
|
1378
|
+
}
|
|
1379
|
+
if (features.activation)
|
|
1380
|
+
pair("border", "activationBorderColor", { key: "activationBkgColor" });
|
|
1381
|
+
if (features.numbered)
|
|
1382
|
+
pair("text", "sequenceNumberColor", { key: "signalColor" });
|
|
1383
|
+
for (const [i, rect] of features.rects.entries()) {
|
|
1384
|
+
const fill = { colour: rect };
|
|
1385
|
+
const where = { suffix: " (rect)", at: rectLines[i] };
|
|
1386
|
+
pair("text", "signalTextColor", fill, where);
|
|
1387
|
+
pair("border", "signalColor", fill, where);
|
|
1388
|
+
if (features.frame)
|
|
1389
|
+
pair("text", "loopTextColor", fill, where);
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
var scoreSequence = async (source, profile, canvas = GITHUB_CANVAS) => {
|
|
1393
|
+
const out = { pairs: [], skipped: [] };
|
|
1394
|
+
const { lineOf, rectLines } = sequenceLines(source);
|
|
1395
|
+
const mermaid = await getMermaidCore();
|
|
1396
|
+
const parsed = await parseSequence(mermaid, source);
|
|
1397
|
+
if (!parsed)
|
|
1398
|
+
return out;
|
|
1399
|
+
const { config, db } = parsed;
|
|
1400
|
+
const block = (selector, reason) => {
|
|
1401
|
+
out.skipped.push({ selector, line: lineOf(selector), reason, blocking: true });
|
|
1402
|
+
return out;
|
|
1403
|
+
};
|
|
1404
|
+
const problem = unmodelled(config);
|
|
1405
|
+
if (problem)
|
|
1406
|
+
return block(problem.selector, problem.reason);
|
|
1340
1407
|
const features = sequenceFeatures(db);
|
|
1341
1408
|
const host = profile === "mkdocs-material";
|
|
1342
|
-
const theme = (fallback) => pinned === undefined ? fallback : String(pinned);
|
|
1343
1409
|
let variables;
|
|
1344
1410
|
try {
|
|
1345
|
-
variables = resolveVariables(mermaid, config,
|
|
1411
|
+
variables = resolveVariables(mermaid, config, sequenceThemes(config.theme, host));
|
|
1346
1412
|
} catch (err) {
|
|
1347
1413
|
return block("themeVariables", `Mermaid could not resolve the theme: ${message(err)}`);
|
|
1348
1414
|
}
|
|
1349
1415
|
for (const t of THEMES) {
|
|
1350
1416
|
const page = host ? MKDOCS_MATERIAL[t].bg : canvas[t];
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
return value;
|
|
1357
|
-
};
|
|
1358
|
-
const pair = (kind, fgKey, bg, suffix = "", at) => {
|
|
1359
|
-
const selector = fgKey + suffix;
|
|
1360
|
-
const line = at ?? lineOf(fgKey);
|
|
1361
|
-
try {
|
|
1362
|
-
const box = compositeOver("key" in bg ? color(bg.key) : bg.colour, page);
|
|
1363
|
-
pairs.push({ ...scoreHostPair(kind, themeVariable(selector, line), t, color(fgKey), box), advisory: host });
|
|
1364
|
-
} catch (err) {
|
|
1365
|
-
skipped.push({ selector, line, reason: `${t} sequence pair unparseable: ${message(err)}`, blocking: true });
|
|
1366
|
-
}
|
|
1367
|
-
};
|
|
1368
|
-
const onPage = { colour: page };
|
|
1369
|
-
if (host) {
|
|
1370
|
-
pair("border", "actorLineColor", onPage);
|
|
1371
|
-
pair("border", "signalColor", onPage, " (arrowhead)");
|
|
1372
|
-
if (features.activation)
|
|
1373
|
-
pair("border", "activationBorderColor", { key: "activationBkgColor" });
|
|
1374
|
-
continue;
|
|
1375
|
-
}
|
|
1376
|
-
pair("text", "actorTextColor", { key: "actorBkg" });
|
|
1377
|
-
pair("border", "actorBorder", { key: "actorBkg" });
|
|
1378
|
-
if (features.humanActor) {
|
|
1379
|
-
pair("text", "actorTextColor", onPage, " (human actor label)");
|
|
1380
|
-
pair("border", "actorBorder", onPage, " (human actor glyph)");
|
|
1381
|
-
}
|
|
1382
|
-
pair("border", "actorLineColor", onPage);
|
|
1383
|
-
pair("text", "signalTextColor", onPage);
|
|
1384
|
-
pair("border", "signalColor", onPage);
|
|
1385
|
-
if (features.note)
|
|
1386
|
-
pair("text", "noteTextColor", { key: "noteBkgColor" });
|
|
1387
|
-
if (features.frame) {
|
|
1388
|
-
pair("text", "labelTextColor", { key: "labelBoxBkgColor" });
|
|
1389
|
-
pair("text", "loopTextColor", onPage);
|
|
1390
|
-
pair("border", "labelBoxBorderColor", onPage, " (loop frame)");
|
|
1391
|
-
}
|
|
1392
|
-
if (features.activation)
|
|
1393
|
-
pair("border", "activationBorderColor", { key: "activationBkgColor" });
|
|
1394
|
-
if (features.numbered)
|
|
1395
|
-
pair("text", "sequenceNumberColor", { key: "signalColor" });
|
|
1396
|
-
for (const [i, rect] of features.rects.entries()) {
|
|
1397
|
-
const fill = { colour: rect };
|
|
1398
|
-
const at = rectLines[i];
|
|
1399
|
-
pair("text", "signalTextColor", fill, " (rect)", at);
|
|
1400
|
-
pair("border", "signalColor", fill, " (rect)", at);
|
|
1401
|
-
if (features.frame)
|
|
1402
|
-
pair("text", "loopTextColor", fill, " (rect)", at);
|
|
1403
|
-
}
|
|
1417
|
+
const pair = sequencePair({ theme: t, page, vars: variables[t], host, lineOf, out });
|
|
1418
|
+
if (host)
|
|
1419
|
+
scoreHostSequence(pair, page, features);
|
|
1420
|
+
else
|
|
1421
|
+
scoreAuthorSequence(pair, page, features, rectLines);
|
|
1404
1422
|
}
|
|
1405
|
-
return
|
|
1423
|
+
return out;
|
|
1406
1424
|
};
|
|
1407
|
-
var scoreDiagram = (source, directives,
|
|
1425
|
+
var scoreDiagram = ({ source, directives, keyword }, profile, canvas = GITHUB_CANVAS) => keyword === "sequenceDiagram" ? scoreSequence(source, profile, canvas) : Promise.resolve(scoreForProfile(directives, profile, keyword, canvas));
|
|
1408
1426
|
var detectProfile = (filePath) => {
|
|
1409
1427
|
let dir = dirname(resolve(filePath));
|
|
1410
1428
|
for (let i = 0;i < 20; i++) {
|
|
@@ -1496,7 +1514,7 @@ var canonicalPreset = (name) => {
|
|
|
1496
1514
|
var configFromPreset = (name) => {
|
|
1497
1515
|
const canonical = canonicalPreset(name);
|
|
1498
1516
|
const preset = canonical ? PRESETS[canonical] : undefined;
|
|
1499
|
-
if (!canonical
|
|
1517
|
+
if (!(canonical && preset)) {
|
|
1500
1518
|
throw new Error(`Unknown preset '${name}'. Valid: ${PRESET_NAMES.join(", ")}`);
|
|
1501
1519
|
}
|
|
1502
1520
|
return {
|
|
@@ -1575,7 +1593,7 @@ var measure = async (diagram, config) => {
|
|
|
1575
1593
|
return null;
|
|
1576
1594
|
return { stats, metrics: calculateComplexity(stats, config) };
|
|
1577
1595
|
};
|
|
1578
|
-
var finding = (ctx, diagram, rule, message2, remediation) => ({
|
|
1596
|
+
var finding = (ctx, diagram, rule, { message: message2, remediation }) => ({
|
|
1579
1597
|
file: ctx.file,
|
|
1580
1598
|
line: diagram.startLine,
|
|
1581
1599
|
endLine: diagram.endLine,
|
|
@@ -1626,7 +1644,7 @@ var contrastOf = (diagram, settings) => {
|
|
|
1626
1644
|
}
|
|
1627
1645
|
let result = bySettings.get(key);
|
|
1628
1646
|
if (!result) {
|
|
1629
|
-
result = scoreDiagram(diagram
|
|
1647
|
+
result = scoreDiagram(diagram, profile, canvas);
|
|
1630
1648
|
bySettings.set(key, result);
|
|
1631
1649
|
}
|
|
1632
1650
|
return result;
|
|
@@ -1648,7 +1666,7 @@ var complexityRule = (spec) => ({
|
|
|
1648
1666
|
continue;
|
|
1649
1667
|
const hit = spec.hit(measured, config);
|
|
1650
1668
|
if (hit)
|
|
1651
|
-
found.push(finding(ctx, diagram, spec.id, hit
|
|
1669
|
+
found.push(finding(ctx, diagram, spec.id, hit));
|
|
1652
1670
|
}
|
|
1653
1671
|
return found;
|
|
1654
1672
|
}
|
|
@@ -1793,6 +1811,30 @@ var contrastDirectiveUnauditable = {
|
|
|
1793
1811
|
var AA_NORMAL2 = 4.5;
|
|
1794
1812
|
var CANDIDATES = ["#000000", "#ffffff"];
|
|
1795
1813
|
var ratioOn = (fg, bg) => wcagRatio(compositeOver(fg, bg), bg);
|
|
1814
|
+
var remediationOf = (profile, kind) => {
|
|
1815
|
+
if (profile === "mkdocs-material") {
|
|
1816
|
+
return "The host theme forces the label text, so change the fill until the host text reads on it in both themes.";
|
|
1817
|
+
}
|
|
1818
|
+
if (kind === "themeVariables") {
|
|
1819
|
+
return "A sequence diagram colour is a theme variable, and text with no box of its own sits on the page, so set the variable to a colour that reads on its background in both themes, or remove it and let the theme set it.";
|
|
1820
|
+
}
|
|
1821
|
+
if (kind === "linkStyle") {
|
|
1822
|
+
return "An edge label sits on Mermaid's own label box, light in one theme and grey in the other, and no one colour reads on both, so drop color: from the linkStyle and let the theme set the label text.";
|
|
1823
|
+
}
|
|
1824
|
+
return "Darken or lighten the fill, or change color:, until the ratio reaches 4.5:1 in every theme.";
|
|
1825
|
+
};
|
|
1826
|
+
var textFinding = (ctx, diagram, p, profile) => {
|
|
1827
|
+
const where = [p.theme, p.row].filter(Boolean).join("/");
|
|
1828
|
+
const line = fileLine(diagram, p.line);
|
|
1829
|
+
return {
|
|
1830
|
+
file: ctx.file,
|
|
1831
|
+
line,
|
|
1832
|
+
endLine: line,
|
|
1833
|
+
rule: RULE.TEXT,
|
|
1834
|
+
message: `${p.directive.kind} ${p.selector}: text ${p.assessment.ratio.toFixed(2)}:1 < ${AA_NORMAL2} ` + `(${p.foreground} on ${p.background}${where ? `, ${where}` : ""})`,
|
|
1835
|
+
remediation: remediationOf(profile, p.directive.kind)
|
|
1836
|
+
};
|
|
1837
|
+
};
|
|
1796
1838
|
var contrastTextBelowAa = {
|
|
1797
1839
|
id: RULE.TEXT,
|
|
1798
1840
|
category: "contrast",
|
|
@@ -1803,18 +1845,8 @@ var contrastTextBelowAa = {
|
|
|
1803
1845
|
const found = [];
|
|
1804
1846
|
for (const diagram of ctx.doc.diagrams) {
|
|
1805
1847
|
for (const p of (await contrastOf(diagram, settings)).pairs) {
|
|
1806
|
-
if (p.kind
|
|
1807
|
-
|
|
1808
|
-
const where = [p.theme, p.row].filter(Boolean).join("/");
|
|
1809
|
-
const line = fileLine(diagram, p.line);
|
|
1810
|
-
found.push({
|
|
1811
|
-
file: ctx.file,
|
|
1812
|
-
line,
|
|
1813
|
-
endLine: line,
|
|
1814
|
-
rule: RULE.TEXT,
|
|
1815
|
-
message: `${p.directive.kind} ${p.selector}: text ${p.assessment.ratio.toFixed(2)}:1 < ${AA_NORMAL2} ` + `(${p.foreground} on ${p.background}${where ? `, ${where}` : ""})`,
|
|
1816
|
-
remediation: settings.profile === "mkdocs-material" ? "The host theme forces the label text, so change the fill until the host text reads on it in both themes." : p.directive.kind === "themeVariables" ? "A sequence diagram colour is a theme variable, and text with no box of its own sits on the page, so set the variable to a colour that reads on its background in both themes, or remove it and let the theme set it." : p.directive.kind === "linkStyle" ? "An edge label sits on Mermaid's own label box, light in one theme and grey in the other, and no one colour reads on both, so drop color: from the linkStyle and let the theme set the label text." : "Darken or lighten the fill, or change color:, until the ratio reaches 4.5:1 in every theme."
|
|
1817
|
-
});
|
|
1848
|
+
if (p.kind === "text" && !p.passes)
|
|
1849
|
+
found.push(textFinding(ctx, diagram, p, settings.profile));
|
|
1818
1850
|
}
|
|
1819
1851
|
}
|
|
1820
1852
|
return found;
|
|
@@ -1873,7 +1905,7 @@ var parseDiagramUnparseable = {
|
|
|
1873
1905
|
if (!parseFailed(diagram.source, stats))
|
|
1874
1906
|
continue;
|
|
1875
1907
|
const remediation = EXTERNAL_TYPES.has(stats.diagram_type) ? `No fixer can help: mermaidjs-lint has no ${stats.diagram_type} parser. Redraw it as a type mermaid core includes, such as sequenceDiagram, or turn this rule off in the config.` : "Verify the diagram renders in mermaid.js and fix its syntax. Complexity rules are skipped for this diagram while it cannot be parsed.";
|
|
1876
|
-
found.push(finding(ctx, diagram, RULE.UNPARSEABLE, messageOf(stats), remediation));
|
|
1908
|
+
found.push(finding(ctx, diagram, RULE.UNPARSEABLE, { message: messageOf(stats), remediation }));
|
|
1877
1909
|
}
|
|
1878
1910
|
return found;
|
|
1879
1911
|
}
|
|
@@ -1885,7 +1917,10 @@ var parseFenceUnterminated = {
|
|
|
1885
1917
|
id: RULE.FENCE_UNTERMINATED,
|
|
1886
1918
|
category: "parse",
|
|
1887
1919
|
summary: "a mermaid fence that is never closed",
|
|
1888
|
-
check: (ctx) => ctx.doc.diagrams.filter((d) => !d.terminated).map((d) => finding(ctx, d, RULE.FENCE_UNTERMINATED,
|
|
1920
|
+
check: (ctx) => ctx.doc.diagrams.filter((d) => !d.terminated).map((d) => finding(ctx, d, RULE.FENCE_UNTERMINATED, {
|
|
1921
|
+
message: `mermaid fence opened at line ${d.startLine} is never closed`,
|
|
1922
|
+
remediation: "Add a closing fence directly after the last line of the diagram, with the opening line's prefix: `> ` in a quote, the item's indent in a list."
|
|
1923
|
+
})),
|
|
1889
1924
|
fix: (ctx) => {
|
|
1890
1925
|
const edits = [];
|
|
1891
1926
|
const { src } = ctx.doc;
|
|
@@ -1982,20 +2017,20 @@ var checkCanvas = (where, rule, name, value) => {
|
|
|
1982
2017
|
}
|
|
1983
2018
|
}
|
|
1984
2019
|
};
|
|
1985
|
-
var
|
|
1986
|
-
const
|
|
1987
|
-
|
|
1988
|
-
const
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
throw new RuleOptionError(where, rule.id, name, `rule "${rule.id}" has no option "${name}"; ${list}`);
|
|
1992
|
-
}
|
|
1993
|
-
if (typeOf(value) !== want) {
|
|
1994
|
-
throw new RuleOptionError(where, rule.id, name, `option "${name}" of "${rule.id}" must be ${describeType(want)}`);
|
|
1995
|
-
}
|
|
1996
|
-
if (want === "canvas")
|
|
1997
|
-
checkCanvas(where, rule, name, value);
|
|
2020
|
+
var checkOption = (where, rule, name, value) => {
|
|
2021
|
+
const want = rule.options?.[name];
|
|
2022
|
+
if (!want) {
|
|
2023
|
+
const accepted = Object.keys(rule.options ?? {});
|
|
2024
|
+
const list = accepted.length > 0 ? `it accepts ${accepted.join(", ")}` : "it takes no options";
|
|
2025
|
+
throw new RuleOptionError(where, rule.id, name, `rule "${rule.id}" has no option "${name}"; ${list}`);
|
|
1998
2026
|
}
|
|
2027
|
+
if (typeOf(value) !== want) {
|
|
2028
|
+
throw new RuleOptionError(where, rule.id, name, `option "${name}" of "${rule.id}" must be ${describeType(want)}`);
|
|
2029
|
+
}
|
|
2030
|
+
if (want === "canvas")
|
|
2031
|
+
checkCanvas(where, rule, name, value);
|
|
2032
|
+
};
|
|
2033
|
+
var checkSharedOptions = (where, rule, options) => {
|
|
1999
2034
|
if (options.profile === "mkdocs-material" && options.canvas !== undefined && rule.options?.canvas === "canvas") {
|
|
2000
2035
|
throw new RuleOptionError(where, rule.id, "canvas", `option "canvas" of "${rule.id}" applies under the github profile only, and this rule sets profile mkdocs-material, whose page is Material's own`);
|
|
2001
2036
|
}
|
|
@@ -2006,10 +2041,12 @@ var checkOptions = (where, rule, options) => {
|
|
|
2006
2041
|
throw new RuleOptionError(where, rule.id, "profile", `option "profile" of "${rule.id}" must be one of ${PROFILES.join(", ")}`);
|
|
2007
2042
|
}
|
|
2008
2043
|
};
|
|
2009
|
-
var
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2012
|
-
|
|
2044
|
+
var checkOptions = (where, rule, options) => {
|
|
2045
|
+
for (const [name, value] of Object.entries(options))
|
|
2046
|
+
checkOption(where, rule, name, value);
|
|
2047
|
+
checkSharedOptions(where, rule, options);
|
|
2048
|
+
};
|
|
2049
|
+
var applyConfig = (rules, config, ignoreUnknownPluginIds) => {
|
|
2013
2050
|
const off = new Set;
|
|
2014
2051
|
const options = new Map;
|
|
2015
2052
|
const ignored = [];
|
|
@@ -2028,6 +2065,13 @@ var buildRegistry = (input) => {
|
|
|
2028
2065
|
if (setting.severity === "off")
|
|
2029
2066
|
off.add(id);
|
|
2030
2067
|
}
|
|
2068
|
+
return { off, options, ignored };
|
|
2069
|
+
};
|
|
2070
|
+
var buildRegistry = (input) => {
|
|
2071
|
+
const { rules, fixOrder, categories, sources = [], config, ignoreUnknownPluginIds = false } = input;
|
|
2072
|
+
const selection = input.selection ?? {};
|
|
2073
|
+
checkSelection(selection, categories);
|
|
2074
|
+
const { off, options, ignored } = applyConfig(rules, config, ignoreUnknownPluginIds);
|
|
2031
2075
|
for (const rule of rules) {
|
|
2032
2076
|
if (selection.enable?.includes(rule.category))
|
|
2033
2077
|
off.delete(rule.id);
|
|
@@ -2080,6 +2124,17 @@ var checkRunFlags = (flags, prefix = "") => {
|
|
|
2080
2124
|
var canvasOptions = (rule) => Object.entries(rule.options ?? {}).filter(([, type]) => type === "canvas").map(([name]) => name);
|
|
2081
2125
|
var hasFlagCanvas = (flags) => THEMES.some((side) => flags.canvas?.[side] !== undefined);
|
|
2082
2126
|
var setsAside = (rule, options, flags, file) => !hasFlagCanvas(flags) && rule.options?.profile !== undefined && (options.profile ?? "auto") === "auto" && canvasOptions(rule).some((name) => options[name] !== undefined) && detectProfile(file) === "mkdocs-material";
|
|
2127
|
+
var mergeCanvas = (current, canvas) => {
|
|
2128
|
+
const merged = {
|
|
2129
|
+
...typeof current === "object" && current !== null ? current : {}
|
|
2130
|
+
};
|
|
2131
|
+
for (const side of THEMES) {
|
|
2132
|
+
const value = canvas[side];
|
|
2133
|
+
if (value !== undefined)
|
|
2134
|
+
merged[side] = value;
|
|
2135
|
+
}
|
|
2136
|
+
return merged;
|
|
2137
|
+
};
|
|
2083
2138
|
var optionsFor = (rule, configured, flags, file) => {
|
|
2084
2139
|
const options = { ...configured };
|
|
2085
2140
|
for (const name of ["preset", "profile"]) {
|
|
@@ -2091,18 +2146,8 @@ var optionsFor = (rule, configured, flags, file) => {
|
|
|
2091
2146
|
options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
2092
2147
|
const canvas = flags.canvas;
|
|
2093
2148
|
if (canvas && hasFlagCanvas(flags)) {
|
|
2094
|
-
for (const name of canvasOptions(rule))
|
|
2095
|
-
|
|
2096
|
-
const merged = {
|
|
2097
|
-
...typeof current === "object" && current !== null ? current : {}
|
|
2098
|
-
};
|
|
2099
|
-
for (const side of THEMES) {
|
|
2100
|
-
const value = canvas[side];
|
|
2101
|
-
if (value !== undefined)
|
|
2102
|
-
merged[side] = value;
|
|
2103
|
-
}
|
|
2104
|
-
options[name] = merged;
|
|
2105
|
-
}
|
|
2149
|
+
for (const name of canvasOptions(rule))
|
|
2150
|
+
options[name] = mergeCanvas(options[name], canvas);
|
|
2106
2151
|
}
|
|
2107
2152
|
if (file !== undefined && setsAside(rule, options, flags, file)) {
|
|
2108
2153
|
for (const name of canvasOptions(rule))
|
|
@@ -2127,6 +2172,7 @@ import { extname, join as join2, resolve as resolve2 } from "node:path";
|
|
|
2127
2172
|
var DIAGRAM_FILE_EXTS = new Set([...DIAGRAM_EXTS, ".md", ".markdown"]);
|
|
2128
2173
|
var SKIP_DIRS = new Set(["node_modules", "vendor", "dist", "build", "tmp", "__pycache__"]);
|
|
2129
2174
|
var skipDir = (name) => name.startsWith(".") || SKIP_DIRS.has(name);
|
|
2175
|
+
var isDiagramFile = (name) => DIAGRAM_FILE_EXTS.has(extname(name).toLowerCase());
|
|
2130
2176
|
var linksToDir = (path) => {
|
|
2131
2177
|
try {
|
|
2132
2178
|
return statSync(path).isDirectory();
|
|
@@ -2139,6 +2185,24 @@ var collectFiles = (paths) => {
|
|
|
2139
2185
|
const unreadable = [];
|
|
2140
2186
|
const symlinks = [];
|
|
2141
2187
|
const seen = new Set;
|
|
2188
|
+
const add = (abs) => {
|
|
2189
|
+
if (seen.has(abs))
|
|
2190
|
+
return;
|
|
2191
|
+
seen.add(abs);
|
|
2192
|
+
files.push(abs);
|
|
2193
|
+
};
|
|
2194
|
+
const visit = (dir, entry) => {
|
|
2195
|
+
const abs = join2(dir, entry.name);
|
|
2196
|
+
if (entry.isSymbolicLink()) {
|
|
2197
|
+
if (!skipDir(entry.name) && (isDiagramFile(entry.name) || linksToDir(abs)))
|
|
2198
|
+
symlinks.push(abs);
|
|
2199
|
+
} else if (entry.isDirectory()) {
|
|
2200
|
+
if (!skipDir(entry.name))
|
|
2201
|
+
walk(abs);
|
|
2202
|
+
} else if (entry.isFile() && isDiagramFile(entry.name)) {
|
|
2203
|
+
add(abs);
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2142
2206
|
const walk = (dir) => {
|
|
2143
2207
|
let entries;
|
|
2144
2208
|
try {
|
|
@@ -2147,22 +2211,8 @@ var collectFiles = (paths) => {
|
|
|
2147
2211
|
unreadable.push(dir);
|
|
2148
2212
|
return;
|
|
2149
2213
|
}
|
|
2150
|
-
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name)))
|
|
2151
|
-
|
|
2152
|
-
if (entry.isSymbolicLink()) {
|
|
2153
|
-
if (!skipDir(entry.name) && (DIAGRAM_FILE_EXTS.has(extname(entry.name).toLowerCase()) || linksToDir(abs))) {
|
|
2154
|
-
symlinks.push(abs);
|
|
2155
|
-
}
|
|
2156
|
-
} else if (entry.isDirectory()) {
|
|
2157
|
-
if (!skipDir(entry.name))
|
|
2158
|
-
walk(abs);
|
|
2159
|
-
} else if (entry.isFile() && DIAGRAM_FILE_EXTS.has(extname(entry.name).toLowerCase())) {
|
|
2160
|
-
if (!seen.has(abs)) {
|
|
2161
|
-
seen.add(abs);
|
|
2162
|
-
files.push(abs);
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2214
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name)))
|
|
2215
|
+
visit(dir, entry);
|
|
2166
2216
|
};
|
|
2167
2217
|
for (const p of paths) {
|
|
2168
2218
|
const abs = resolve2(p);
|
|
@@ -2170,9 +2220,8 @@ var collectFiles = (paths) => {
|
|
|
2170
2220
|
const st = statSync(abs);
|
|
2171
2221
|
if (st.isDirectory()) {
|
|
2172
2222
|
walk(abs);
|
|
2173
|
-
} else if (st.isFile()
|
|
2174
|
-
|
|
2175
|
-
files.push(abs);
|
|
2223
|
+
} else if (st.isFile()) {
|
|
2224
|
+
add(abs);
|
|
2176
2225
|
}
|
|
2177
2226
|
} catch {
|
|
2178
2227
|
unreadable.push(p);
|
|
@@ -2186,21 +2235,24 @@ var OMIT = new Set(["position", "data"]);
|
|
|
2186
2235
|
var normalise = (node, opts = {}) => {
|
|
2187
2236
|
const out = {};
|
|
2188
2237
|
const isDiagram = node.type === "code" && node.lang === "mermaid";
|
|
2238
|
+
const blank = isDiagram && Boolean(opts.blankDiagrams);
|
|
2189
2239
|
for (const [key, value] of Object.entries(node)) {
|
|
2190
2240
|
if (OMIT.has(key))
|
|
2191
2241
|
continue;
|
|
2192
|
-
if (
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
if (isDiagram && opts.blankDiagrams)
|
|
2196
|
-
continue;
|
|
2197
|
-
out.value = node.type === "text" ? value.replace(/\s+/g, " ").trim() : value;
|
|
2198
|
-
} else {
|
|
2199
|
-
out[key] = value;
|
|
2200
|
-
}
|
|
2242
|
+
if (blank && isStringValue(key, value))
|
|
2243
|
+
continue;
|
|
2244
|
+
out[key] = normaliseField(node, key, value, opts);
|
|
2201
2245
|
}
|
|
2202
2246
|
return out;
|
|
2203
2247
|
};
|
|
2248
|
+
var isStringValue = (key, value) => key === "value" && typeof value === "string";
|
|
2249
|
+
var normaliseField = (node, key, value, opts) => {
|
|
2250
|
+
if (key === "children" && Array.isArray(value))
|
|
2251
|
+
return value.map((child) => normalise(child, opts));
|
|
2252
|
+
if (isStringValue(key, value))
|
|
2253
|
+
return node.type === "text" ? value.replace(/\s+/g, " ").trim() : value;
|
|
2254
|
+
return value;
|
|
2255
|
+
};
|
|
2204
2256
|
var sameJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
2205
2257
|
var sameTree = (a, b, opts) => sameJson(normalise(a, opts), normalise(b, opts));
|
|
2206
2258
|
var sameDiagrams = async (before, after) => {
|
|
@@ -2265,63 +2317,68 @@ var overlaps = (edits) => {
|
|
|
2265
2317
|
return sorted.some((e, i) => i > 0 && e.start < sorted[i - 1].end);
|
|
2266
2318
|
};
|
|
2267
2319
|
var wellFormed = (e, length) => Number.isInteger(e.start) && Number.isInteger(e.end) && e.start >= 0 && e.start <= e.end && e.end <= length && typeof e.text === "string";
|
|
2320
|
+
var keyOf = (run, e) => `${e.rule}\x00${run.current.slice(e.start, e.end)}\x00${e.text}`;
|
|
2321
|
+
var attempt = async (run, doc, edits) => {
|
|
2322
|
+
const next = splice(run.current, edits);
|
|
2323
|
+
const after = buildDocModel(next, run.kind);
|
|
2324
|
+
for (const e of edits)
|
|
2325
|
+
if (await verify(e, doc, after) !== "accept")
|
|
2326
|
+
return false;
|
|
2327
|
+
run.current = next;
|
|
2328
|
+
run.applied.push(...edits);
|
|
2329
|
+
return true;
|
|
2330
|
+
};
|
|
2331
|
+
var soundEdits = (run, proposed) => proposed.filter((e) => {
|
|
2332
|
+
if (wellFormed(e, run.current.length))
|
|
2333
|
+
return true;
|
|
2334
|
+
const key = `${e.rule}\x00malformed\x00${e.start}:${e.end}`;
|
|
2335
|
+
if (!run.refusedKeys.has(key)) {
|
|
2336
|
+
run.refusedKeys.add(key);
|
|
2337
|
+
run.refused.push(e);
|
|
2338
|
+
}
|
|
2339
|
+
return false;
|
|
2340
|
+
});
|
|
2341
|
+
var applyRule = async (run, doc, rule) => {
|
|
2342
|
+
const options = optionsFor(rule, run.registry.options.get(rule.id), run.flags, run.file);
|
|
2343
|
+
const proposed = await rule.fix?.({ doc, file: run.file, options, helpers: HELPERS }) ?? [];
|
|
2344
|
+
const edits = soundEdits(run, proposed).filter((e) => run.current.slice(e.start, e.end) !== e.text && !run.refusedKeys.has(keyOf(run, e)));
|
|
2345
|
+
if (edits.length === 0)
|
|
2346
|
+
return false;
|
|
2347
|
+
if (edits.length > 1 && !overlaps(edits) && await attempt(run, doc, edits))
|
|
2348
|
+
return true;
|
|
2349
|
+
for (const edit of edits) {
|
|
2350
|
+
if (await attempt(run, doc, [edit]))
|
|
2351
|
+
return true;
|
|
2352
|
+
run.refusedKeys.add(keyOf(run, edit));
|
|
2353
|
+
run.refused.push(edit);
|
|
2354
|
+
}
|
|
2355
|
+
return false;
|
|
2356
|
+
};
|
|
2357
|
+
var runPass = async (run) => {
|
|
2358
|
+
const doc = buildDocModel(run.current, run.kind);
|
|
2359
|
+
for (const rule of run.registry.fixOrder)
|
|
2360
|
+
if (await applyRule(run, doc, rule))
|
|
2361
|
+
return rule.id;
|
|
2362
|
+
return;
|
|
2363
|
+
};
|
|
2268
2364
|
var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
2269
2365
|
checkRunFlags(flags);
|
|
2270
|
-
const
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2366
|
+
const run = {
|
|
2367
|
+
file,
|
|
2368
|
+
flags,
|
|
2369
|
+
registry,
|
|
2370
|
+
kind: documentKind(file),
|
|
2371
|
+
current: src,
|
|
2372
|
+
applied: [],
|
|
2373
|
+
refused: [],
|
|
2374
|
+
refusedKeys: new Set
|
|
2375
|
+
};
|
|
2275
2376
|
const keptBy = [];
|
|
2276
2377
|
for (let pass = 0;pass < MAX_PASSES; pass++) {
|
|
2277
|
-
const
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
const after = buildDocModel(next, kind);
|
|
2282
|
-
for (const e of edits)
|
|
2283
|
-
if (await verify(e, doc, after) !== "accept")
|
|
2284
|
-
return false;
|
|
2285
|
-
current = next;
|
|
2286
|
-
applied.push(...edits);
|
|
2287
|
-
return true;
|
|
2288
|
-
};
|
|
2289
|
-
let progressed = false;
|
|
2290
|
-
for (const rule of registry.fixOrder) {
|
|
2291
|
-
const options = optionsFor(rule, registry.options.get(rule.id), flags, file);
|
|
2292
|
-
const proposed = await rule.fix?.({ doc, file, options, helpers: HELPERS }) ?? [];
|
|
2293
|
-
const sound = proposed.filter((e) => {
|
|
2294
|
-
if (wellFormed(e, current.length))
|
|
2295
|
-
return true;
|
|
2296
|
-
const key = `${e.rule}\x00malformed\x00${e.start}:${e.end}`;
|
|
2297
|
-
if (!refusedKeys.has(key)) {
|
|
2298
|
-
refusedKeys.add(key);
|
|
2299
|
-
refused.push(e);
|
|
2300
|
-
}
|
|
2301
|
-
return false;
|
|
2302
|
-
});
|
|
2303
|
-
const edits = sound.filter((e) => current.slice(e.start, e.end) !== e.text && !refusedKeys.has(keyOf(e)));
|
|
2304
|
-
if (edits.length === 0)
|
|
2305
|
-
continue;
|
|
2306
|
-
if (edits.length > 1 && !overlaps(edits) && await attempt(edits)) {
|
|
2307
|
-
progressed = true;
|
|
2308
|
-
} else {
|
|
2309
|
-
for (const edit of edits) {
|
|
2310
|
-
if (await attempt([edit])) {
|
|
2311
|
-
progressed = true;
|
|
2312
|
-
break;
|
|
2313
|
-
}
|
|
2314
|
-
refusedKeys.add(keyOf(edit));
|
|
2315
|
-
refused.push(edit);
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
if (progressed) {
|
|
2319
|
-
keptBy.push(rule.id);
|
|
2320
|
-
break;
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
if (!progressed)
|
|
2324
|
-
return { output: current, applied, refused };
|
|
2378
|
+
const kept = await runPass(run);
|
|
2379
|
+
if (kept === undefined)
|
|
2380
|
+
return { output: run.current, applied: run.applied, refused: run.refused };
|
|
2381
|
+
keptBy.push(kept);
|
|
2325
2382
|
}
|
|
2326
2383
|
const lingering = [...new Set(keptBy.slice(-RECENT_PASSES))].sort();
|
|
2327
2384
|
throw new FixNotConvergingError(file, MAX_PASSES, lingering);
|
|
@@ -2439,26 +2496,22 @@ var localSources = async (root) => {
|
|
|
2439
2496
|
}
|
|
2440
2497
|
return names.filter((name) => LOCAL_EXTENSIONS.has(extname3(name)) && !name.endsWith(".d.ts") && !name.endsWith(".d.mts")).map((name) => ({ kind: "local", spec: join4(LOCAL_RULES, name), path: join4(dir, name) }));
|
|
2441
2498
|
};
|
|
2499
|
+
var CONDITIONS = ["node", "import", "default"];
|
|
2500
|
+
var pickFirst = (candidates) => {
|
|
2501
|
+
for (const item of candidates) {
|
|
2502
|
+
const found = pick(item);
|
|
2503
|
+
if (found)
|
|
2504
|
+
return found;
|
|
2505
|
+
}
|
|
2506
|
+
return null;
|
|
2507
|
+
};
|
|
2442
2508
|
var pick = (target) => {
|
|
2443
2509
|
if (typeof target === "string")
|
|
2444
2510
|
return target;
|
|
2445
|
-
if (Array.isArray(target))
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
return found;
|
|
2450
|
-
}
|
|
2451
|
-
return null;
|
|
2452
|
-
}
|
|
2453
|
-
if (isRecord2(target)) {
|
|
2454
|
-
for (const condition of ["node", "import", "default"]) {
|
|
2455
|
-
if (condition in target) {
|
|
2456
|
-
const found = pick(target[condition]);
|
|
2457
|
-
if (found)
|
|
2458
|
-
return found;
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2511
|
+
if (Array.isArray(target))
|
|
2512
|
+
return pickFirst(target);
|
|
2513
|
+
if (isRecord2(target))
|
|
2514
|
+
return pickFirst(CONDITIONS.filter((c) => (c in target)).map((c) => target[c]));
|
|
2462
2515
|
return null;
|
|
2463
2516
|
};
|
|
2464
2517
|
var entryOf = (pkg) => {
|
|
@@ -2560,23 +2613,40 @@ var importModule = async (source, runtime) => {
|
|
|
2560
2613
|
};
|
|
2561
2614
|
var OPTION_TYPES = new Set(["number", "string", "boolean", "string[]", "canvas"]);
|
|
2562
2615
|
var RESERVED_OPTIONS = ["preset", "profile"];
|
|
2563
|
-
var
|
|
2616
|
+
var checkRuleCategory = (spec, key, category, own) => {
|
|
2564
2617
|
const where = `rule "${key}"`;
|
|
2565
|
-
if (!isRecord3(raw))
|
|
2566
|
-
throw new PluginContractError(spec, `${where} must be an object`);
|
|
2567
|
-
if (!RULE_KEY.test(key)) {
|
|
2568
|
-
throw new PluginContractError(spec, `${where} is not a valid key; use lower-case words joined by hyphens`);
|
|
2569
|
-
}
|
|
2570
|
-
const { category, summary, options, check, fix } = raw;
|
|
2571
2618
|
if (typeof category !== "string" || !CATEGORY_WORD.test(category)) {
|
|
2572
2619
|
throw new PluginContractError(spec, `${where} needs a category of one lower-case word`);
|
|
2573
2620
|
}
|
|
2574
|
-
if (!BUILTIN_CATEGORIES.has(category)
|
|
2621
|
+
if (!(BUILTIN_CATEGORIES.has(category) || own.has(category))) {
|
|
2575
2622
|
throw new PluginContractError(spec, `${where} uses the category "${category}", which this plugin does not declare in meta.categories`);
|
|
2576
2623
|
}
|
|
2577
2624
|
if (!key.startsWith(`${category}-`)) {
|
|
2578
2625
|
throw new PluginContractError(spec, `${where} must start with its category, as "${category}-short-name"`);
|
|
2579
2626
|
}
|
|
2627
|
+
};
|
|
2628
|
+
var checkRuleOptions = (spec, where, options) => {
|
|
2629
|
+
if (options === undefined)
|
|
2630
|
+
return;
|
|
2631
|
+
const valid = isRecord3(options) && Object.values(options).every((t) => OPTION_TYPES.has(t));
|
|
2632
|
+
if (!valid) {
|
|
2633
|
+
throw new PluginContractError(spec, `${where} options must map names to number, string, boolean, string[] or canvas`);
|
|
2634
|
+
}
|
|
2635
|
+
for (const name of RESERVED_OPTIONS) {
|
|
2636
|
+
if (name in options && options[name] !== "string") {
|
|
2637
|
+
throw new PluginContractError(spec, `${where} declares option "${name}" as ${String(options[name])}, but "${name}" is reserved for the built-in ${name}, which is a string set by --${name}; declare it as "string" to follow the run's ${name}, or pick another name`);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
};
|
|
2641
|
+
var checkRule = (spec, key, raw, own) => {
|
|
2642
|
+
const where = `rule "${key}"`;
|
|
2643
|
+
if (!isRecord3(raw))
|
|
2644
|
+
throw new PluginContractError(spec, `${where} must be an object`);
|
|
2645
|
+
if (!RULE_KEY.test(key)) {
|
|
2646
|
+
throw new PluginContractError(spec, `${where} is not a valid key; use lower-case words joined by hyphens`);
|
|
2647
|
+
}
|
|
2648
|
+
const { category, summary, options, check, fix } = raw;
|
|
2649
|
+
checkRuleCategory(spec, key, category, own);
|
|
2580
2650
|
if (typeof summary !== "string" || summary === "")
|
|
2581
2651
|
throw new PluginContractError(spec, `${where} needs a summary`);
|
|
2582
2652
|
if (typeof check !== "function")
|
|
@@ -2584,17 +2654,7 @@ var checkRule = (spec, key, raw, own) => {
|
|
|
2584
2654
|
if (fix !== undefined && typeof fix !== "function") {
|
|
2585
2655
|
throw new PluginContractError(spec, `${where} fix must be a function`);
|
|
2586
2656
|
}
|
|
2587
|
-
|
|
2588
|
-
const valid = isRecord3(options) && Object.values(options).every((t) => OPTION_TYPES.has(t));
|
|
2589
|
-
if (!valid) {
|
|
2590
|
-
throw new PluginContractError(spec, `${where} options must map names to number, string, boolean, string[] or canvas`);
|
|
2591
|
-
}
|
|
2592
|
-
for (const name of RESERVED_OPTIONS) {
|
|
2593
|
-
if (name in options && options[name] !== "string") {
|
|
2594
|
-
throw new PluginContractError(spec, `${where} declares option "${name}" as ${String(options[name])}, but "${name}" is reserved for the built-in ${name}, which is a string set by --${name}; declare it as "string" to follow the run's ${name}, or pick another name`);
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2657
|
+
checkRuleOptions(spec, where, options);
|
|
2598
2658
|
return raw;
|
|
2599
2659
|
};
|
|
2600
2660
|
var checkCategories = (spec, raw) => {
|
|
@@ -2615,7 +2675,7 @@ var checkCategories = (spec, raw) => {
|
|
|
2615
2675
|
return raw;
|
|
2616
2676
|
};
|
|
2617
2677
|
var parsePlugin = (spec, raw, reserved) => {
|
|
2618
|
-
if (!isRecord3(raw)
|
|
2678
|
+
if (!(isRecord3(raw) && isRecord3(raw.meta) && isRecord3(raw.rules))) {
|
|
2619
2679
|
throw new PluginContractError(spec, "the default export must be { meta, rules }");
|
|
2620
2680
|
}
|
|
2621
2681
|
const { name, namespace, apiVersion } = raw.meta;
|
|
@@ -2635,6 +2695,31 @@ var parsePlugin = (spec, raw, reserved) => {
|
|
|
2635
2695
|
return { meta: { name, namespace, apiVersion, categories }, rules };
|
|
2636
2696
|
};
|
|
2637
2697
|
var loadPackage = async (source, reserved) => parsePlugin(source.spec, (await importModule(source)).default, reserved);
|
|
2698
|
+
var keyOf2 = (source) => basename(source.path, extname4(source.path));
|
|
2699
|
+
var groupByKey = (sources) => {
|
|
2700
|
+
const byKey = new Map;
|
|
2701
|
+
for (const source of sources)
|
|
2702
|
+
byKey.set(keyOf2(source), [...byKey.get(keyOf2(source)) ?? [], source]);
|
|
2703
|
+
return byKey;
|
|
2704
|
+
};
|
|
2705
|
+
var duplicateKeyError = (key, group) => new PluginContractError(group[0].spec, `rule "${key}" is defined by more than one file: ${group.map((s) => s.spec).join(", ")}; each rule comes from exactly one file, so rename or remove all but one`);
|
|
2706
|
+
var mergeCategories = (seen, spec, declared) => {
|
|
2707
|
+
for (const [word, description] of Object.entries(declared)) {
|
|
2708
|
+
if (seen.categories[word] !== undefined && seen.categories[word] !== description) {
|
|
2709
|
+
throw new PluginConflictError(spec, seen.owner[word], `declares category "${word}" with a different description than another local file`);
|
|
2710
|
+
}
|
|
2711
|
+
seen.categories[word] = description;
|
|
2712
|
+
seen.owner[word] = spec;
|
|
2713
|
+
}
|
|
2714
|
+
};
|
|
2715
|
+
var importLocal = async (source, seen, runtime) => {
|
|
2716
|
+
const mod = await importModule(source, runtime);
|
|
2717
|
+
if (mod.default === undefined) {
|
|
2718
|
+
throw new PluginContractError(source.spec, "has no default export; export one rule");
|
|
2719
|
+
}
|
|
2720
|
+
mergeCategories(seen, source.spec, checkCategories(source.spec, mod.categories));
|
|
2721
|
+
return mod.default;
|
|
2722
|
+
};
|
|
2638
2723
|
var loadLocal = async (sources, runtime) => {
|
|
2639
2724
|
const failures = [];
|
|
2640
2725
|
const fail = (err) => {
|
|
@@ -2642,35 +2727,19 @@ var loadLocal = async (sources, runtime) => {
|
|
|
2642
2727
|
throw err;
|
|
2643
2728
|
failures.push(err);
|
|
2644
2729
|
};
|
|
2645
|
-
const
|
|
2646
|
-
const
|
|
2730
|
+
const seen = { categories: {}, owner: {} };
|
|
2731
|
+
const { categories } = seen;
|
|
2647
2732
|
const loaded = [];
|
|
2648
|
-
const
|
|
2649
|
-
const byKey = new Map;
|
|
2650
|
-
for (const source of sources)
|
|
2651
|
-
byKey.set(keyOf(source), [...byKey.get(keyOf(source)) ?? [], source]);
|
|
2733
|
+
const byKey = groupByKey(sources);
|
|
2652
2734
|
for (const [key, group] of byKey) {
|
|
2653
|
-
if (group.length
|
|
2654
|
-
|
|
2655
|
-
fail(new PluginContractError(group[0].spec, `rule "${key}" is defined by more than one file: ${group.map((s) => s.spec).join(", ")}; each rule comes from exactly one file, so rename or remove all but one`));
|
|
2735
|
+
if (group.length > 1)
|
|
2736
|
+
fail(duplicateKeyError(key, group));
|
|
2656
2737
|
}
|
|
2657
2738
|
for (const source of sources) {
|
|
2658
|
-
if ((byKey.get(
|
|
2739
|
+
if ((byKey.get(keyOf2(source))?.length ?? 0) > 1)
|
|
2659
2740
|
continue;
|
|
2660
2741
|
try {
|
|
2661
|
-
|
|
2662
|
-
if (mod.default === undefined) {
|
|
2663
|
-
throw new PluginContractError(source.spec, "has no default export; export one rule");
|
|
2664
|
-
}
|
|
2665
|
-
const declared = checkCategories(source.spec, mod.categories);
|
|
2666
|
-
for (const [word, description] of Object.entries(declared)) {
|
|
2667
|
-
if (categories[word] !== undefined && categories[word] !== description) {
|
|
2668
|
-
throw new PluginConflictError(source.spec, owner[word], `declares category "${word}" with a different description than another local file`);
|
|
2669
|
-
}
|
|
2670
|
-
categories[word] = description;
|
|
2671
|
-
owner[word] = source.spec;
|
|
2672
|
-
}
|
|
2673
|
-
loaded.push({ key: keyOf(source), source, raw: mod.default });
|
|
2742
|
+
loaded.push({ key: keyOf2(source), source, raw: await importLocal(source, seen, runtime) });
|
|
2674
2743
|
} catch (err) {
|
|
2675
2744
|
fail(err);
|
|
2676
2745
|
}
|
|
@@ -2701,7 +2770,7 @@ var toRule = (namespace, key, rule) => {
|
|
|
2701
2770
|
} catch (err) {
|
|
2702
2771
|
return report(`threw: ${message2(err)}`);
|
|
2703
2772
|
}
|
|
2704
|
-
if (!Array.isArray(found)
|
|
2773
|
+
if (!(Array.isArray(found) && found.every(validFinding))) {
|
|
2705
2774
|
return report("returned something other than a list of { line, message } findings");
|
|
2706
2775
|
}
|
|
2707
2776
|
return found.map((f) => ({
|
|
@@ -2723,7 +2792,7 @@ var toRule = (namespace, key, rule) => {
|
|
|
2723
2792
|
if (!Array.isArray(edits))
|
|
2724
2793
|
throw new PluginFixerError(id, "fixer returned something other than a list of edits");
|
|
2725
2794
|
return edits.map((e) => {
|
|
2726
|
-
if (!isRecord3(e)
|
|
2795
|
+
if (!(isRecord3(e) && isRecord3(e.expect) && EXPECTATIONS.has(e.expect.kind))) {
|
|
2727
2796
|
throw new PluginFixerError(id, "fixer returned an edit with no valid expectation");
|
|
2728
2797
|
}
|
|
2729
2798
|
return { ...e, rule: id };
|
|
@@ -2733,26 +2802,7 @@ var toRule = (namespace, key, rule) => {
|
|
|
2733
2802
|
};
|
|
2734
2803
|
|
|
2735
2804
|
// src/plugins/index.ts
|
|
2736
|
-
var
|
|
2737
|
-
const { sources: found, failures: unresolved } = await discoverSources(root, config, mode === "off");
|
|
2738
|
-
const set = {
|
|
2739
|
-
rules: [],
|
|
2740
|
-
categories: new Map(BUILTIN_CATEGORIES),
|
|
2741
|
-
sources: [],
|
|
2742
|
-
skipped: [],
|
|
2743
|
-
failures: []
|
|
2744
|
-
};
|
|
2745
|
-
if (mode === "off") {
|
|
2746
|
-
return { ...set, skipped: [...found.map((s) => s.spec), ...unresolved.map((u) => u.error.source)] };
|
|
2747
|
-
}
|
|
2748
|
-
const settle = (error, kind) => {
|
|
2749
|
-
if (mode === "strict")
|
|
2750
|
-
throw error;
|
|
2751
|
-
if (!set.failures.some((f) => f.source === error.source))
|
|
2752
|
-
set.failures.push({ source: error.source, kind, error });
|
|
2753
|
-
};
|
|
2754
|
-
for (const { error, kind } of unresolved)
|
|
2755
|
-
settle(error, kind);
|
|
2805
|
+
var loadAll = async (found, settle) => {
|
|
2756
2806
|
const loaded = [];
|
|
2757
2807
|
const locals = found.filter((s) => s.kind === "local");
|
|
2758
2808
|
if (locals.length > 0) {
|
|
@@ -2771,28 +2821,59 @@ var loadPlugins = async (root, config, mode) => {
|
|
|
2771
2821
|
settle(err, source.kind);
|
|
2772
2822
|
}
|
|
2773
2823
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2824
|
+
return loaded;
|
|
2825
|
+
};
|
|
2826
|
+
var conflictOf = (plugin, spec, claims) => {
|
|
2827
|
+
const { namespace } = plugin.meta;
|
|
2828
|
+
const words = Object.keys(plugin.meta.categories ?? {});
|
|
2829
|
+
const taken = claims.namespaces.get(namespace);
|
|
2830
|
+
const clash = words.find((w) => claims.declaredBy.has(w));
|
|
2831
|
+
if (!(taken || clash))
|
|
2832
|
+
return null;
|
|
2833
|
+
const other = taken ?? claims.declaredBy.get(clash);
|
|
2834
|
+
const reason2 = taken ? `namespace "${namespace}" is already used by ${other}` : `category "${clash}" is already declared by ${other}`;
|
|
2835
|
+
return new PluginConflictError(spec, other, reason2);
|
|
2836
|
+
};
|
|
2837
|
+
var acceptClaims = (loaded, set, settle) => {
|
|
2838
|
+
const claims = { namespaces: new Map, declaredBy: new Map };
|
|
2776
2839
|
const accepted = [];
|
|
2777
2840
|
for (const entry of loaded) {
|
|
2778
2841
|
const { plugin, spec, kind } = entry;
|
|
2779
|
-
const
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
const clash = words.find((w) => declaredBy.has(w));
|
|
2783
|
-
if (taken || clash) {
|
|
2784
|
-
const other = taken ?? declaredBy.get(clash);
|
|
2785
|
-
const reason2 = taken ? `namespace "${namespace}" is already used by ${other}` : `category "${clash}" is already declared by ${other}`;
|
|
2786
|
-
settle(new PluginConflictError(spec, other, reason2), kind);
|
|
2842
|
+
const conflict = conflictOf(plugin, spec, claims);
|
|
2843
|
+
if (conflict) {
|
|
2844
|
+
settle(conflict, kind);
|
|
2787
2845
|
continue;
|
|
2788
2846
|
}
|
|
2789
|
-
namespaces.set(namespace, spec);
|
|
2847
|
+
claims.namespaces.set(plugin.meta.namespace, spec);
|
|
2790
2848
|
for (const [word, description] of Object.entries(plugin.meta.categories ?? {})) {
|
|
2791
|
-
declaredBy.set(word, spec);
|
|
2849
|
+
claims.declaredBy.set(word, spec);
|
|
2792
2850
|
set.categories.set(word, description);
|
|
2793
2851
|
}
|
|
2794
2852
|
accepted.push(entry);
|
|
2795
2853
|
}
|
|
2854
|
+
return accepted;
|
|
2855
|
+
};
|
|
2856
|
+
var loadPlugins = async (root, config, mode) => {
|
|
2857
|
+
const { sources: found, failures: unresolved } = await discoverSources(root, config, mode === "off");
|
|
2858
|
+
const set = {
|
|
2859
|
+
rules: [],
|
|
2860
|
+
categories: new Map(BUILTIN_CATEGORIES),
|
|
2861
|
+
sources: [],
|
|
2862
|
+
skipped: [],
|
|
2863
|
+
failures: []
|
|
2864
|
+
};
|
|
2865
|
+
if (mode === "off") {
|
|
2866
|
+
return { ...set, skipped: [...found.map((s) => s.spec), ...unresolved.map((u) => u.error.source)] };
|
|
2867
|
+
}
|
|
2868
|
+
const settle = (error, kind) => {
|
|
2869
|
+
if (mode === "strict")
|
|
2870
|
+
throw error;
|
|
2871
|
+
if (!set.failures.some((f) => f.source === error.source))
|
|
2872
|
+
set.failures.push({ source: error.source, kind, error });
|
|
2873
|
+
};
|
|
2874
|
+
for (const { error, kind } of unresolved)
|
|
2875
|
+
settle(error, kind);
|
|
2876
|
+
const accepted = acceptClaims(await loadAll(found, settle), set, settle);
|
|
2796
2877
|
for (const { plugin, kind, spec } of accepted) {
|
|
2797
2878
|
const rules = Object.entries(plugin.rules).map(([key, rule]) => toRule(plugin.meta.namespace, key, rule));
|
|
2798
2879
|
set.rules.push(...rules);
|
|
@@ -3012,84 +3093,72 @@ var refuseOutsideLinks = async (files, root, cwd) => {
|
|
|
3012
3093
|
throw new FixOutsideRootError(display(file, cwd), target, root);
|
|
3013
3094
|
}
|
|
3014
3095
|
};
|
|
3015
|
-
var
|
|
3016
|
-
let parsed;
|
|
3096
|
+
var parseArgv = (argv) => {
|
|
3017
3097
|
try {
|
|
3018
|
-
|
|
3098
|
+
return parse2(argv);
|
|
3019
3099
|
} catch (err) {
|
|
3020
3100
|
throw new FlagError("argv", err instanceof Error ? err.message : String(err));
|
|
3021
3101
|
}
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
console.log(USAGE);
|
|
3025
|
-
return 0;
|
|
3026
|
-
}
|
|
3027
|
-
if (values.version) {
|
|
3028
|
-
console.log(package_default.version);
|
|
3029
|
-
return 0;
|
|
3030
|
-
}
|
|
3031
|
-
const listing = values["list-rules"];
|
|
3032
|
-
const validating = values["validate-plugins"];
|
|
3102
|
+
};
|
|
3103
|
+
var runFlagsOf = (values) => {
|
|
3033
3104
|
const canvas = {
|
|
3034
3105
|
...values["canvas-light"] !== undefined && { light: values["canvas-light"] },
|
|
3035
3106
|
...values["canvas-dark"] !== undefined && { dark: values["canvas-dark"] }
|
|
3036
3107
|
};
|
|
3037
|
-
|
|
3108
|
+
return {
|
|
3038
3109
|
preset: values.preset,
|
|
3039
3110
|
profile: values.profile,
|
|
3040
3111
|
...Object.keys(canvas).length > 0 && { canvas }
|
|
3041
3112
|
};
|
|
3042
|
-
|
|
3113
|
+
};
|
|
3114
|
+
var checkPluginFlags = (values) => {
|
|
3043
3115
|
if (values["no-plugins"] && values["lenient-plugins"]) {
|
|
3044
3116
|
throw new FlagError("--lenient-plugins", "--no-plugins and --lenient-plugins contradict each other");
|
|
3045
3117
|
}
|
|
3046
|
-
if (
|
|
3118
|
+
if (values["validate-plugins"] && (values["no-plugins"] || values.fix || values["list-rules"])) {
|
|
3047
3119
|
throw new FlagError("--validate-plugins", "--validate-plugins takes no --no-plugins, --fix or --list-rules");
|
|
3048
3120
|
}
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
...positionals.length > 0 ? ["a path"] : []
|
|
3058
|
-
];
|
|
3059
|
-
if (ignored.length > 0) {
|
|
3060
|
-
throw new FlagError("--list-rules", `--list-rules takes only --config and --no-plugins or --lenient-plugins, not ${ignored.join(", ")}`);
|
|
3061
|
-
}
|
|
3121
|
+
};
|
|
3122
|
+
var checkListingFlags = (values, positionals) => {
|
|
3123
|
+
const ignored = [
|
|
3124
|
+
...["fix", "json", "preset", "profile", "canvas-light", "canvas-dark"].filter((name) => values[name] !== undefined && values[name] !== false).map((name) => `--${name}`),
|
|
3125
|
+
...positionals.length > 0 ? ["a path"] : []
|
|
3126
|
+
];
|
|
3127
|
+
if (ignored.length > 0) {
|
|
3128
|
+
throw new FlagError("--list-rules", `--list-rules takes only --config and --no-plugins or --lenient-plugins, not ${ignored.join(", ")}`);
|
|
3062
3129
|
}
|
|
3130
|
+
};
|
|
3131
|
+
var checkPositionals = (values, positionals) => {
|
|
3132
|
+
const validating = values["validate-plugins"];
|
|
3063
3133
|
if (validating && positionals.length > 0) {
|
|
3064
3134
|
throw new FlagError("files", "--validate-plugins runs no check, so it takes no files");
|
|
3065
3135
|
}
|
|
3066
|
-
if (positionals.length === 0 && !
|
|
3136
|
+
if (positionals.length === 0 && !(values["list-rules"] || validating)) {
|
|
3067
3137
|
throw new FlagError("files", "no files given");
|
|
3068
|
-
const { root, registry, skipped, failures } = await setUp({
|
|
3069
|
-
cwd,
|
|
3070
|
-
configPath: values.config,
|
|
3071
|
-
noPlugins: values["no-plugins"],
|
|
3072
|
-
lenient: values["lenient-plugins"],
|
|
3073
|
-
collect: validating,
|
|
3074
|
-
selection: { enable, disable, prefix: "--" }
|
|
3075
|
-
});
|
|
3076
|
-
if (validating) {
|
|
3077
|
-
const result = validate(registry, failures, Boolean(values.json));
|
|
3078
|
-
for (const line of result.stderr)
|
|
3079
|
-
console.error(line);
|
|
3080
|
-
if (result.stdout)
|
|
3081
|
-
console.log(result.stdout);
|
|
3082
|
-
return result.code;
|
|
3083
3138
|
}
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3139
|
+
};
|
|
3140
|
+
var checkCombinations = (values, positionals) => {
|
|
3141
|
+
checkPluginFlags(values);
|
|
3142
|
+
const enable = categoryWords("--category", values.category);
|
|
3143
|
+
const disable = categoryWords("--disable-category", values["disable-category"]);
|
|
3144
|
+
const validating = values["validate-plugins"];
|
|
3145
|
+
if (validating && enable.length + disable.length > 0) {
|
|
3146
|
+
throw new FlagError("--validate-plugins", "--validate-plugins checks every plugin, so it takes no category flag");
|
|
3092
3147
|
}
|
|
3148
|
+
if (values["list-rules"] && !validating)
|
|
3149
|
+
checkListingFlags(values, positionals);
|
|
3150
|
+
checkPositionals(values, positionals);
|
|
3151
|
+
return { enable, disable };
|
|
3152
|
+
};
|
|
3153
|
+
var printValidation = (registry, failures, json) => {
|
|
3154
|
+
const result = validate(registry, failures, json);
|
|
3155
|
+
for (const line of result.stderr)
|
|
3156
|
+
console.error(line);
|
|
3157
|
+
if (result.stdout)
|
|
3158
|
+
console.log(result.stdout);
|
|
3159
|
+
return result.code;
|
|
3160
|
+
};
|
|
3161
|
+
var filesToCheck = (positionals, cwd) => {
|
|
3093
3162
|
const { files, unreadable, symlinks } = collectFiles(positionals.map((p) => resolve5(cwd, p)));
|
|
3094
3163
|
if (unreadable.length > 0)
|
|
3095
3164
|
throw new FlagError("files", `cannot read ${unreadable.join(", ")}`);
|
|
@@ -3098,39 +3167,83 @@ var run = async (argv, cwd) => {
|
|
|
3098
3167
|
}
|
|
3099
3168
|
if (files.length === 0)
|
|
3100
3169
|
throw new FlagError("files", "no matching files");
|
|
3101
|
-
|
|
3102
|
-
|
|
3170
|
+
return files;
|
|
3171
|
+
};
|
|
3172
|
+
var reportSetAside = (files, flags, registry, cwd) => {
|
|
3103
3173
|
const setAside = files.filter((file) => canvasSetAside(file, flags, registry)).map((f) => display(f, cwd));
|
|
3104
3174
|
if (setAside.length > 0) {
|
|
3105
3175
|
console.error(`mermaidjs-lint: config canvas set aside for ${plural(setAside.length, "file")} scored under mkdocs-material: ${setAside.join(", ")}`);
|
|
3106
3176
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3177
|
+
return setAside;
|
|
3178
|
+
};
|
|
3179
|
+
var checkFile = async (file, { flags, registry, fix, cwd }) => {
|
|
3180
|
+
let src = await readFile3(file, "utf8");
|
|
3181
|
+
if (fix) {
|
|
3182
|
+
const fixed = (await fixSourceReport(src, file, flags, registry)).output;
|
|
3183
|
+
if (fixed !== src) {
|
|
3184
|
+
await writeFile(file, fixed);
|
|
3185
|
+
src = fixed;
|
|
3115
3186
|
}
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
}));
|
|
3120
|
-
|
|
3121
|
-
|
|
3187
|
+
}
|
|
3188
|
+
const shown = display(file, cwd);
|
|
3189
|
+
const found = await checkModel(buildDocModel(src, documentKind(file)), file, flags, registry);
|
|
3190
|
+
return found.map((f) => ({ ...f, file: shown }));
|
|
3191
|
+
};
|
|
3192
|
+
var printReport = (json, { all, files, registry, failures, setAside }) => {
|
|
3193
|
+
if (json) {
|
|
3122
3194
|
const skippedPlugins = failures.map((f) => ({
|
|
3123
3195
|
source: f.source,
|
|
3124
3196
|
kind: f.kind,
|
|
3125
3197
|
code: f.error.code,
|
|
3126
3198
|
message: reasonOf(f)
|
|
3127
3199
|
}));
|
|
3128
|
-
console.log(JSON.stringify({ findings: all, files
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
console.log(
|
|
3200
|
+
console.log(JSON.stringify({ findings: all, files, plugins: registry.sources, skippedPlugins, canvasSetAside: setAside }, null, 2));
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
3203
|
+
for (const f of all)
|
|
3204
|
+
console.log(reportLine(f, f.file));
|
|
3205
|
+
console.log(`${all.length} finding(s) in ${files} file(s)`);
|
|
3206
|
+
};
|
|
3207
|
+
var run = async (argv, cwd) => {
|
|
3208
|
+
const { values, positionals } = parseArgv(argv);
|
|
3209
|
+
if (values.help) {
|
|
3210
|
+
console.log(USAGE);
|
|
3211
|
+
return 0;
|
|
3212
|
+
}
|
|
3213
|
+
if (values.version) {
|
|
3214
|
+
console.log(package_default.version);
|
|
3215
|
+
return 0;
|
|
3133
3216
|
}
|
|
3217
|
+
const flags = runFlagsOf(values);
|
|
3218
|
+
checkRunFlags(flags, "--");
|
|
3219
|
+
const { enable, disable } = checkCombinations(values, positionals);
|
|
3220
|
+
const { root, registry, skipped, failures } = await setUp({
|
|
3221
|
+
cwd,
|
|
3222
|
+
configPath: values.config,
|
|
3223
|
+
noPlugins: values["no-plugins"],
|
|
3224
|
+
lenient: values["lenient-plugins"],
|
|
3225
|
+
collect: values["validate-plugins"],
|
|
3226
|
+
selection: { enable, disable, prefix: "--" }
|
|
3227
|
+
});
|
|
3228
|
+
if (values["validate-plugins"])
|
|
3229
|
+
return printValidation(registry, failures, Boolean(values.json));
|
|
3230
|
+
const note = loadedLine(registry.sources, skipped);
|
|
3231
|
+
if (note)
|
|
3232
|
+
console.error(note);
|
|
3233
|
+
for (const line of skippedLines(failures, registry.ignoredSettings))
|
|
3234
|
+
console.error(line);
|
|
3235
|
+
if (values["list-rules"]) {
|
|
3236
|
+
console.log(listRules(registry));
|
|
3237
|
+
return 0;
|
|
3238
|
+
}
|
|
3239
|
+
const files = filesToCheck(positionals, cwd);
|
|
3240
|
+
const fix = Boolean(values.fix);
|
|
3241
|
+
if (fix)
|
|
3242
|
+
await refuseOutsideLinks(files, root, cwd);
|
|
3243
|
+
const setAside = reportSetAside(files, flags, registry, cwd);
|
|
3244
|
+
const perFile = await Promise.all(files.map((file) => checkFile(file, { flags, registry, fix, cwd })));
|
|
3245
|
+
const all = perFile.flat();
|
|
3246
|
+
printReport(Boolean(values.json), { all, files: files.length, registry, failures, setAside });
|
|
3134
3247
|
return all.length > 0 ? 1 : 0;
|
|
3135
3248
|
};
|
|
3136
3249
|
var describeError = (err) => err instanceof MermaidLintError ? `error[${err.code}]: ${err.message}` : `error: ${err instanceof Error ? err.message : String(err)}`;
|