@jpeakai/mermaidjs-lint 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -53
- package/dist/bin.js +202 -44
- package/dist/index.js +206 -45
- package/dist/testing.js +86 -23
- package/examples/architecture_beta_iconify_logos.mmd +27 -0
- package/examples/flowchart_fontawesome_icons.mmd +31 -0
- package/examples/layout_cosebilkent_mindmap.mmd +19 -0
- package/examples/layout_dagre_classic.mmd +22 -0
- package/examples/layout_dagre_handdrawn.mmd +23 -0
- package/examples/layout_dagre_neo.mmd +26 -0
- package/examples/layout_elk_default.mmd +22 -0
- package/examples/layout_elk_tuned.mmd +25 -0
- package/examples/layout_tidytree_mindmap.mmd +19 -0
- package/package.json +2 -2
- package/src/check.ts +2 -3
- package/src/cli.ts +45 -23
- package/src/config.ts +3 -2
- package/src/errors.ts +18 -3
- package/src/files.ts +3 -1
- package/src/fix/engine.ts +4 -4
- package/src/fix/verify.ts +2 -2
- package/src/index.ts +1 -1
- package/src/model.ts +10 -0
- package/src/parse/color.ts +2 -2
- package/src/parse/contrast.ts +59 -5
- package/src/parse/metrics.ts +16 -8
- package/src/parse/stats.ts +45 -19
- package/src/plugins/contract.ts +1 -1
- package/src/plugins/discover.ts +5 -5
- package/src/plugins/load.ts +5 -3
- package/src/project.ts +11 -4
- package/src/rules/complexity-node-count-hard-limit.ts +1 -1
- package/src/rules/complexity-visual-score-critical.ts +2 -2
- package/src/rules/contrast-text-below-aa.ts +6 -2
- package/src/rules/index.ts +2 -1
- package/src/rules/parse-diagram-unparseable.ts +29 -14
- package/src/rules/registry.ts +7 -4
- package/src/rules/shared.ts +1 -1
- package/src/rules/types.ts +2 -2
- package/src/testing.ts +4 -3
package/dist/index.js
CHANGED
|
@@ -183,17 +183,20 @@ function detectKeyword(content) {
|
|
|
183
183
|
}
|
|
184
184
|
async function extractStats(content) {
|
|
185
185
|
const keyword = detectKeyword(content) ?? "unknown";
|
|
186
|
-
|
|
186
|
+
const langium = keyword in LANGIUM_TYPES;
|
|
187
|
+
let langiumError = null;
|
|
188
|
+
if (langium) {
|
|
187
189
|
try {
|
|
188
190
|
return await extractLangiumStats(content, keyword);
|
|
189
|
-
} catch {
|
|
191
|
+
} catch (err) {
|
|
192
|
+
langiumError = parserReason(err);
|
|
193
|
+
}
|
|
190
194
|
}
|
|
191
|
-
let
|
|
195
|
+
let coreError;
|
|
192
196
|
try {
|
|
193
197
|
return await extractCoreStats(content, keyword);
|
|
194
198
|
} catch (err) {
|
|
195
|
-
|
|
196
|
-
`)[0] ?? null;
|
|
199
|
+
coreError = parserReason(err);
|
|
197
200
|
}
|
|
198
201
|
return {
|
|
199
202
|
nodes: 0,
|
|
@@ -202,11 +205,20 @@ async function extractStats(content) {
|
|
|
202
205
|
max_subgraph_depth: 0,
|
|
203
206
|
node_ids: [],
|
|
204
207
|
subgraph_names: [],
|
|
205
|
-
parser_used: "mermaid-core",
|
|
208
|
+
parser_used: langium ? "langium" : "mermaid-core",
|
|
206
209
|
diagram_type: keyword,
|
|
207
|
-
error
|
|
210
|
+
error: langiumError ?? coreError
|
|
208
211
|
};
|
|
209
212
|
}
|
|
213
|
+
var POINTER = /^-*\^$/;
|
|
214
|
+
var parserReason = (err) => {
|
|
215
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
216
|
+
const lines = message.split(`
|
|
217
|
+
`);
|
|
218
|
+
if (message.startsWith("No diagram type detected"))
|
|
219
|
+
return (lines[0] ?? "").trim();
|
|
220
|
+
return lines.filter((line, i) => !POINTER.test(line.trim()) && !POINTER.test((lines[i + 1] ?? "").trim())).join(" ").replace(/\s+/g, " ").trim();
|
|
221
|
+
};
|
|
210
222
|
async function langiumParseAny(type, content) {
|
|
211
223
|
switch (type) {
|
|
212
224
|
case "architecture":
|
|
@@ -692,6 +704,11 @@ var collectMermaid = (node, out) => {
|
|
|
692
704
|
for (const child of node.children ?? [])
|
|
693
705
|
collectMermaid(child, out);
|
|
694
706
|
};
|
|
707
|
+
var DIAGRAM_EXTS = [".mmd", ".mermaid"];
|
|
708
|
+
var documentKind = (file) => {
|
|
709
|
+
const lower = file.toLowerCase();
|
|
710
|
+
return DIAGRAM_EXTS.some((ext) => lower.endsWith(ext)) ? "diagram" : "markdown";
|
|
711
|
+
};
|
|
695
712
|
var buildDocModel = (src, kind = "markdown") => {
|
|
696
713
|
const lineStarts = lineStartsOf(src);
|
|
697
714
|
if (kind === "diagram") {
|
|
@@ -769,6 +786,14 @@ class ConfigNotFoundError extends UsageError {
|
|
|
769
786
|
}
|
|
770
787
|
}
|
|
771
788
|
|
|
789
|
+
class ConfigCodeError extends UsageError {
|
|
790
|
+
file;
|
|
791
|
+
constructor(file) {
|
|
792
|
+
super(`${file}: a .mjs config is code, and --no-plugins runs no project code; pass --config with a .json config, or drop --no-plugins`, "config-code");
|
|
793
|
+
this.file = file;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
772
797
|
class AmbiguousConfigError extends UsageError {
|
|
773
798
|
files;
|
|
774
799
|
constructor(root, files) {
|
|
@@ -881,6 +906,14 @@ var MKDOCS_MATERIAL = {
|
|
|
881
906
|
};
|
|
882
907
|
var GITHUB = { light: { bg: "#ffffff" }, dark: { bg: "#0d1117" } };
|
|
883
908
|
var THEMES = ["light", "dark"];
|
|
909
|
+
var MERMAID_EDGE_LABEL = {
|
|
910
|
+
light: { box: "rgba(232,232,232,0.8)", backing: "rgba(232,232,232,0.5)" },
|
|
911
|
+
dark: { box: "#585858", backing: "rgba(88,88,88,0.5)" }
|
|
912
|
+
};
|
|
913
|
+
var edgeLabelBackground = (theme) => {
|
|
914
|
+
const { box, backing } = MERMAID_EDGE_LABEL[theme];
|
|
915
|
+
return compositeOver(box, compositeOver(box, compositeOver(backing, GITHUB[theme].bg)));
|
|
916
|
+
};
|
|
884
917
|
var MERMAID_ER = {
|
|
885
918
|
light: { odd: "#ffffff", even: "#f1f1ff", label: "#333333" },
|
|
886
919
|
dark: { odd: "#2c2d2d", even: "#060606", label: "#cccccc" }
|
|
@@ -901,10 +934,27 @@ var scorePair = (kind, d, fg, bg) => {
|
|
|
901
934
|
};
|
|
902
935
|
};
|
|
903
936
|
var message = (err) => err instanceof Error ? err.message : String(err);
|
|
937
|
+
var scoreEdgeLabel = (d, pairs, skipped) => {
|
|
938
|
+
const { color } = d.properties;
|
|
939
|
+
if (!color)
|
|
940
|
+
return;
|
|
941
|
+
try {
|
|
942
|
+
pairs.push(...THEMES.map((t) => scoreHostPair("text", d, t, color, edgeLabelBackground(t))));
|
|
943
|
+
} catch (err) {
|
|
944
|
+
skipped.push({
|
|
945
|
+
selector: d.selector,
|
|
946
|
+
line: d.line,
|
|
947
|
+
reason: `edge label text unparseable: ${message(err)}`,
|
|
948
|
+
blocking: true
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
};
|
|
904
952
|
var scoreGithub = (directives) => {
|
|
905
953
|
const pairs = [];
|
|
906
954
|
const skipped = [];
|
|
907
955
|
for (const d of directives) {
|
|
956
|
+
if (d.kind === "linkStyle")
|
|
957
|
+
scoreEdgeLabel(d, pairs, skipped);
|
|
908
958
|
if (d.kind !== "classDef" && d.kind !== "style")
|
|
909
959
|
continue;
|
|
910
960
|
const { fill, color, stroke } = d.properties;
|
|
@@ -969,6 +1019,13 @@ var scoreMkdocs = (directives) => {
|
|
|
969
1019
|
const pairs = [];
|
|
970
1020
|
const skipped = [];
|
|
971
1021
|
for (const d of directives) {
|
|
1022
|
+
if (d.kind === "linkStyle" && d.properties.color) {
|
|
1023
|
+
skipped.push({
|
|
1024
|
+
selector: d.selector,
|
|
1025
|
+
line: d.line,
|
|
1026
|
+
reason: "color: ignored, because the host theme forces edge label text"
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
972
1029
|
if (d.kind !== "classDef" && d.kind !== "style")
|
|
973
1030
|
continue;
|
|
974
1031
|
const { fill, stroke, color } = d.properties;
|
|
@@ -1191,15 +1248,17 @@ var calculateComplexity = (stats, config) => {
|
|
|
1191
1248
|
};
|
|
1192
1249
|
};
|
|
1193
1250
|
var parseFailed = (content, stats) => {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
return t && !t.startsWith("%%") && t !== "---";
|
|
1198
|
-
});
|
|
1199
|
-
if (lines.length <= 1)
|
|
1251
|
+
if (stats.error !== null)
|
|
1252
|
+
return true;
|
|
1253
|
+
if (stats.parser_used === "langium" || stats.nodes > 0)
|
|
1200
1254
|
return false;
|
|
1201
|
-
return
|
|
1255
|
+
return sourceLines(content) > 1;
|
|
1202
1256
|
};
|
|
1257
|
+
var sourceLines = (content) => content.split(`
|
|
1258
|
+
`).filter((l) => {
|
|
1259
|
+
const t = l.trim();
|
|
1260
|
+
return t && !t.startsWith("%%") && t !== "---";
|
|
1261
|
+
}).length;
|
|
1203
1262
|
|
|
1204
1263
|
// src/rules/shared.ts
|
|
1205
1264
|
var COMPLEXITY_OPTIONS = {
|
|
@@ -1325,7 +1384,7 @@ var complexityNodeCountCognitiveLimit = complexityRule({
|
|
|
1325
1384
|
// src/rules/complexity-node-count-hard-limit.ts
|
|
1326
1385
|
var complexityNodeCountHardLimit = complexityRule({
|
|
1327
1386
|
id: RULE.NODES_HARD,
|
|
1328
|
-
summary: "more nodes than
|
|
1387
|
+
summary: "more nodes than the preset's hard limit",
|
|
1329
1388
|
hit: ({ stats }, c) => stats.nodes > c.node_hard_limit ? {
|
|
1330
1389
|
message: `${stats.nodes} nodes > ${c.node_hard_limit} hard limit`,
|
|
1331
1390
|
remediation: "Split immediately. This diagram is beyond any comprehensible size. " + `${boundaryHint(stats.subgraph_names).replace(/^along /, "Split along ")}. ${splitTarget(c)}`
|
|
@@ -1356,9 +1415,9 @@ var complexityVisualScoreAcceptableExceeded = complexityRule({
|
|
|
1356
1415
|
// src/rules/complexity-visual-score-critical.ts
|
|
1357
1416
|
var complexityVisualScoreCritical = complexityRule({
|
|
1358
1417
|
id: RULE.VCS_CRITICAL,
|
|
1359
|
-
summary: "a visual complexity score above the
|
|
1418
|
+
summary: "a visual complexity score above the preset's complex threshold",
|
|
1360
1419
|
hit: ({ stats, metrics }, c) => metrics.visual_complexity_score > c.vcs_complex ? {
|
|
1361
|
-
message: `VCS ${metrics.visual_complexity_score.toFixed(1)} > ${c.vcs_complex}
|
|
1420
|
+
message: `VCS ${metrics.visual_complexity_score.toFixed(1)} > ${c.vcs_complex} complex threshold`,
|
|
1362
1421
|
remediation: `Reduce diagram complexity. Split ${boundaryHint(stats.subgraph_names)}, remove redundant edges, ` + `or collapse leaf nodes into labelled groups. ${splitTarget(c)}`
|
|
1363
1422
|
} : null
|
|
1364
1423
|
});
|
|
@@ -1444,7 +1503,7 @@ var contrastTextBelowAa = {
|
|
|
1444
1503
|
endLine: line,
|
|
1445
1504
|
rule: RULE.TEXT,
|
|
1446
1505
|
message: `${p.directive.kind} ${p.selector}: text ${p.assessment.ratio.toFixed(2)}:1 < ${AA_NORMAL2} ` + `(${p.foreground} on ${p.background}${where ? `, ${where}` : ""})`,
|
|
1447
|
-
remediation: profile === "mkdocs-material" ? "The host theme forces the label text, so change the fill until the host text reads on it in both themes." : "Darken or lighten the fill, or change color:, until the ratio reaches 4.5:1 in every theme."
|
|
1506
|
+
remediation: 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 === "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."
|
|
1448
1507
|
});
|
|
1449
1508
|
}
|
|
1450
1509
|
}
|
|
@@ -1481,18 +1540,30 @@ var contrastTextBelowAa = {
|
|
|
1481
1540
|
};
|
|
1482
1541
|
|
|
1483
1542
|
// src/rules/parse-diagram-unparseable.ts
|
|
1543
|
+
var EXTERNAL_TYPES = new Set(["zenuml"]);
|
|
1544
|
+
var messageOf = (stats) => {
|
|
1545
|
+
const type = stats.diagram_type;
|
|
1546
|
+
if (EXTERNAL_TYPES.has(type)) {
|
|
1547
|
+
return `${type} is drawn by an external mermaid add-on that mermaidjs-lint does not include, so it cannot be parsed or checked`;
|
|
1548
|
+
}
|
|
1549
|
+
if (stats.error !== null) {
|
|
1550
|
+
const lines = /\bline \d/.test(stats.error) ? " (its line numbers count within the diagram, not the file)" : "";
|
|
1551
|
+
return `${type} did not parse (parser: ${stats.parser_used})${lines}: ${stats.error}`;
|
|
1552
|
+
}
|
|
1553
|
+
return `${type} parsed to 0 nodes from multi-line source with no error (parser: ${stats.parser_used}), a silent parse failure`;
|
|
1554
|
+
};
|
|
1484
1555
|
var parseDiagramUnparseable = {
|
|
1485
1556
|
id: RULE.UNPARSEABLE,
|
|
1486
1557
|
category: "parse",
|
|
1487
|
-
summary: "a
|
|
1558
|
+
summary: "a diagram mermaid's parser rejects, or reads as zero nodes from several lines",
|
|
1488
1559
|
check: async (ctx) => {
|
|
1489
1560
|
const found = [];
|
|
1490
1561
|
for (const diagram of ctx.doc.diagrams) {
|
|
1491
1562
|
const stats = await diagram.stats();
|
|
1492
1563
|
if (!parseFailed(diagram.source, stats))
|
|
1493
1564
|
continue;
|
|
1494
|
-
const
|
|
1495
|
-
found.push(finding(ctx, diagram, RULE.UNPARSEABLE,
|
|
1565
|
+
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.";
|
|
1566
|
+
found.push(finding(ctx, diagram, RULE.UNPARSEABLE, messageOf(stats), remediation));
|
|
1496
1567
|
}
|
|
1497
1568
|
return found;
|
|
1498
1569
|
}
|
|
@@ -1634,16 +1705,87 @@ var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
|
|
|
1634
1705
|
const findings = (await Promise.all(perRule)).flat();
|
|
1635
1706
|
return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
|
|
1636
1707
|
};
|
|
1637
|
-
var checkMarkdown = (src, file, flags = {}, registry = BUILTIN) => checkModel(buildDocModel(src, file
|
|
1708
|
+
var checkMarkdown = (src, file, flags = {}, registry = BUILTIN) => checkModel(buildDocModel(src, documentKind(file)), file, flags, registry);
|
|
1638
1709
|
// src/cli.ts
|
|
1639
1710
|
import { readFile as readFile3, writeFile } from "node:fs/promises";
|
|
1640
1711
|
import { relative } from "node:path";
|
|
1641
1712
|
import { parseArgs } from "node:util";
|
|
1713
|
+
// package.json
|
|
1714
|
+
var package_default = {
|
|
1715
|
+
name: "@jpeakai/mermaidjs-lint",
|
|
1716
|
+
version: "0.2.0",
|
|
1717
|
+
type: "module",
|
|
1718
|
+
description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
|
|
1719
|
+
license: "MIT",
|
|
1720
|
+
keywords: [
|
|
1721
|
+
"mermaid",
|
|
1722
|
+
"mermaidjs",
|
|
1723
|
+
"lint",
|
|
1724
|
+
"diagram",
|
|
1725
|
+
"markdown",
|
|
1726
|
+
"documentation"
|
|
1727
|
+
],
|
|
1728
|
+
repository: {
|
|
1729
|
+
type: "git",
|
|
1730
|
+
url: "git+https://github.com/jpeakai/jpai-mermaidjs-lint.git"
|
|
1731
|
+
},
|
|
1732
|
+
homepage: "https://jpeakai.github.io/jpai-mermaidjs-lint/",
|
|
1733
|
+
bugs: "https://github.com/jpeakai/jpai-mermaidjs-lint/issues",
|
|
1734
|
+
packageManager: "bun@1.3.12",
|
|
1735
|
+
publishConfig: {
|
|
1736
|
+
access: "public"
|
|
1737
|
+
},
|
|
1738
|
+
scripts: {
|
|
1739
|
+
prepublishOnly: "make check",
|
|
1740
|
+
prepack: "make build"
|
|
1741
|
+
},
|
|
1742
|
+
bin: {
|
|
1743
|
+
"mermaidjs-lint": "dist/bin.js"
|
|
1744
|
+
},
|
|
1745
|
+
exports: {
|
|
1746
|
+
".": {
|
|
1747
|
+
bun: "./src/index.ts",
|
|
1748
|
+
types: "./src/index.ts",
|
|
1749
|
+
default: "./dist/index.js"
|
|
1750
|
+
},
|
|
1751
|
+
"./testing": {
|
|
1752
|
+
bun: "./src/testing.ts",
|
|
1753
|
+
types: "./src/testing.ts",
|
|
1754
|
+
default: "./dist/testing.js"
|
|
1755
|
+
}
|
|
1756
|
+
},
|
|
1757
|
+
engines: {
|
|
1758
|
+
node: ">=20"
|
|
1759
|
+
},
|
|
1760
|
+
files: [
|
|
1761
|
+
"src",
|
|
1762
|
+
"dist",
|
|
1763
|
+
"examples"
|
|
1764
|
+
],
|
|
1765
|
+
dependencies: {
|
|
1766
|
+
"@mermaid-js/parser": "^1.1.0",
|
|
1767
|
+
"colorjs.io": "^0.6.1",
|
|
1768
|
+
"happy-dom": "^20.9.0",
|
|
1769
|
+
"mdast-util-from-markdown": "^2.0.3",
|
|
1770
|
+
"mdast-util-frontmatter": "^2.0.1",
|
|
1771
|
+
"mdast-util-gfm": "^3.1.0",
|
|
1772
|
+
mermaid: "^11.14.0",
|
|
1773
|
+
"micromark-extension-frontmatter": "^2.0.0",
|
|
1774
|
+
"micromark-extension-gfm": "^3.0.0"
|
|
1775
|
+
},
|
|
1776
|
+
devDependencies: {
|
|
1777
|
+
"@biomejs/biome": "^2",
|
|
1778
|
+
"@jpeakai/prose-gates": "^0.2.0",
|
|
1779
|
+
"@types/bun": "latest",
|
|
1780
|
+
"@types/mdast": "^4",
|
|
1781
|
+
typescript: "^5"
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1642
1784
|
|
|
1643
1785
|
// src/files.ts
|
|
1644
1786
|
import { readdirSync, statSync } from "node:fs";
|
|
1645
1787
|
import { extname, join as join2, resolve as resolve2 } from "node:path";
|
|
1646
|
-
var DIAGRAM_FILE_EXTS = new Set([
|
|
1788
|
+
var DIAGRAM_FILE_EXTS = new Set([...DIAGRAM_EXTS, ".md", ".markdown"]);
|
|
1647
1789
|
var SKIP_DIRS = new Set(["node_modules", "vendor", "dist", "build", "tmp", "__pycache__"]);
|
|
1648
1790
|
var skipDir = (name) => name.startsWith(".") || SKIP_DIRS.has(name);
|
|
1649
1791
|
var collectFiles = (paths) => {
|
|
@@ -1755,7 +1897,7 @@ var overlaps = (edits) => {
|
|
|
1755
1897
|
};
|
|
1756
1898
|
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";
|
|
1757
1899
|
var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
1758
|
-
const kind = file
|
|
1900
|
+
const kind = documentKind(file);
|
|
1759
1901
|
const applied = [];
|
|
1760
1902
|
const refused = [];
|
|
1761
1903
|
const refusedKeys = new Set;
|
|
@@ -1814,7 +1956,7 @@ var fixSource = async (src, file, flags = {}, registry = BUILTIN) => (await fixS
|
|
|
1814
1956
|
|
|
1815
1957
|
// src/project.ts
|
|
1816
1958
|
import { access as access2 } from "node:fs/promises";
|
|
1817
|
-
import { dirname as dirname3, join as join5, resolve as resolve4 } from "node:path";
|
|
1959
|
+
import { dirname as dirname3, extname as extname5, join as join5, resolve as resolve4 } from "node:path";
|
|
1818
1960
|
|
|
1819
1961
|
// src/config.ts
|
|
1820
1962
|
import { access, readFile } from "node:fs/promises";
|
|
@@ -2283,6 +2425,8 @@ var setUp = async ({
|
|
|
2283
2425
|
} else {
|
|
2284
2426
|
file = await findConfig(root);
|
|
2285
2427
|
}
|
|
2428
|
+
if (noPlugins && file !== null && extname5(file) === ".mjs")
|
|
2429
|
+
throw new ConfigCodeError(file);
|
|
2286
2430
|
const config = file ? await loadConfig(file) : NO_CONFIG;
|
|
2287
2431
|
let mode = "strict";
|
|
2288
2432
|
if (noPlugins || config.plugins === false)
|
|
@@ -2298,39 +2442,47 @@ var setUp = async ({
|
|
|
2298
2442
|
categories: plugins.categories,
|
|
2299
2443
|
sources: plugins.sources,
|
|
2300
2444
|
config,
|
|
2301
|
-
ignoreUnknownPluginIds: mode
|
|
2445
|
+
ignoreUnknownPluginIds: mode !== "strict"
|
|
2302
2446
|
});
|
|
2303
2447
|
return { registry, skipped: plugins.skipped, failures: plugins.failures };
|
|
2304
2448
|
};
|
|
2305
2449
|
|
|
2306
2450
|
// src/cli.ts
|
|
2307
|
-
var
|
|
2451
|
+
var SYNOPSIS = `usage: mermaidjs-lint <path...> [--fix] [--json] [--preset NAME] [--profile NAME] [--config FILE]
|
|
2308
2452
|
[--no-plugins | --lenient-plugins]
|
|
2309
2453
|
mermaidjs-lint --list-rules
|
|
2310
2454
|
mermaidjs-lint --validate-plugins [--json] [--config FILE]
|
|
2455
|
+
mermaidjs-lint --version | -h, --help`;
|
|
2456
|
+
var USAGE = `${SYNOPSIS}
|
|
2311
2457
|
|
|
2312
2458
|
Deterministic Mermaid diagram gates (* = autofixable):
|
|
2313
2459
|
${CATEGORIES.map((c) => [` ${c}:`, ...RULES.filter((r) => r.category === c).map((r) => ` ${r.id}${r.fix ? "*" : " "} ${r.summary}`)].join(`
|
|
2314
2460
|
`)).join(`
|
|
2315
2461
|
`)}
|
|
2316
2462
|
|
|
2317
|
-
A path is a
|
|
2318
|
-
|
|
2319
|
-
|
|
2463
|
+
A path is a file or a directory, and every path must exist. A file ending in
|
|
2464
|
+
${DIAGRAM_EXTS.join(" or ")} is read as one raw diagram, and any other file is read as markdown
|
|
2465
|
+
whose mermaid fences are checked. A directory is walked for ${[...DIAGRAM_FILE_EXTS].join(", ")} files,
|
|
2466
|
+
skipping dot-folders and ${[...SKIP_DIRS].join(", ")}.
|
|
2467
|
+
Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lint/reference/rules/
|
|
2320
2468
|
|
|
2321
|
-
--preset NAME sets the density for every complexity rule: ${PRESET_NAMES.join(", ")}
|
|
2322
|
-
(aliases low, medium, high).
|
|
2323
|
-
|
|
2324
|
-
|
|
2469
|
+
-p, --preset NAME sets the density for every complexity rule: ${PRESET_NAMES.join(", ")}
|
|
2470
|
+
(aliases low, medium, high). It overrides a preset set in the config, and a
|
|
2471
|
+
numeric threshold set in the config overrides both.
|
|
2472
|
+
--profile NAME sets how contrast is read: ${PROFILES.join(", ")}. It overrides a profile set in the config.
|
|
2473
|
+
auto reads mkdocs-material when any parent folder holds mkdocs.yml or mkdocs.yaml, else github.
|
|
2325
2474
|
--fix applies every fix it can prove safe, then reports what remains.
|
|
2326
2475
|
A fixer that is unsure leaves the text alone and the finding stays.
|
|
2327
2476
|
--config FILE reads that config instead of mermaidjs-lint.config.json or .mjs at the project root.
|
|
2328
2477
|
Plugins load on their own from .mermaidjs-lint/rules and from dependencies named mermaidjs-lint-plugin-*.
|
|
2329
|
-
--no-plugins runs the built-in rules only
|
|
2478
|
+
--no-plugins runs the built-in rules only and loads no plugin. It reads a .json config as usual,
|
|
2479
|
+
and stops with a usage error rather than import a .mjs config, which is code.
|
|
2330
2480
|
--lenient-plugins skips a plugin that fails to load and says so, instead of stopping the run.
|
|
2331
2481
|
--validate-plugins loads and checks every plugin, reports every failure, and runs no check or fix.
|
|
2332
2482
|
--list-rules prints every active rule by category, including plugin rules, and where each plugin came from.
|
|
2333
|
-
|
|
2483
|
+
--version prints the version, to quote in a bug report.
|
|
2484
|
+
Exits 0 when no findings remain, 1 when findings remain or a plugin fails to load (a lenient run
|
|
2485
|
+
skips a broken plugin, so it alone does not exit 1), and 2 on a usage error.`;
|
|
2334
2486
|
var parse2 = (argv) => parseArgs({
|
|
2335
2487
|
args: argv,
|
|
2336
2488
|
options: {
|
|
@@ -2343,7 +2495,8 @@ var parse2 = (argv) => parseArgs({
|
|
|
2343
2495
|
"no-plugins": { type: "boolean", default: false },
|
|
2344
2496
|
"lenient-plugins": { type: "boolean", default: false },
|
|
2345
2497
|
"validate-plugins": { type: "boolean", default: false },
|
|
2346
|
-
"list-rules": { type: "boolean", default: false }
|
|
2498
|
+
"list-rules": { type: "boolean", default: false },
|
|
2499
|
+
version: { type: "boolean", default: false }
|
|
2347
2500
|
},
|
|
2348
2501
|
allowPositionals: true,
|
|
2349
2502
|
strict: true
|
|
@@ -2405,6 +2558,10 @@ var validate = (registry, failures, json) => {
|
|
|
2405
2558
|
const line = sources.length === 0 ? "mermaidjs-lint: no plugins found" : `mermaidjs-lint: plugins valid: ${namesOf(registry.sources)}`;
|
|
2406
2559
|
return { code, stdout: line, stderr: [] };
|
|
2407
2560
|
};
|
|
2561
|
+
var reportLine = (f, file) => {
|
|
2562
|
+
const where = f.endLine > f.line ? `${f.line}-${f.endLine}` : `${f.line}`;
|
|
2563
|
+
return `${file}:${where} ${f.rule} ${f.message}`;
|
|
2564
|
+
};
|
|
2408
2565
|
var display = (file, cwd) => {
|
|
2409
2566
|
const rel = relative(cwd, file);
|
|
2410
2567
|
return !rel || rel.startsWith("..") ? file : rel;
|
|
@@ -2421,6 +2578,10 @@ var run = async (argv, cwd) => {
|
|
|
2421
2578
|
console.log(USAGE);
|
|
2422
2579
|
return 0;
|
|
2423
2580
|
}
|
|
2581
|
+
if (values.version) {
|
|
2582
|
+
console.log(package_default.version);
|
|
2583
|
+
return 0;
|
|
2584
|
+
}
|
|
2424
2585
|
const listing = values["list-rules"];
|
|
2425
2586
|
const validating = values["validate-plugins"];
|
|
2426
2587
|
if (values.preset !== undefined && canonicalPreset(values.preset) === null) {
|
|
@@ -2479,9 +2640,7 @@ var run = async (argv, cwd) => {
|
|
|
2479
2640
|
src = fixed;
|
|
2480
2641
|
}
|
|
2481
2642
|
}
|
|
2482
|
-
|
|
2483
|
-
const found = await checkModel(buildDocModel(src, kind), display(file, cwd), flags, registry);
|
|
2484
|
-
return found;
|
|
2643
|
+
return checkModel(buildDocModel(src, documentKind(file)), display(file, cwd), flags, registry);
|
|
2485
2644
|
}));
|
|
2486
2645
|
const all = perFile.flat();
|
|
2487
2646
|
if (values.json) {
|
|
@@ -2493,10 +2652,8 @@ var run = async (argv, cwd) => {
|
|
|
2493
2652
|
}));
|
|
2494
2653
|
console.log(JSON.stringify({ findings: all, files: files.length, plugins: registry.sources, skippedPlugins }, null, 2));
|
|
2495
2654
|
} else {
|
|
2496
|
-
for (const f of all)
|
|
2497
|
-
|
|
2498
|
-
console.log(`${f.file}:${where} ${f.rule} ${f.message}`);
|
|
2499
|
-
}
|
|
2655
|
+
for (const f of all)
|
|
2656
|
+
console.log(reportLine(f, f.file));
|
|
2500
2657
|
console.log(`${all.length} finding(s) in ${files.length} file(s)`);
|
|
2501
2658
|
}
|
|
2502
2659
|
return all.length > 0 ? 1 : 0;
|
|
@@ -2509,7 +2666,8 @@ var main = async (argv = process.argv.slice(2), cwd = process.cwd()) => {
|
|
|
2509
2666
|
if (!(err instanceof UsageError))
|
|
2510
2667
|
throw err;
|
|
2511
2668
|
console.error(describeError(err));
|
|
2512
|
-
console.error(
|
|
2669
|
+
console.error(SYNOPSIS);
|
|
2670
|
+
console.error("run mermaidjs-lint --help for more");
|
|
2513
2671
|
return 2;
|
|
2514
2672
|
}
|
|
2515
2673
|
};
|
|
@@ -2523,6 +2681,7 @@ export {
|
|
|
2523
2681
|
fixSource,
|
|
2524
2682
|
findProjectRoot,
|
|
2525
2683
|
extractStats,
|
|
2684
|
+
documentKind,
|
|
2526
2685
|
detectKeyword,
|
|
2527
2686
|
collectFiles,
|
|
2528
2687
|
checkModel,
|
|
@@ -2546,9 +2705,11 @@ export {
|
|
|
2546
2705
|
MermaidLintError,
|
|
2547
2706
|
FlagError,
|
|
2548
2707
|
FIX_ORDER,
|
|
2708
|
+
DIAGRAM_EXTS,
|
|
2549
2709
|
ConfigShapeError,
|
|
2550
2710
|
ConfigNotFoundError,
|
|
2551
2711
|
ConfigFileError,
|
|
2712
|
+
ConfigCodeError,
|
|
2552
2713
|
CATEGORIES,
|
|
2553
2714
|
BUILTIN_CATEGORIES,
|
|
2554
2715
|
BUILTIN,
|