@jpeakai/mermaidjs-lint 0.3.0 → 0.4.1

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/dist/index.js CHANGED
@@ -1,6 +1,184 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
+ // src/errors.ts
5
+ class MermaidLintError extends Error {
6
+ code;
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.code = code;
10
+ this.name = new.target.name;
11
+ }
12
+ }
13
+
14
+ class UsageError extends MermaidLintError {
15
+ constructor(message, code = "usage") {
16
+ super(code, message);
17
+ }
18
+ }
19
+
20
+ class FlagError extends UsageError {
21
+ flag;
22
+ constructor(flag, message) {
23
+ super(message, "flag");
24
+ this.flag = flag;
25
+ }
26
+ }
27
+
28
+ class ConfigFileError extends UsageError {
29
+ file;
30
+ constructor(file, message) {
31
+ super(`${file}: ${message}`, "config-file");
32
+ this.file = file;
33
+ }
34
+ }
35
+
36
+ class ConfigNotFoundError extends UsageError {
37
+ path;
38
+ constructor(path) {
39
+ super(`--config ${path}: no such file`, "config-not-found");
40
+ this.path = path;
41
+ }
42
+ }
43
+
44
+ class ConfigCodeError extends UsageError {
45
+ file;
46
+ constructor(file) {
47
+ 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");
48
+ this.file = file;
49
+ }
50
+ }
51
+
52
+ class AmbiguousConfigError extends UsageError {
53
+ files;
54
+ constructor(root, files) {
55
+ super(`more than one config file at ${root}: ${files.join(", ")}`, "config-ambiguous");
56
+ this.files = files;
57
+ }
58
+ }
59
+
60
+ class ConfigShapeError extends UsageError {
61
+ file;
62
+ key;
63
+ constructor(file, message, key = null) {
64
+ super(`${file}: ${message}`, "config-shape");
65
+ this.file = file;
66
+ this.key = key;
67
+ }
68
+ }
69
+
70
+ class RuleSettingError extends UsageError {
71
+ file;
72
+ rule;
73
+ constructor(file, rule, message) {
74
+ super(`${file}: rule "${rule}" ${message}`, "rule-setting");
75
+ this.file = file;
76
+ this.rule = rule;
77
+ }
78
+ }
79
+
80
+ class UnknownRuleError extends UsageError {
81
+ file;
82
+ rule;
83
+ known;
84
+ constructor(file, rule, known) {
85
+ super(`${file}: unknown rule "${rule}"; a retired or misspelt id cannot be switched off. Known rules: ${known.join(", ")}`, "unknown-rule");
86
+ this.file = file;
87
+ this.rule = rule;
88
+ this.known = known;
89
+ }
90
+ }
91
+
92
+ class RuleOptionError extends UsageError {
93
+ file;
94
+ rule;
95
+ option;
96
+ constructor(file, rule, option, message) {
97
+ super(`${file}: ${message}`, "rule-option");
98
+ this.file = file;
99
+ this.rule = rule;
100
+ this.option = option;
101
+ }
102
+ }
103
+
104
+ class FixOutsideRootError extends UsageError {
105
+ file;
106
+ target;
107
+ constructor(file, target, root) {
108
+ super(`${file} is a symbolic link to ${target}, outside the project root ${root}; --fix does not write through it. Fix the target in its own project, or run on the target path itself`, "fix-outside-root");
109
+ this.file = file;
110
+ this.target = target;
111
+ }
112
+ }
113
+
114
+ class PackageJsonError extends MermaidLintError {
115
+ file;
116
+ constructor(file) {
117
+ super("package-json", `${file} is not a JSON object; fix or remove it`);
118
+ this.file = file;
119
+ }
120
+ }
121
+
122
+ class FixNotConvergingError extends MermaidLintError {
123
+ file;
124
+ rules;
125
+ constructor(file, passes, rules) {
126
+ super("fix-not-converging", `${file}: --fix did not converge after ${passes} passes; ${rules.join(", ")} kept proposing edits. A fixer must propose nothing once its edit is applied`);
127
+ this.file = file;
128
+ this.rules = rules;
129
+ }
130
+ }
131
+
132
+ class PluginLoadError extends MermaidLintError {
133
+ source;
134
+ constructor(source, reason, code = "plugin") {
135
+ super(code, `plugin ${source}: ${reason}`);
136
+ this.source = source;
137
+ }
138
+ }
139
+
140
+ class PluginImportError extends PluginLoadError {
141
+ constructor(source, reason) {
142
+ super(source, `could not be imported: ${reason}`, "plugin-import");
143
+ }
144
+ }
145
+
146
+ class PluginContractError extends PluginLoadError {
147
+ constructor(source, reason) {
148
+ super(source, reason, "plugin-contract");
149
+ }
150
+ }
151
+
152
+ class PluginConflictError extends PluginLoadError {
153
+ other;
154
+ constructor(source, other, reason) {
155
+ super(source, reason, "plugin-conflict");
156
+ this.other = other;
157
+ }
158
+ }
159
+
160
+ class PluginNotInstalledError extends PluginLoadError {
161
+ root;
162
+ constructor(source, root) {
163
+ super(source, `is declared or listed but not installed; looked from ${root} upward. Install it, or remove it`, "plugin-not-installed");
164
+ this.root = root;
165
+ }
166
+ }
167
+
168
+ class PluginSourceError extends PluginLoadError {
169
+ constructor(source, reason) {
170
+ super(source, reason, "plugin-source");
171
+ }
172
+ }
173
+
174
+ class PluginFixerError extends MermaidLintError {
175
+ rule;
176
+ constructor(rule, reason) {
177
+ super("plugin-fixer", `plugin rule ${rule} ${reason}`);
178
+ this.rule = rule;
179
+ }
180
+ }
181
+
4
182
  // src/model.ts
