@jpeakai/mermaidjs-lint 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.1",
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"]) {
@@ -1837,6 +1846,7 @@ var optionsFor = (rule, configured, flags) => {
1837
1846
  return options;
1838
1847
  };
1839
1848
  var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
1849
+ checkRunFlags(flags);
1840
1850
  const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
1841
1851
  const findings = (await Promise.all(perRule)).flat();
1842
1852
  return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
@@ -1987,6 +1997,7 @@ var overlaps = (edits) => {
1987
1997
  };
1988
1998
  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
1999
  var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
2000
+ checkRunFlags(flags);
1990
2001
  const kind = documentKind(file);
1991
2002
  const applied = [];
1992
2003
  const refused = [];
@@ -2600,6 +2611,7 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
2600
2611
  numeric threshold set in the config overrides both.
2601
2612
  --profile NAME sets how contrast is read: ${PROFILES.join(", ")}. It overrides a profile set in the config.
2602
2613
  auto reads mkdocs-material when any parent folder holds mkdocs.yml or mkdocs.yaml, else github.
2614
+ --json prints one JSON object instead of text lines.
2603
2615
  --fix applies every fix it can prove safe, then reports what remains.
2604
2616
  A fixer that is unsure leaves the text alone and the finding stays.
2605
2617
  --config FILE reads that config instead of mermaidjs-lint.config.json or .mjs at the project root.
@@ -2727,12 +2739,7 @@ var run = async (argv, cwd) => {
2727
2739
  }
2728
2740
  const listing = values["list-rules"];
2729
2741
  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
- }
2742
+ checkRunFlags({ preset: values.preset, profile: values.profile }, "--");
2736
2743
  if (values["no-plugins"] && values["lenient-plugins"]) {
2737
2744
  throw new FlagError("--lenient-plugins", "--no-plugins and --lenient-plugins contradict each other");
2738
2745
  }
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"]) {
@@ -1759,6 +1768,7 @@ var optionsFor = (rule, configured, flags) => {
1759
1768
  return options;
1760
1769
  };
1761
1770
  var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
1771
+ checkRunFlags(flags);
1762
1772
  const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
1763
1773
  const findings = (await Promise.all(perRule)).flat();
1764
1774
  return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
@@ -1771,7 +1781,7 @@ import { parseArgs } from "node:util";
1771
1781
  // package.json
1772
1782
  var package_default = {
1773
1783
  name: "@jpeakai/mermaidjs-lint",
1774
- version: "0.4.0",
1784
+ version: "0.4.1",
1775
1785
  type: "module",
1776
1786
  description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
1777
1787
  license: "MIT",
@@ -1986,6 +1996,7 @@ var overlaps = (edits) => {
1986
1996
  };
1987
1997
  var wellFormed = (e, length) => Number.isInteger(e.start) && Number.isInteger(e.end) && e.start >= 0 && e.start <= e.end && e.end <= length && typeof e.text === "string";
1988
1998
  var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
1999
+ checkRunFlags(flags);
1989
2000
  const kind = documentKind(file);
1990
2001
  const applied = [];
1991
2002
  const refused = [];
@@ -2600,6 +2611,7 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
2600
2611
  numeric threshold set in the config overrides both.
2601
2612
  --profile NAME sets how contrast is read: ${PROFILES.join(", ")}. It overrides a profile set in the config.
2602
2613
  auto reads mkdocs-material when any parent folder holds mkdocs.yml or mkdocs.yaml, else github.
2614
+ --json prints one JSON object instead of text lines.
2603
2615
  --fix applies every fix it can prove safe, then reports what remains.
2604
2616
  A fixer that is unsure leaves the text alone and the finding stays.
2605
2617
  --config FILE reads that config instead of mermaidjs-lint.config.json or .mjs at the project root.
@@ -2727,12 +2739,7 @@ var run = async (argv, cwd) => {
2727
2739
  }
2728
2740
  const listing = values["list-rules"];
2729
2741
  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
- }
2742
+ checkRunFlags({ preset: values.preset, profile: values.profile }, "--");
2736
2743
  if (values["no-plugins"] && values["lenient-plugins"]) {
2737
2744
  throw new FlagError("--lenient-plugins", "--no-plugins and --lenient-plugins contradict each other");
2738
2745
  }
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"]) {
@@ -1648,6 +1665,7 @@ var optionsFor = (rule, configured, flags) => {
1648
1665
  return options;
1649
1666
  };
1650
1667
  var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
1668
+ checkRunFlags(flags);
1651
1669
  const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
1652
1670
  const findings = (await Promise.all(perRule)).flat();
1653
1671
  return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
@@ -1739,6 +1757,7 @@ var overlaps = (edits) => {
1739
1757
  };
1740
1758
  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
1759
  var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
1760
+ checkRunFlags(flags);
1742
1761
  const kind = documentKind(file);
1743
1762
  const applied = [];
1744
1763
  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.1",
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,6 +18,22 @@ 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
38
  // option the rule declares.
20
39
  export const optionsFor = (rule: Rule, configured: RuleOptions | undefined, flags: RunFlags): RuleOptions => {
@@ -32,6 +51,7 @@ export const checkModel = async (
32
51
  flags: RunFlags = {},
33
52
  registry: Registry = BUILTIN,
34
53
  ): Promise<Finding[]> => {
54
+ checkRunFlags(flags);
35
55
  const perRule = registry.rules.map((rule) =>
36
56
  rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }),
37
57
  );
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[] = [];