@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/README.md +5 -4
- package/dist/bin.js +234 -196
- package/dist/index.js +234 -196
- package/dist/testing.js +129 -79
- package/dist/types/check.d.ts +1 -0
- package/dist/types/parse/stats.d.ts +1 -0
- package/package.json +1 -1
- package/src/check.ts +20 -0
- package/src/cli.ts +4 -8
- package/src/errors.ts +1 -1
- package/src/fix/engine.ts +2 -1
- package/src/fix/verify.ts +27 -4
- package/src/parse/metrics.ts +8 -1
- package/src/parse/stats.ts +33 -5
- package/src/rules/complexity-subgraph-nesting-too-deep.ts +5 -0
- package/src/rules/parse-fence-unterminated.ts +1 -1
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
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
|
}
|