@jpeakai/mermaidjs-lint 0.1.0 → 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.
Files changed (40) hide show
  1. package/README.md +82 -53
  2. package/dist/bin.js +202 -44
  3. package/dist/index.js +206 -45
  4. package/dist/testing.js +86 -23
  5. package/examples/architecture_beta_iconify_logos.mmd +27 -0
  6. package/examples/flowchart_fontawesome_icons.mmd +31 -0
  7. package/examples/layout_cosebilkent_mindmap.mmd +19 -0
  8. package/examples/layout_dagre_classic.mmd +22 -0
  9. package/examples/layout_dagre_handdrawn.mmd +23 -0
  10. package/examples/layout_dagre_neo.mmd +26 -0
  11. package/examples/layout_elk_default.mmd +22 -0
  12. package/examples/layout_elk_tuned.mmd +25 -0
  13. package/examples/layout_tidytree_mindmap.mmd +19 -0
  14. package/package.json +2 -2
  15. package/src/check.ts +2 -3
  16. package/src/cli.ts +45 -23
  17. package/src/config.ts +3 -2
  18. package/src/errors.ts +18 -3
  19. package/src/files.ts +3 -1
  20. package/src/fix/engine.ts +4 -4
  21. package/src/fix/verify.ts +2 -2
  22. package/src/index.ts +1 -1
  23. package/src/model.ts +10 -0
  24. package/src/parse/color.ts +2 -2
  25. package/src/parse/contrast.ts +59 -5
  26. package/src/parse/metrics.ts +16 -8
  27. package/src/parse/stats.ts +45 -19
  28. package/src/plugins/contract.ts +1 -1
  29. package/src/plugins/discover.ts +5 -5
  30. package/src/plugins/load.ts +5 -3
  31. package/src/project.ts +11 -4
  32. package/src/rules/complexity-node-count-hard-limit.ts +1 -1
  33. package/src/rules/complexity-visual-score-critical.ts +2 -2
  34. package/src/rules/contrast-text-below-aa.ts +6 -2
  35. package/src/rules/index.ts +2 -1
  36. package/src/rules/parse-diagram-unparseable.ts +29 -14
  37. package/src/rules/registry.ts +7 -4
  38. package/src/rules/shared.ts +1 -1
  39. package/src/rules/types.ts +2 -2
  40. 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
- if (keyword in LANGIUM_TYPES) {
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 error = null;
195
+ let coreError;
192
196
  try {
193
197
  return await extractCoreStats(content, keyword);
194
198
  } catch (err) {
195
- error = (err instanceof Error ? err.message : String(err)).split(`
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
- const lines = content.split(`
1195
- `).filter((l) => {
1196
- const t = l.trim();
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 stats.nodes === 0;
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 any diagram can hold, above the hard limit",
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 critical threshold",
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} critical threshold`,
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 multi-line diagram that mermaid's parser reads as zero nodes",
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 why = stats.error ? `: ${stats.error}` : "";
1495
- found.push(finding(ctx, diagram, RULE.UNPARSEABLE, `${stats.diagram_type} yielded 0 nodes from multi-line source (parser: ${stats.parser_used})${why}`, "Verify the diagram renders in mermaid.js and fix its syntax. Complexity rules are skipped for this diagram while it cannot be parsed."));
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.toLowerCase().endsWith(".mmd") ? "diagram" : "markdown"), file, flags, registry);
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([".mmd", ".md", ".markdown"]);
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.toLowerCase().endsWith(".mmd") ? "diagram" : "markdown";
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 === "lenient" || mode === "collect"
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 USAGE = `usage: mermaidjs-lint <path...> [--fix] [--json] [--preset NAME] [--profile NAME] [--config FILE]
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 .mmd file, a markdown file whose mermaid fences are checked, or a
2318
- directory, which is walked for both.
2319
- Examples of every rule, with fixes: RULES.md
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). Rule options and the config can set it per rule.
2323
- --profile NAME sets how contrast is read: ${PROFILES.join(", ")}.
2324
- auto reads mkdocs-material when an ancestor holds mkdocs.yml, else github.
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, for a baseline or a repo you do not trust.
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
- Exits 1 when findings remain or a plugin fails to load, 2 on usage error.`;
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
- const kind = file.toLowerCase().endsWith(".mmd") ? "diagram" : "markdown";
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
- const where = f.endLine > f.line ? `${f.line}-${f.endLine}` : `${f.line}`;
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(USAGE);
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,