@jpeakai/mermaidjs-lint 0.6.0 → 0.6.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 +39 -17
- package/dist/index.js +40 -17
- package/dist/testing.js +28 -15
- package/dist/types/check.d.ts +2 -1
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/check.ts +61 -12
- package/src/cli.ts +13 -2
- package/src/errors.ts +12 -3
- package/src/fix/engine.ts +1 -1
- package/src/index.ts +1 -1
- package/src/rules/shared.ts +3 -1
- package/src/rules/types.ts +2 -2
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.6.
|
|
12
|
+
version: "0.6.1",
|
|
13
13
|
type: "module",
|
|
14
14
|
description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
|
|
15
15
|
license: "MIT",
|
|
@@ -181,8 +181,9 @@ class RuleOptionError extends UsageError {
|
|
|
181
181
|
|
|
182
182
|
class CanvasProfileError extends UsageError {
|
|
183
183
|
file;
|
|
184
|
-
constructor(file) {
|
|
185
|
-
|
|
184
|
+
constructor(file, cause = "flag") {
|
|
185
|
+
const hint = cause === "flag" ? "run --canvas-light and --canvas-dark over the GitHub-rendered paths only, or set the canvas in the config, which a Material file sets aside" : "remove the mkdocs-material profile setting for this run, or remove the canvas";
|
|
186
|
+
super(`${file}: a canvas applies under the github profile only, and this file is scored under mkdocs-material, whose page is Material's own; ${hint}`, "canvas-profile");
|
|
186
187
|
this.file = file;
|
|
187
188
|
}
|
|
188
189
|
}
|
|
@@ -1609,8 +1610,9 @@ var settingsOf = (ctx) => {
|
|
|
1609
1610
|
if (problem)
|
|
1610
1611
|
throw new Error(`canvas.${side} ${problem}: ${JSON.stringify(setting[side])}`);
|
|
1611
1612
|
}
|
|
1612
|
-
if (profile === "mkdocs-material")
|
|
1613
|
-
throw new CanvasProfileError(ctx.file);
|
|
1613
|
+
if (profile === "mkdocs-material") {
|
|
1614
|
+
throw new CanvasProfileError(ctx.file, ctx.options.profile === "mkdocs-material" ? "profile" : "flag");
|
|
1615
|
+
}
|
|
1614
1616
|
return { profile, canvas: resolveCanvas(setting) };
|
|
1615
1617
|
};
|
|
1616
1618
|
var scored = new WeakMap;
|
|
@@ -2048,7 +2050,10 @@ var checkRunFlags = (flags, prefix = "") => {
|
|
|
2048
2050
|
throw new FlagError(`${prefix}profile`, `${prefix}profile must be one of ${PROFILES.join(", ")}, not "${profile}"`);
|
|
2049
2051
|
}
|
|
2050
2052
|
};
|
|
2051
|
-
var
|
|
2053
|
+
var canvasOptions = (rule) => Object.entries(rule.options ?? {}).filter(([, type]) => type === "canvas").map(([name]) => name);
|
|
2054
|
+
var hasFlagCanvas = (flags) => THEMES.some((side) => flags.canvas?.[side] !== undefined);
|
|
2055
|
+
var setsAside = (rule, options, flags, file) => !hasFlagCanvas(flags) && rule.options?.profile !== undefined && (options.profile ?? "auto") === "auto" && canvasOptions(rule).some((name) => options[name] !== undefined) && detectProfile(file) === "mkdocs-material";
|
|
2056
|
+
var optionsFor = (rule, configured, flags, file) => {
|
|
2052
2057
|
const options = { ...configured };
|
|
2053
2058
|
for (const name of ["preset", "profile"]) {
|
|
2054
2059
|
const value = flags[name];
|
|
@@ -2058,21 +2063,33 @@ var optionsFor = (rule, configured, flags) => {
|
|
|
2058
2063
|
if (typeof options.preset === "string")
|
|
2059
2064
|
options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
2060
2065
|
const canvas = flags.canvas;
|
|
2061
|
-
if (canvas &&
|
|
2062
|
-
const
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2066
|
+
if (canvas && hasFlagCanvas(flags)) {
|
|
2067
|
+
for (const name of canvasOptions(rule)) {
|
|
2068
|
+
const current = options[name];
|
|
2069
|
+
const merged = {
|
|
2070
|
+
...typeof current === "object" && current !== null ? current : {}
|
|
2071
|
+
};
|
|
2072
|
+
for (const side of THEMES) {
|
|
2073
|
+
const value = canvas[side];
|
|
2074
|
+
if (value !== undefined)
|
|
2075
|
+
merged[side] = value;
|
|
2076
|
+
}
|
|
2077
|
+
options[name] = merged;
|
|
2068
2078
|
}
|
|
2069
|
-
|
|
2079
|
+
}
|
|
2080
|
+
if (file !== undefined && setsAside(rule, options, flags, file)) {
|
|
2081
|
+
for (const name of canvasOptions(rule))
|
|
2082
|
+
delete options[name];
|
|
2070
2083
|
}
|
|
2071
2084
|
return options;
|
|
2072
2085
|
};
|
|
2086
|
+
var canvasSetAside = (file, flags = {}, registry = BUILTIN) => registry.rules.some((rule) => {
|
|
2087
|
+
const options = optionsFor(rule, registry.options.get(rule.id), flags);
|
|
2088
|
+
return setsAside(rule, options, flags, file);
|
|
2089
|
+
});
|
|
2073
2090
|
var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
|
|
2074
2091
|
checkRunFlags(flags);
|
|
2075
|
-
const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
|
|
2092
|
+
const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags, file), helpers: HELPERS }));
|
|
2076
2093
|
const findings = (await Promise.all(perRule)).flat();
|
|
2077
2094
|
return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
|
|
2078
2095
|
};
|
|
@@ -2244,7 +2261,7 @@ var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
|
2244
2261
|
};
|
|
2245
2262
|
let progressed = false;
|
|
2246
2263
|
for (const rule of registry.fixOrder) {
|
|
2247
|
-
const options = optionsFor(rule, registry.options.get(rule.id), flags);
|
|
2264
|
+
const options = optionsFor(rule, registry.options.get(rule.id), flags, file);
|
|
2248
2265
|
const proposed = await rule.fix?.({ doc, file, options, helpers: HELPERS }) ?? [];
|
|
2249
2266
|
const sound = proposed.filter((e) => {
|
|
2250
2267
|
if (wellFormed(e, current.length))
|
|
@@ -2839,7 +2856,8 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
|
|
|
2839
2856
|
--canvas-light COLOUR and --canvas-dark COLOUR set the page a host draws in each theme, under the github
|
|
2840
2857
|
profile only. A translucent fill, an edge label box and a sequence diagram's page-backed text are scored
|
|
2841
2858
|
on it. Each is an opaque CSS colour such as #faf8f9, and a side not given keeps the config's, or GitHub's
|
|
2842
|
-
(#ffffff light, #0d1117 dark). A file scored under mkdocs-material refuses a canvas with a usage
|
|
2859
|
+
(#ffffff light, #0d1117 dark). A file scored under mkdocs-material refuses a canvas flag with a usage
|
|
2860
|
+
error, and sets a config canvas aside when auto picked the profile.
|
|
2843
2861
|
--json prints one JSON object instead of text lines.
|
|
2844
2862
|
--fix applies every fix it can prove safe, then reports what remains.
|
|
2845
2863
|
A fixer that is unsure leaves the text alone and the finding stays.
|
|
@@ -3025,6 +3043,10 @@ var run = async (argv, cwd) => {
|
|
|
3025
3043
|
throw new FlagError("files", "no matching files");
|
|
3026
3044
|
if (values.fix)
|
|
3027
3045
|
await refuseOutsideLinks(files, root, cwd);
|
|
3046
|
+
const setAside = files.filter((file) => canvasSetAside(file, flags, registry));
|
|
3047
|
+
if (setAside.length > 0) {
|
|
3048
|
+
console.error(`mermaidjs-lint: config canvas set aside for ${plural(setAside.length, "file")} scored under mkdocs-material: ${setAside.map((f) => display(f, cwd)).join(", ")}`);
|
|
3049
|
+
}
|
|
3028
3050
|
const perFile = await Promise.all(files.map(async (file) => {
|
|
3029
3051
|
let src = await readFile3(file, "utf8");
|
|
3030
3052
|
if (values.fix) {
|
package/dist/index.js
CHANGED
|
@@ -103,8 +103,9 @@ class RuleOptionError extends UsageError {
|
|
|
103
103
|
|
|
104
104
|
class CanvasProfileError extends UsageError {
|
|
105
105
|
file;
|
|
106
|
-
constructor(file) {
|
|
107
|
-
|
|
106
|
+
constructor(file, cause = "flag") {
|
|
107
|
+
const hint = cause === "flag" ? "run --canvas-light and --canvas-dark over the GitHub-rendered paths only, or set the canvas in the config, which a Material file sets aside" : "remove the mkdocs-material profile setting for this run, or remove the canvas";
|
|
108
|
+
super(`${file}: a canvas applies under the github profile only, and this file is scored under mkdocs-material, whose page is Material's own; ${hint}`, "canvas-profile");
|
|
108
109
|
this.file = file;
|
|
109
110
|
}
|
|
110
111
|
}
|
|
@@ -1531,8 +1532,9 @@ var settingsOf = (ctx) => {
|
|
|
1531
1532
|
if (problem)
|
|
1532
1533
|
throw new Error(`canvas.${side} ${problem}: ${JSON.stringify(setting[side])}`);
|
|
1533
1534
|
}
|
|
1534
|
-
if (profile === "mkdocs-material")
|
|
1535
|
-
throw new CanvasProfileError(ctx.file);
|
|
1535
|
+
if (profile === "mkdocs-material") {
|
|
1536
|
+
throw new CanvasProfileError(ctx.file, ctx.options.profile === "mkdocs-material" ? "profile" : "flag");
|
|
1537
|
+
}
|
|
1536
1538
|
return { profile, canvas: resolveCanvas(setting) };
|
|
1537
1539
|
};
|
|
1538
1540
|
var scored = new WeakMap;
|
|
@@ -1970,7 +1972,10 @@ var checkRunFlags = (flags, prefix = "") => {
|
|
|
1970
1972
|
throw new FlagError(`${prefix}profile`, `${prefix}profile must be one of ${PROFILES.join(", ")}, not "${profile}"`);
|
|
1971
1973
|
}
|
|
1972
1974
|
};
|
|
1973
|
-
var
|
|
1975
|
+
var canvasOptions = (rule) => Object.entries(rule.options ?? {}).filter(([, type]) => type === "canvas").map(([name]) => name);
|
|
1976
|
+
var hasFlagCanvas = (flags) => THEMES.some((side) => flags.canvas?.[side] !== undefined);
|
|
1977
|
+
var setsAside = (rule, options, flags, file) => !hasFlagCanvas(flags) && rule.options?.profile !== undefined && (options.profile ?? "auto") === "auto" && canvasOptions(rule).some((name) => options[name] !== undefined) && detectProfile(file) === "mkdocs-material";
|
|
1978
|
+
var optionsFor = (rule, configured, flags, file) => {
|
|
1974
1979
|
const options = { ...configured };
|
|
1975
1980
|
for (const name of ["preset", "profile"]) {
|
|
1976
1981
|
const value = flags[name];
|
|
@@ -1980,21 +1985,33 @@ var optionsFor = (rule, configured, flags) => {
|
|
|
1980
1985
|
if (typeof options.preset === "string")
|
|
1981
1986
|
options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
1982
1987
|
const canvas = flags.canvas;
|
|
1983
|
-
if (canvas &&
|
|
1984
|
-
const
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1988
|
+
if (canvas && hasFlagCanvas(flags)) {
|
|
1989
|
+
for (const name of canvasOptions(rule)) {
|
|
1990
|
+
const current = options[name];
|
|
1991
|
+
const merged = {
|
|
1992
|
+
...typeof current === "object" && current !== null ? current : {}
|
|
1993
|
+
};
|
|
1994
|
+
for (const side of THEMES) {
|
|
1995
|
+
const value = canvas[side];
|
|
1996
|
+
if (value !== undefined)
|
|
1997
|
+
merged[side] = value;
|
|
1998
|
+
}
|
|
1999
|
+
options[name] = merged;
|
|
1990
2000
|
}
|
|
1991
|
-
|
|
2001
|
+
}
|
|
2002
|
+
if (file !== undefined && setsAside(rule, options, flags, file)) {
|
|
2003
|
+
for (const name of canvasOptions(rule))
|
|
2004
|
+
delete options[name];
|
|
1992
2005
|
}
|
|
1993
2006
|
return options;
|
|
1994
2007
|
};
|
|
2008
|
+
var canvasSetAside = (file, flags = {}, registry = BUILTIN) => registry.rules.some((rule) => {
|
|
2009
|
+
const options = optionsFor(rule, registry.options.get(rule.id), flags);
|
|
2010
|
+
return setsAside(rule, options, flags, file);
|
|
2011
|
+
});
|
|
1995
2012
|
var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
|
|
1996
2013
|
checkRunFlags(flags);
|
|
1997
|
-
const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
|
|
2014
|
+
const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags, file), helpers: HELPERS }));
|
|
1998
2015
|
const findings = (await Promise.all(perRule)).flat();
|
|
1999
2016
|
return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
|
|
2000
2017
|
};
|
|
@@ -2006,7 +2023,7 @@ import { parseArgs } from "node:util";
|
|
|
2006
2023
|
// package.json
|
|
2007
2024
|
var package_default = {
|
|
2008
2025
|
name: "@jpeakai/mermaidjs-lint",
|
|
2009
|
-
version: "0.6.
|
|
2026
|
+
version: "0.6.1",
|
|
2010
2027
|
type: "module",
|
|
2011
2028
|
description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
|
|
2012
2029
|
license: "MIT",
|
|
@@ -2243,7 +2260,7 @@ var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
|
2243
2260
|
};
|
|
2244
2261
|
let progressed = false;
|
|
2245
2262
|
for (const rule of registry.fixOrder) {
|
|
2246
|
-
const options = optionsFor(rule, registry.options.get(rule.id), flags);
|
|
2263
|
+
const options = optionsFor(rule, registry.options.get(rule.id), flags, file);
|
|
2247
2264
|
const proposed = await rule.fix?.({ doc, file, options, helpers: HELPERS }) ?? [];
|
|
2248
2265
|
const sound = proposed.filter((e) => {
|
|
2249
2266
|
if (wellFormed(e, current.length))
|
|
@@ -2839,7 +2856,8 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
|
|
|
2839
2856
|
--canvas-light COLOUR and --canvas-dark COLOUR set the page a host draws in each theme, under the github
|
|
2840
2857
|
profile only. A translucent fill, an edge label box and a sequence diagram's page-backed text are scored
|
|
2841
2858
|
on it. Each is an opaque CSS colour such as #faf8f9, and a side not given keeps the config's, or GitHub's
|
|
2842
|
-
(#ffffff light, #0d1117 dark). A file scored under mkdocs-material refuses a canvas with a usage
|
|
2859
|
+
(#ffffff light, #0d1117 dark). A file scored under mkdocs-material refuses a canvas flag with a usage
|
|
2860
|
+
error, and sets a config canvas aside when auto picked the profile.
|
|
2843
2861
|
--json prints one JSON object instead of text lines.
|
|
2844
2862
|
--fix applies every fix it can prove safe, then reports what remains.
|
|
2845
2863
|
A fixer that is unsure leaves the text alone and the finding stays.
|
|
@@ -3025,6 +3043,10 @@ var run = async (argv, cwd) => {
|
|
|
3025
3043
|
throw new FlagError("files", "no matching files");
|
|
3026
3044
|
if (values.fix)
|
|
3027
3045
|
await refuseOutsideLinks(files, root, cwd);
|
|
3046
|
+
const setAside = files.filter((file) => canvasSetAside(file, flags, registry));
|
|
3047
|
+
if (setAside.length > 0) {
|
|
3048
|
+
console.error(`mermaidjs-lint: config canvas set aside for ${plural(setAside.length, "file")} scored under mkdocs-material: ${setAside.map((f) => display(f, cwd)).join(", ")}`);
|
|
3049
|
+
}
|
|
3028
3050
|
const perFile = await Promise.all(files.map(async (file) => {
|
|
3029
3051
|
let src = await readFile3(file, "utf8");
|
|
3030
3052
|
if (values.fix) {
|
|
@@ -3082,6 +3104,7 @@ export {
|
|
|
3082
3104
|
collectFiles,
|
|
3083
3105
|
checkModel,
|
|
3084
3106
|
checkMarkdown,
|
|
3107
|
+
canvasSetAside,
|
|
3085
3108
|
buildRegistry,
|
|
3086
3109
|
buildDocModel,
|
|
3087
3110
|
UsageError,
|
package/dist/testing.js
CHANGED
|
@@ -50,8 +50,9 @@ class RuleOptionError extends UsageError {
|
|
|
50
50
|
|
|
51
51
|
class CanvasProfileError extends UsageError {
|
|
52
52
|
file;
|
|
53
|
-
constructor(file) {
|
|
54
|
-
|
|
53
|
+
constructor(file, cause = "flag") {
|
|
54
|
+
const hint = cause === "flag" ? "run --canvas-light and --canvas-dark over the GitHub-rendered paths only, or set the canvas in the config, which a Material file sets aside" : "remove the mkdocs-material profile setting for this run, or remove the canvas";
|
|
55
|
+
super(`${file}: a canvas applies under the github profile only, and this file is scored under mkdocs-material, whose page is Material's own; ${hint}`, "canvas-profile");
|
|
55
56
|
this.file = file;
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -1429,8 +1430,9 @@ var settingsOf = (ctx) => {
|
|
|
1429
1430
|
if (problem)
|
|
1430
1431
|
throw new Error(`canvas.${side} ${problem}: ${JSON.stringify(setting[side])}`);
|
|
1431
1432
|
}
|
|
1432
|
-
if (profile === "mkdocs-material")
|
|
1433
|
-
throw new CanvasProfileError(ctx.file);
|
|
1433
|
+
if (profile === "mkdocs-material") {
|
|
1434
|
+
throw new CanvasProfileError(ctx.file, ctx.options.profile === "mkdocs-material" ? "profile" : "flag");
|
|
1435
|
+
}
|
|
1434
1436
|
return { profile, canvas: resolveCanvas(setting) };
|
|
1435
1437
|
};
|
|
1436
1438
|
var scored = new WeakMap;
|
|
@@ -1867,7 +1869,10 @@ var checkRunFlags = (flags, prefix = "") => {
|
|
|
1867
1869
|
throw new FlagError(`${prefix}profile`, `${prefix}profile must be one of ${PROFILES.join(", ")}, not "${profile}"`);
|
|
1868
1870
|
}
|
|
1869
1871
|
};
|
|
1870
|
-
var
|
|
1872
|
+
var canvasOptions = (rule) => Object.entries(rule.options ?? {}).filter(([, type]) => type === "canvas").map(([name]) => name);
|
|
1873
|
+
var hasFlagCanvas = (flags) => THEMES.some((side) => flags.canvas?.[side] !== undefined);
|
|
1874
|
+
var setsAside = (rule, options, flags, file) => !hasFlagCanvas(flags) && rule.options?.profile !== undefined && (options.profile ?? "auto") === "auto" && canvasOptions(rule).some((name) => options[name] !== undefined) && detectProfile(file) === "mkdocs-material";
|
|
1875
|
+
var optionsFor = (rule, configured, flags, file) => {
|
|
1871
1876
|
const options = { ...configured };
|
|
1872
1877
|
for (const name of ["preset", "profile"]) {
|
|
1873
1878
|
const value = flags[name];
|
|
@@ -1877,21 +1882,29 @@ var optionsFor = (rule, configured, flags) => {
|
|
|
1877
1882
|
if (typeof options.preset === "string")
|
|
1878
1883
|
options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
1879
1884
|
const canvas = flags.canvas;
|
|
1880
|
-
if (canvas &&
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1885
|
+
if (canvas && hasFlagCanvas(flags)) {
|
|
1886
|
+
for (const name of canvasOptions(rule)) {
|
|
1887
|
+
const current = options[name];
|
|
1888
|
+
const merged = {
|
|
1889
|
+
...typeof current === "object" && current !== null ? current : {}
|
|
1890
|
+
};
|
|
1891
|
+
for (const side of THEMES) {
|
|
1892
|
+
const value = canvas[side];
|
|
1893
|
+
if (value !== undefined)
|
|
1894
|
+
merged[side] = value;
|
|
1895
|
+
}
|
|
1896
|
+
options[name] = merged;
|
|
1887
1897
|
}
|
|
1888
|
-
|
|
1898
|
+
}
|
|
1899
|
+
if (file !== undefined && setsAside(rule, options, flags, file)) {
|
|
1900
|
+
for (const name of canvasOptions(rule))
|
|
1901
|
+
delete options[name];
|
|
1889
1902
|
}
|
|
1890
1903
|
return options;
|
|
1891
1904
|
};
|
|
1892
1905
|
var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
|
|
1893
1906
|
checkRunFlags(flags);
|
|
1894
|
-
const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
|
|
1907
|
+
const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags, file), helpers: HELPERS }));
|
|
1895
1908
|
const findings = (await Promise.all(perRule)).flat();
|
|
1896
1909
|
return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
|
|
1897
1910
|
};
|
|
@@ -2004,7 +2017,7 @@ var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
|
2004
2017
|
};
|
|
2005
2018
|
let progressed = false;
|
|
2006
2019
|
for (const rule of registry.fixOrder) {
|
|
2007
|
-
const options = optionsFor(rule, registry.options.get(rule.id), flags);
|
|
2020
|
+
const options = optionsFor(rule, registry.options.get(rule.id), flags, file);
|
|
2008
2021
|
const proposed = await rule.fix?.({ doc, file, options, helpers: HELPERS }) ?? [];
|
|
2009
2022
|
const sound = proposed.filter((e) => {
|
|
2010
2023
|
if (wellFormed(e, current.length))
|
package/dist/types/check.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface RunFlags {
|
|
|
8
8
|
canvas?: CanvasSetting;
|
|
9
9
|
}
|
|
10
10
|
export declare const checkRunFlags: (flags: RunFlags, prefix?: string) => void;
|
|
11
|
-
export declare const optionsFor: (rule: Rule, configured: RuleOptions | undefined, flags: RunFlags) => RuleOptions;
|
|
11
|
+
export declare const optionsFor: (rule: Rule, configured: RuleOptions | undefined, flags: RunFlags, file?: string) => RuleOptions;
|
|
12
|
+
export declare const canvasSetAside: (file: string, flags?: RunFlags, registry?: Registry) => boolean;
|
|
12
13
|
export declare const checkModel: (doc: DocModel, file: string, flags?: RunFlags, registry?: Registry) => Promise<Finding[]>;
|
|
13
14
|
export declare const checkMarkdown: (src: string, file: string, flags?: RunFlags, registry?: Registry) => Promise<Finding[]>;
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ export declare class RuleOptionError extends UsageError {
|
|
|
49
49
|
}
|
|
50
50
|
export declare class CanvasProfileError extends UsageError {
|
|
51
51
|
readonly file: string;
|
|
52
|
-
constructor(file: string);
|
|
52
|
+
constructor(file: string, cause?: "flag" | "profile");
|
|
53
53
|
}
|
|
54
54
|
export declare class FixOutsideRootError extends UsageError {
|
|
55
55
|
readonly file: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { checkMarkdown, checkModel, type RunFlags } from "./check.ts";
|
|
1
|
+
export { canvasSetAside, checkMarkdown, checkModel, type RunFlags } from "./check.ts";
|
|
2
2
|
export { main } from "./cli.ts";
|
|
3
3
|
export { type Config, loadConfig, type PluginLoading, parseConfig, type RuleSetting, type Severity } from "./config.ts";
|
|
4
4
|
export * from "./errors.ts";
|
package/package.json
CHANGED
package/src/check.ts
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import { FlagError } from "./errors.ts";
|
|
8
8
|
import { buildDocModel, type DocModel, documentKind } from "./model.ts";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type CanvasSetting,
|
|
11
|
+
canvasColourProblem,
|
|
12
|
+
detectProfile,
|
|
13
|
+
PROFILES,
|
|
14
|
+
THEMES,
|
|
15
|
+
type Theme,
|
|
16
|
+
} from "./parse/contrast.ts";
|
|
10
17
|
import { canonicalPreset, PRESET_NAMES } from "./parse/metrics.ts";
|
|
11
18
|
import { BUILTIN, HELPERS, type Registry } from "./rules/registry.ts";
|
|
12
19
|
import type { Finding, Rule, RuleOptions } from "./rules/types.ts";
|
|
@@ -57,10 +64,36 @@ export const checkRunFlags = (flags: RunFlags, prefix = ""): void => {
|
|
|
57
64
|
}
|
|
58
65
|
};
|
|
59
66
|
|
|
67
|
+
// Every option the rule declares with the canvas type, whatever its name.
|
|
68
|
+
const canvasOptions = (rule: Rule): string[] =>
|
|
69
|
+
Object.entries(rule.options ?? {})
|
|
70
|
+
.filter(([, type]) => type === "canvas")
|
|
71
|
+
.map(([name]) => name);
|
|
72
|
+
|
|
73
|
+
const hasFlagCanvas = (flags: RunFlags): boolean => THEMES.some((side) => flags.canvas?.[side] !== undefined);
|
|
74
|
+
|
|
75
|
+
// A canvas from the config is set aside, rather than refused, for a file the
|
|
76
|
+
// auto profile reads as mkdocs-material. The config covers the whole tree and
|
|
77
|
+
// has no per-path setting, so refusing it would stop every run over a repo
|
|
78
|
+
// that holds a Material site beside its GitHub-rendered markdown. A canvas
|
|
79
|
+
// flag, or an explicit mkdocs-material profile, still refuses (MJL-0019).
|
|
80
|
+
const setsAside = (rule: Rule, options: Record<string, unknown>, flags: RunFlags, file: string): boolean =>
|
|
81
|
+
!hasFlagCanvas(flags) &&
|
|
82
|
+
rule.options?.profile !== undefined &&
|
|
83
|
+
(options.profile ?? "auto") === "auto" &&
|
|
84
|
+
canvasOptions(rule).some((name) => options[name] !== undefined) &&
|
|
85
|
+
detectProfile(file) === "mkdocs-material";
|
|
86
|
+
|
|
60
87
|
// The options a rule runs with: its configured options, then a flag for any
|
|
61
88
|
// option the rule declares. A preset reaches the rule by its canonical name,
|
|
62
|
-
// so a plugin rule never has to resolve an alias such as `high` or `H`.
|
|
63
|
-
|
|
89
|
+
// so a plugin rule never has to resolve an alias such as `high` or `H`. Given
|
|
90
|
+
// the file, a config canvas the file sets aside is dropped.
|
|
91
|
+
export const optionsFor = (
|
|
92
|
+
rule: Rule,
|
|
93
|
+
configured: RuleOptions | undefined,
|
|
94
|
+
flags: RunFlags,
|
|
95
|
+
file?: string,
|
|
96
|
+
): RuleOptions => {
|
|
64
97
|
const options: Record<string, unknown> = { ...configured };
|
|
65
98
|
for (const name of ["preset", "profile"] as const) {
|
|
66
99
|
const value = flags[name];
|
|
@@ -68,20 +101,36 @@ export const optionsFor = (rule: Rule, configured: RuleOptions | undefined, flag
|
|
|
68
101
|
}
|
|
69
102
|
if (typeof options.preset === "string") options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
70
103
|
// A canvas flag sets one side over the configured canvas, so --canvas-dark
|
|
71
|
-
// alone keeps a configured light side.
|
|
104
|
+
// alone keeps a configured light side. It reaches every option of the
|
|
105
|
+
// canvas type, not only one named canvas.
|
|
72
106
|
const canvas = flags.canvas;
|
|
73
|
-
if (canvas &&
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
107
|
+
if (canvas && hasFlagCanvas(flags)) {
|
|
108
|
+
for (const name of canvasOptions(rule)) {
|
|
109
|
+
const current = options[name];
|
|
110
|
+
const merged: Record<string, string> = {
|
|
111
|
+
...(typeof current === "object" && current !== null ? (current as Record<string, string>) : {}),
|
|
112
|
+
};
|
|
113
|
+
for (const side of THEMES) {
|
|
114
|
+
const value = canvas[side];
|
|
115
|
+
if (value !== undefined) merged[side] = value;
|
|
116
|
+
}
|
|
117
|
+
options[name] = merged;
|
|
79
118
|
}
|
|
80
|
-
|
|
119
|
+
}
|
|
120
|
+
if (file !== undefined && setsAside(rule, options, flags, file)) {
|
|
121
|
+
for (const name of canvasOptions(rule)) delete options[name];
|
|
81
122
|
}
|
|
82
123
|
return options;
|
|
83
124
|
};
|
|
84
125
|
|
|
126
|
+
// Whether any rule sets its config canvas aside for this file, so the command
|
|
127
|
+
// can say which files were scored without it.
|
|
128
|
+
export const canvasSetAside = (file: string, flags: RunFlags = {}, registry: Registry = BUILTIN): boolean =>
|
|
129
|
+
registry.rules.some((rule) => {
|
|
130
|
+
const options = optionsFor(rule, registry.options.get(rule.id), flags);
|
|
131
|
+
return setsAside(rule, options as Record<string, unknown>, flags, file);
|
|
132
|
+
});
|
|
133
|
+
|
|
85
134
|
export const checkModel = async (
|
|
86
135
|
doc: DocModel,
|
|
87
136
|
file: string,
|
|
@@ -90,7 +139,7 @@ export const checkModel = async (
|
|
|
90
139
|
): Promise<Finding[]> => {
|
|
91
140
|
checkRunFlags(flags);
|
|
92
141
|
const perRule = registry.rules.map((rule) =>
|
|
93
|
-
rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }),
|
|
142
|
+
rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags, file), helpers: HELPERS }),
|
|
94
143
|
);
|
|
95
144
|
const findings = (await Promise.all(perRule)).flat();
|
|
96
145
|
return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
|
package/src/cli.ts
CHANGED
|
@@ -5,7 +5,7 @@ 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, checkRunFlags, type RunFlags } from "./check.ts";
|
|
8
|
+
import { canvasSetAside, 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";
|
|
@@ -55,7 +55,8 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
|
|
|
55
55
|
--canvas-light COLOUR and --canvas-dark COLOUR set the page a host draws in each theme, under the github
|
|
56
56
|
profile only. A translucent fill, an edge label box and a sequence diagram's page-backed text are scored
|
|
57
57
|
on it. Each is an opaque CSS colour such as #faf8f9, and a side not given keeps the config's, or GitHub's
|
|
58
|
-
(#ffffff light, #0d1117 dark). A file scored under mkdocs-material refuses a canvas with a usage
|
|
58
|
+
(#ffffff light, #0d1117 dark). A file scored under mkdocs-material refuses a canvas flag with a usage
|
|
59
|
+
error, and sets a config canvas aside when auto picked the profile.
|
|
59
60
|
--json prints one JSON object instead of text lines.
|
|
60
61
|
--fix applies every fix it can prove safe, then reports what remains.
|
|
61
62
|
A fixer that is unsure leaves the text alone and the finding stays.
|
|
@@ -281,6 +282,16 @@ const run = async (argv: string[], cwd: string): Promise<number> => {
|
|
|
281
282
|
if (files.length === 0) throw new FlagError("files", "no matching files");
|
|
282
283
|
if (values.fix) await refuseOutsideLinks(files, root, cwd);
|
|
283
284
|
|
|
285
|
+
// A config canvas is set aside for a file the auto profile reads as
|
|
286
|
+
// mkdocs-material, and one line names every such file, so the run says what
|
|
287
|
+
// it scored without the canvas.
|
|
288
|
+
const setAside = files.filter((file) => canvasSetAside(file, flags, registry));
|
|
289
|
+
if (setAside.length > 0) {
|
|
290
|
+
console.error(
|
|
291
|
+
`mermaidjs-lint: config canvas set aside for ${plural(setAside.length, "file")} scored under mkdocs-material: ${setAside.map((f) => display(f, cwd)).join(", ")}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
284
295
|
// Every rule, in check and in fix, sees the file as an absolute path, so a
|
|
285
296
|
// rule that reads the path sees one form, and the auto profile is found
|
|
286
297
|
// from the file's own folder whatever the process's working directory. The
|
package/src/errors.ts
CHANGED
|
@@ -132,13 +132,22 @@ export class RuleOptionError extends UsageError {
|
|
|
132
132
|
|
|
133
133
|
// A canvas was set for a file scored under mkdocs-material, whose page is
|
|
134
134
|
// Material's own, so the canvas would change nothing. Ignoring it would report
|
|
135
|
-
// a pass the caller did not ask for, so the run stops instead.
|
|
135
|
+
// a pass the caller did not ask for, so the run stops instead. A config canvas
|
|
136
|
+
// on a file the auto profile reads as mkdocs-material is set aside, not
|
|
137
|
+
// refused (MJL-0019), so this error comes from one of two explicit settings,
|
|
138
|
+
// and its hint names the way out of each.
|
|
136
139
|
export class CanvasProfileError extends UsageError {
|
|
137
140
|
readonly file: string;
|
|
138
141
|
|
|
139
|
-
|
|
142
|
+
// "flag": a canvas flag met a file the auto profile reads as mkdocs-material.
|
|
143
|
+
// "profile": a canvas met an explicitly set mkdocs-material profile.
|
|
144
|
+
constructor(file: string, cause: "flag" | "profile" = "flag") {
|
|
145
|
+
const hint =
|
|
146
|
+
cause === "flag"
|
|
147
|
+
? "run --canvas-light and --canvas-dark over the GitHub-rendered paths only, or set the canvas in the config, which a Material file sets aside"
|
|
148
|
+
: "remove the mkdocs-material profile setting for this run, or remove the canvas";
|
|
140
149
|
super(
|
|
141
|
-
`${file}: a canvas applies under the github profile only, and this file is scored under mkdocs-material, whose page is Material's own;
|
|
150
|
+
`${file}: a canvas applies under the github profile only, and this file is scored under mkdocs-material, whose page is Material's own; ${hint}`,
|
|
142
151
|
"canvas-profile",
|
|
143
152
|
);
|
|
144
153
|
this.file = file;
|
package/src/fix/engine.ts
CHANGED
|
@@ -73,7 +73,7 @@ export const fixSourceReport = async (
|
|
|
73
73
|
|
|
74
74
|
let progressed = false;
|
|
75
75
|
for (const rule of registry.fixOrder) {
|
|
76
|
-
const options = optionsFor(rule, registry.options.get(rule.id), flags);
|
|
76
|
+
const options = optionsFor(rule, registry.options.get(rule.id), flags, file);
|
|
77
77
|
const proposed = (await rule.fix?.({ doc, file, options, helpers: HELPERS })) ?? [];
|
|
78
78
|
const sound = proposed.filter((e) => {
|
|
79
79
|
if (wellFormed(e, current.length)) return true;
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Public API.
|
|
2
2
|
|
|
3
|
-
export { checkMarkdown, checkModel, type RunFlags } from "./check.ts";
|
|
3
|
+
export { canvasSetAside, checkMarkdown, checkModel, type RunFlags } from "./check.ts";
|
|
4
4
|
export { main } from "./cli.ts";
|
|
5
5
|
export { type Config, loadConfig, type PluginLoading, parseConfig, type RuleSetting, type Severity } from "./config.ts";
|
|
6
6
|
export * from "./errors.ts";
|
package/src/rules/shared.ts
CHANGED
|
@@ -109,7 +109,9 @@ export const settingsOf = (ctx: RuleContext): ContrastSettings => {
|
|
|
109
109
|
const problem = canvasColourProblem(setting[side]);
|
|
110
110
|
if (problem) throw new Error(`canvas.${side} ${problem}: ${JSON.stringify(setting[side])}`);
|
|
111
111
|
}
|
|
112
|
-
if (profile === "mkdocs-material")
|
|
112
|
+
if (profile === "mkdocs-material") {
|
|
113
|
+
throw new CanvasProfileError(ctx.file, ctx.options.profile === "mkdocs-material" ? "profile" : "flag");
|
|
114
|
+
}
|
|
113
115
|
return { profile, canvas: resolveCanvas(setting) };
|
|
114
116
|
};
|
|
115
117
|
|
package/src/rules/types.ts
CHANGED
|
@@ -45,8 +45,8 @@ export interface Finding {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// "canvas" is an object { light?, dark? } of opaque CSS colours: the page a
|
|
48
|
-
// diagram is drawn on.
|
|
49
|
-
// --canvas-dark, as the contrast rules do.
|
|
48
|
+
// diagram is drawn on. Every option of this type, whatever its name, receives
|
|
49
|
+
// --canvas-light and --canvas-dark, as the contrast rules do.
|
|
50
50
|
export type OptionType = "number" | "string" | "boolean" | "string[]" | "canvas";
|
|
51
51
|
|
|
52
52
|
export type OptionSpec = Readonly<Record<string, OptionType>>;
|