5
183
  import { fromMarkdown } from "mdast-util-from-markdown";
6
184
  import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
@@ -203,6 +381,7 @@ async function extractStats(content) {
203
381
  edges: 0,
204
382
  subgraphs: 0,
205
383
  max_subgraph_depth: 0,
384
+ max_tree_depth: 0,
206
385
  node_ids: [],
207
386
  subgraph_names: [],
208
387
  parser_used: langium ? "langium" : "mermaid-core",
@@ -252,6 +431,7 @@ async function extractLangiumStats(content, keyword) {
252
431
  const subgraphNames = [];
253
432
  let edges = 0;
254
433
  let maxDepth = 0;
434
+ let treeDepth = 0;
255
435
  switch (mapped) {
256
436
  case "architecture": {
257
437
  const services = ast.services ?? [];
@@ -298,7 +478,7 @@ async function extractLangiumStats(content, keyword) {
298
478
  const stats = indentTreeStats(nodes.map((n, i) => ({ label: n.name ?? `node_${i}`, indent: n.indent ?? 0 })));
299
479
  nodeIds.push(...stats.labels);
300
480
  edges = stats.edges;
301
- maxDepth = stats.depth;
481
+ treeDepth = stats.depth;
302
482
  break;
303
483
  }
304
484
  case "treemap": {
@@ -306,7 +486,7 @@ async function extractLangiumStats(content, keyword) {
306
486
  const stats = indentTreeStats(rows.map((r, i) => ({ label: r.item?.name ?? `row_${i}`, indent: r.indent ?? 0 })));
307
487
  nodeIds.push(...stats.labels);
308
488
  edges = stats.edges;
309
- maxDepth = stats.depth;
489
+ treeDepth = stats.depth;
310
490
  break;
311
491
  }
312
492
  case "wardley": {
@@ -326,6 +506,7 @@ async function extractLangiumStats(content, keyword) {
326
506
  edges,
327
507
  subgraphs: subgraphNames.length,
328
508
  max_subgraph_depth: maxDepth,
509
+ max_tree_depth: treeDepth,
329
510
  node_ids: [...new Set(nodeIds.filter(Boolean))].sort(),
330
511
  subgraph_names: subgraphNames,
331
512
  parser_used: "langium",
@@ -376,12 +557,13 @@ async function extractCoreStats(content, keyword) {
376
557
  if (!adapter) {
377
558
  throw new Error(`no core adapter for diagram.type="${diagramType}" (keyword="${keyword}")`);
378
559
  }
379
- const { nodeIds, edges, subgraphNames, maxDepth } = adapter(diagram.db);
560
+ const { nodeIds, edges, subgraphNames, maxDepth, treeDepth = 0 } = adapter(diagram.db);
380
561
  return {
381
562
  nodes: nodeIds.length,
382
563
  edges,
383
564
  subgraphs: subgraphNames.length,
384
565
  max_subgraph_depth: maxDepth,
566
+ max_tree_depth: treeDepth,
385
567
  node_ids: [...nodeIds].sort(),
386
568
  subgraph_names: subgraphNames,
387
569
  parser_used: "mermaid-core",
@@ -392,6 +574,7 @@ async function extractCoreStats(content, keyword) {
392
574
  var CORE_ADAPTERS = {
393
575
  "flowchart-v2": adaptFlowchart,
394
576
  flowchart: adaptFlowchart,
577
+ "flowchart-elk": adaptFlowchart,
395
578
  graph: adaptFlowchart,
396
579
  classDiagram: adaptClass,
397
580
  "classDiagram-v2": adaptClass,
@@ -515,7 +698,13 @@ function adaptIshikawa(db) {
515
698
  walk(c, depth + 1);
516
699
  };
517
700
  walk(root, 0);
518
- return { nodeIds: labels, edges: Math.max(0, labels.length - 1), subgraphNames: [], maxDepth };
701
+ return {
702
+ nodeIds: labels,
703
+ edges: Math.max(0, labels.length - 1),
704
+ subgraphNames: [],
705
+ maxDepth: 0,
706
+ treeDepth: maxDepth
707
+ };
519
708
  }
520
709
  function adaptKanban(db) {
521
710
  const data = callGetter(db, "getData");
@@ -543,7 +732,13 @@ function adaptMindmap(db) {
543
732
  walk(c, depth + 1);
544
733
  };
545
734
  walk(root, 0);
546
- return { nodeIds: labels, edges: Math.max(0, labels.length - 1), subgraphNames: [], maxDepth };
735
+ return {
736
+ nodeIds: labels,
737
+ edges: Math.max(0, labels.length - 1),
738
+ subgraphNames: [],
739
+ maxDepth: 0,
740
+ treeDepth: maxDepth
741
+ };
547
742
  }
548
743
  function adaptQuadrantChart(db) {
549
744
  const data = callGetter(db, "getQuadrantData");
@@ -759,184 +954,6 @@ var buildDocModel = (src, kind = "markdown") => {
759
954
  return { src, kind, tree, diagrams };
760
955
  };
761
956
 
762
- // src/errors.ts
763
- class MermaidLintError extends Error {
764
- code;
765
- constructor(code, message) {
766
- super(message);
767
- this.code = code;
768
- this.name = new.target.name;
769
- }
770
- }
771
-
772
- class UsageError extends MermaidLintError {
773
- constructor(message, code = "usage") {
774
- super(code, message);
775
- }
776
- }
777
-
778
- class FlagError extends UsageError {
779
- flag;
780
- constructor(flag, message) {
781
- super(message, "flag");
782
- this.flag = flag;
783
- }
784
- }
785
-
786
- class ConfigFileError extends UsageError {
787
- file;
788
- constructor(file, message) {
789
- super(`${file}: ${message}`, "config-file");
790
- this.file = file;
791
- }
792
- }
793
-
794
- class ConfigNotFoundError extends UsageError {
795
- path;
796
- constructor(path) {
797
- super(`--config ${path}: no such file`, "config-not-found");
798
- this.path = path;
799
- }
800
- }
801
-
802
- class ConfigCodeError extends UsageError {
803
- file;
804
- constructor(file) {
805
- 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");
806
- this.file = file;
807
- }
808
- }
809
-
810
- class AmbiguousConfigError extends UsageError {
811
- files;
812
- constructor(root, files) {
813
- super(`more than one config file at ${root}: ${files.join(", ")}`, "config-ambiguous");
814
- this.files = files;
815
- }
816
- }
817
-
818
- class ConfigShapeError extends UsageError {
819
- file;
820
- key;
821
- constructor(file, message, key = null) {
822
- super(`${file}: ${message}`, "config-shape");
823
- this.file = file;
824
- this.key = key;
825
- }
826
- }
827
-
828
- class RuleSettingError extends UsageError {
829
- file;
830
- rule;
831
- constructor(file, rule, message) {
832
- super(`${file}: rule "${rule}" ${message}`, "rule-setting");
833
- this.file = file;
834
- this.rule = rule;
835
- }
836
- }
837
-
838
- class UnknownRuleError extends UsageError {
839
- file;
840
- rule;
841
- known;
842
- constructor(file, rule, known) {
843
- super(`${file}: unknown rule "${rule}"; a retired or misspelt id cannot be switched off. Known rules: ${known.join(", ")}`, "unknown-rule");
844
- this.file = file;
845
- this.rule = rule;
846
- this.known = known;
847
- }
848
- }
849
-
850
- class RuleOptionError extends UsageError {
851
- file;
852
- rule;
853
- option;
854
- constructor(file, rule, option, message) {
855
- super(`${file}: ${message}`, "rule-option");
856
- this.file = file;
857
- this.rule = rule;
858
- this.option = option;
859
- }
860
- }
861
-
862
- class FixOutsideRootError extends UsageError {
863
- file;
864
- target;
865
- constructor(file, target, root) {
866
- super(`${file} is a symbolic link to ${target}, outside the project root ${root}; --fix does not write through it. Fix the target in its own project, or name it directly`, "fix-outside-root");
867
- this.file = file;
868
- this.target = target;
869
- }
870
- }
871
-
872
- class PackageJsonError extends MermaidLintError {
873
- file;
874
- constructor(file) {
875
- super("package-json", `${file} is not a JSON object; fix or remove it`);
876
- this.file = file;
877
- }
878
- }
879
-
880
- class FixNotConvergingError extends MermaidLintError {
881
- file;
882
- rules;
883
- constructor(file, passes, rules) {
884
- super("fix-not-converging", `${file}: --fix did not converge after ${passes} passes; ${rules.join(", ")} kept proposing edits. A fixer must propose nothing once its edit is applied`);
885
- this.file = file;
886
- this.rules = rules;
887
- }
888
- }
889
-
890
- class PluginLoadError extends MermaidLintError {
891
- source;
892
- constructor(source, reason, code = "plugin") {
893
- super(code, `plugin ${source}: ${reason}`);
894
- this.source = source;
895
- }
896
- }
897
-
898
- class PluginImportError extends PluginLoadError {
899
- constructor(source, reason) {
900
- super(source, `could not be imported: ${reason}`, "plugin-import");
901
- }
902
- }
903
-
904
- class PluginContractError extends PluginLoadError {
905
- constructor(source, reason) {
906
- super(source, reason, "plugin-contract");
907
- }
908
- }
909
-
910
- class PluginConflictError extends PluginLoadError {
911
- other;
912
- constructor(source, other, reason) {
913
- super(source, reason, "plugin-conflict");
914
- this.other = other;
915
- }
916
- }
917
-
918
- class PluginNotInstalledError extends PluginLoadError {
919
- root;
920
- constructor(source, root) {
921
- super(source, `is declared or listed but not installed; looked from ${root} upward. Install it, or remove it`, "plugin-not-installed");
922
- this.root = root;
923
- }
924
- }
925
-
926
- class PluginSourceError extends PluginLoadError {
927
- constructor(source, reason) {
928
- super(source, reason, "plugin-source");
929
- }
930
- }
931
-
932
- class PluginFixerError extends MermaidLintError {
933
- rule;
934
- constructor(rule, reason) {
935
- super("plugin-fixer", `plugin rule ${rule} ${reason}`);
936
- this.rule = rule;
937
- }
938
- }
939
-
940
957
  // src/parse/contrast.ts
941
958
  import { existsSync } from "node:fs";
942
959
  import { dirname, join, resolve } from "node:path";
@@ -1278,7 +1295,7 @@ var calculateComplexity = (stats, config) => {
1278
1295
  const n = stats.nodes;
1279
1296
  const e = stats.edges;
1280
1297
  const s = stats.subgraphs;
1281
- const d = stats.max_subgraph_depth;
1298
+ const d = Math.max(stats.max_subgraph_depth, stats.max_tree_depth);
1282
1299
  const base = n + e * config.edge_weight + s * config.subgraph_weight;
1283
1300
  const vcs = base * (1 + d * config.depth_weight);
1284
1301
  const maxEdges = n > 1 ? n * (n - 1) : 0;
@@ -1616,7 +1633,7 @@ var parseFenceUnterminated = {
1616
1633
  id: RULE.FENCE_UNTERMINATED,
1617
1634
  category: "parse",
1618
1635
  summary: "a mermaid fence that is never closed",
1619
- check: (ctx) => ctx.doc.diagrams.filter((d) => !d.terminated).map((d) => finding(ctx, d, RULE.FENCE_UNTERMINATED, `mermaid fence opened at line ${d.startLine} is never closed`, "Add a closing fence after the last line of the diagram.")),
1636
+ check: (ctx) => ctx.doc.diagrams.filter((d) => !d.terminated).map((d) => finding(ctx, d, RULE.FENCE_UNTERMINATED, `mermaid fence opened at line ${d.startLine} is never closed`, "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.")),
1620
1637
  fix: (ctx) => {
1621
1638
  const edits = [];
1622
1639
  const { src } = ctx.doc;
@@ -1732,6 +1749,15 @@ var buildRegistry = (input) => {
1732
1749
  var BUILTIN = buildRegistry({ rules: RULES, fixOrder: FIX_ORDER, categories: BUILTIN_CATEGORIES });
1733
1750
 
1734
1751
  // src/check.ts
1752
+ var checkRunFlags = (flags, prefix = "") => {
1753
+ const { preset, profile } = flags;
1754
+ if (preset !== undefined && canonicalPreset(preset) === null) {
1755
+ throw new FlagError(`${prefix}preset`, `${prefix}preset must be one of ${PRESET_NAMES.join(", ")}, not "${preset}"`);
1756
+ }
1757
+ if (profile !== undefined && !PROFILES.includes(profile)) {
1758
+ throw new FlagError(`${prefix}profile`, `${prefix}profile must be one of ${PROFILES.join(", ")}, not "${profile}"`);
1759
+ }
1760
+ };
1735
1761
  var optionsFor = (rule, configured, flags) => {
1736
1762
  const options = { ...configured };
1737
1763
  for (const name of ["preset", "profile"]) {
@@ -1742,6 +1768,7 @@ var optionsFor = (rule, configured, flags) => {
1742
1768
  return options;
1743
1769
  };
1744
1770
  var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
1771
+ checkRunFlags(flags);
1745
1772
  const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
1746
1773
  const findings = (await Promise.all(perRule)).flat();
1747
1774
  return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
@@ -1754,7 +1781,7 @@ import { parseArgs } from "node:util";
1754
1781
  // package.json
1755
1782
  var package_default = {
1756
1783
  name: "@jpeakai/mermaidjs-lint",
1757
- version: "0.3.0",
1784
+ version: "0.4.1",
1758
1785
  type: "module",
1759
1786
  description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
1760
1787
  license: "MIT",
@@ -1916,26 +1943,40 @@ var sameDiagrams = async (before, after) => {
1916
1943
  if (!now || was.keyword !== now.keyword)
1917
1944
  return false;
1918
1945
  const [a, b] = await Promise.all([was.stats(), now.stats()]);
1919
- const shape = (s) => [
1946
+ const shape = (s, source) => [
1947
+ parseFailed(source, s),
1920
1948
  s.diagram_type,
1921
1949
  s.nodes,
1922
1950
  s.edges,
1923
1951
  s.subgraphs,
1924
1952
  s.max_subgraph_depth,
1953
+ s.max_tree_depth,
1925
1954
  s.node_ids,
1926
1955
  s.subgraph_names
1927
1956
  ];
1928
- if (!sameJson(shape(a), shape(b)))
1957
+ if (!sameJson(shape(a, was.source), shape(b, now.source)))
1929
1958
  return false;
1930
1959
  }
1931
1960
  return true;
1932
1961
  };
1962
+ var breaksAParse = async (before, after) => {
1963
+ for (const [i, was] of before.diagrams.entries()) {
1964
+ const now = after.diagrams[i];
1965
+ if (!now)
1966
+ continue;
1967
+ const [a, b] = await Promise.all([was.stats(), now.stats()]);
1968
+ if (!parseFailed(was.source, a) && parseFailed(now.source, b))
1969
+ return true;
1970
+ }
1971
+ return false;
1972
+ };
1933
1973
  var verify = async (edit, before, after) => {
1934
1974
  if (edit.expect.kind === "same-tree") {
1935
1975
  if (!sameTree(before.tree, after.tree, {}))
1936
1976
  return "refuse";
1937
- if (before.kind === "markdown" && after.kind === "markdown")
1938
- return "accept";
1977
+ if (before.kind === "markdown" && after.kind === "markdown") {
1978
+ return await breaksAParse(before, after) ? "refuse" : "accept";
1979
+ }
1939
1980
  }
1940
1981
  return await sameDiagrams(before, after) ? "accept" : "refuse";
1941
1982
  };
@@ -1955,6 +1996,7 @@ var overlaps = (edits) => {
1955
1996
  };
1956
1997
  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";
1957
1998
  var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
1999
+ checkRunFlags(flags);
1958
2000
  const kind = documentKind(file);
1959
2001
  const applied = [];
1960
2002
  const refused = [];
@@ -2569,6 +2611,7 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
2569
2611
  numeric threshold set in the config overrides both.
2570
2612
  --profile NAME sets how contrast is read: ${PROFILES.join(", ")}. It overrides a profile set in the config.
2571
2613
  auto reads mkdocs-material when any parent folder holds mkdocs.yml or mkdocs.yaml, else github.
2614
+ --json prints one JSON object instead of text lines.
2572
2615
  --fix applies every fix it can prove safe, then reports what remains.
2573
2616
  A fixer that is unsure leaves the text alone and the finding stays.
2574
2617
  --config FILE reads that config instead of mermaidjs-lint.config.json or .mjs at the project root.
@@ -2696,12 +2739,7 @@ var run = async (argv, cwd) => {
2696
2739
  }
2697
2740
  const listing = values["list-rules"];
2698
2741
  const validating = values["validate-plugins"];
2699
- if (values.preset !== undefined && canonicalPreset(values.preset) === null) {
2700
- throw new FlagError("--preset", `--preset must be one of ${PRESET_NAMES.join(", ")}, not "${values.preset}"`);
2701
- }
2702
- if (values.profile !== undefined && !PROFILES.includes(values.profile)) {
2703
- throw new FlagError("--profile", `--profile must be one of ${PROFILES.join(", ")}, not "${values.profile}"`);
2704
- }
2742
+ checkRunFlags({ preset: values.preset, profile: values.profile }, "--");
2705
2743
  if (values["no-plugins"] && values["lenient-plugins"]) {
2706
2744
  throw new FlagError("--lenient-plugins", "--no-plugins and --lenient-plugins contradict each other");
2707
2745
  }