@jpeakai/mermaidjs-lint 0.5.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 +169 -44
- package/dist/index.js +172 -44
- package/dist/testing.js +142 -40
- package/dist/types/check.d.ts +4 -1
- package/dist/types/errors.d.ts +4 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/parse/contrast.d.ts +11 -5
- package/dist/types/rules/shared.d.ts +7 -2
- package/dist/types/rules/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/check.ts +92 -8
- package/src/cli.ts +31 -4
- package/src/errors.ts +24 -0
- package/src/fix/engine.ts +1 -1
- package/src/index.ts +2 -1
- package/src/parse/contrast.ts +63 -21
- package/src/plugins/load.ts +5 -2
- package/src/rules/contrast-border-below-ui-minimum.ts +3 -3
- package/src/rules/contrast-directive-unauditable.ts +3 -3
- package/src/rules/contrast-text-below-aa.ts +9 -8
- package/src/rules/registry.ts +39 -2
- package/src/rules/shared.ts +53 -13
- package/src/rules/types.ts +4 -1
- package/src/testing.ts +2 -1
package/dist/bin.js
CHANGED
|
@@ -9,7 +9,7 @@ import { parseArgs } from "node:util";
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "@jpeakai/mermaidjs-lint",
|
|
12
|
-
version: "0.
|
|
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",
|
|
@@ -179,6 +179,15 @@ class RuleOptionError extends UsageError {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
class CanvasProfileError extends UsageError {
|
|
183
|
+
file;
|
|
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");
|
|
187
|
+
this.file = file;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
182
191
|
class FixOutsideRootError extends UsageError {
|
|
183
192
|
file;
|
|
184
193
|
target;
|
|
@@ -1041,15 +1050,27 @@ var MKDOCS_MATERIAL = {
|
|
|
1041
1050
|
light: { bg: "#ffffff", text: "#36464e" },
|
|
1042
1051
|
dark: { bg: "#1e2129", text: "hsl(225 18% 86% / 0.82)" }
|
|
1043
1052
|
};
|
|
1044
|
-
var
|
|
1053
|
+
var GITHUB_CANVAS = { light: "#ffffff", dark: "#0d1117" };
|
|
1045
1054
|
var THEMES = ["light", "dark"];
|
|
1055
|
+
var canvasColourProblem = (value) => {
|
|
1056
|
+
if (typeof value !== "string")
|
|
1057
|
+
return "is not a colour string";
|
|
1058
|
+
let alpha;
|
|
1059
|
+
try {
|
|
1060
|
+
alpha = alphaOf(value);
|
|
1061
|
+
} catch {
|
|
1062
|
+
return "is not a CSS colour";
|
|
1063
|
+
}
|
|
1064
|
+
return alpha < 1 ? "is translucent, and a page canvas must be opaque" : null;
|
|
1065
|
+
};
|
|
1066
|
+
var resolveCanvas = (setting) => ({ ...GITHUB_CANVAS, ...setting });
|
|
1046
1067
|
var MERMAID_EDGE_LABEL = {
|
|
1047
1068
|
light: { box: "rgba(232,232,232,0.8)", backing: "rgba(232,232,232,0.5)" },
|
|
1048
1069
|
dark: { box: "#585858", backing: "rgba(88,88,88,0.5)" }
|
|
1049
1070
|
};
|
|
1050
|
-
var edgeLabelBackground = (theme) => {
|
|
1071
|
+
var edgeLabelBackground = (theme, canvas = GITHUB_CANVAS) => {
|
|
1051
1072
|
const { box, backing } = MERMAID_EDGE_LABEL[theme];
|
|
1052
|
-
return compositeOver(box, compositeOver(box, compositeOver(backing,
|
|
1073
|
+
return compositeOver(box, compositeOver(box, compositeOver(backing, canvas[theme])));
|
|
1053
1074
|
};
|
|
1054
1075
|
var MERMAID_ER = {
|
|
1055
1076
|
light: { odd: "#ffffff", even: "#f1f1ff", label: "#333333" },
|
|
@@ -1071,12 +1092,12 @@ var scorePair = (kind, d, fg, bg) => {
|
|
|
1071
1092
|
};
|
|
1072
1093
|
};
|
|
1073
1094
|
var message = (err) => err instanceof Error ? err.message : String(err);
|
|
1074
|
-
var scoreEdgeLabel = (d, pairs, skipped) => {
|
|
1095
|
+
var scoreEdgeLabel = (d, canvas, pairs, skipped) => {
|
|
1075
1096
|
const { color } = d.properties;
|
|
1076
1097
|
if (!color)
|
|
1077
1098
|
return;
|
|
1078
1099
|
try {
|
|
1079
|
-
pairs.push(...THEMES.map((t) => scoreHostPair("text", d, t, color, edgeLabelBackground(t))));
|
|
1100
|
+
pairs.push(...THEMES.map((t) => scoreHostPair("text", d, t, color, edgeLabelBackground(t, canvas))));
|
|
1080
1101
|
} catch (err) {
|
|
1081
1102
|
skipped.push({
|
|
1082
1103
|
selector: d.selector,
|
|
@@ -1086,12 +1107,12 @@ var scoreEdgeLabel = (d, pairs, skipped) => {
|
|
|
1086
1107
|
});
|
|
1087
1108
|
}
|
|
1088
1109
|
};
|
|
1089
|
-
var scoreGithub = (directives) => {
|
|
1110
|
+
var scoreGithub = (directives, canvas = GITHUB_CANVAS) => {
|
|
1090
1111
|
const pairs = [];
|
|
1091
1112
|
const skipped = [];
|
|
1092
1113
|
for (const d of directives) {
|
|
1093
1114
|
if (d.kind === "linkStyle")
|
|
1094
|
-
scoreEdgeLabel(d, pairs, skipped);
|
|
1115
|
+
scoreEdgeLabel(d, canvas, pairs, skipped);
|
|
1095
1116
|
if (d.kind !== "classDef" && d.kind !== "style")
|
|
1096
1117
|
continue;
|
|
1097
1118
|
const { fill, color, stroke } = d.properties;
|
|
@@ -1113,7 +1134,7 @@ var scoreGithub = (directives) => {
|
|
|
1113
1134
|
note("fill is fully transparent, so there is no box to anchor contrast");
|
|
1114
1135
|
continue;
|
|
1115
1136
|
}
|
|
1116
|
-
const boxes = alpha < 1 ? THEMES.map((t) => [compositeOver(fill,
|
|
1137
|
+
const boxes = alpha < 1 ? THEMES.map((t) => [compositeOver(fill, canvas[t]), t]) : [[fill, undefined]];
|
|
1117
1138
|
if (!color) {
|
|
1118
1139
|
note("no color: declared beside fill:, and the github profile requires one " + "(if the host forces label text, use the mkdocs-material profile)", true);
|
|
1119
1140
|
}
|
|
@@ -1246,10 +1267,10 @@ var scoreEr = (directives) => {
|
|
|
1246
1267
|
}
|
|
1247
1268
|
return { pairs, skipped };
|
|
1248
1269
|
};
|
|
1249
|
-
var scoreForProfile = (directives, profile, keyword) => {
|
|
1270
|
+
var scoreForProfile = (directives, profile, keyword, canvas = GITHUB_CANVAS) => {
|
|
1250
1271
|
if (profile === "mkdocs-material")
|
|
1251
1272
|
return scoreMkdocs(directives);
|
|
1252
|
-
return keyword === "erDiagram" ? scoreEr(directives) : scoreGithub(directives);
|
|
1273
|
+
return keyword === "erDiagram" ? scoreEr(directives) : scoreGithub(directives, canvas);
|
|
1253
1274
|
};
|
|
1254
1275
|
var SEQUENCE_THEMES = ["base", "default", "dark", "forest", "neutral"];
|
|
1255
1276
|
var LINE = { NOTE: 2, ACTIVE_START: 17, RECT_START: 22, AUTONUMBER: 26 };
|
|
@@ -1286,7 +1307,7 @@ var themeVariable = (selector, line) => ({
|
|
|
1286
1307
|
spans: {},
|
|
1287
1308
|
line
|
|
1288
1309
|
});
|
|
1289
|
-
var scoreSequence = async (source, profile) => {
|
|
1310
|
+
var scoreSequence = async (source, profile, canvas = GITHUB_CANVAS) => {
|
|
1290
1311
|
const pairs = [];
|
|
1291
1312
|
const skipped = [];
|
|
1292
1313
|
const lines = source.split(`
|
|
@@ -1326,7 +1347,7 @@ var scoreSequence = async (source, profile) => {
|
|
|
1326
1347
|
return block("themeVariables", `Mermaid could not resolve the theme: ${message(err)}`);
|
|
1327
1348
|
}
|
|
1328
1349
|
for (const t of THEMES) {
|
|
1329
|
-
const page =
|
|
1350
|
+
const page = host ? MKDOCS_MATERIAL[t].bg : canvas[t];
|
|
1330
1351
|
const vars = variables[t];
|
|
1331
1352
|
const color = (key) => {
|
|
1332
1353
|
const value = vars[key];
|
|
@@ -1383,7 +1404,7 @@ var scoreSequence = async (source, profile) => {
|
|
|
1383
1404
|
}
|
|
1384
1405
|
return { pairs, skipped };
|
|
1385
1406
|
};
|
|
1386
|
-
var scoreDiagram = (source, directives, profile, keyword) => keyword === "sequenceDiagram" ? scoreSequence(source, profile) : Promise.resolve(scoreForProfile(directives, profile, keyword));
|
|
1407
|
+
var scoreDiagram = (source, directives, profile, keyword, canvas = GITHUB_CANVAS) => keyword === "sequenceDiagram" ? scoreSequence(source, profile, canvas) : Promise.resolve(scoreForProfile(directives, profile, keyword, canvas));
|
|
1387
1408
|
var detectProfile = (filePath) => {
|
|
1388
1409
|
let dir = dirname(resolve(filePath));
|
|
1389
1410
|
for (let i = 0;i < 20; i++) {
|
|
@@ -1547,7 +1568,7 @@ var COMPLEXITY_OPTIONS = {
|
|
|
1547
1568
|
subgraphWeight: "number",
|
|
1548
1569
|
depthWeight: "number"
|
|
1549
1570
|
};
|
|
1550
|
-
var CONTRAST_OPTIONS = { profile: "string" };
|
|
1571
|
+
var CONTRAST_OPTIONS = { profile: "string", canvas: "canvas" };
|
|
1551
1572
|
var measure = async (diagram, config) => {
|
|
1552
1573
|
const stats = await diagram.stats();
|
|
1553
1574
|
if (parseFailed(diagram.source, stats))
|
|
@@ -1577,17 +1598,36 @@ var profileOf = (ctx) => {
|
|
|
1577
1598
|
}
|
|
1578
1599
|
return wanted === "auto" ? detectProfile(ctx.file) : wanted;
|
|
1579
1600
|
};
|
|
1580
|
-
var
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
if (
|
|
1584
|
-
|
|
1585
|
-
|
|
1601
|
+
var settingsOf = (ctx) => {
|
|
1602
|
+
const profile = profileOf(ctx);
|
|
1603
|
+
const setting = ctx.options.canvas;
|
|
1604
|
+
if (setting === undefined)
|
|
1605
|
+
return { profile, canvas: GITHUB_CANVAS };
|
|
1606
|
+
for (const side of THEMES) {
|
|
1607
|
+
if (!(side in setting))
|
|
1608
|
+
continue;
|
|
1609
|
+
const problem = canvasColourProblem(setting[side]);
|
|
1610
|
+
if (problem)
|
|
1611
|
+
throw new Error(`canvas.${side} ${problem}: ${JSON.stringify(setting[side])}`);
|
|
1586
1612
|
}
|
|
1587
|
-
|
|
1613
|
+
if (profile === "mkdocs-material") {
|
|
1614
|
+
throw new CanvasProfileError(ctx.file, ctx.options.profile === "mkdocs-material" ? "profile" : "flag");
|
|
1615
|
+
}
|
|
1616
|
+
return { profile, canvas: resolveCanvas(setting) };
|
|
1617
|
+
};
|
|
1618
|
+
var scored = new WeakMap;
|
|
1619
|
+
var contrastOf = (diagram, settings) => {
|
|
1620
|
+
const { profile, canvas } = settings;
|
|
1621
|
+
const key = `${profile}\x00${canvas.light}\x00${canvas.dark}`;
|
|
1622
|
+
let bySettings = scored.get(diagram);
|
|
1623
|
+
if (!bySettings) {
|
|
1624
|
+
bySettings = new Map;
|
|
1625
|
+
scored.set(diagram, bySettings);
|
|
1626
|
+
}
|
|
1627
|
+
let result = bySettings.get(key);
|
|
1588
1628
|
if (!result) {
|
|
1589
|
-
result = scoreDiagram(diagram.source, diagram.directives, profile, diagram.keyword);
|
|
1590
|
-
|
|
1629
|
+
result = scoreDiagram(diagram.source, diagram.directives, profile, diagram.keyword, canvas);
|
|
1630
|
+
bySettings.set(key, result);
|
|
1591
1631
|
}
|
|
1592
1632
|
return result;
|
|
1593
1633
|
};
|
|
@@ -1700,10 +1740,10 @@ var contrastBorderBelowUiMinimum = {
|
|
|
1700
1740
|
summary: "a border below 3:1 against its fill",
|
|
1701
1741
|
options: CONTRAST_OPTIONS,
|
|
1702
1742
|
check: async (ctx) => {
|
|
1703
|
-
const
|
|
1743
|
+
const settings = settingsOf(ctx);
|
|
1704
1744
|
const found = [];
|
|
1705
1745
|
for (const diagram of ctx.doc.diagrams) {
|
|
1706
|
-
for (const p of (await contrastOf(diagram,
|
|
1746
|
+
for (const p of (await contrastOf(diagram, settings)).pairs) {
|
|
1707
1747
|
if (p.kind !== "border" || p.passes || p.advisory)
|
|
1708
1748
|
continue;
|
|
1709
1749
|
const line = fileLine(diagram, p.line);
|
|
@@ -1728,10 +1768,10 @@ var contrastDirectiveUnauditable = {
|
|
|
1728
1768
|
summary: "a style directive whose contrast cannot be audited",
|
|
1729
1769
|
options: CONTRAST_OPTIONS,
|
|
1730
1770
|
check: async (ctx) => {
|
|
1731
|
-
const
|
|
1771
|
+
const settings = settingsOf(ctx);
|
|
1732
1772
|
const found = [];
|
|
1733
1773
|
for (const diagram of ctx.doc.diagrams) {
|
|
1734
|
-
for (const s of (await contrastOf(diagram,
|
|
1774
|
+
for (const s of (await contrastOf(diagram, settings)).skipped) {
|
|
1735
1775
|
if (!s.blocking)
|
|
1736
1776
|
continue;
|
|
1737
1777
|
const line = fileLine(diagram, s.line);
|
|
@@ -1759,10 +1799,10 @@ var contrastTextBelowAa = {
|
|
|
1759
1799
|
summary: "label text below 4.5:1 against its fill",
|
|
1760
1800
|
options: CONTRAST_OPTIONS,
|
|
1761
1801
|
check: async (ctx) => {
|
|
1762
|
-
const
|
|
1802
|
+
const settings = settingsOf(ctx);
|
|
1763
1803
|
const found = [];
|
|
1764
1804
|
for (const diagram of ctx.doc.diagrams) {
|
|
1765
|
-
for (const p of (await contrastOf(diagram,
|
|
1805
|
+
for (const p of (await contrastOf(diagram, settings)).pairs) {
|
|
1766
1806
|
if (p.kind !== "text" || p.passes)
|
|
1767
1807
|
continue;
|
|
1768
1808
|
const where = [p.theme, p.row].filter(Boolean).join("/");
|
|
@@ -1773,19 +1813,19 @@ var contrastTextBelowAa = {
|
|
|
1773
1813
|
endLine: line,
|
|
1774
1814
|
rule: RULE.TEXT,
|
|
1775
1815
|
message: `${p.directive.kind} ${p.selector}: text ${p.assessment.ratio.toFixed(2)}:1 < ${AA_NORMAL2} ` + `(${p.foreground} on ${p.background}${where ? `, ${where}` : ""})`,
|
|
1776
|
-
remediation: profile === "mkdocs-material" ? "The host theme forces the label text, so change the fill until the host text reads on it in both themes." : p.directive.kind === "themeVariables" ? "A sequence diagram colour is a theme variable, and text with no box of its own sits on the page, so set the variable to a colour that reads on its background in both themes, or remove it and let the theme set it." : p.directive.kind === "linkStyle" ? "An edge label sits on Mermaid's own label box, light in one theme and grey in the other, and no one colour reads on both, so drop color: from the linkStyle and let the theme set the label text." : "Darken or lighten the fill, or change color:, until the ratio reaches 4.5:1 in every theme."
|
|
1816
|
+
remediation: settings.profile === "mkdocs-material" ? "The host theme forces the label text, so change the fill until the host text reads on it in both themes." : p.directive.kind === "themeVariables" ? "A sequence diagram colour is a theme variable, and text with no box of its own sits on the page, so set the variable to a colour that reads on its background in both themes, or remove it and let the theme set it." : p.directive.kind === "linkStyle" ? "An edge label sits on Mermaid's own label box, light in one theme and grey in the other, and no one colour reads on both, so drop color: from the linkStyle and let the theme set the label text." : "Darken or lighten the fill, or change color:, until the ratio reaches 4.5:1 in every theme."
|
|
1777
1817
|
});
|
|
1778
1818
|
}
|
|
1779
1819
|
}
|
|
1780
1820
|
return found;
|
|
1781
1821
|
},
|
|
1782
1822
|
fix: async (ctx) => {
|
|
1783
|
-
const
|
|
1784
|
-
if (profile === "mkdocs-material")
|
|
1823
|
+
const settings = settingsOf(ctx);
|
|
1824
|
+
if (settings.profile === "mkdocs-material")
|
|
1785
1825
|
return [];
|
|
1786
1826
|
const edits = [];
|
|
1787
1827
|
for (const diagram of ctx.doc.diagrams) {
|
|
1788
|
-
const { pairs } = await contrastOf(diagram,
|
|
1828
|
+
const { pairs } = await contrastOf(diagram, settings);
|
|
1789
1829
|
const failing = new Set(pairs.filter((p) => p.kind === "text" && !p.passes).map((p) => p.directive));
|
|
1790
1830
|
for (const directive of failing) {
|
|
1791
1831
|
const span = directive.spans.color;
|
|
@@ -1905,8 +1945,27 @@ var typeOf = (value) => {
|
|
|
1905
1945
|
return "boolean";
|
|
1906
1946
|
if (Array.isArray(value) && value.every((v) => typeof v === "string"))
|
|
1907
1947
|
return "string[]";
|
|
1948
|
+
if (isCanvasShape(value))
|
|
1949
|
+
return "canvas";
|
|
1908
1950
|
return null;
|
|
1909
1951
|
};
|
|
1952
|
+
var isCanvasShape = (value) => {
|
|
1953
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1954
|
+
return false;
|
|
1955
|
+
const keys = Object.keys(value);
|
|
1956
|
+
return keys.length > 0 && keys.every((k) => THEMES.includes(k));
|
|
1957
|
+
};
|
|
1958
|
+
var describeType = (want) => want === "canvas" ? 'an object such as { "light": "#ffffff", "dark": "#0d1117" }' : `a ${want}`;
|
|
1959
|
+
var checkCanvas = (where, rule, name, value) => {
|
|
1960
|
+
for (const side of THEMES) {
|
|
1961
|
+
if (!(side in value))
|
|
1962
|
+
continue;
|
|
1963
|
+
const problem = canvasColourProblem(value[side]);
|
|
1964
|
+
if (problem) {
|
|
1965
|
+
throw new RuleOptionError(where, rule.id, name, `option "${name}.${side}" of "${rule.id}" ${problem}: ${JSON.stringify(value[side])}`);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1910
1969
|
var checkOptions = (where, rule, options) => {
|
|
1911
1970
|
const accepted = Object.keys(rule.options ?? {});
|
|
1912
1971
|
for (const [name, value] of Object.entries(options)) {
|
|
@@ -1916,8 +1975,13 @@ var checkOptions = (where, rule, options) => {
|
|
|
1916
1975
|
throw new RuleOptionError(where, rule.id, name, `rule "${rule.id}" has no option "${name}"; ${list}`);
|
|
1917
1976
|
}
|
|
1918
1977
|
if (typeOf(value) !== want) {
|
|
1919
|
-
throw new RuleOptionError(where, rule.id, name, `option "${name}" of "${rule.id}" must be
|
|
1978
|
+
throw new RuleOptionError(where, rule.id, name, `option "${name}" of "${rule.id}" must be ${describeType(want)}`);
|
|
1920
1979
|
}
|
|
1980
|
+
if (want === "canvas")
|
|
1981
|
+
checkCanvas(where, rule, name, value);
|
|
1982
|
+
}
|
|
1983
|
+
if (options.profile === "mkdocs-material" && options.canvas !== undefined && rule.options?.canvas === "canvas") {
|
|
1984
|
+
throw new RuleOptionError(where, rule.id, "canvas", `option "canvas" of "${rule.id}" applies under the github profile only, and this rule sets profile mkdocs-material, whose page is Material's own`);
|
|
1921
1985
|
}
|
|
1922
1986
|
if (typeof options.preset === "string" && canonicalPreset(options.preset) === null) {
|
|
1923
1987
|
throw new RuleOptionError(where, rule.id, "preset", `option "preset" of "${rule.id}" is not a known preset`);
|
|
@@ -1961,8 +2025,24 @@ var buildRegistry = (input) => {
|
|
|
1961
2025
|
var BUILTIN = buildRegistry({ rules: RULES, fixOrder: FIX_ORDER, categories: BUILTIN_CATEGORIES });
|
|
1962
2026
|
|
|
1963
2027
|
// src/check.ts
|
|
2028
|
+
var canvasName = (side, prefix) => prefix ? `${prefix}canvas-${side}` : `canvas.${side}`;
|
|
1964
2029
|
var checkRunFlags = (flags, prefix = "") => {
|
|
1965
|
-
const { preset, profile } = flags;
|
|
2030
|
+
const { preset, profile, canvas } = flags;
|
|
2031
|
+
for (const side of THEMES) {
|
|
2032
|
+
if (canvas?.[side] === undefined)
|
|
2033
|
+
continue;
|
|
2034
|
+
const problem = canvasColourProblem(canvas[side]);
|
|
2035
|
+
if (problem) {
|
|
2036
|
+
const name = canvasName(side, prefix);
|
|
2037
|
+
throw new FlagError(name, `${name} ${problem}: "${canvas[side]}"; give an opaque CSS colour, such as #ffffff`);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
const sides = THEMES.filter((side) => canvas?.[side] !== undefined);
|
|
2041
|
+
const first = sides[0];
|
|
2042
|
+
if (profile === "mkdocs-material" && first !== undefined) {
|
|
2043
|
+
const name = canvasName(first, prefix);
|
|
2044
|
+
throw new FlagError(name, `${name} applies under the github profile only; mkdocs-material scores Material's own page colours`);
|
|
2045
|
+
}
|
|
1966
2046
|
if (preset !== undefined && canonicalPreset(preset) === null) {
|
|
1967
2047
|
throw new FlagError(`${prefix}preset`, `${prefix}preset must be one of ${PRESET_NAMES.join(", ")}, not "${preset}"`);
|
|
1968
2048
|
}
|
|
@@ -1970,7 +2050,10 @@ var checkRunFlags = (flags, prefix = "") => {
|
|
|
1970
2050
|
throw new FlagError(`${prefix}profile`, `${prefix}profile must be one of ${PROFILES.join(", ")}, not "${profile}"`);
|
|
1971
2051
|
}
|
|
1972
2052
|
};
|
|
1973
|
-
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) => {
|
|
1974
2057
|
const options = { ...configured };
|
|
1975
2058
|
for (const name of ["preset", "profile"]) {
|
|
1976
2059
|
const value = flags[name];
|
|
@@ -1979,11 +2062,34 @@ var optionsFor = (rule, configured, flags) => {
|
|
|
1979
2062
|
}
|
|
1980
2063
|
if (typeof options.preset === "string")
|
|
1981
2064
|
options.preset = canonicalPreset(options.preset) ?? options.preset;
|
|
2065
|
+
const canvas = flags.canvas;
|
|
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;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
if (file !== undefined && setsAside(rule, options, flags, file)) {
|
|
2081
|
+
for (const name of canvasOptions(rule))
|
|
2082
|
+
delete options[name];
|
|
2083
|
+
}
|
|
1982
2084
|
return options;
|
|
1983
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
|
+
});
|
|
1984
2090
|
var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
|
|
1985
2091
|
checkRunFlags(flags);
|
|
1986
|
-
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 }));
|
|
1987
2093
|
const findings = (await Promise.all(perRule)).flat();
|
|
1988
2094
|
return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
|
|
1989
2095
|
};
|
|
@@ -2155,7 +2261,7 @@ var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
|
2155
2261
|
};
|
|
2156
2262
|
let progressed = false;
|
|
2157
2263
|
for (const rule of registry.fixOrder) {
|
|
2158
|
-
const options = optionsFor(rule, registry.options.get(rule.id), flags);
|
|
2264
|
+
const options = optionsFor(rule, registry.options.get(rule.id), flags, file);
|
|
2159
2265
|
const proposed = await rule.fix?.({ doc, file, options, helpers: HELPERS }) ?? [];
|
|
2160
2266
|
const sound = proposed.filter((e) => {
|
|
2161
2267
|
if (wellFormed(e, current.length))
|
|
@@ -2425,7 +2531,7 @@ var importModule = async (source, runtime) => {
|
|
|
2425
2531
|
throw new PluginImportError(source.spec, message2(err));
|
|
2426
2532
|
}
|
|
2427
2533
|
};
|
|
2428
|
-
var OPTION_TYPES = new Set(["number", "string", "boolean", "string[]"]);
|
|
2534
|
+
var OPTION_TYPES = new Set(["number", "string", "boolean", "string[]", "canvas"]);
|
|
2429
2535
|
var RESERVED_OPTIONS = ["preset", "profile"];
|
|
2430
2536
|
var checkRule = (spec, key, raw, own) => {
|
|
2431
2537
|
const where = `rule "${key}"`;
|
|
@@ -2454,7 +2560,7 @@ var checkRule = (spec, key, raw, own) => {
|
|
|
2454
2560
|
if (options !== undefined) {
|
|
2455
2561
|
const valid = isRecord3(options) && Object.values(options).every((t) => OPTION_TYPES.has(t));
|
|
2456
2562
|
if (!valid) {
|
|
2457
|
-
throw new PluginContractError(spec, `${where} options must map names to number, string, boolean
|
|
2563
|
+
throw new PluginContractError(spec, `${where} options must map names to number, string, boolean, string[] or canvas`);
|
|
2458
2564
|
}
|
|
2459
2565
|
for (const name of RESERVED_OPTIONS) {
|
|
2460
2566
|
if (name in options && options[name] !== "string") {
|
|
@@ -2721,7 +2827,7 @@ var setUp = async ({
|
|
|
2721
2827
|
|
|
2722
2828
|
// src/cli.ts
|
|
2723
2829
|
var SYNOPSIS = `usage: mermaidjs-lint <path...> [--fix] [--json] [--preset NAME] [--profile NAME] [--config FILE]
|
|
2724
|
-
[--no-plugins | --lenient-plugins]
|
|
2830
|
+
[--canvas-light COLOUR] [--canvas-dark COLOUR] [--no-plugins | --lenient-plugins]
|
|
2725
2831
|
mermaidjs-lint --list-rules
|
|
2726
2832
|
mermaidjs-lint --validate-plugins [--json] [--config FILE]
|
|
2727
2833
|
mermaidjs-lint --version | -h, --help`;
|
|
@@ -2747,6 +2853,11 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
|
|
|
2747
2853
|
numeric threshold set in the config overrides both.
|
|
2748
2854
|
--profile NAME sets how contrast is read: ${PROFILES.join(", ")}. It overrides a profile set in the config.
|
|
2749
2855
|
auto reads mkdocs-material when any parent folder holds mkdocs.yml or mkdocs.yaml, else github.
|
|
2856
|
+
--canvas-light COLOUR and --canvas-dark COLOUR set the page a host draws in each theme, under the github
|
|
2857
|
+
profile only. A translucent fill, an edge label box and a sequence diagram's page-backed text are scored
|
|
2858
|
+
on it. Each is an opaque CSS colour such as #faf8f9, and a side not given keeps the config's, or GitHub's
|
|
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.
|
|
2750
2861
|
--json prints one JSON object instead of text lines.
|
|
2751
2862
|
--fix applies every fix it can prove safe, then reports what remains.
|
|
2752
2863
|
A fixer that is unsure leaves the text alone and the finding stays.
|
|
@@ -2770,6 +2881,8 @@ var parse2 = (argv) => parseArgs({
|
|
|
2770
2881
|
help: { type: "boolean", short: "h", default: false },
|
|
2771
2882
|
preset: { type: "string", short: "p" },
|
|
2772
2883
|
profile: { type: "string" },
|
|
2884
|
+
"canvas-light": { type: "string" },
|
|
2885
|
+
"canvas-dark": { type: "string" },
|
|
2773
2886
|
config: { type: "string" },
|
|
2774
2887
|
"no-plugins": { type: "boolean", default: false },
|
|
2775
2888
|
"lenient-plugins": { type: "boolean", default: false },
|
|
@@ -2875,7 +2988,16 @@ var run = async (argv, cwd) => {
|
|
|
2875
2988
|
}
|
|
2876
2989
|
const listing = values["list-rules"];
|
|
2877
2990
|
const validating = values["validate-plugins"];
|
|
2878
|
-
|
|
2991
|
+
const canvas = {
|
|
2992
|
+
...values["canvas-light"] !== undefined && { light: values["canvas-light"] },
|
|
2993
|
+
...values["canvas-dark"] !== undefined && { dark: values["canvas-dark"] }
|
|
2994
|
+
};
|
|
2995
|
+
const flags = {
|
|
2996
|
+
preset: values.preset,
|
|
2997
|
+
profile: values.profile,
|
|
2998
|
+
...Object.keys(canvas).length > 0 && { canvas }
|
|
2999
|
+
};
|
|
3000
|
+
checkRunFlags(flags, "--");
|
|
2879
3001
|
if (values["no-plugins"] && values["lenient-plugins"]) {
|
|
2880
3002
|
throw new FlagError("--lenient-plugins", "--no-plugins and --lenient-plugins contradict each other");
|
|
2881
3003
|
}
|
|
@@ -2921,7 +3043,10 @@ var run = async (argv, cwd) => {
|
|
|
2921
3043
|
throw new FlagError("files", "no matching files");
|
|
2922
3044
|
if (values.fix)
|
|
2923
3045
|
await refuseOutsideLinks(files, root, cwd);
|
|
2924
|
-
const
|
|
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
|
+
}
|
|
2925
3050
|
const perFile = await Promise.all(files.map(async (file) => {
|
|
2926
3051
|
let src = await readFile3(file, "utf8");
|
|
2927
3052
|
if (values.fix) {
|