@jpeakai/mermaidjs-lint 0.7.0 → 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 +43 -109
- 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/index.js
CHANGED
|
@@ -251,7 +251,7 @@ var parseProperties = (raw, rawStart, line) => {
|
|
|
251
251
|
const key = pair.slice(0, colon).trim().toLowerCase();
|
|
252
252
|
const valueRaw = pair.slice(colon + 1);
|
|
253
253
|
const value = valueRaw.trim();
|
|
254
|
-
if (!key
|
|
254
|
+
if (!(key && value))
|
|
255
255
|
continue;
|
|
256
256
|
properties[key] = value;
|
|
257
257
|
const lead = valueRaw.length - valueRaw.trimStart().length;
|
|
@@ -406,7 +406,7 @@ var parserReason = (err) => {
|
|
|
406
406
|
`);
|
|
407
407
|
if (message.startsWith("No diagram type detected"))
|
|
408
408
|
return (lines[0] ?? "").trim();
|
|
409
|
-
return lines.filter((line, i) => !POINTER.test(line.trim())
|
|
409
|
+
return lines.filter((line, i) => !(POINTER.test(line.trim()) || POINTER.test((lines[i + 1] ?? "").trim()))).join(" ").replace(/\s+/g, " ").trim();
|
|
410
410
|
};
|
|
411
411
|
async function langiumParseAny(type, content) {
|
|
412
412
|
switch (type) {
|
|
@@ -437,80 +437,7 @@ async function extractLangiumStats(content, keyword) {
|
|
|
437
437
|
if (!mapped)
|
|
438
438
|
throw new Error(`not a langium keyword: ${keyword}`);
|
|
439
439
|
const ast = await langiumParseAny(mapped, content);
|
|
440
|
-
const nodeIds = [];
|
|
441
|
-
const subgraphNames = [];
|
|
442
|
-
let edges = 0;
|
|
443
|
-
let maxDepth = 0;
|
|
444
|
-
let treeDepth = 0;
|
|
445
|
-
switch (mapped) {
|
|
446
|
-
case "architecture": {
|
|
447
|
-
const services = ast.services ?? [];
|
|
448
|
-
const groups = ast.groups ?? [];
|
|
449
|
-
const edgeArr = ast.edges ?? [];
|
|
450
|
-
nodeIds.push(...services.map((s) => s.id ?? ""));
|
|
451
|
-
subgraphNames.push(...groups.map((g) => g.title ?? g.id ?? ""));
|
|
452
|
-
maxDepth = architectureDepth(groups);
|
|
453
|
-
edges = edgeArr.length;
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
case "pie": {
|
|
457
|
-
const sections = ast.sections ?? [];
|
|
458
|
-
nodeIds.push(...sections.map((s, i) => s.label ?? `slice_${i}`));
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
case "packet": {
|
|
462
|
-
const blocks = ast.blocks ?? [];
|
|
463
|
-
nodeIds.push(...blocks.map((_, i) => `block_${i}`));
|
|
464
|
-
break;
|
|
465
|
-
}
|
|
466
|
-
case "gitGraph": {
|
|
467
|
-
const statements = ast.statements ?? [];
|
|
468
|
-
for (const s of statements) {
|
|
469
|
-
if (s.$type === "Commit")
|
|
470
|
-
nodeIds.push(s.id ?? `commit_${nodeIds.length}`);
|
|
471
|
-
else if (s.$type === "Branch")
|
|
472
|
-
subgraphNames.push(s.name ?? `branch_${subgraphNames.length}`);
|
|
473
|
-
else if (s.$type === "Merge")
|
|
474
|
-
edges++;
|
|
475
|
-
}
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
case "radar": {
|
|
479
|
-
const axes = ast.axes ?? [];
|
|
480
|
-
const curves = ast.curves ?? [];
|
|
481
|
-
nodeIds.push(...axes.map((a, i) => a.name ?? `axis_${i}`));
|
|
482
|
-
nodeIds.push(...curves.map((c, i) => c.name ?? `curve_${i}`));
|
|
483
|
-
edges = axes.length * curves.length;
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
486
|
-
case "treeView": {
|
|
487
|
-
const nodes = ast.nodes ?? [];
|
|
488
|
-
const stats = indentTreeStats(nodes.map((n, i) => ({ label: n.name ?? `node_${i}`, indent: n.indent ?? 0 })));
|
|
489
|
-
nodeIds.push(...stats.labels);
|
|
490
|
-
edges = stats.edges;
|
|
491
|
-
treeDepth = stats.depth;
|
|
492
|
-
break;
|
|
493
|
-
}
|
|
494
|
-
case "treemap": {
|
|
495
|
-
const rows = ast.TreemapRows ?? [];
|
|
496
|
-
const stats = indentTreeStats(rows.map((r, i) => ({ label: r.item?.name ?? `row_${i}`, indent: r.indent ?? 0 })));
|
|
497
|
-
nodeIds.push(...stats.labels);
|
|
498
|
-
edges = stats.edges;
|
|
499
|
-
treeDepth = stats.depth;
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
502
|
-
case "wardley": {
|
|
503
|
-
const components = ast.components ?? [];
|
|
504
|
-
const anchors = ast.anchors ?? [];
|
|
505
|
-
const links = ast.links ?? [];
|
|
506
|
-
nodeIds.push(...anchors.map((a, i) => a.name ?? `anchor_${i}`));
|
|
507
|
-
nodeIds.push(...components.map((c, i) => c.name ?? `comp_${i}`));
|
|
508
|
-
edges = links.length;
|
|
509
|
-
break;
|
|
510
|
-
}
|
|
511
|
-
case "info":
|
|
512
|
-
break;
|
|
513
|
-
}
|
|
440
|
+
const { nodeIds, edges, subgraphNames, maxDepth, treeDepth = 0 } = LANGIUM_ADAPTERS[mapped](ast);
|
|
514
441
|
return {
|
|
515
442
|
nodes: new Set(nodeIds.filter(Boolean)).size,
|
|
516
443
|
edges,
|
|
@@ -524,6 +451,85 @@ async function extractLangiumStats(content, keyword) {
|
|
|
524
451
|
error: null
|
|
525
452
|
};
|
|
526
453
|
}
|
|
454
|
+
var LANGIUM_ADAPTERS = {
|
|
455
|
+
architecture: adaptLangiumArchitecture,
|
|
456
|
+
info: adaptLangiumInfo,
|
|
457
|
+
pie: adaptLangiumPie,
|
|
458
|
+
gitGraph: adaptLangiumGitGraph,
|
|
459
|
+
packet: adaptLangiumPacket,
|
|
460
|
+
radar: adaptLangiumRadar,
|
|
461
|
+
treemap: adaptLangiumTreemap,
|
|
462
|
+
treeView: adaptLangiumTreeView,
|
|
463
|
+
wardley: adaptLangiumWardley
|
|
464
|
+
};
|
|
465
|
+
function adaptLangiumArchitecture(ast) {
|
|
466
|
+
const services = ast.services ?? [];
|
|
467
|
+
const groups = ast.groups ?? [];
|
|
468
|
+
const edgeArr = ast.edges ?? [];
|
|
469
|
+
return {
|
|
470
|
+
nodeIds: services.map((s) => s.id ?? ""),
|
|
471
|
+
edges: edgeArr.length,
|
|
472
|
+
subgraphNames: groups.map((g) => g.title ?? g.id ?? ""),
|
|
473
|
+
maxDepth: architectureDepth(groups)
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function adaptLangiumInfo() {
|
|
477
|
+
return { nodeIds: [], edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
478
|
+
}
|
|
479
|
+
function adaptLangiumPie(ast) {
|
|
480
|
+
const sections = ast.sections ?? [];
|
|
481
|
+
return { nodeIds: sections.map((s, i) => s.label ?? `slice_${i}`), edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
482
|
+
}
|
|
483
|
+
function adaptLangiumPacket(ast) {
|
|
484
|
+
const blocks = ast.blocks ?? [];
|
|
485
|
+
return { nodeIds: blocks.map((_, i) => `block_${i}`), edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
486
|
+
}
|
|
487
|
+
function adaptLangiumGitGraph(ast) {
|
|
488
|
+
const statements = ast.statements ?? [];
|
|
489
|
+
const nodeIds = [];
|
|
490
|
+
const subgraphNames = [];
|
|
491
|
+
let edges = 0;
|
|
492
|
+
for (const s of statements) {
|
|
493
|
+
if (s.$type === "Commit")
|
|
494
|
+
nodeIds.push(s.id ?? `commit_${nodeIds.length}`);
|
|
495
|
+
else if (s.$type === "Branch")
|
|
496
|
+
subgraphNames.push(s.name ?? `branch_${subgraphNames.length}`);
|
|
497
|
+
else if (s.$type === "Merge")
|
|
498
|
+
edges++;
|
|
499
|
+
}
|
|
500
|
+
return { nodeIds, edges, subgraphNames, maxDepth: 0 };
|
|
501
|
+
}
|
|
502
|
+
function adaptLangiumRadar(ast) {
|
|
503
|
+
const axes = ast.axes ?? [];
|
|
504
|
+
const curves = ast.curves ?? [];
|
|
505
|
+
return {
|
|
506
|
+
nodeIds: [...axes.map((a, i) => a.name ?? `axis_${i}`), ...curves.map((c, i) => c.name ?? `curve_${i}`)],
|
|
507
|
+
edges: axes.length * curves.length,
|
|
508
|
+
subgraphNames: [],
|
|
509
|
+
maxDepth: 0
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function adaptLangiumTreeView(ast) {
|
|
513
|
+
const nodes = ast.nodes ?? [];
|
|
514
|
+
const stats = indentTreeStats(nodes.map((n, i) => ({ label: n.name ?? `node_${i}`, indent: n.indent ?? 0 })));
|
|
515
|
+
return { nodeIds: stats.labels, edges: stats.edges, subgraphNames: [], maxDepth: 0, treeDepth: stats.depth };
|
|
516
|
+
}
|
|
517
|
+
function adaptLangiumTreemap(ast) {
|
|
518
|
+
const rows = ast.TreemapRows ?? [];
|
|
519
|
+
const stats = indentTreeStats(rows.map((r, i) => ({ label: r.item?.name ?? `row_${i}`, indent: r.indent ?? 0 })));
|
|
520
|
+
return { nodeIds: stats.labels, edges: stats.edges, subgraphNames: [], maxDepth: 0, treeDepth: stats.depth };
|
|
521
|
+
}
|
|
522
|
+
function adaptLangiumWardley(ast) {
|
|
523
|
+
const components = ast.components ?? [];
|
|
524
|
+
const anchors = ast.anchors ?? [];
|
|
525
|
+
const links = ast.links ?? [];
|
|
526
|
+
return {
|
|
527
|
+
nodeIds: [...anchors.map((a, i) => a.name ?? `anchor_${i}`), ...components.map((c, i) => c.name ?? `comp_${i}`)],
|
|
528
|
+
edges: links.length,
|
|
529
|
+
subgraphNames: [],
|
|
530
|
+
maxDepth: 0
|
|
531
|
+
};
|
|
532
|
+
}
|
|
527
533
|
function architectureDepth(groups) {
|
|
528
534
|
const parentOf = new Map;
|
|
529
535
|
for (const g of groups)
|
|
@@ -947,7 +953,7 @@ var buildDocModel = (src, kind = "markdown") => {
|
|
|
947
953
|
collectMermaid(tree, fences);
|
|
948
954
|
const diagrams = fences.flatMap((node, index) => {
|
|
949
955
|
const { start, end } = node.position ?? {};
|
|
950
|
-
if (!start
|
|
956
|
+
if (!(start && end) || start.offset === undefined || end.offset === undefined)
|
|
951
957
|
return [];
|
|
952
958
|
return [
|
|
953
959
|
makeDiagram(src, lineStarts, {
|
|
@@ -1014,72 +1020,71 @@ var scorePair = (kind, d, fg, bg) => {
|
|
|
1014
1020
|
};
|
|
1015
1021
|
};
|
|
1016
1022
|
var message = (err) => err instanceof Error ? err.message : String(err);
|
|
1017
|
-
var
|
|
1023
|
+
var skip = (out, d, reason, blocking) => {
|
|
1024
|
+
out.skipped.push({ selector: d.selector, line: d.line, reason, ...blocking && { blocking } });
|
|
1025
|
+
};
|
|
1026
|
+
var scoreEdgeLabel = (d, canvas, out) => {
|
|
1018
1027
|
const { color } = d.properties;
|
|
1019
1028
|
if (!color)
|
|
1020
1029
|
return;
|
|
1021
1030
|
try {
|
|
1022
|
-
pairs.push(...THEMES.map((t) => scoreHostPair("text", d, t, color, edgeLabelBackground(t, canvas))));
|
|
1031
|
+
out.pairs.push(...THEMES.map((t) => scoreHostPair("text", d, t, { fg: color, bg: edgeLabelBackground(t, canvas) })));
|
|
1023
1032
|
} catch (err) {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1033
|
+
skip(out, d, `edge label text unparseable: ${message(err)}`, true);
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
var githubBoxes = (fill, alpha, canvas) => alpha < 1 ? THEMES.map((t) => [compositeOver(fill, canvas[t]), t]) : [[fill, undefined]];
|
|
1037
|
+
var scoreGithubPair = (out, d, kind, { fg, bg, theme }) => {
|
|
1038
|
+
try {
|
|
1039
|
+
out.pairs.push({ ...scorePair(kind, d, fg, bg), ...theme && { theme } });
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
skip(out, d, `${kind} pair unparseable: ${message(err)}`, true);
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
var fillAlpha = (out, d, fill) => {
|
|
1045
|
+
try {
|
|
1046
|
+
return alphaOf(fill);
|
|
1047
|
+
} catch (err) {
|
|
1048
|
+
skip(out, d, `fill unparseable: ${message(err)}`, true);
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
var scoreGithubStyle = (d, canvas, out) => {
|
|
1053
|
+
const { fill, color, stroke } = d.properties;
|
|
1054
|
+
if (!fill) {
|
|
1055
|
+
const reason = color || stroke ? "no fill declared, so text and border contrast have no anchor" : "no color properties";
|
|
1056
|
+
skip(out, d, reason);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const alpha = fillAlpha(out, d, fill);
|
|
1060
|
+
if (alpha === null)
|
|
1061
|
+
return;
|
|
1062
|
+
if (alpha === 0) {
|
|
1063
|
+
skip(out, d, "fill is fully transparent, so there is no box to anchor contrast");
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const boxes = githubBoxes(fill, alpha, canvas);
|
|
1067
|
+
if (!color) {
|
|
1068
|
+
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);
|
|
1069
|
+
}
|
|
1070
|
+
for (const [bg, theme] of boxes) {
|
|
1071
|
+
if (color)
|
|
1072
|
+
scoreGithubPair(out, d, "text", { fg: color, bg, theme });
|
|
1073
|
+
if (stroke)
|
|
1074
|
+
scoreGithubPair(out, d, "border", { fg: stroke, bg, theme });
|
|
1030
1075
|
}
|
|
1031
1076
|
};
|
|
1032
1077
|
var scoreGithub = (directives, canvas = GITHUB_CANVAS) => {
|
|
1033
|
-
const
|
|
1034
|
-
const skipped = [];
|
|
1078
|
+
const out = { pairs: [], skipped: [] };
|
|
1035
1079
|
for (const d of directives) {
|
|
1036
1080
|
if (d.kind === "linkStyle")
|
|
1037
|
-
scoreEdgeLabel(d, canvas,
|
|
1038
|
-
if (d.kind
|
|
1039
|
-
|
|
1040
|
-
const { fill, color, stroke } = d.properties;
|
|
1041
|
-
const note = (reason, blocking) => {
|
|
1042
|
-
skipped.push({ selector: d.selector, line: d.line, reason, ...blocking && { blocking } });
|
|
1043
|
-
};
|
|
1044
|
-
if (!fill) {
|
|
1045
|
-
note(color || stroke ? "no fill declared, so text and border contrast have no anchor" : "no color properties");
|
|
1046
|
-
continue;
|
|
1047
|
-
}
|
|
1048
|
-
let alpha;
|
|
1049
|
-
try {
|
|
1050
|
-
alpha = alphaOf(fill);
|
|
1051
|
-
} catch (err) {
|
|
1052
|
-
note(`fill unparseable: ${message(err)}`, true);
|
|
1053
|
-
continue;
|
|
1054
|
-
}
|
|
1055
|
-
if (alpha === 0) {
|
|
1056
|
-
note("fill is fully transparent, so there is no box to anchor contrast");
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
const boxes = alpha < 1 ? THEMES.map((t) => [compositeOver(fill, canvas[t]), t]) : [[fill, undefined]];
|
|
1060
|
-
if (!color) {
|
|
1061
|
-
note("no color: declared beside fill:, and the github profile requires one " + "(if the host forces label text, use the mkdocs-material profile)", true);
|
|
1062
|
-
}
|
|
1063
|
-
for (const [box, theme] of boxes) {
|
|
1064
|
-
if (color) {
|
|
1065
|
-
try {
|
|
1066
|
-
pairs.push({ ...scorePair("text", d, color, box), ...theme && { theme } });
|
|
1067
|
-
} catch (err) {
|
|
1068
|
-
note(`text pair unparseable: ${message(err)}`, true);
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
if (stroke) {
|
|
1072
|
-
try {
|
|
1073
|
-
pairs.push({ ...scorePair("border", d, stroke, box), ...theme && { theme } });
|
|
1074
|
-
} catch (err) {
|
|
1075
|
-
note(`border pair unparseable: ${message(err)}`, true);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1081
|
+
scoreEdgeLabel(d, canvas, out);
|
|
1082
|
+
if (d.kind === "classDef" || d.kind === "style")
|
|
1083
|
+
scoreGithubStyle(d, canvas, out);
|
|
1079
1084
|
}
|
|
1080
|
-
return
|
|
1085
|
+
return out;
|
|
1081
1086
|
};
|
|
1082
|
-
var scoreHostPair = (kind, d, theme, fgRaw, box) => {
|
|
1087
|
+
var scoreHostPair = (kind, d, theme, { fg: fgRaw, bg: box }) => {
|
|
1083
1088
|
const fg = compositeOver(fgRaw, box);
|
|
1084
1089
|
const assessment = wcagAssess(fg, box);
|
|
1085
1090
|
return {
|
|
@@ -1095,99 +1100,83 @@ var scoreHostPair = (kind, d, theme, fgRaw, box) => {
|
|
|
1095
1100
|
advisory: kind === "border"
|
|
1096
1101
|
};
|
|
1097
1102
|
};
|
|
1098
|
-
var
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
skipped.push({ selector: d.selector, line: d.line, reason });
|
|
1114
|
-
};
|
|
1115
|
-
if (!fill) {
|
|
1116
|
-
note("no fill declared, and the host theme controls the text");
|
|
1117
|
-
continue;
|
|
1103
|
+
var scoreMkdocsStyle = (d, out) => {
|
|
1104
|
+
const { fill, stroke, color } = d.properties;
|
|
1105
|
+
if (!fill) {
|
|
1106
|
+
skip(out, d, "no fill declared, and the host theme controls the text");
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
if (color)
|
|
1110
|
+
skip(out, d, "color: ignored, because the host theme forces label text");
|
|
1111
|
+
for (const theme of THEMES) {
|
|
1112
|
+
let box;
|
|
1113
|
+
try {
|
|
1114
|
+
box = compositeOver(fill, MKDOCS_MATERIAL[theme].bg);
|
|
1115
|
+
} catch (err) {
|
|
1116
|
+
skip(out, d, `fill unparseable: ${message(err)}`, true);
|
|
1117
|
+
break;
|
|
1118
1118
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
for (const theme of THEMES) {
|
|
1122
|
-
let box;
|
|
1119
|
+
out.pairs.push(scoreHostPair("text", d, theme, { fg: MKDOCS_MATERIAL[theme].text, bg: box }));
|
|
1120
|
+
if (stroke) {
|
|
1123
1121
|
try {
|
|
1124
|
-
|
|
1122
|
+
out.pairs.push(scoreHostPair("border", d, theme, { fg: stroke, bg: box }));
|
|
1125
1123
|
} catch (err) {
|
|
1126
|
-
|
|
1127
|
-
selector: d.selector,
|
|
1128
|
-
line: d.line,
|
|
1129
|
-
reason: `fill unparseable: ${message(err)}`,
|
|
1130
|
-
blocking: true
|
|
1131
|
-
});
|
|
1132
|
-
break;
|
|
1133
|
-
}
|
|
1134
|
-
pairs.push(scoreHostPair("text", d, theme, MKDOCS_MATERIAL[theme].text, box));
|
|
1135
|
-
if (stroke) {
|
|
1136
|
-
try {
|
|
1137
|
-
pairs.push(scoreHostPair("border", d, theme, stroke, box));
|
|
1138
|
-
} catch (err) {
|
|
1139
|
-
skipped.push({
|
|
1140
|
-
selector: d.selector,
|
|
1141
|
-
line: d.line,
|
|
1142
|
-
reason: `border pair unparseable: ${message(err)}`,
|
|
1143
|
-
blocking: true
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1124
|
+
skip(out, d, `border pair unparseable: ${message(err)}`, true);
|
|
1146
1125
|
}
|
|
1147
1126
|
}
|
|
1148
1127
|
}
|
|
1149
|
-
|
|
1128
|
+
};
|
|
1129
|
+
var scoreMkdocs = (directives) => {
|
|
1130
|
+
const out = { pairs: [], skipped: [] };
|
|
1131
|
+
for (const d of directives) {
|
|
1132
|
+
if (d.kind === "linkStyle" && d.properties.color) {
|
|
1133
|
+
skip(out, d, "color: ignored, because the host theme forces edge label text");
|
|
1134
|
+
}
|
|
1135
|
+
if (d.kind === "classDef" || d.kind === "style")
|
|
1136
|
+
scoreMkdocsStyle(d, out);
|
|
1137
|
+
}
|
|
1138
|
+
return out;
|
|
1139
|
+
};
|
|
1140
|
+
var erRows = (theme, fill) => {
|
|
1141
|
+
const anchor = MERMAID_ER[theme];
|
|
1142
|
+
return [
|
|
1143
|
+
["odd", anchor.odd],
|
|
1144
|
+
["even", fill ? compositeOver(fill, anchor.even) : anchor.even]
|
|
1145
|
+
];
|
|
1146
|
+
};
|
|
1147
|
+
var erPairs = (d) => {
|
|
1148
|
+
const { fill, color, stroke } = d.properties;
|
|
1149
|
+
const scored = [];
|
|
1150
|
+
const score = (kind, theme, row, colours) => {
|
|
1151
|
+
scored.push({ ...scoreHostPair(kind, d, theme, colours), row, advisory: kind === "border" });
|
|
1152
|
+
};
|
|
1153
|
+
for (const theme of THEMES) {
|
|
1154
|
+
for (const [row, bg] of erRows(theme, fill)) {
|
|
1155
|
+
if (color || row === "even" && fill)
|
|
1156
|
+
score("text", theme, row, { fg: color ?? MERMAID_ER[theme].label, bg });
|
|
1157
|
+
if (stroke)
|
|
1158
|
+
score("border", theme, row, { fg: stroke, bg });
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return scored;
|
|
1150
1162
|
};
|
|
1151
1163
|
var scoreEr = (directives) => {
|
|
1152
|
-
const
|
|
1153
|
-
const skipped = [];
|
|
1164
|
+
const out = { pairs: [], skipped: [] };
|
|
1154
1165
|
for (const d of directives) {
|
|
1155
1166
|
if (d.kind !== "classDef" && d.kind !== "style")
|
|
1156
1167
|
continue;
|
|
1157
1168
|
const { fill, color, stroke } = d.properties;
|
|
1158
|
-
if (!fill
|
|
1159
|
-
|
|
1169
|
+
if (!(fill || color || stroke)) {
|
|
1170
|
+
skip(out, d, "no color properties");
|
|
1160
1171
|
continue;
|
|
1161
1172
|
}
|
|
1162
|
-
const scored = [];
|
|
1163
|
-
const score = (kind, theme, row, fg, bg) => {
|
|
1164
|
-
scored.push({ ...scoreHostPair(kind, d, theme, fg, bg), row, advisory: kind === "border" });
|
|
1165
|
-
};
|
|
1166
1173
|
try {
|
|
1167
|
-
|
|
1168
|
-
const anchor = MERMAID_ER[theme];
|
|
1169
|
-
const rows = [
|
|
1170
|
-
["odd", anchor.odd],
|
|
1171
|
-
["even", fill ? compositeOver(fill, anchor.even) : anchor.even]
|
|
1172
|
-
];
|
|
1173
|
-
for (const [row, bg] of rows) {
|
|
1174
|
-
if (color || row === "even" && fill)
|
|
1175
|
-
score("text", theme, row, color ?? anchor.label, bg);
|
|
1176
|
-
if (stroke)
|
|
1177
|
-
score("border", theme, row, stroke, bg);
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
pairs.push(...scored);
|
|
1174
|
+
out.pairs.push(...erPairs(d));
|
|
1181
1175
|
} catch (err) {
|
|
1182
|
-
|
|
1183
|
-
selector: d.selector,
|
|
1184
|
-
line: d.line,
|
|
1185
|
-
reason: `erDiagram pair unparseable: ${message(err)}`,
|
|
1186
|
-
blocking: true
|
|
1187
|
-
});
|
|
1176
|
+
skip(out, d, `erDiagram pair unparseable: ${message(err)}`, true);
|
|
1188
1177
|
}
|
|
1189
1178
|
}
|
|
1190
|
-
return
|
|
1179
|
+
return out;
|
|
1191
1180
|
};
|
|
1192
1181
|
var scoreForProfile = (directives, profile, keyword, canvas = GITHUB_CANVAS) => {
|
|
1193
1182
|
if (profile === "mkdocs-material")
|
|
@@ -1229,9 +1218,7 @@ var themeVariable = (selector, line) => ({
|
|
|
1229
1218
|
spans: {},
|
|
1230
1219
|
line
|
|
1231
1220
|
});
|
|
1232
|
-
var
|
|
1233
|
-
const pairs = [];
|
|
1234
|
-
const skipped = [];
|
|
1221
|
+
var sequenceLines = (source) => {
|
|
1235
1222
|
const lines = source.split(`
|
|
1236
1223
|
`);
|
|
1237
1224
|
const keywordLine = lines.findIndex((l) => /^\s*sequenceDiagram\b/.test(l)) + 1;
|
|
@@ -1239,94 +1226,125 @@ var scoreSequence = async (source, profile, canvas = GITHUB_CANVAS) => {
|
|
|
1239
1226
|
const found = lines.findLastIndex((l) => new RegExp(`\\b${key}["']?\\s*:`).test(l));
|
|
1240
1227
|
return found < 0 ? keywordLine : found + 1;
|
|
1241
1228
|
};
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
let db;
|
|
1229
|
+
return { lineOf, rectLines: lines.flatMap((l, i) => /^\s*rect\s/.test(l) ? [i + 1] : []) };
|
|
1230
|
+
};
|
|
1231
|
+
var parseSequence = async (mermaid, source) => {
|
|
1246
1232
|
try {
|
|
1247
|
-
config =
|
|
1248
|
-
db =
|
|
1233
|
+
const { config } = await mermaid.parse(source);
|
|
1234
|
+
const { db } = await mermaid.mermaidAPI.getDiagramFromText(source);
|
|
1235
|
+
return { config, db };
|
|
1249
1236
|
} catch {
|
|
1250
|
-
return
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
var unmodelled = (config) => {
|
|
1241
|
+
if (config.themeCSS !== undefined) {
|
|
1242
|
+
return { selector: "themeCSS", reason: "themeCSS restyles the diagram with CSS the audit cannot model" };
|
|
1251
1243
|
}
|
|
1252
|
-
const block = (selector, reason) => {
|
|
1253
|
-
skipped.push({ selector, line: lineOf(selector), reason, blocking: true });
|
|
1254
|
-
return { pairs, skipped };
|
|
1255
|
-
};
|
|
1256
|
-
if (config.themeCSS !== undefined)
|
|
1257
|
-
return block("themeCSS", "themeCSS restyles the diagram with CSS the audit cannot model");
|
|
1258
1244
|
const pinned = config.theme;
|
|
1259
1245
|
if (pinned !== undefined && !SEQUENCE_THEMES.includes(String(pinned))) {
|
|
1260
|
-
return
|
|
1246
|
+
return {
|
|
1247
|
+
selector: "theme",
|
|
1248
|
+
reason: `the ${String(pinned)} theme is not modelled, only ${SEQUENCE_THEMES.join(", ")}`
|
|
1249
|
+
};
|
|
1261
1250
|
}
|
|
1251
|
+
return null;
|
|
1252
|
+
};
|
|
1253
|
+
var sequenceThemes = (pinned, host) => {
|
|
1254
|
+
const theme = (fallback) => pinned === undefined ? fallback : String(pinned);
|
|
1255
|
+
return { light: theme("default"), dark: theme(host ? "default" : "dark") };
|
|
1256
|
+
};
|
|
1257
|
+
var sequencePair = ({ theme: t, page, vars, host, lineOf, out }) => (kind, fgKey, bg, { suffix = "", at } = {}) => {
|
|
1258
|
+
const color = (key) => {
|
|
1259
|
+
const value = vars[key];
|
|
1260
|
+
if (typeof value !== "string")
|
|
1261
|
+
throw new Error(`${key} is not a resolved colour`);
|
|
1262
|
+
return value;
|
|
1263
|
+
};
|
|
1264
|
+
const selector = fgKey + suffix;
|
|
1265
|
+
const line = at ?? lineOf(fgKey);
|
|
1266
|
+
try {
|
|
1267
|
+
const box = compositeOver("key" in bg ? color(bg.key) : bg.colour, page);
|
|
1268
|
+
out.pairs.push({
|
|
1269
|
+
...scoreHostPair(kind, themeVariable(selector, line), t, { fg: color(fgKey), bg: box }),
|
|
1270
|
+
advisory: host
|
|
1271
|
+
});
|
|
1272
|
+
} catch (err) {
|
|
1273
|
+
out.skipped.push({ selector, line, reason: `${t} sequence pair unparseable: ${message(err)}`, blocking: true });
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
var scoreHostSequence = (pair, page, features) => {
|
|
1277
|
+
const onPage = { colour: page };
|
|
1278
|
+
pair("border", "actorLineColor", onPage);
|
|
1279
|
+
pair("border", "signalColor", onPage, { suffix: " (arrowhead)" });
|
|
1280
|
+
if (features.activation)
|
|
1281
|
+
pair("border", "activationBorderColor", { key: "activationBkgColor" });
|
|
1282
|
+
};
|
|
1283
|
+
var scoreAuthorSequence = (pair, page, features, rectLines) => {
|
|
1284
|
+
const onPage = { colour: page };
|
|
1285
|
+
pair("text", "actorTextColor", { key: "actorBkg" });
|
|
1286
|
+
pair("border", "actorBorder", { key: "actorBkg" });
|
|
1287
|
+
if (features.humanActor) {
|
|
1288
|
+
pair("text", "actorTextColor", onPage, { suffix: " (human actor label)" });
|
|
1289
|
+
pair("border", "actorBorder", onPage, { suffix: " (human actor glyph)" });
|
|
1290
|
+
}
|
|
1291
|
+
pair("border", "actorLineColor", onPage);
|
|
1292
|
+
pair("text", "signalTextColor", onPage);
|
|
1293
|
+
pair("border", "signalColor", onPage);
|
|
1294
|
+
if (features.note)
|
|
1295
|
+
pair("text", "noteTextColor", { key: "noteBkgColor" });
|
|
1296
|
+
if (features.frame) {
|
|
1297
|
+
pair("text", "labelTextColor", { key: "labelBoxBkgColor" });
|
|
1298
|
+
pair("text", "loopTextColor", onPage);
|
|
1299
|
+
pair("border", "labelBoxBorderColor", onPage, { suffix: " (loop frame)" });
|
|
1300
|
+
}
|
|
1301
|
+
if (features.activation)
|
|
1302
|
+
pair("border", "activationBorderColor", { key: "activationBkgColor" });
|
|
1303
|
+
if (features.numbered)
|
|
1304
|
+
pair("text", "sequenceNumberColor", { key: "signalColor" });
|
|
1305
|
+
for (const [i, rect] of features.rects.entries()) {
|
|
1306
|
+
const fill = { colour: rect };
|
|
1307
|
+
const where = { suffix: " (rect)", at: rectLines[i] };
|
|
1308
|
+
pair("text", "signalTextColor", fill, where);
|
|
1309
|
+
pair("border", "signalColor", fill, where);
|
|
1310
|
+
if (features.frame)
|
|
1311
|
+
pair("text", "loopTextColor", fill, where);
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
var scoreSequence = async (source, profile, canvas = GITHUB_CANVAS) => {
|
|
1315
|
+
const out = { pairs: [], skipped: [] };
|
|
1316
|
+
const { lineOf, rectLines } = sequenceLines(source);
|
|
1317
|
+
const mermaid = await getMermaidCore();
|
|
1318
|
+
const parsed = await parseSequence(mermaid, source);
|
|
1319
|
+
if (!parsed)
|
|
1320
|
+
return out;
|
|
1321
|
+
const { config, db } = parsed;
|
|
1322
|
+
const block = (selector, reason) => {
|
|
1323
|
+
out.skipped.push({ selector, line: lineOf(selector), reason, blocking: true });
|
|
1324
|
+
return out;
|
|
1325
|
+
};
|
|
1326
|
+
const problem = unmodelled(config);
|
|
1327
|
+
if (problem)
|
|
1328
|
+
return block(problem.selector, problem.reason);
|
|
1262
1329
|
const features = sequenceFeatures(db);
|
|
1263
1330
|
const host = profile === "mkdocs-material";
|
|
1264
|
-
const theme = (fallback) => pinned === undefined ? fallback : String(pinned);
|
|
1265
1331
|
let variables;
|
|
1266
1332
|
try {
|
|
1267
|
-
variables = resolveVariables(mermaid, config,
|
|
1333
|
+
variables = resolveVariables(mermaid, config, sequenceThemes(config.theme, host));
|
|
1268
1334
|
} catch (err) {
|
|
1269
1335
|
return block("themeVariables", `Mermaid could not resolve the theme: ${message(err)}`);
|
|
1270
1336
|
}
|
|
1271
1337
|
for (const t of THEMES) {
|
|
1272
1338
|
const page = host ? MKDOCS_MATERIAL[t].bg : canvas[t];
|
|
1273
|
-
const
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
return value;
|
|
1279
|
-
};
|
|
1280
|
-
const pair = (kind, fgKey, bg, suffix = "", at) => {
|
|
1281
|
-
const selector = fgKey + suffix;
|
|
1282
|
-
const line = at ?? lineOf(fgKey);
|
|
1283
|
-
try {
|
|
1284
|
-
const box = compositeOver("key" in bg ? color(bg.key) : bg.colour, page);
|
|
1285
|
-
pairs.push({ ...scoreHostPair(kind, themeVariable(selector, line), t, color(fgKey), box), advisory: host });
|
|
1286
|
-
} catch (err) {
|
|
1287
|
-
skipped.push({ selector, line, reason: `${t} sequence pair unparseable: ${message(err)}`, blocking: true });
|
|
1288
|
-
}
|
|
1289
|
-
};
|
|
1290
|
-
const onPage = { colour: page };
|
|
1291
|
-
if (host) {
|
|
1292
|
-
pair("border", "actorLineColor", onPage);
|
|
1293
|
-
pair("border", "signalColor", onPage, " (arrowhead)");
|
|
1294
|
-
if (features.activation)
|
|
1295
|
-
pair("border", "activationBorderColor", { key: "activationBkgColor" });
|
|
1296
|
-
continue;
|
|
1297
|
-
}
|
|
1298
|
-
pair("text", "actorTextColor", { key: "actorBkg" });
|
|
1299
|
-
pair("border", "actorBorder", { key: "actorBkg" });
|
|
1300
|
-
if (features.humanActor) {
|
|
1301
|
-
pair("text", "actorTextColor", onPage, " (human actor label)");
|
|
1302
|
-
pair("border", "actorBorder", onPage, " (human actor glyph)");
|
|
1303
|
-
}
|
|
1304
|
-
pair("border", "actorLineColor", onPage);
|
|
1305
|
-
pair("text", "signalTextColor", onPage);
|
|
1306
|
-
pair("border", "signalColor", onPage);
|
|
1307
|
-
if (features.note)
|
|
1308
|
-
pair("text", "noteTextColor", { key: "noteBkgColor" });
|
|
1309
|
-
if (features.frame) {
|
|
1310
|
-
pair("text", "labelTextColor", { key: "labelBoxBkgColor" });
|
|
1311
|
-
pair("text", "loopTextColor", onPage);
|
|
1312
|
-
pair("border", "labelBoxBorderColor", onPage, " (loop frame)");
|
|
1313
|
-
}
|
|
1314
|
-
if (features.activation)
|
|
1315
|
-
pair("border", "activationBorderColor", { key: "activationBkgColor" });
|
|
1316
|
-
if (features.numbered)
|
|
1317
|
-
pair("text", "sequenceNumberColor", { key: "signalColor" });
|
|
1318
|
-
for (const [i, rect] of features.rects.entries()) {
|
|
1319
|
-
const fill = { colour: rect };
|
|
1320
|
-
const at = rectLines[i];
|
|
1321
|
-
pair("text", "signalTextColor", fill, " (rect)", at);
|
|
1322
|
-
pair("border", "signalColor", fill, " (rect)", at);
|
|
1323
|
-
if (features.frame)
|
|
1324
|
-
pair("text", "loopTextColor", fill, " (rect)", at);
|
|
1325
|
-
}
|
|
1339
|
+
const pair = sequencePair({ theme: t, page, vars: variables[t], host, lineOf, out });
|
|
1340
|
+
if (host)
|
|
1341
|
+
scoreHostSequence(pair, page, features);
|
|
1342
|
+
else
|
|
1343
|
+
scoreAuthorSequence(pair, page, features, rectLines);
|
|
1326
1344
|
}
|
|
1327
|
-
return
|
|
1345
|
+
return out;
|
|
1328
1346
|
};
|
|
1329
|
-
var scoreDiagram = (source, directives,
|
|
1347
|
+
var scoreDiagram = ({ source, directives, keyword }, profile, canvas = GITHUB_CANVAS) => keyword === "sequenceDiagram" ? scoreSequence(source, profile, canvas) : Promise.resolve(scoreForProfile(directives, profile, keyword, canvas));
|
|
1330
1348
|
var detectProfile = (filePath) => {
|
|
1331
1349
|
let dir = dirname(resolve(filePath));
|
|
1332
1350
|
for (let i = 0;i < 20; i++) {
|
|
@@ -1418,7 +1436,7 @@ var canonicalPreset = (name) => {
|
|
|
1418
1436
|
var configFromPreset = (name) => {
|
|
1419
1437
|
const canonical = canonicalPreset(name);
|
|
1420
1438
|
const preset = canonical ? PRESETS[canonical] : undefined;
|
|
1421
|
-
if (!canonical
|
|
1439
|
+
if (!(canonical && preset)) {
|
|
1422
1440
|
throw new Error(`Unknown preset '${name}'. Valid: ${PRESET_NAMES.join(", ")}`);
|
|
1423
1441
|
}
|
|
1424
1442
|
return {
|
|
@@ -1497,7 +1515,7 @@ var measure = async (diagram, config) => {
|
|
|
1497
1515
|
return null;
|
|
1498
1516
|
return { stats, metrics: calculateComplexity(stats, config) };
|
|
1499
1517
|
};
|
|
1500
|
-
var finding = (ctx, diagram, rule, message2, remediation) => ({
|
|
1518
|
+
var finding = (ctx, diagram, rule, { message: message2, remediation }) => ({
|
|
1501
1519
|
file: ctx.file,
|
|
1502
1520
|
line: diagram.startLine,
|
|
1503
1521
|
endLine: diagram.endLine,
|
|
@@ -1548,7 +1566,7 @@ var contrastOf = (diagram, settings) => {
|
|
|
1548
1566
|
}
|
|
1549
1567
|
let result = bySettings.get(key);
|
|
1550
1568
|
if (!result) {
|
|
1551
|
-
result = scoreDiagram(diagram
|
|
1569
|
+
result = scoreDiagram(diagram, profile, canvas);
|
|
1552
1570
|
bySettings.set(key, result);
|
|
1553
1571
|
}
|
|
1554
1572
|
return result;
|
|
@@ -1570,7 +1588,7 @@ var complexityRule = (spec) => ({
|
|
|
1570
1588
|
continue;
|
|
1571
1589
|
const hit = spec.hit(measured, config);
|
|
1572
1590
|
if (hit)
|
|
1573
|
-
found.push(finding(ctx, diagram, spec.id, hit
|
|
1591
|
+
found.push(finding(ctx, diagram, spec.id, hit));
|
|
1574
1592
|
}
|
|
1575
1593
|
return found;
|
|
1576
1594
|
}
|
|
@@ -1715,6 +1733,30 @@ var contrastDirectiveUnauditable = {
|
|
|
1715
1733
|
var AA_NORMAL2 = 4.5;
|
|
1716
1734
|
var CANDIDATES = ["#000000", "#ffffff"];
|
|
1717
1735
|
var ratioOn = (fg, bg) => wcagRatio(compositeOver(fg, bg), bg);
|
|
1736
|
+
var remediationOf = (profile, kind) => {
|
|
1737
|
+
if (profile === "mkdocs-material") {
|
|
1738
|
+
return "The host theme forces the label text, so change the fill until the host text reads on it in both themes.";
|
|
1739
|
+
}
|
|
1740
|
+
if (kind === "themeVariables") {
|
|
1741
|
+
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.";
|
|
1742
|
+
}
|
|
1743
|
+
if (kind === "linkStyle") {
|
|
1744
|
+
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.";
|
|
1745
|
+
}
|
|
1746
|
+
return "Darken or lighten the fill, or change color:, until the ratio reaches 4.5:1 in every theme.";
|
|
1747
|
+
};
|
|
1748
|
+
var textFinding = (ctx, diagram, p, profile) => {
|
|
1749
|
+
const where = [p.theme, p.row].filter(Boolean).join("/");
|
|
1750
|
+
const line = fileLine(diagram, p.line);
|
|
1751
|
+
return {
|
|
1752
|
+
file: ctx.file,
|
|
1753
|
+
line,
|
|
1754
|
+
endLine: line,
|
|
1755
|
+
rule: RULE.TEXT,
|
|
1756
|
+
message: `${p.directive.kind} ${p.selector}: text ${p.assessment.ratio.toFixed(2)}:1 < ${AA_NORMAL2} ` + `(${p.foreground} on ${p.background}${where ? `, ${where}` : ""})`,
|
|
1757
|
+
remediation: remediationOf(profile, p.directive.kind)
|
|
1758
|
+
};
|
|
1759
|
+
};
|
|
1718
1760
|
var contrastTextBelowAa = {
|
|
1719
1761
|
id: RULE.TEXT,
|
|
1720
1762
|
category: "contrast",
|
|
@@ -1725,18 +1767,8 @@ var contrastTextBelowAa = {
|
|
|
1725
1767
|
const found = [];
|
|
1726
1768
|
for (const diagram of ctx.doc.diagrams) {
|
|
1727
1769
|
for (const p of (await contrastOf(diagram, settings)).pairs) {
|
|
1728
|
-
if (p.kind
|
|
1729
|
-
|
|
1730
|
-
const where = [p.theme, p.row].filter(Boolean).join("/");
|
|
1731
|
-
const line = fileLine(diagram, p.line);
|
|
1732
|
-
found.push({
|
|
1733
|
-
file: ctx.file,
|
|
1734
|
-
line,
|
|
1735
|
-
endLine: line,
|
|
1736
|
-
rule: RULE.TEXT,
|
|
1737
|
-
message: `${p.directive.kind} ${p.selector}: text ${p.assessment.ratio.toFixed(2)}:1 < ${AA_NORMAL2} ` + `(${p.foreground} on ${p.background}${where ? `, ${where}` : ""})`,
|
|
1738
|
-
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."
|
|
1739
|
-
});
|
|
1770
|
+
if (p.kind === "text" && !p.passes)
|
|
1771
|
+
found.push(textFinding(ctx, diagram, p, settings.profile));
|
|
1740
1772
|
}
|
|
1741
1773
|
}
|
|
1742
1774
|
return found;
|
|
@@ -1795,7 +1827,7 @@ var parseDiagramUnparseable = {
|
|
|
1795
1827
|
if (!parseFailed(diagram.source, stats))
|
|
1796
1828
|
continue;
|
|
1797
1829
|
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.";
|
|
1798
|
-
found.push(finding(ctx, diagram, RULE.UNPARSEABLE, messageOf(stats), remediation));
|
|
1830
|
+
found.push(finding(ctx, diagram, RULE.UNPARSEABLE, { message: messageOf(stats), remediation }));
|
|
1799
1831
|
}
|
|
1800
1832
|
return found;
|
|
1801
1833
|
}
|
|
@@ -1807,7 +1839,10 @@ var parseFenceUnterminated = {
|
|
|
1807
1839
|
id: RULE.FENCE_UNTERMINATED,
|
|
1808
1840
|
category: "parse",
|
|
1809
1841
|
summary: "a mermaid fence that is never closed",
|
|
1810
|
-
check: (ctx) => ctx.doc.diagrams.filter((d) => !d.terminated).map((d) => finding(ctx, d, RULE.FENCE_UNTERMINATED,
|
|
1842
|
+
check: (ctx) => ctx.doc.diagrams.filter((d) => !d.terminated).map((d) => finding(ctx, d, RULE.FENCE_UNTERMINATED, {
|
|
1843
|
+
message: `mermaid fence opened at line ${d.startLine} is never closed`,
|
|
1844
|
+
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."
|
|
1845
|
+
})),
|
|
1811
1846
|
fix: (ctx) => {
|
|
1812
1847
|
const edits = [];
|
|
1813
1848
|
const { src } = ctx.doc;
|
|
@@ -1904,20 +1939,20 @@ var checkCanvas = (where, rule, name, value) => {
|
|
|
1904
1939
|
}
|
|
1905
1940
|
}
|
|
1906
1941
|
};
|
|
1907
|
-
var
|
|
1908
|
-
const
|
|
1909
|
-
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
throw new RuleOptionError(where, rule.id, name, `rule "${rule.id}" has no option "${name}"; ${list}`);
|
|
1914
|
-
}
|
|
1915
|
-
if (typeOf(value) !== want) {
|
|
1916
|
-
throw new RuleOptionError(where, rule.id, name, `option "${name}" of "${rule.id}" must be ${describeType(want)}`);
|
|
1917
|
-
}
|
|
1918
|
-
if (want === "canvas")
|
|
1919
|
-
checkCanvas(where, rule, name, value);
|
|
1942
|
+
var checkOption = (where, rule, name, value) => {
|
|
1943
|
+
const want = rule.options?.[name];
|
|
1944
|
+
if (!want) {
|
|
1945
|
+
const accepted = Object.keys(rule.options ?? {});
|
|
1946
|
+
const list = accepted.length > 0 ? `it accepts ${accepted.join(", ")}` : "it takes no options";
|
|
1947
|
+
throw new RuleOptionError(where, rule.id, name, `rule "${rule.id}" has no option "${name}"; ${list}`);
|
|
1920
1948
|
}
|
|
1949
|
+
if (typeOf(value) !== want) {
|
|
1950
|
+
throw new RuleOptionError(where, rule.id, name, `option "${name}" of "${rule.id}" must be ${describeType(want)}`);
|
|
1951
|
+
}
|
|
1952
|
+
if (want === "canvas")
|
|
1953
|
+
checkCanvas(where, rule, name, value);
|
|
1954
|
+
};
|
|
1955
|
+
var checkSharedOptions = (where, rule, options) => {
|
|
1921
1956
|
if (options.profile === "mkdocs-material" && options.canvas !== undefined && rule.options?.canvas === "canvas") {
|
|
1922
1957
|
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`);
|
|
1923
1958
|
}
|
|
@@ -1928,10 +1963,12 @@ var checkOptions = (where, rule, options) => {
|
|
|
1928
1963
|
throw new RuleOptionError(where, rule.id, "profile", `option "profile" of "${rule.id}" must be one of ${PROFILES.join(", ")}`);
|
|
1929
1964
|
}
|
|
1930
1965
|
};
|
|
1931
|
-
var
|
|
1932
|
-
const
|
|
1933
|
-
|
|
1934
|
-
|
|
1966
|
+
var checkOptions = (where, rule, options) => {
|
|
1967
|
+
for (const [name, value] of Object.entries(options))
|
|
1968
|
+
checkOption(where, rule, name, value);
|
|
1969
|
+
checkSharedOptions(where, rule, options);
|
|
1970
|
+
};
|
|
1971
|
+
var applyConfig = (rules, config, ignoreUnknownPluginIds) => {
|
|
1935
1972
|
const off = new Set;
|
|
1936
1973
|
const options = new Map;
|
|
1937
1974
|
const ignored = [];
|
|
@@ -1950,6 +1987,13 @@ var buildRegistry = (input) => {
|
|
|
1950
1987
|
if (setting.severity === "off")
|
|
1951
1988
|
off.add(id);
|
|
1952
1989
|
}
|
|
1990
|
+
return { off, options, ignored };
|
|
1991
|
+
};
|
|
1992
|
+
var buildRegistry = (input) => {
|
|
1993
|
+
const { rules, fixOrder, categories, sources = [], config, ignoreUnknownPluginIds = false } = input;
|
|
1994
|
+
const selection = input.selection ?? {};
|
|
1995
|
+
checkSelection(selection, categories);
|
|
1996
|
+
const { off, options, ignored } = applyConfig(rules, config, ignoreUnknownPluginIds);
|
|
1953
1997
|
for (const rule of rules) {
|
|
1954
1998
|
if (selection.enable?.includes(rule.category))
|
|
1955
1999
|
off.delete(rule.id);
|
|
@@ -2002,6 +2046,17 @@ var checkRunFlags = (flags, prefix = "") => {
|
|
|
2002
2046
|
var canvasOptions = (rule) => Object.entries(rule.options ?? {}).filter(([, type]) => type === "canvas").map(([name]) => name);
|
|
2003
2047
|
var hasFlagCanvas = (flags) => THEMES.some((side) => flags.canvas?.[side] !== undefined);
|
|
2004
2048
|
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";
|
|
2049
|
+
var mergeCanvas = (current, canvas) => {
|
|
2050
|
+
const merged = {
|
|
2051
|
+
...typeof current === "object" && current !== null ? current : {}
|
|
2052
|
+
};
|
|
2053
|
+
for (const side of THEMES) {
|
|
2054
|
+
const value = canvas[side];
|
|
2055
|
+
if (value !== undefined)
|
|
2056
|
+
merged[side] = value;
|
|
2057
|
+
}
|
|
2058
|
+
return merged;
|
|
2059
|
+
};
|
|
2005
2060
|
var optionsFor = (rule, configured, flags, file) => {
|
|
2006
2061
|
const options = { ...configured };
|
|
2007
2062
|
for (const name of ["preset", "profile"]) {
|
|
@@ -2013,18 +2068,8 @@ var optionsFor = (rule, configured, flags, file) => {
|
|
|
2013
2068
|
options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
2014
2069
|
const canvas = flags.canvas;
|
|
2015
2070
|
if (canvas && hasFlagCanvas(flags)) {
|
|
2016
|
-
for (const name of canvasOptions(rule))
|
|
2017
|
-
|
|
2018
|
-
const merged = {
|
|
2019
|
-
...typeof current === "object" && current !== null ? current : {}
|
|
2020
|
-
};
|
|
2021
|
-
for (const side of THEMES) {
|
|
2022
|
-
const value = canvas[side];
|
|
2023
|
-
if (value !== undefined)
|
|
2024
|
-
merged[side] = value;
|
|
2025
|
-
}
|
|
2026
|
-
options[name] = merged;
|
|
2027
|
-
}
|
|
2071
|
+
for (const name of canvasOptions(rule))
|
|
2072
|
+
options[name] = mergeCanvas(options[name], canvas);
|
|
2028
2073
|
}
|
|
2029
2074
|
if (file !== undefined && setsAside(rule, options, flags, file)) {
|
|
2030
2075
|
for (const name of canvasOptions(rule))
|
|
@@ -2050,7 +2095,7 @@ import { parseArgs } from "node:util";
|
|
|
2050
2095
|
// package.json
|
|
2051
2096
|
var package_default = {
|
|
2052
2097
|
name: "@jpeakai/mermaidjs-lint",
|
|
2053
|
-
version: "0.7.
|
|
2098
|
+
version: "0.7.2",
|
|
2054
2099
|
type: "module",
|
|
2055
2100
|
description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
|
|
2056
2101
|
license: "MIT",
|
|
@@ -2126,6 +2171,7 @@ import { extname, join as join2, resolve as resolve2 } from "node:path";
|
|
|
2126
2171
|
var DIAGRAM_FILE_EXTS = new Set([...DIAGRAM_EXTS, ".md", ".markdown"]);
|
|
2127
2172
|
var SKIP_DIRS = new Set(["node_modules", "vendor", "dist", "build", "tmp", "__pycache__"]);
|
|
2128
2173
|
var skipDir = (name) => name.startsWith(".") || SKIP_DIRS.has(name);
|
|
2174
|
+
var isDiagramFile = (name) => DIAGRAM_FILE_EXTS.has(extname(name).toLowerCase());
|
|
2129
2175
|
var linksToDir = (path) => {
|
|
2130
2176
|
try {
|
|
2131
2177
|
return statSync(path).isDirectory();
|
|
@@ -2138,6 +2184,24 @@ var collectFiles = (paths) => {
|
|
|
2138
2184
|
const unreadable = [];
|
|
2139
2185
|
const symlinks = [];
|
|
2140
2186
|
const seen = new Set;
|
|
2187
|
+
const add = (abs) => {
|
|
2188
|
+
if (seen.has(abs))
|
|
2189
|
+
return;
|
|
2190
|
+
seen.add(abs);
|
|
2191
|
+
files.push(abs);
|
|
2192
|
+
};
|
|
2193
|
+
const visit = (dir, entry) => {
|
|
2194
|
+
const abs = join2(dir, entry.name);
|
|
2195
|
+
if (entry.isSymbolicLink()) {
|
|
2196
|
+
if (!skipDir(entry.name) && (isDiagramFile(entry.name) || linksToDir(abs)))
|
|
2197
|
+
symlinks.push(abs);
|
|
2198
|
+
} else if (entry.isDirectory()) {
|
|
2199
|
+
if (!skipDir(entry.name))
|
|
2200
|
+
walk(abs);
|
|
2201
|
+
} else if (entry.isFile() && isDiagramFile(entry.name)) {
|
|
2202
|
+
add(abs);
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2141
2205
|
const walk = (dir) => {
|
|
2142
2206
|
let entries;
|
|
2143
2207
|
try {
|
|
@@ -2146,22 +2210,8 @@ var collectFiles = (paths) => {
|
|
|
2146
2210
|
unreadable.push(dir);
|
|
2147
2211
|
return;
|
|
2148
2212
|
}
|
|
2149
|
-
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name)))
|
|
2150
|
-
|
|
2151
|
-
if (entry.isSymbolicLink()) {
|
|
2152
|
-
if (!skipDir(entry.name) && (DIAGRAM_FILE_EXTS.has(extname(entry.name).toLowerCase()) || linksToDir(abs))) {
|
|
2153
|
-
symlinks.push(abs);
|
|
2154
|
-
}
|
|
2155
|
-
} else if (entry.isDirectory()) {
|
|
2156
|
-
if (!skipDir(entry.name))
|
|
2157
|
-
walk(abs);
|
|
2158
|
-
} else if (entry.isFile() && DIAGRAM_FILE_EXTS.has(extname(entry.name).toLowerCase())) {
|
|
2159
|
-
if (!seen.has(abs)) {
|
|
2160
|
-
seen.add(abs);
|
|
2161
|
-
files.push(abs);
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2213
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name)))
|
|
2214
|
+
visit(dir, entry);
|
|
2165
2215
|
};
|
|
2166
2216
|
for (const p of paths) {
|
|
2167
2217
|
const abs = resolve2(p);
|
|
@@ -2169,9 +2219,8 @@ var collectFiles = (paths) => {
|
|
|
2169
2219
|
const st = statSync(abs);
|
|
2170
2220
|
if (st.isDirectory()) {
|
|
2171
2221
|
walk(abs);
|
|
2172
|
-
} else if (st.isFile()
|
|
2173
|
-
|
|
2174
|
-
files.push(abs);
|
|
2222
|
+
} else if (st.isFile()) {
|
|
2223
|
+
add(abs);
|
|
2175
2224
|
}
|
|
2176
2225
|
} catch {
|
|
2177
2226
|
unreadable.push(p);
|
|
@@ -2185,21 +2234,24 @@ var OMIT = new Set(["position", "data"]);
|
|
|
2185
2234
|
var normalise = (node, opts = {}) => {
|
|
2186
2235
|
const out = {};
|
|
2187
2236
|
const isDiagram = node.type === "code" && node.lang === "mermaid";
|
|
2237
|
+
const blank = isDiagram && Boolean(opts.blankDiagrams);
|
|
2188
2238
|
for (const [key, value] of Object.entries(node)) {
|
|
2189
2239
|
if (OMIT.has(key))
|
|
2190
2240
|
continue;
|
|
2191
|
-
if (
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
if (isDiagram && opts.blankDiagrams)
|
|
2195
|
-
continue;
|
|
2196
|
-
out.value = node.type === "text" ? value.replace(/\s+/g, " ").trim() : value;
|
|
2197
|
-
} else {
|
|
2198
|
-
out[key] = value;
|
|
2199
|
-
}
|
|
2241
|
+
if (blank && isStringValue(key, value))
|
|
2242
|
+
continue;
|
|
2243
|
+
out[key] = normaliseField(node, key, value, opts);
|
|
2200
2244
|
}
|
|
2201
2245
|
return out;
|
|
2202
2246
|
};
|
|
2247
|
+
var isStringValue = (key, value) => key === "value" && typeof value === "string";
|
|
2248
|
+
var normaliseField = (node, key, value, opts) => {
|
|
2249
|
+
if (key === "children" && Array.isArray(value))
|
|
2250
|
+
return value.map((child) => normalise(child, opts));
|
|
2251
|
+
if (isStringValue(key, value))
|
|
2252
|
+
return node.type === "text" ? value.replace(/\s+/g, " ").trim() : value;
|
|
2253
|
+
return value;
|
|
2254
|
+
};
|
|
2203
2255
|
var sameJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
2204
2256
|
var sameTree = (a, b, opts) => sameJson(normalise(a, opts), normalise(b, opts));
|
|
2205
2257
|
var sameDiagrams = async (before, after) => {
|
|
@@ -2264,63 +2316,68 @@ var overlaps = (edits) => {
|
|
|
2264
2316
|
return sorted.some((e, i) => i > 0 && e.start < sorted[i - 1].end);
|
|
2265
2317
|
};
|
|
2266
2318
|
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";
|
|
2319
|
+
var keyOf = (run, e) => `${e.rule}\x00${run.current.slice(e.start, e.end)}\x00${e.text}`;
|
|
2320
|
+
var attempt = async (run, doc, edits) => {
|
|
2321
|
+
const next = splice(run.current, edits);
|
|
2322
|
+
const after = buildDocModel(next, run.kind);
|
|
2323
|
+
for (const e of edits)
|
|
2324
|
+
if (await verify(e, doc, after) !== "accept")
|
|
2325
|
+
return false;
|
|
2326
|
+
run.current = next;
|
|
2327
|
+
run.applied.push(...edits);
|
|
2328
|
+
return true;
|
|
2329
|
+
};
|
|
2330
|
+
var soundEdits = (run, proposed) => proposed.filter((e) => {
|
|
2331
|
+
if (wellFormed(e, run.current.length))
|
|
2332
|
+
return true;
|
|
2333
|
+
const key = `${e.rule}\x00malformed\x00${e.start}:${e.end}`;
|
|
2334
|
+
if (!run.refusedKeys.has(key)) {
|
|
2335
|
+
run.refusedKeys.add(key);
|
|
2336
|
+
run.refused.push(e);
|
|
2337
|
+
}
|
|
2338
|
+
return false;
|
|
2339
|
+
});
|
|
2340
|
+
var applyRule = async (run, doc, rule) => {
|
|
2341
|
+
const options = optionsFor(rule, run.registry.options.get(rule.id), run.flags, run.file);
|
|
2342
|
+
const proposed = await rule.fix?.({ doc, file: run.file, options, helpers: HELPERS }) ?? [];
|
|
2343
|
+
const edits = soundEdits(run, proposed).filter((e) => run.current.slice(e.start, e.end) !== e.text && !run.refusedKeys.has(keyOf(run, e)));
|
|
2344
|
+
if (edits.length === 0)
|
|
2345
|
+
return false;
|
|
2346
|
+
if (edits.length > 1 && !overlaps(edits) && await attempt(run, doc, edits))
|
|
2347
|
+
return true;
|
|
2348
|
+
for (const edit of edits) {
|
|
2349
|
+
if (await attempt(run, doc, [edit]))
|
|
2350
|
+
return true;
|
|
2351
|
+
run.refusedKeys.add(keyOf(run, edit));
|
|
2352
|
+
run.refused.push(edit);
|
|
2353
|
+
}
|
|
2354
|
+
return false;
|
|
2355
|
+
};
|
|
2356
|
+
var runPass = async (run) => {
|
|
2357
|
+
const doc = buildDocModel(run.current, run.kind);
|
|
2358
|
+
for (const rule of run.registry.fixOrder)
|
|
2359
|
+
if (await applyRule(run, doc, rule))
|
|
2360
|
+
return rule.id;
|
|
2361
|
+
return;
|
|
2362
|
+
};
|
|
2267
2363
|
var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
2268
2364
|
checkRunFlags(flags);
|
|
2269
|
-
const
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2365
|
+
const run = {
|
|
2366
|
+
file,
|
|
2367
|
+
flags,
|
|
2368
|
+
registry,
|
|
2369
|
+
kind: documentKind(file),
|
|
2370
|
+
current: src,
|
|
2371
|
+
applied: [],
|
|
2372
|
+
refused: [],
|
|
2373
|
+
refusedKeys: new Set
|
|
2374
|
+
};
|
|
2274
2375
|
const keptBy = [];
|
|
2275
2376
|
for (let pass = 0;pass < MAX_PASSES; pass++) {
|
|
2276
|
-
const
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
const after = buildDocModel(next, kind);
|
|
2281
|
-
for (const e of edits)
|
|
2282
|
-
if (await verify(e, doc, after) !== "accept")
|
|
2283
|
-
return false;
|
|
2284
|
-
current = next;
|
|
2285
|
-
applied.push(...edits);
|
|
2286
|
-
return true;
|
|
2287
|
-
};
|
|
2288
|
-
let progressed = false;
|
|
2289
|
-
for (const rule of registry.fixOrder) {
|
|
2290
|
-
const options = optionsFor(rule, registry.options.get(rule.id), flags, file);
|
|
2291
|
-
const proposed = await rule.fix?.({ doc, file, options, helpers: HELPERS }) ?? [];
|
|
2292
|
-
const sound = proposed.filter((e) => {
|
|
2293
|
-
if (wellFormed(e, current.length))
|
|
2294
|
-
return true;
|
|
2295
|
-
const key = `${e.rule}\x00malformed\x00${e.start}:${e.end}`;
|
|
2296
|
-
if (!refusedKeys.has(key)) {
|
|
2297
|
-
refusedKeys.add(key);
|
|
2298
|
-
refused.push(e);
|
|
2299
|
-
}
|
|
2300
|
-
return false;
|
|
2301
|
-
});
|
|
2302
|
-
const edits = sound.filter((e) => current.slice(e.start, e.end) !== e.text && !refusedKeys.has(keyOf(e)));
|
|
2303
|
-
if (edits.length === 0)
|
|
2304
|
-
continue;
|
|
2305
|
-
if (edits.length > 1 && !overlaps(edits) && await attempt(edits)) {
|
|
2306
|
-
progressed = true;
|
|
2307
|
-
} else {
|
|
2308
|
-
for (const edit of edits) {
|
|
2309
|
-
if (await attempt([edit])) {
|
|
2310
|
-
progressed = true;
|
|
2311
|
-
break;
|
|
2312
|
-
}
|
|
2313
|
-
refusedKeys.add(keyOf(edit));
|
|
2314
|
-
refused.push(edit);
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
if (progressed) {
|
|
2318
|
-
keptBy.push(rule.id);
|
|
2319
|
-
break;
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
if (!progressed)
|
|
2323
|
-
return { output: current, applied, refused };
|
|
2377
|
+
const kept = await runPass(run);
|
|
2378
|
+
if (kept === undefined)
|
|
2379
|
+
return { output: run.current, applied: run.applied, refused: run.refused };
|
|
2380
|
+
keptBy.push(kept);
|
|
2324
2381
|
}
|
|
2325
2382
|
const lingering = [...new Set(keptBy.slice(-RECENT_PASSES))].sort();
|
|
2326
2383
|
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)}`;
|