@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 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.0",
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
- if (values.preset !== undefined && canonicalPreset(values.preset) === null) {
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.0",
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
- if (values.preset !== undefined && canonicalPreset(values.preset) === null) {
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 = [];
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jpeakai/mermaidjs-lint",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "description": "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
6
6
  "license": "MIT",
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 { canonicalPreset, PRESET_NAMES } from "./parse/metrics.ts";
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
- if (values.preset !== undefined && canonicalPreset(values.preset) === null) {
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[] = [];