@jpeakai/mermaidjs-lint 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +194 -185
- package/dist/index.js +194 -185
- package/dist/testing.js +89 -68
- package/dist/types/check.d.ts +1 -0
- package/package.json +1 -1
- package/src/check.ts +23 -1
- package/src/cli.ts +4 -8
- package/src/fix/engine.ts +2 -1
package/dist/bin.js
CHANGED
|
@@ -9,7 +9,7 @@ import { parseArgs } from "node:util";
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "@jpeakai/mermaidjs-lint",
|
|
12
|
-
version: "0.4.
|
|
12
|
+
version: "0.4.2",
|
|
13
13
|
type: "module",
|
|
14
14
|
description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
|
|
15
15
|
license: "MIT",
|
|
@@ -79,6 +79,184 @@ var package_default = {
|
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
// src/errors.ts
|
|
83
|
+
class MermaidLintError extends Error {
|
|
84
|
+
code;
|
|
85
|
+
constructor(code, message) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.code = code;
|
|
88
|
+
this.name = new.target.name;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class UsageError extends MermaidLintError {
|
|
93
|
+
constructor(message, code = "usage") {
|
|
94
|
+
super(code, message);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class FlagError extends UsageError {
|
|
99
|
+
flag;
|
|
100
|
+
constructor(flag, message) {
|
|
101
|
+
super(message, "flag");
|
|
102
|
+
this.flag = flag;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
class ConfigFileError extends UsageError {
|
|
107
|
+
file;
|
|
108
|
+
constructor(file, message) {
|
|
109
|
+
super(`${file}: ${message}`, "config-file");
|
|
110
|
+
this.file = file;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class ConfigNotFoundError extends UsageError {
|
|
115
|
+
path;
|
|
116
|
+
constructor(path) {
|
|
117
|
+
super(`--config ${path}: no such file`, "config-not-found");
|
|
118
|
+
this.path = path;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class ConfigCodeError extends UsageError {
|
|
123
|
+
file;
|
|
124
|
+
constructor(file) {
|
|
125
|
+
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");
|
|
126
|
+
this.file = file;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class AmbiguousConfigError extends UsageError {
|
|
131
|
+
files;
|
|
132
|
+
constructor(root, files) {
|
|
133
|
+
super(`more than one config file at ${root}: ${files.join(", ")}`, "config-ambiguous");
|
|
134
|
+
this.files = files;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
class ConfigShapeError extends UsageError {
|
|
139
|
+
file;
|
|
140
|
+
key;
|
|
141
|
+
constructor(file, message, key = null) {
|
|
142
|
+
super(`${file}: ${message}`, "config-shape");
|
|
143
|
+
this.file = file;
|
|
144
|
+
this.key = key;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
class RuleSettingError extends UsageError {
|
|
149
|
+
file;
|
|
150
|
+
rule;
|
|
151
|
+
constructor(file, rule, message) {
|
|
152
|
+
super(`${file}: rule "${rule}" ${message}`, "rule-setting");
|
|
153
|
+
this.file = file;
|
|
154
|
+
this.rule = rule;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
class UnknownRuleError extends UsageError {
|
|
159
|
+
file;
|
|
160
|
+
rule;
|
|
161
|
+
known;
|
|
162
|
+
constructor(file, rule, known) {
|
|
163
|
+
super(`${file}: unknown rule "${rule}"; a retired or misspelt id cannot be switched off. Known rules: ${known.join(", ")}`, "unknown-rule");
|
|
164
|
+
this.file = file;
|
|
165
|
+
this.rule = rule;
|
|
166
|
+
this.known = known;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
class RuleOptionError extends UsageError {
|
|
171
|
+
file;
|
|
172
|
+
rule;
|
|
173
|
+
option;
|
|
174
|
+
constructor(file, rule, option, message) {
|
|
175
|
+
super(`${file}: ${message}`, "rule-option");
|
|
176
|
+
this.file = file;
|
|
177
|
+
this.rule = rule;
|
|
178
|
+
this.option = option;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
class FixOutsideRootError extends UsageError {
|
|
183
|
+
file;
|
|
184
|
+
target;
|
|
185
|
+
constructor(file, target, root) {
|
|
186
|
+
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");
|
|
187
|
+
this.file = file;
|
|
188
|
+
this.target = target;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
class PackageJsonError extends MermaidLintError {
|
|
193
|
+
file;
|
|
194
|
+
constructor(file) {
|
|
195
|
+
super("package-json", `${file} is not a JSON object; fix or remove it`);
|
|
196
|
+
this.file = file;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
class FixNotConvergingError extends MermaidLintError {
|
|
201
|
+
file;
|
|
202
|
+
rules;
|
|
203
|
+
constructor(file, passes, rules) {
|
|
204
|
+
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`);
|
|
205
|
+
this.file = file;
|
|
206
|
+
this.rules = rules;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
class PluginLoadError extends MermaidLintError {
|
|
211
|
+
source;
|
|
212
|
+
constructor(source, reason, code = "plugin") {
|
|
213
|
+
super(code, `plugin ${source}: ${reason}`);
|
|
214
|
+
this.source = source;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
class PluginImportError extends PluginLoadError {
|
|
219
|
+
constructor(source, reason) {
|
|
220
|
+
super(source, `could not be imported: ${reason}`, "plugin-import");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
class PluginContractError extends PluginLoadError {
|
|
225
|
+
constructor(source, reason) {
|
|
226
|
+
super(source, reason, "plugin-contract");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
class PluginConflictError extends PluginLoadError {
|
|
231
|
+
other;
|
|
232
|
+
constructor(source, other, reason) {
|
|
233
|
+
super(source, reason, "plugin-conflict");
|
|
234
|
+
this.other = other;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
class PluginNotInstalledError extends PluginLoadError {
|
|
239
|
+
root;
|
|
240
|
+
constructor(source, root) {
|
|
241
|
+
super(source, `is declared or listed but not installed; looked from ${root} upward. Install it, or remove it`, "plugin-not-installed");
|
|
242
|
+
this.root = root;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
class PluginSourceError extends PluginLoadError {
|
|
247
|
+
constructor(source, reason) {
|
|
248
|
+
super(source, reason, "plugin-source");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
class PluginFixerError extends MermaidLintError {
|
|
253
|
+
rule;
|
|
254
|
+
constructor(rule, reason) {
|
|
255
|
+
super("plugin-fixer", `plugin rule ${rule} ${reason}`);
|
|
256
|
+
this.rule = rule;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
82
260
|
// src/model.ts
|
|
83
261
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
84
262
|
import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
|
|
@@ -854,184 +1032,6 @@ var buildDocModel = (src, kind = "markdown") => {
|
|
|
854
1032
|
return { src, kind, tree, diagrams };
|
|
855
1033
|
};
|
|
856
1034
|
|
|
857
|
-
// src/errors.ts
|
|
858
|
-
class MermaidLintError extends Error {
|
|
859
|
-
code;
|
|
860
|
-
constructor(code, message) {
|
|
861
|
-
super(message);
|
|
862
|
-
this.code = code;
|
|
863
|
-
this.name = new.target.name;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
class UsageError extends MermaidLintError {
|
|
868
|
-
constructor(message, code = "usage") {
|
|
869
|
-
super(code, message);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
class FlagError extends UsageError {
|
|
874
|
-
flag;
|
|
875
|
-
constructor(flag, message) {
|
|
876
|
-
super(message, "flag");
|
|
877
|
-
this.flag = flag;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
class ConfigFileError extends UsageError {
|
|
882
|
-
file;
|
|
883
|
-
constructor(file, message) {
|
|
884
|
-
super(`${file}: ${message}`, "config-file");
|
|
885
|
-
this.file = file;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
class ConfigNotFoundError extends UsageError {
|
|
890
|
-
path;
|
|
891
|
-
constructor(path) {
|
|
892
|
-
super(`--config ${path}: no such file`, "config-not-found");
|
|
893
|
-
this.path = path;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
class ConfigCodeError extends UsageError {
|
|
898
|
-
file;
|
|
899
|
-
constructor(file) {
|
|
900
|
-
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");
|
|
901
|
-
this.file = file;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
class AmbiguousConfigError extends UsageError {
|
|
906
|
-
files;
|
|
907
|
-
constructor(root, files) {
|
|
908
|
-
super(`more than one config file at ${root}: ${files.join(", ")}`, "config-ambiguous");
|
|
909
|
-
this.files = files;
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
class ConfigShapeError extends UsageError {
|
|
914
|
-
file;
|
|
915
|
-
key;
|
|
916
|
-
constructor(file, message, key = null) {
|
|
917
|
-
super(`${file}: ${message}`, "config-shape");
|
|
918
|
-
this.file = file;
|
|
919
|
-
this.key = key;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
class RuleSettingError extends UsageError {
|
|
924
|
-
file;
|
|
925
|
-
rule;
|
|
926
|
-
constructor(file, rule, message) {
|
|
927
|
-
super(`${file}: rule "${rule}" ${message}`, "rule-setting");
|
|
928
|
-
this.file = file;
|
|
929
|
-
this.rule = rule;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
class UnknownRuleError extends UsageError {
|
|
934
|
-
file;
|
|
935
|
-
rule;
|
|
936
|
-
known;
|
|
937
|
-
constructor(file, rule, known) {
|
|
938
|
-
super(`${file}: unknown rule "${rule}"; a retired or misspelt id cannot be switched off. Known rules: ${known.join(", ")}`, "unknown-rule");
|
|
939
|
-
this.file = file;
|
|
940
|
-
this.rule = rule;
|
|
941
|
-
this.known = known;
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
class RuleOptionError extends UsageError {
|
|
946
|
-
file;
|
|
947
|
-
rule;
|
|
948
|
-
option;
|
|
949
|
-
constructor(file, rule, option, message) {
|
|
950
|
-
super(`${file}: ${message}`, "rule-option");
|
|
951
|
-
this.file = file;
|
|
952
|
-
this.rule = rule;
|
|
953
|
-
this.option = option;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
class FixOutsideRootError extends UsageError {
|
|
958
|
-
file;
|
|
959
|
-
target;
|
|
960
|
-
constructor(file, target, root) {
|
|
961
|
-
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");
|
|
962
|
-
this.file = file;
|
|
963
|
-
this.target = target;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
class PackageJsonError extends MermaidLintError {
|
|
968
|
-
file;
|
|
969
|
-
constructor(file) {
|
|
970
|
-
super("package-json", `${file} is not a JSON object; fix or remove it`);
|
|
971
|
-
this.file = file;
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
class FixNotConvergingError extends MermaidLintError {
|
|
976
|
-
file;
|
|
977
|
-
rules;
|
|
978
|
-
constructor(file, passes, rules) {
|
|
979
|
-
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`);
|
|
980
|
-
this.file = file;
|
|
981
|
-
this.rules = rules;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
class PluginLoadError extends MermaidLintError {
|
|
986
|
-
source;
|
|
987
|
-
constructor(source, reason, code = "plugin") {
|
|
988
|
-
super(code, `plugin ${source}: ${reason}`);
|
|
989
|
-
this.source = source;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
class PluginImportError extends PluginLoadError {
|
|
994
|
-
constructor(source, reason) {
|
|
995
|
-
super(source, `could not be imported: ${reason}`, "plugin-import");
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
class PluginContractError extends PluginLoadError {
|
|
1000
|
-
constructor(source, reason) {
|
|
1001
|
-
super(source, reason, "plugin-contract");
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
class PluginConflictError extends PluginLoadError {
|
|
1006
|
-
other;
|
|
1007
|
-
constructor(source, other, reason) {
|
|
1008
|
-
super(source, reason, "plugin-conflict");
|
|
1009
|
-
this.other = other;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
class PluginNotInstalledError extends PluginLoadError {
|
|
1014
|
-
root;
|
|
1015
|
-
constructor(source, root) {
|
|
1016
|
-
super(source, `is declared or listed but not installed; looked from ${root} upward. Install it, or remove it`, "plugin-not-installed");
|
|
1017
|
-
this.root = root;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
class PluginSourceError extends PluginLoadError {
|
|
1022
|
-
constructor(source, reason) {
|
|
1023
|
-
super(source, reason, "plugin-source");
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
class PluginFixerError extends MermaidLintError {
|
|
1028
|
-
rule;
|
|
1029
|
-
constructor(rule, reason) {
|
|
1030
|
-
super("plugin-fixer", `plugin rule ${rule} ${reason}`);
|
|
1031
|
-
this.rule = rule;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
1035
|
// src/parse/contrast.ts
|
|
1036
1036
|
import { existsSync } from "node:fs";
|
|
1037
1037
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -1827,6 +1827,15 @@ var buildRegistry = (input) => {
|
|
|
1827
1827
|
var BUILTIN = buildRegistry({ rules: RULES, fixOrder: FIX_ORDER, categories: BUILTIN_CATEGORIES });
|
|
1828
1828
|
|
|
1829
1829
|
// src/check.ts
|
|
1830
|
+
var checkRunFlags = (flags, prefix = "") => {
|
|
1831
|
+
const { preset, profile } = flags;
|
|
1832
|
+
if (preset !== undefined && canonicalPreset(preset) === null) {
|
|
1833
|
+
throw new FlagError(`${prefix}preset`, `${prefix}preset must be one of ${PRESET_NAMES.join(", ")}, not "${preset}"`);
|
|
1834
|
+
}
|
|
1835
|
+
if (profile !== undefined && !PROFILES.includes(profile)) {
|
|
1836
|
+
throw new FlagError(`${prefix}profile`, `${prefix}profile must be one of ${PROFILES.join(", ")}, not "${profile}"`);
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1830
1839
|
var optionsFor = (rule, configured, flags) => {
|
|
1831
1840
|
const options = { ...configured };
|
|
1832
1841
|
for (const name of ["preset", "profile"]) {
|
|
@@ -1834,9 +1843,12 @@ var optionsFor = (rule, configured, flags) => {
|
|
|
1834
1843
|
if (value !== undefined && rule.options?.[name])
|
|
1835
1844
|
options[name] = value;
|
|
1836
1845
|
}
|
|
1846
|
+
if (typeof options.preset === "string")
|
|
1847
|
+
options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
1837
1848
|
return options;
|
|
1838
1849
|
};
|
|
1839
1850
|
var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
|
|
1851
|
+
checkRunFlags(flags);
|
|
1840
1852
|
const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
|
|
1841
1853
|
const findings = (await Promise.all(perRule)).flat();
|
|
1842
1854
|
return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
|
|
@@ -1987,6 +1999,7 @@ var overlaps = (edits) => {
|
|
|
1987
1999
|
};
|
|
1988
2000
|
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";
|
|
1989
2001
|
var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
2002
|
+
checkRunFlags(flags);
|
|
1990
2003
|
const kind = documentKind(file);
|
|
1991
2004
|
const applied = [];
|
|
1992
2005
|
const refused = [];
|
|
@@ -2600,6 +2613,7 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
|
|
|
2600
2613
|
numeric threshold set in the config overrides both.
|
|
2601
2614
|
--profile NAME sets how contrast is read: ${PROFILES.join(", ")}. It overrides a profile set in the config.
|
|
2602
2615
|
auto reads mkdocs-material when any parent folder holds mkdocs.yml or mkdocs.yaml, else github.
|
|
2616
|
+
--json prints one JSON object instead of text lines.
|
|
2603
2617
|
--fix applies every fix it can prove safe, then reports what remains.
|
|
2604
2618
|
A fixer that is unsure leaves the text alone and the finding stays.
|
|
2605
2619
|
--config FILE reads that config instead of mermaidjs-lint.config.json or .mjs at the project root.
|
|
@@ -2727,12 +2741,7 @@ var run = async (argv, cwd) => {
|
|
|
2727
2741
|
}
|
|
2728
2742
|
const listing = values["list-rules"];
|
|
2729
2743
|
const validating = values["validate-plugins"];
|
|
2730
|
-
|
|
2731
|
-
throw new FlagError("--preset", `--preset must be one of ${PRESET_NAMES.join(", ")}, not "${values.preset}"`);
|
|
2732
|
-
}
|
|
2733
|
-
if (values.profile !== undefined && !PROFILES.includes(values.profile)) {
|
|
2734
|
-
throw new FlagError("--profile", `--profile must be one of ${PROFILES.join(", ")}, not "${values.profile}"`);
|
|
2735
|
-
}
|
|
2744
|
+
checkRunFlags({ preset: values.preset, profile: values.profile }, "--");
|
|
2736
2745
|
if (values["no-plugins"] && values["lenient-plugins"]) {
|
|
2737
2746
|
throw new FlagError("--lenient-plugins", "--no-plugins and --lenient-plugins contradict each other");
|
|
2738
2747
|
}
|
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";
|
|
@@ -776,184 +954,6 @@ var buildDocModel = (src, kind = "markdown") => {
|
|
|
776
954
|
return { src, kind, tree, diagrams };
|
|
777
955
|
};
|
|
778
956
|
|
|
779
|
-
// src/errors.ts
|
|
780
|
-
class MermaidLintError extends Error {
|
|
781
|
-
code;
|
|
782
|
-
constructor(code, message) {
|
|
783
|
-
super(message);
|
|
784
|
-
this.code = code;
|
|
785
|
-
this.name = new.target.name;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
class UsageError extends MermaidLintError {
|
|
790
|
-
constructor(message, code = "usage") {
|
|
791
|
-
super(code, message);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
class FlagError extends UsageError {
|
|
796
|
-
flag;
|
|
797
|
-
constructor(flag, message) {
|
|
798
|
-
super(message, "flag");
|
|
799
|
-
this.flag = flag;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
class ConfigFileError extends UsageError {
|
|
804
|
-
file;
|
|
805
|
-
constructor(file, message) {
|
|
806
|
-
super(`${file}: ${message}`, "config-file");
|
|
807
|
-
this.file = file;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
class ConfigNotFoundError extends UsageError {
|
|
812
|
-
path;
|
|
813
|
-
constructor(path) {
|
|
814
|
-
super(`--config ${path}: no such file`, "config-not-found");
|
|
815
|
-
this.path = path;
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
class ConfigCodeError extends UsageError {
|
|
820
|
-
file;
|
|
821
|
-
constructor(file) {
|
|
822
|
-
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");
|
|
823
|
-
this.file = file;
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
class AmbiguousConfigError extends UsageError {
|
|
828
|
-
files;
|
|
829
|
-
constructor(root, files) {
|
|
830
|
-
super(`more than one config file at ${root}: ${files.join(", ")}`, "config-ambiguous");
|
|
831
|
-
this.files = files;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
class ConfigShapeError extends UsageError {
|
|
836
|
-
file;
|
|
837
|
-
key;
|
|
838
|
-
constructor(file, message, key = null) {
|
|
839
|
-
super(`${file}: ${message}`, "config-shape");
|
|
840
|
-
this.file = file;
|
|
841
|
-
this.key = key;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
class RuleSettingError extends UsageError {
|
|
846
|
-
file;
|
|
847
|
-
rule;
|
|
848
|
-
constructor(file, rule, message) {
|
|
849
|
-
super(`${file}: rule "${rule}" ${message}`, "rule-setting");
|
|
850
|
-
this.file = file;
|
|
851
|
-
this.rule = rule;
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
class UnknownRuleError extends UsageError {
|
|
856
|
-
file;
|
|
857
|
-
rule;
|
|
858
|
-
known;
|
|
859
|
-
constructor(file, rule, known) {
|
|
860
|
-
super(`${file}: unknown rule "${rule}"; a retired or misspelt id cannot be switched off. Known rules: ${known.join(", ")}`, "unknown-rule");
|
|
861
|
-
this.file = file;
|
|
862
|
-
this.rule = rule;
|
|
863
|
-
this.known = known;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
class RuleOptionError extends UsageError {
|
|
868
|
-
file;
|
|
869
|
-
rule;
|
|
870
|
-
option;
|
|
871
|
-
constructor(file, rule, option, message) {
|
|
872
|
-
super(`${file}: ${message}`, "rule-option");
|
|
873
|
-
this.file = file;
|
|
874
|
-
this.rule = rule;
|
|
875
|
-
this.option = option;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
class FixOutsideRootError extends UsageError {
|
|
880
|
-
file;
|
|
881
|
-
target;
|
|
882
|
-
constructor(file, target, root) {
|
|
883
|
-
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");
|
|
884
|
-
this.file = file;
|
|
885
|
-
this.target = target;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
class PackageJsonError extends MermaidLintError {
|
|
890
|
-
file;
|
|
891
|
-
constructor(file) {
|
|
892
|
-
super("package-json", `${file} is not a JSON object; fix or remove it`);
|
|
893
|
-
this.file = file;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
class FixNotConvergingError extends MermaidLintError {
|
|
898
|
-
file;
|
|
899
|
-
rules;
|
|
900
|
-
constructor(file, passes, rules) {
|
|
901
|
-
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`);
|
|
902
|
-
this.file = file;
|
|
903
|
-
this.rules = rules;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
class PluginLoadError extends MermaidLintError {
|
|
908
|
-
source;
|
|
909
|
-
constructor(source, reason, code = "plugin") {
|
|
910
|
-
super(code, `plugin ${source}: ${reason}`);
|
|
911
|
-
this.source = source;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
class PluginImportError extends PluginLoadError {
|
|
916
|
-
constructor(source, reason) {
|
|
917
|
-
super(source, `could not be imported: ${reason}`, "plugin-import");
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
class PluginContractError extends PluginLoadError {
|
|
922
|
-
constructor(source, reason) {
|
|
923
|
-
super(source, reason, "plugin-contract");
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
class PluginConflictError extends PluginLoadError {
|
|
928
|
-
other;
|
|
929
|
-
constructor(source, other, reason) {
|
|
930
|
-
super(source, reason, "plugin-conflict");
|
|
931
|
-
this.other = other;
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
class PluginNotInstalledError extends PluginLoadError {
|
|
936
|
-
root;
|
|
937
|
-
constructor(source, root) {
|
|
938
|
-
super(source, `is declared or listed but not installed; looked from ${root} upward. Install it, or remove it`, "plugin-not-installed");
|
|
939
|
-
this.root = root;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
class PluginSourceError extends PluginLoadError {
|
|
944
|
-
constructor(source, reason) {
|
|
945
|
-
super(source, reason, "plugin-source");
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
class PluginFixerError extends MermaidLintError {
|
|
950
|
-
rule;
|
|
951
|
-
constructor(rule, reason) {
|
|
952
|
-
super("plugin-fixer", `plugin rule ${rule} ${reason}`);
|
|
953
|
-
this.rule = rule;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
957
|
// src/parse/contrast.ts
|
|
958
958
|
import { existsSync } from "node:fs";
|
|
959
959
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -1749,6 +1749,15 @@ var buildRegistry = (input) => {
|
|
|
1749
1749
|
var BUILTIN = buildRegistry({ rules: RULES, fixOrder: FIX_ORDER, categories: BUILTIN_CATEGORIES });
|
|
1750
1750
|
|
|
1751
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
|
+
};
|
|
1752
1761
|
var optionsFor = (rule, configured, flags) => {
|
|
1753
1762
|
const options = { ...configured };
|
|
1754
1763
|
for (const name of ["preset", "profile"]) {
|
|
@@ -1756,9 +1765,12 @@ var optionsFor = (rule, configured, flags) => {
|
|
|
1756
1765
|
if (value !== undefined && rule.options?.[name])
|
|
1757
1766
|
options[name] = value;
|
|
1758
1767
|
}
|
|
1768
|
+
if (typeof options.preset === "string")
|
|
1769
|
+
options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
1759
1770
|
return options;
|
|
1760
1771
|
};
|
|
1761
1772
|
var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
|
|
1773
|
+
checkRunFlags(flags);
|
|
1762
1774
|
const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
|
|
1763
1775
|
const findings = (await Promise.all(perRule)).flat();
|
|
1764
1776
|
return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
|
|
@@ -1771,7 +1783,7 @@ import { parseArgs } from "node:util";
|
|
|
1771
1783
|
// package.json
|
|
1772
1784
|
var package_default = {
|
|
1773
1785
|
name: "@jpeakai/mermaidjs-lint",
|
|
1774
|
-
version: "0.4.
|
|
1786
|
+
version: "0.4.2",
|
|
1775
1787
|
type: "module",
|
|
1776
1788
|
description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
|
|
1777
1789
|
license: "MIT",
|
|
@@ -1986,6 +1998,7 @@ var overlaps = (edits) => {
|
|
|
1986
1998
|
};
|
|
1987
1999
|
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";
|
|
1988
2000
|
var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
2001
|
+
checkRunFlags(flags);
|
|
1989
2002
|
const kind = documentKind(file);
|
|
1990
2003
|
const applied = [];
|
|
1991
2004
|
const refused = [];
|
|
@@ -2600,6 +2613,7 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
|
|
|
2600
2613
|
numeric threshold set in the config overrides both.
|
|
2601
2614
|
--profile NAME sets how contrast is read: ${PROFILES.join(", ")}. It overrides a profile set in the config.
|
|
2602
2615
|
auto reads mkdocs-material when any parent folder holds mkdocs.yml or mkdocs.yaml, else github.
|
|
2616
|
+
--json prints one JSON object instead of text lines.
|
|
2603
2617
|
--fix applies every fix it can prove safe, then reports what remains.
|
|
2604
2618
|
A fixer that is unsure leaves the text alone and the finding stays.
|
|
2605
2619
|
--config FILE reads that config instead of mermaidjs-lint.config.json or .mjs at the project root.
|
|
@@ -2727,12 +2741,7 @@ var run = async (argv, cwd) => {
|
|
|
2727
2741
|
}
|
|
2728
2742
|
const listing = values["list-rules"];
|
|
2729
2743
|
const validating = values["validate-plugins"];
|
|
2730
|
-
|
|
2731
|
-
throw new FlagError("--preset", `--preset must be one of ${PRESET_NAMES.join(", ")}, not "${values.preset}"`);
|
|
2732
|
-
}
|
|
2733
|
-
if (values.profile !== undefined && !PROFILES.includes(values.profile)) {
|
|
2734
|
-
throw new FlagError("--profile", `--profile must be one of ${PROFILES.join(", ")}, not "${values.profile}"`);
|
|
2735
|
-
}
|
|
2744
|
+
checkRunFlags({ preset: values.preset, profile: values.profile }, "--");
|
|
2736
2745
|
if (values["no-plugins"] && values["lenient-plugins"]) {
|
|
2737
2746
|
throw new FlagError("--lenient-plugins", "--no-plugins and --lenient-plugins contradict each other");
|
|
2738
2747
|
}
|
package/dist/testing.js
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
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
|
+
class UnknownRuleError extends UsageError {
|
|
28
|
+
file;
|
|
29
|
+
rule;
|
|
30
|
+
known;
|
|
31
|
+
constructor(file, rule, known) {
|
|
32
|
+
super(`${file}: unknown rule "${rule}"; a retired or misspelt id cannot be switched off. Known rules: ${known.join(", ")}`, "unknown-rule");
|
|
33
|
+
this.file = file;
|
|
34
|
+
this.rule = rule;
|
|
35
|
+
this.known = known;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class RuleOptionError extends UsageError {
|
|
40
|
+
file;
|
|
41
|
+
rule;
|
|
42
|
+
option;
|
|
43
|
+
constructor(file, rule, option, message) {
|
|
44
|
+
super(`${file}: ${message}`, "rule-option");
|
|
45
|
+
this.file = file;
|
|
46
|
+
this.rule = rule;
|
|
47
|
+
this.option = option;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
class FixNotConvergingError extends MermaidLintError {
|
|
51
|
+
file;
|
|
52
|
+
rules;
|
|
53
|
+
constructor(file, passes, rules) {
|
|
54
|
+
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`);
|
|
55
|
+
this.file = file;
|
|
56
|
+
this.rules = rules;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class PluginLoadError extends MermaidLintError {
|
|
61
|
+
source;
|
|
62
|
+
constructor(source, reason, code = "plugin") {
|
|
63
|
+
super(code, `plugin ${source}: ${reason}`);
|
|
64
|
+
this.source = source;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
class PluginContractError extends PluginLoadError {
|
|
68
|
+
constructor(source, reason) {
|
|
69
|
+
super(source, reason, "plugin-contract");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
class PluginFixerError extends MermaidLintError {
|
|
73
|
+
rule;
|
|
74
|
+
constructor(rule, reason) {
|
|
75
|
+
super("plugin-fixer", `plugin rule ${rule} ${reason}`);
|
|
76
|
+
this.rule = rule;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
4
80
|
// src/model.ts
|
|
5
81
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
6
82
|
import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
|
|
@@ -776,74 +852,6 @@ var buildDocModel = (src, kind = "markdown") => {
|
|
|
776
852
|
return { src, kind, tree, diagrams };
|
|
777
853
|
};
|
|
778
854
|
|
|
779
|
-
// src/errors.ts
|
|
780
|
-
class MermaidLintError extends Error {
|
|
781
|
-
code;
|
|
782
|
-
constructor(code, message) {
|
|
783
|
-
super(message);
|
|
784
|
-
this.code = code;
|
|
785
|
-
this.name = new.target.name;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
class UsageError extends MermaidLintError {
|
|
790
|
-
constructor(message, code = "usage") {
|
|
791
|
-
super(code, message);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
class UnknownRuleError extends UsageError {
|
|
795
|
-
file;
|
|
796
|
-
rule;
|
|
797
|
-
known;
|
|
798
|
-
constructor(file, rule, known) {
|
|
799
|
-
super(`${file}: unknown rule "${rule}"; a retired or misspelt id cannot be switched off. Known rules: ${known.join(", ")}`, "unknown-rule");
|
|
800
|
-
this.file = file;
|
|
801
|
-
this.rule = rule;
|
|
802
|
-
this.known = known;
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
class RuleOptionError extends UsageError {
|
|
807
|
-
file;
|
|
808
|
-
rule;
|
|
809
|
-
option;
|
|
810
|
-
constructor(file, rule, option, message) {
|
|
811
|
-
super(`${file}: ${message}`, "rule-option");
|
|
812
|
-
this.file = file;
|
|
813
|
-
this.rule = rule;
|
|
814
|
-
this.option = option;
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
class FixNotConvergingError extends MermaidLintError {
|
|
818
|
-
file;
|
|
819
|
-
rules;
|
|
820
|
-
constructor(file, passes, rules) {
|
|
821
|
-
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`);
|
|
822
|
-
this.file = file;
|
|
823
|
-
this.rules = rules;
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
class PluginLoadError extends MermaidLintError {
|
|
828
|
-
source;
|
|
829
|
-
constructor(source, reason, code = "plugin") {
|
|
830
|
-
super(code, `plugin ${source}: ${reason}`);
|
|
831
|
-
this.source = source;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
class PluginContractError extends PluginLoadError {
|
|
835
|
-
constructor(source, reason) {
|
|
836
|
-
super(source, reason, "plugin-contract");
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
class PluginFixerError extends MermaidLintError {
|
|
840
|
-
rule;
|
|
841
|
-
constructor(rule, reason) {
|
|
842
|
-
super("plugin-fixer", `plugin rule ${rule} ${reason}`);
|
|
843
|
-
this.rule = rule;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
855
|
// src/parse/contrast.ts
|
|
848
856
|
import { existsSync } from "node:fs";
|
|
849
857
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -1638,6 +1646,15 @@ var buildRegistry = (input) => {
|
|
|
1638
1646
|
var BUILTIN = buildRegistry({ rules: RULES, fixOrder: FIX_ORDER, categories: BUILTIN_CATEGORIES });
|
|
1639
1647
|
|
|
1640
1648
|
// src/check.ts
|
|
1649
|
+
var checkRunFlags = (flags, prefix = "") => {
|
|
1650
|
+
const { preset, profile } = flags;
|
|
1651
|
+
if (preset !== undefined && canonicalPreset(preset) === null) {
|
|
1652
|
+
throw new FlagError(`${prefix}preset`, `${prefix}preset must be one of ${PRESET_NAMES.join(", ")}, not "${preset}"`);
|
|
1653
|
+
}
|
|
1654
|
+
if (profile !== undefined && !PROFILES.includes(profile)) {
|
|
1655
|
+
throw new FlagError(`${prefix}profile`, `${prefix}profile must be one of ${PROFILES.join(", ")}, not "${profile}"`);
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1641
1658
|
var optionsFor = (rule, configured, flags) => {
|
|
1642
1659
|
const options = { ...configured };
|
|
1643
1660
|
for (const name of ["preset", "profile"]) {
|
|
@@ -1645,9 +1662,12 @@ var optionsFor = (rule, configured, flags) => {
|
|
|
1645
1662
|
if (value !== undefined && rule.options?.[name])
|
|
1646
1663
|
options[name] = value;
|
|
1647
1664
|
}
|
|
1665
|
+
if (typeof options.preset === "string")
|
|
1666
|
+
options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
1648
1667
|
return options;
|
|
1649
1668
|
};
|
|
1650
1669
|
var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
|
|
1670
|
+
checkRunFlags(flags);
|
|
1651
1671
|
const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
|
|
1652
1672
|
const findings = (await Promise.all(perRule)).flat();
|
|
1653
1673
|
return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
|
|
@@ -1739,6 +1759,7 @@ var overlaps = (edits) => {
|
|
|
1739
1759
|
};
|
|
1740
1760
|
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";
|
|
1741
1761
|
var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
1762
|
+
checkRunFlags(flags);
|
|
1742
1763
|
const kind = documentKind(file);
|
|
1743
1764
|
const applied = [];
|
|
1744
1765
|
const refused = [];
|
package/dist/types/check.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface RunFlags {
|
|
|
5
5
|
preset?: string;
|
|
6
6
|
profile?: string;
|
|
7
7
|
}
|
|
8
|
+
export declare const checkRunFlags: (flags: RunFlags, prefix?: string) => void;
|
|
8
9
|
export declare const optionsFor: (rule: Rule, configured: RuleOptions | undefined, flags: RunFlags) => RuleOptions;
|
|
9
10
|
export declare const checkModel: (doc: DocModel, file: string, flags?: RunFlags, registry?: Registry) => Promise<Finding[]>;
|
|
10
11
|
export declare const checkMarkdown: (src: string, file: string, flags?: RunFlags, registry?: Registry) => Promise<Finding[]>;
|
package/package.json
CHANGED
package/src/check.ts
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
// them. A rule may be synchronous or return a promise, so a rule that waits on
|
|
5
5
|
// a tool or a service overlaps with the others.
|
|
6
6
|
|
|
7
|
+
import { FlagError } from "./errors.ts";
|
|
7
8
|
import { buildDocModel, type DocModel, documentKind } from "./model.ts";
|
|
9
|
+
import { PROFILES } from "./parse/contrast.ts";
|
|
10
|
+
import { canonicalPreset, PRESET_NAMES } from "./parse/metrics.ts";
|
|
8
11
|
import { BUILTIN, HELPERS, type Registry } from "./rules/registry.ts";
|
|
9
12
|
import type { Finding, Rule, RuleOptions } from "./rules/types.ts";
|
|
10
13
|
|
|
@@ -15,14 +18,32 @@ export interface RunFlags {
|
|
|
15
18
|
profile?: string;
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
// A preset or profile that no rule can take fails as a usage error, with the
|
|
22
|
+
// code the command gives a bad flag, so a library caller can branch on it. The
|
|
23
|
+
// command passes "--" as the prefix, so the message names its own flag.
|
|
24
|
+
export const checkRunFlags = (flags: RunFlags, prefix = ""): void => {
|
|
25
|
+
const { preset, profile } = flags;
|
|
26
|
+
if (preset !== undefined && canonicalPreset(preset) === null) {
|
|
27
|
+
throw new FlagError(
|
|
28
|
+
`${prefix}preset`,
|
|
29
|
+
`${prefix}preset must be one of ${PRESET_NAMES.join(", ")}, not "${preset}"`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (profile !== undefined && !PROFILES.includes(profile as (typeof PROFILES)[number])) {
|
|
33
|
+
throw new FlagError(`${prefix}profile`, `${prefix}profile must be one of ${PROFILES.join(", ")}, not "${profile}"`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
18
37
|
// The options a rule runs with: its configured options, then a flag for any
|
|
19
|
-
// option the rule declares.
|
|
38
|
+
// option the rule declares. A preset reaches the rule by its canonical name,
|
|
39
|
+
// so a plugin rule never has to resolve an alias such as `high` or `H`.
|
|
20
40
|
export const optionsFor = (rule: Rule, configured: RuleOptions | undefined, flags: RunFlags): RuleOptions => {
|
|
21
41
|
const options: Record<string, unknown> = { ...configured };
|
|
22
42
|
for (const name of ["preset", "profile"] as const) {
|
|
23
43
|
const value = flags[name];
|
|
24
44
|
if (value !== undefined && rule.options?.[name]) options[name] = value;
|
|
25
45
|
}
|
|
46
|
+
if (typeof options.preset === "string") options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
26
47
|
return options;
|
|
27
48
|
};
|
|
28
49
|
|
|
@@ -32,6 +53,7 @@ export const checkModel = async (
|
|
|
32
53
|
flags: RunFlags = {},
|
|
33
54
|
registry: Registry = BUILTIN,
|
|
34
55
|
): Promise<Finding[]> => {
|
|
56
|
+
checkRunFlags(flags);
|
|
35
57
|
const perRule = registry.rules.map((rule) =>
|
|
36
58
|
rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }),
|
|
37
59
|
);
|
package/src/cli.ts
CHANGED
|
@@ -5,13 +5,13 @@ import { lstat, readFile, realpath, writeFile } from "node:fs/promises";
|
|
|
5
5
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import pkg from "../package.json" with { type: "json" };
|
|
8
|
-
import { checkModel, type RunFlags } from "./check.ts";
|
|
8
|
+
import { checkModel, checkRunFlags, type RunFlags } from "./check.ts";
|
|
9
9
|
import { FixOutsideRootError, FlagError, MermaidLintError, UsageError } from "./errors.ts";
|
|
10
10
|
import { collectFiles, DIAGRAM_FILE_EXTS, SKIP_DIRS } from "./files.ts";
|
|
11
11
|
import { fixSourceReport } from "./fix/engine.ts";
|
|
12
12
|
import { buildDocModel, DIAGRAM_EXTS, documentKind } from "./model.ts";
|
|
13
13
|
import { PROFILES } from "./parse/contrast.ts";
|
|
14
|
-
import {
|
|
14
|
+
import { PRESET_NAMES } from "./parse/metrics.ts";
|
|
15
15
|
import type { PluginFailure } from "./plugins/index.ts";
|
|
16
16
|
import { setUp } from "./project.ts";
|
|
17
17
|
import { RULES } from "./rules/index.ts";
|
|
@@ -52,6 +52,7 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
|
|
|
52
52
|
numeric threshold set in the config overrides both.
|
|
53
53
|
--profile NAME sets how contrast is read: ${PROFILES.join(", ")}. It overrides a profile set in the config.
|
|
54
54
|
auto reads mkdocs-material when any parent folder holds mkdocs.yml or mkdocs.yaml, else github.
|
|
55
|
+
--json prints one JSON object instead of text lines.
|
|
55
56
|
--fix applies every fix it can prove safe, then reports what remains.
|
|
56
57
|
A fixer that is unsure leaves the text alone and the finding stays.
|
|
57
58
|
--config FILE reads that config instead of mermaidjs-lint.config.json or .mjs at the project root.
|
|
@@ -220,12 +221,7 @@ const run = async (argv: string[], cwd: string): Promise<number> => {
|
|
|
220
221
|
}
|
|
221
222
|
const listing = values["list-rules"];
|
|
222
223
|
const validating = values["validate-plugins"];
|
|
223
|
-
|
|
224
|
-
throw new FlagError("--preset", `--preset must be one of ${PRESET_NAMES.join(", ")}, not "${values.preset}"`);
|
|
225
|
-
}
|
|
226
|
-
if (values.profile !== undefined && !PROFILES.includes(values.profile as (typeof PROFILES)[number])) {
|
|
227
|
-
throw new FlagError("--profile", `--profile must be one of ${PROFILES.join(", ")}, not "${values.profile}"`);
|
|
228
|
-
}
|
|
224
|
+
checkRunFlags({ preset: values.preset, profile: values.profile }, "--");
|
|
229
225
|
if (values["no-plugins"] && values["lenient-plugins"]) {
|
|
230
226
|
throw new FlagError("--lenient-plugins", "--no-plugins and --lenient-plugins contradict each other");
|
|
231
227
|
}
|
package/src/fix/engine.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// or single edit that survives verification. Offsets are only valid for the source they were
|
|
4
4
|
// computed from, so every accepted edit restarts the pass.
|
|
5
5
|
|
|
6
|
-
import { optionsFor, type RunFlags } from "../check.ts";
|
|
6
|
+
import { checkRunFlags, optionsFor, type RunFlags } from "../check.ts";
|
|
7
7
|
import { FixNotConvergingError } from "../errors.ts";
|
|
8
8
|
import { buildDocModel, documentKind } from "../model.ts";
|
|
9
9
|
import { BUILTIN, HELPERS, type Registry } from "../rules/registry.ts";
|
|
@@ -46,6 +46,7 @@ export const fixSourceReport = async (
|
|
|
46
46
|
flags: RunFlags = {},
|
|
47
47
|
registry: Registry = BUILTIN,
|
|
48
48
|
): Promise<FixReport> => {
|
|
49
|
+
checkRunFlags(flags);
|
|
49
50
|
const kind = documentKind(file);
|
|
50
51
|
const applied: Edit[] = [];
|
|
51
52
|
const refused: Edit[] = [];
|