@jpeakai/mermaidjs-lint 0.2.0 → 0.4.0
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/README.md +12 -9
- package/dist/bin.js +214 -64
- package/dist/index.js +217 -64
- package/dist/testing.js +99 -32
- package/dist/types/bin.d.ts +2 -0
- package/dist/types/check.d.ts +10 -0
- package/dist/types/cli.d.ts +9 -0
- package/dist/types/config.d.ts +20 -0
- package/dist/types/errors.d.ts +88 -0
- package/dist/types/files.d.ts +8 -0
- package/dist/types/fix/engine.d.ts +10 -0
- package/dist/types/fix/verify.d.ts +12 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/model.d.ts +27 -0
- package/dist/types/parse/color.d.ts +28 -0
- package/dist/types/parse/contrast.d.ts +35 -0
- package/dist/types/parse/metrics.d.ts +48 -0
- package/dist/types/parse/stats.d.ts +25 -0
- package/dist/types/plugins/contract.d.ts +24 -0
- package/dist/types/plugins/discover.d.ts +20 -0
- package/dist/types/plugins/index.d.ts +19 -0
- package/dist/types/plugins/load.d.ts +14 -0
- package/dist/types/plugins/runtime.d.ts +6 -0
- package/dist/types/project.d.ts +18 -0
- package/dist/types/rules/complexity-node-count-acceptable-exceeded.d.ts +1 -0
- package/dist/types/rules/complexity-node-count-cognitive-limit.d.ts +1 -0
- package/dist/types/rules/complexity-node-count-hard-limit.d.ts +1 -0
- package/dist/types/rules/complexity-subgraph-nesting-too-deep.d.ts +2 -0
- package/dist/types/rules/complexity-visual-score-acceptable-exceeded.d.ts +1 -0
- package/dist/types/rules/complexity-visual-score-critical.d.ts +1 -0
- package/dist/types/rules/complexity.d.ts +12 -0
- package/dist/types/rules/contrast-border-below-ui-minimum.d.ts +2 -0
- package/dist/types/rules/contrast-directive-unauditable.d.ts +2 -0
- package/dist/types/rules/contrast-text-below-aa.d.ts +2 -0
- package/dist/types/rules/index.d.ts +3 -0
- package/dist/types/rules/parse-diagram-unparseable.d.ts +2 -0
- package/dist/types/rules/parse-fence-unterminated.d.ts +2 -0
- package/dist/types/rules/registry.d.ts +29 -0
- package/dist/types/rules/shared.d.ts +18 -0
- package/dist/types/rules/types.d.ts +67 -0
- package/dist/types/testing.d.ts +15 -0
- package/package.json +5 -4
- package/src/cli.ts +47 -15
- package/src/errors.ts +46 -0
- package/src/files.ts +21 -2
- package/src/fix/engine.ts +22 -10
- package/src/fix/verify.ts +31 -3
- package/src/index.ts +5 -3
- package/src/parse/metrics.ts +8 -1
- package/src/parse/stats.ts +72 -16
- package/src/plugins/contract.ts +4 -2
- package/src/plugins/discover.ts +38 -15
- package/src/plugins/index.ts +5 -3
- package/src/plugins/load.ts +32 -1
- package/src/project.ts +3 -1
- package/src/rules/complexity-subgraph-nesting-too-deep.ts +5 -0
- package/src/rules/parse-fence-unterminated.ts +1 -1
package/dist/testing.js
CHANGED
|
@@ -203,6 +203,7 @@ async function extractStats(content) {
|
|
|
203
203
|
edges: 0,
|
|
204
204
|
subgraphs: 0,
|
|
205
205
|
max_subgraph_depth: 0,
|
|
206
|
+
max_tree_depth: 0,
|
|
206
207
|
node_ids: [],
|
|
207
208
|
subgraph_names: [],
|
|
208
209
|
parser_used: langium ? "langium" : "mermaid-core",
|
|
@@ -252,6 +253,7 @@ async function extractLangiumStats(content, keyword) {
|
|
|
252
253
|
const subgraphNames = [];
|
|
253
254
|
let edges = 0;
|
|
254
255
|
let maxDepth = 0;
|
|
256
|
+
let treeDepth = 0;
|
|
255
257
|
switch (mapped) {
|
|
256
258
|
case "architecture": {
|
|
257
259
|
const services = ast.services ?? [];
|
|
@@ -298,7 +300,7 @@ async function extractLangiumStats(content, keyword) {
|
|
|
298
300
|
const stats = indentTreeStats(nodes.map((n, i) => ({ label: n.name ?? `node_${i}`, indent: n.indent ?? 0 })));
|
|
299
301
|
nodeIds.push(...stats.labels);
|
|
300
302
|
edges = stats.edges;
|
|
301
|
-
|
|
303
|
+
treeDepth = stats.depth;
|
|
302
304
|
break;
|
|
303
305
|
}
|
|
304
306
|
case "treemap": {
|
|
@@ -306,7 +308,7 @@ async function extractLangiumStats(content, keyword) {
|
|
|
306
308
|
const stats = indentTreeStats(rows.map((r, i) => ({ label: r.item?.name ?? `row_${i}`, indent: r.indent ?? 0 })));
|
|
307
309
|
nodeIds.push(...stats.labels);
|
|
308
310
|
edges = stats.edges;
|
|
309
|
-
|
|
311
|
+
treeDepth = stats.depth;
|
|
310
312
|
break;
|
|
311
313
|
}
|
|
312
314
|
case "wardley": {
|
|
@@ -326,6 +328,7 @@ async function extractLangiumStats(content, keyword) {
|
|
|
326
328
|
edges,
|
|
327
329
|
subgraphs: subgraphNames.length,
|
|
328
330
|
max_subgraph_depth: maxDepth,
|
|
331
|
+
max_tree_depth: treeDepth,
|
|
329
332
|
node_ids: [...new Set(nodeIds.filter(Boolean))].sort(),
|
|
330
333
|
subgraph_names: subgraphNames,
|
|
331
334
|
parser_used: "langium",
|
|
@@ -376,12 +379,13 @@ async function extractCoreStats(content, keyword) {
|
|
|
376
379
|
if (!adapter) {
|
|
377
380
|
throw new Error(`no core adapter for diagram.type="${diagramType}" (keyword="${keyword}")`);
|
|
378
381
|
}
|
|
379
|
-
const { nodeIds, edges, subgraphNames, maxDepth } = adapter(diagram.db);
|
|
382
|
+
const { nodeIds, edges, subgraphNames, maxDepth, treeDepth = 0 } = adapter(diagram.db);
|
|
380
383
|
return {
|
|
381
384
|
nodes: nodeIds.length,
|
|
382
385
|
edges,
|
|
383
386
|
subgraphs: subgraphNames.length,
|
|
384
387
|
max_subgraph_depth: maxDepth,
|
|
388
|
+
max_tree_depth: treeDepth,
|
|
385
389
|
node_ids: [...nodeIds].sort(),
|
|
386
390
|
subgraph_names: subgraphNames,
|
|
387
391
|
parser_used: "mermaid-core",
|
|
@@ -392,6 +396,7 @@ async function extractCoreStats(content, keyword) {
|
|
|
392
396
|
var CORE_ADAPTERS = {
|
|
393
397
|
"flowchart-v2": adaptFlowchart,
|
|
394
398
|
flowchart: adaptFlowchart,
|
|
399
|
+
"flowchart-elk": adaptFlowchart,
|
|
395
400
|
graph: adaptFlowchart,
|
|
396
401
|
classDiagram: adaptClass,
|
|
397
402
|
"classDiagram-v2": adaptClass,
|
|
@@ -515,7 +520,13 @@ function adaptIshikawa(db) {
|
|
|
515
520
|
walk(c, depth + 1);
|
|
516
521
|
};
|
|
517
522
|
walk(root, 0);
|
|
518
|
-
return {
|
|
523
|
+
return {
|
|
524
|
+
nodeIds: labels,
|
|
525
|
+
edges: Math.max(0, labels.length - 1),
|
|
526
|
+
subgraphNames: [],
|
|
527
|
+
maxDepth: 0,
|
|
528
|
+
treeDepth: maxDepth
|
|
529
|
+
};
|
|
519
530
|
}
|
|
520
531
|
function adaptKanban(db) {
|
|
521
532
|
const data = callGetter(db, "getData");
|
|
@@ -543,16 +554,20 @@ function adaptMindmap(db) {
|
|
|
543
554
|
walk(c, depth + 1);
|
|
544
555
|
};
|
|
545
556
|
walk(root, 0);
|
|
546
|
-
return {
|
|
557
|
+
return {
|
|
558
|
+
nodeIds: labels,
|
|
559
|
+
edges: Math.max(0, labels.length - 1),
|
|
560
|
+
subgraphNames: [],
|
|
561
|
+
maxDepth: 0,
|
|
562
|
+
treeDepth: maxDepth
|
|
563
|
+
};
|
|
547
564
|
}
|
|
548
565
|
function adaptQuadrantChart(db) {
|
|
549
566
|
const data = callGetter(db, "getQuadrantData");
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
});
|
|
555
|
-
return { nodeIds: labels, edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
567
|
+
const textOf = (t) => typeof t === "string" ? t : t?.text ?? "";
|
|
568
|
+
const quadrants = (data?.quadrants ?? []).map((q) => textOf(q.text)).filter((t) => t.trim() !== "");
|
|
569
|
+
const points = (data?.points ?? []).map((p, i) => textOf(p.text) || `point_${i}`);
|
|
570
|
+
return { nodeIds: [...quadrants, ...points], edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
556
571
|
}
|
|
557
572
|
function adaptRequirement(db) {
|
|
558
573
|
const requirements = db.requirements instanceof Map ? db.requirements : new Map;
|
|
@@ -578,18 +593,30 @@ function adaptSankey(db) {
|
|
|
578
593
|
function adaptState(db) {
|
|
579
594
|
const nodes = db.nodes ?? [];
|
|
580
595
|
const edges = db.edges ?? [];
|
|
596
|
+
const groups = nodes.filter((n) => n.isGroup === true);
|
|
597
|
+
const parentOf = new Map(nodes.map((n) => [n.id ?? "", n.parentId]));
|
|
598
|
+
let maxDepth = 0;
|
|
599
|
+
for (const g of groups) {
|
|
600
|
+
let depth = 1;
|
|
601
|
+
const seen = new Set;
|
|
602
|
+
for (let cur = g.parentId;cur && !seen.has(cur); cur = parentOf.get(cur)) {
|
|
603
|
+
seen.add(cur);
|
|
604
|
+
depth++;
|
|
605
|
+
}
|
|
606
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
607
|
+
}
|
|
581
608
|
return {
|
|
582
609
|
nodeIds: nodes.map((n) => n.id ?? ""),
|
|
583
610
|
edges: edges.length,
|
|
584
|
-
subgraphNames:
|
|
585
|
-
maxDepth
|
|
611
|
+
subgraphNames: groups.map((g) => g.id ?? ""),
|
|
612
|
+
maxDepth
|
|
586
613
|
};
|
|
587
614
|
}
|
|
588
615
|
function adaptTimeline(db) {
|
|
589
616
|
const tasks = callGetter(db, "getTasks") ?? [];
|
|
590
617
|
const sections = callGetter(db, "getSections") ?? [];
|
|
591
618
|
return {
|
|
592
|
-
nodeIds: tasks.
|
|
619
|
+
nodeIds: tasks.flatMap((t) => [t.task ?? "", ...(t.events ?? []).map((e) => `${t.task ?? ""}: ${e}`)]),
|
|
593
620
|
edges: 0,
|
|
594
621
|
subgraphNames: sections.map((s) => s.sectionName ?? s.section ?? ""),
|
|
595
622
|
maxDepth: sections.length > 0 ? 1 : 0
|
|
@@ -619,7 +646,10 @@ function adaptVenn(db) {
|
|
|
619
646
|
function adaptXychart(db) {
|
|
620
647
|
const data = callGetter(db, "getXYChartData");
|
|
621
648
|
const categories = data?.xAxis?.categories ?? [];
|
|
622
|
-
|
|
649
|
+
if (categories.length > 0)
|
|
650
|
+
return { nodeIds: categories, edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
651
|
+
const longest = Math.max(0, ...(data?.plots ?? []).map((p) => p.data?.length ?? 0));
|
|
652
|
+
return { nodeIds: Array.from({ length: longest }, (_, i) => `x_${i}`), edges: 0, subgraphNames: [], maxDepth: 0 };
|
|
623
653
|
}
|
|
624
654
|
function subgraphNestingDepth(subGraphs) {
|
|
625
655
|
if (subGraphs.length === 0)
|
|
@@ -784,6 +814,15 @@ class RuleOptionError extends UsageError {
|
|
|
784
814
|
this.option = option;
|
|
785
815
|
}
|
|
786
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
|
+
}
|
|
787
826
|
|
|
788
827
|
class PluginLoadError extends MermaidLintError {
|
|
789
828
|
source;
|
|
@@ -1146,7 +1185,7 @@ var calculateComplexity = (stats, config) => {
|
|
|
1146
1185
|
const n = stats.nodes;
|
|
1147
1186
|
const e = stats.edges;
|
|
1148
1187
|
const s = stats.subgraphs;
|
|
1149
|
-
const d = stats.max_subgraph_depth;
|
|
1188
|
+
const d = Math.max(stats.max_subgraph_depth, stats.max_tree_depth);
|
|
1150
1189
|
const base = n + e * config.edge_weight + s * config.subgraph_weight;
|
|
1151
1190
|
const vcs = base * (1 + d * config.depth_weight);
|
|
1152
1191
|
const maxEdges = n > 1 ? n * (n - 1) : 0;
|
|
@@ -1483,7 +1522,7 @@ var parseFenceUnterminated = {
|
|
|
1483
1522
|
id: RULE.FENCE_UNTERMINATED,
|
|
1484
1523
|
category: "parse",
|
|
1485
1524
|
summary: "a mermaid fence that is never closed",
|
|
1486
|
-
check: (ctx) => ctx.doc.diagrams.filter((d) => !d.terminated).map((d) => finding(ctx, d, RULE.FENCE_UNTERMINATED, `mermaid fence opened at line ${d.startLine} is never closed`, "Add a closing fence after the last line of the diagram.")),
|
|
1525
|
+
check: (ctx) => ctx.doc.diagrams.filter((d) => !d.terminated).map((d) => finding(ctx, d, RULE.FENCE_UNTERMINATED, `mermaid fence opened at line ${d.startLine} is never closed`, "Add a closing fence directly after the last line of the diagram, with the opening line's prefix: `> ` in a quote, the item's indent in a list.")),
|
|
1487
1526
|
fix: (ctx) => {
|
|
1488
1527
|
const edits = [];
|
|
1489
1528
|
const { src } = ctx.doc;
|
|
@@ -1647,29 +1686,47 @@ var sameDiagrams = async (before, after) => {
|
|
|
1647
1686
|
if (!now || was.keyword !== now.keyword)
|
|
1648
1687
|
return false;
|
|
1649
1688
|
const [a, b] = await Promise.all([was.stats(), now.stats()]);
|
|
1650
|
-
const shape = (s) => [
|
|
1689
|
+
const shape = (s, source) => [
|
|
1690
|
+
parseFailed(source, s),
|
|
1651
1691
|
s.diagram_type,
|
|
1652
1692
|
s.nodes,
|
|
1653
1693
|
s.edges,
|
|
1654
1694
|
s.subgraphs,
|
|
1655
1695
|
s.max_subgraph_depth,
|
|
1696
|
+
s.max_tree_depth,
|
|
1656
1697
|
s.node_ids,
|
|
1657
1698
|
s.subgraph_names
|
|
1658
1699
|
];
|
|
1659
|
-
if (!sameJson(shape(a), shape(b)))
|
|
1700
|
+
if (!sameJson(shape(a, was.source), shape(b, now.source)))
|
|
1660
1701
|
return false;
|
|
1661
1702
|
}
|
|
1662
1703
|
return true;
|
|
1663
1704
|
};
|
|
1705
|
+
var breaksAParse = async (before, after) => {
|
|
1706
|
+
for (const [i, was] of before.diagrams.entries()) {
|
|
1707
|
+
const now = after.diagrams[i];
|
|
1708
|
+
if (!now)
|
|
1709
|
+
continue;
|
|
1710
|
+
const [a, b] = await Promise.all([was.stats(), now.stats()]);
|
|
1711
|
+
if (!parseFailed(was.source, a) && parseFailed(now.source, b))
|
|
1712
|
+
return true;
|
|
1713
|
+
}
|
|
1714
|
+
return false;
|
|
1715
|
+
};
|
|
1664
1716
|
var verify = async (edit, before, after) => {
|
|
1665
1717
|
if (edit.expect.kind === "same-tree") {
|
|
1666
|
-
|
|
1718
|
+
if (!sameTree(before.tree, after.tree, {}))
|
|
1719
|
+
return "refuse";
|
|
1720
|
+
if (before.kind === "markdown" && after.kind === "markdown") {
|
|
1721
|
+
return await breaksAParse(before, after) ? "refuse" : "accept";
|
|
1722
|
+
}
|
|
1667
1723
|
}
|
|
1668
1724
|
return await sameDiagrams(before, after) ? "accept" : "refuse";
|
|
1669
1725
|
};
|
|
1670
1726
|
|
|
1671
1727
|
// src/fix/engine.ts
|
|
1672
1728
|
var MAX_PASSES = 1000;
|
|
1729
|
+
var RECENT_PASSES = 50;
|
|
1673
1730
|
var splice = (src, edits) => {
|
|
1674
1731
|
let out = src;
|
|
1675
1732
|
for (const e of [...edits].sort((a, b) => b.start - a.start))
|
|
@@ -1687,6 +1744,7 @@ var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
|
1687
1744
|
const refused = [];
|
|
1688
1745
|
const refusedKeys = new Set;
|
|
1689
1746
|
let current = src;
|
|
1747
|
+
const keptBy = [];
|
|
1690
1748
|
for (let pass = 0;pass < MAX_PASSES; pass++) {
|
|
1691
1749
|
const doc = buildDocModel(current, kind);
|
|
1692
1750
|
const keyOf = (e) => `${e.rule}\x00${current.slice(e.start, e.end)}\x00${e.text}`;
|
|
@@ -1719,35 +1777,39 @@ var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
|
|
|
1719
1777
|
continue;
|
|
1720
1778
|
if (edits.length > 1 && !overlaps(edits) && await attempt(edits)) {
|
|
1721
1779
|
progressed = true;
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1780
|
+
} else {
|
|
1781
|
+
for (const edit of edits) {
|
|
1782
|
+
if (await attempt([edit])) {
|
|
1783
|
+
progressed = true;
|
|
1784
|
+
break;
|
|
1785
|
+
}
|
|
1786
|
+
refusedKeys.add(keyOf(edit));
|
|
1787
|
+
refused.push(edit);
|
|
1728
1788
|
}
|
|
1729
|
-
refusedKeys.add(keyOf(edit));
|
|
1730
|
-
refused.push(edit);
|
|
1731
1789
|
}
|
|
1732
|
-
if (progressed)
|
|
1790
|
+
if (progressed) {
|
|
1791
|
+
keptBy.push(rule.id);
|
|
1733
1792
|
break;
|
|
1793
|
+
}
|
|
1734
1794
|
}
|
|
1735
1795
|
if (!progressed)
|
|
1736
1796
|
return { output: current, applied, refused };
|
|
1737
1797
|
}
|
|
1738
|
-
|
|
1798
|
+
const lingering = [...new Set(keptBy.slice(-RECENT_PASSES))].sort();
|
|
1799
|
+
throw new FixNotConvergingError(file, MAX_PASSES, lingering);
|
|
1739
1800
|
};
|
|
1740
1801
|
|
|
1741
1802
|
// src/plugins/contract.ts
|
|
1742
1803
|
var API_VERSION = 1;
|
|
1743
1804
|
var CATEGORY_WORD = /^[a-z]+$/;
|
|
1744
|
-
var NAMESPACE = /^[a-z][a-z0-9-]*$/;
|
|
1745
|
-
var RULE_KEY = /^[a-z][a-z0-9-]*$/;
|
|
1805
|
+
var NAMESPACE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
1806
|
+
var RULE_KEY = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
1746
1807
|
|
|
1747
1808
|
// src/plugins/load.ts
|
|
1748
1809
|
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1749
1810
|
var message2 = (err) => err instanceof Error ? err.message : String(err);
|
|
1750
1811
|
var OPTION_TYPES = new Set(["number", "string", "boolean", "string[]"]);
|
|
1812
|
+
var RESERVED_OPTIONS = ["preset", "profile"];
|
|
1751
1813
|
var checkRule = (spec, key, raw, own) => {
|
|
1752
1814
|
const where = `rule "${key}"`;
|
|
1753
1815
|
if (!isRecord(raw))
|
|
@@ -1777,6 +1839,11 @@ var checkRule = (spec, key, raw, own) => {
|
|
|
1777
1839
|
if (!valid) {
|
|
1778
1840
|
throw new PluginContractError(spec, `${where} options must map names to number, string, boolean or string[]`);
|
|
1779
1841
|
}
|
|
1842
|
+
for (const name of RESERVED_OPTIONS) {
|
|
1843
|
+
if (name in options && options[name] !== "string") {
|
|
1844
|
+
throw new PluginContractError(spec, `${where} declares option "${name}" as ${String(options[name])}, but "${name}" is reserved for the built-in ${name}, which is a string set by --${name}; declare it as "string" to follow the run's ${name}, or pick another name`);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1780
1847
|
}
|
|
1781
1848
|
return raw;
|
|
1782
1849
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type DocModel } from "./model.ts";
|
|
2
|
+
import { type Registry } from "./rules/registry.ts";
|
|
3
|
+
import type { Finding, Rule, RuleOptions } from "./rules/types.ts";
|
|
4
|
+
export interface RunFlags {
|
|
5
|
+
preset?: string;
|
|
6
|
+
profile?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const optionsFor: (rule: Rule, configured: RuleOptions | undefined, flags: RunFlags) => RuleOptions;
|
|
9
|
+
export declare const checkModel: (doc: DocModel, file: string, flags?: RunFlags, registry?: Registry) => Promise<Finding[]>;
|
|
10
|
+
export declare const checkMarkdown: (src: string, file: string, flags?: RunFlags, registry?: Registry) => Promise<Finding[]>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PluginFailure } from "./plugins/index.ts";
|
|
2
|
+
import type { LoadedSource, Registry } from "./rules/registry.ts";
|
|
3
|
+
import { type Finding } from "./rules/types.ts";
|
|
4
|
+
export declare const loadedLine: (sources: LoadedSource[], skipped: string[]) => string | null;
|
|
5
|
+
export declare const skippedLines: (failures: PluginFailure[], ignored: string[]) => string[];
|
|
6
|
+
export declare const listRules: (registry: Registry) => string;
|
|
7
|
+
export declare const reportLine: (f: Finding, file: string) => string;
|
|
8
|
+
export declare const describeError: (err: unknown) => string;
|
|
9
|
+
export declare const main: (argv?: string[], cwd?: string) => Promise<number>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { UsageError } from "./errors.ts";
|
|
2
|
+
import type { RuleOptions } from "./rules/types.ts";
|
|
3
|
+
export { UsageError };
|
|
4
|
+
export type Severity = "error" | "off";
|
|
5
|
+
export type PluginLoading = "strict" | "lenient";
|
|
6
|
+
export interface RuleSetting {
|
|
7
|
+
severity: Severity;
|
|
8
|
+
options: RuleOptions;
|
|
9
|
+
}
|
|
10
|
+
export interface Config {
|
|
11
|
+
plugins: string[] | false;
|
|
12
|
+
pluginLoading: PluginLoading;
|
|
13
|
+
rules: Map<string, RuleSetting>;
|
|
14
|
+
file: string | null;
|
|
15
|
+
}
|
|
16
|
+
export declare const NO_CONFIG: Config;
|
|
17
|
+
export declare const CONFIG_FILES: readonly ["mermaidjs-lint.config.json", "mermaidjs-lint.config.mjs"];
|
|
18
|
+
export declare const parseConfig: (file: string, raw: unknown) => Config;
|
|
19
|
+
export declare const findConfig: (root: string) => Promise<string | null>;
|
|
20
|
+
export declare const loadConfig: (file: string) => Promise<Config>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export declare class MermaidLintError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
constructor(code: string, message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class UsageError extends MermaidLintError {
|
|
6
|
+
constructor(message: string, code?: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class FlagError extends UsageError {
|
|
9
|
+
readonly flag: string;
|
|
10
|
+
constructor(flag: string, message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class ConfigFileError extends UsageError {
|
|
13
|
+
readonly file: string;
|
|
14
|
+
constructor(file: string, message: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class ConfigNotFoundError extends UsageError {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
constructor(path: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class ConfigCodeError extends UsageError {
|
|
21
|
+
readonly file: string;
|
|
22
|
+
constructor(file: string);
|
|
23
|
+
}
|
|
24
|
+
export declare class AmbiguousConfigError extends UsageError {
|
|
25
|
+
readonly files: string[];
|
|
26
|
+
constructor(root: string, files: string[]);
|
|
27
|
+
}
|
|
28
|
+
export declare class ConfigShapeError extends UsageError {
|
|
29
|
+
readonly file: string;
|
|
30
|
+
readonly key: string | null;
|
|
31
|
+
constructor(file: string, message: string, key?: string | null);
|
|
32
|
+
}
|
|
33
|
+
export declare class RuleSettingError extends UsageError {
|
|
34
|
+
readonly file: string;
|
|
35
|
+
readonly rule: string;
|
|
36
|
+
constructor(file: string, rule: string, message: string);
|
|
37
|
+
}
|
|
38
|
+
export declare class UnknownRuleError extends UsageError {
|
|
39
|
+
readonly file: string;
|
|
40
|
+
readonly rule: string;
|
|
41
|
+
readonly known: string[];
|
|
42
|
+
constructor(file: string, rule: string, known: string[]);
|
|
43
|
+
}
|
|
44
|
+
export declare class RuleOptionError extends UsageError {
|
|
45
|
+
readonly file: string;
|
|
46
|
+
readonly rule: string;
|
|
47
|
+
readonly option: string;
|
|
48
|
+
constructor(file: string, rule: string, option: string, message: string);
|
|
49
|
+
}
|
|
50
|
+
export declare class FixOutsideRootError extends UsageError {
|
|
51
|
+
readonly file: string;
|
|
52
|
+
readonly target: string;
|
|
53
|
+
constructor(file: string, target: string, root: string);
|
|
54
|
+
}
|
|
55
|
+
export declare class PackageJsonError extends MermaidLintError {
|
|
56
|
+
readonly file: string;
|
|
57
|
+
constructor(file: string);
|
|
58
|
+
}
|
|
59
|
+
export declare class FixNotConvergingError extends MermaidLintError {
|
|
60
|
+
readonly file: string;
|
|
61
|
+
readonly rules: string[];
|
|
62
|
+
constructor(file: string, passes: number, rules: string[]);
|
|
63
|
+
}
|
|
64
|
+
export declare class PluginLoadError extends MermaidLintError {
|
|
65
|
+
readonly source: string;
|
|
66
|
+
constructor(source: string, reason: string, code?: string);
|
|
67
|
+
}
|
|
68
|
+
export declare class PluginImportError extends PluginLoadError {
|
|
69
|
+
constructor(source: string, reason: string);
|
|
70
|
+
}
|
|
71
|
+
export declare class PluginContractError extends PluginLoadError {
|
|
72
|
+
constructor(source: string, reason: string);
|
|
73
|
+
}
|
|
74
|
+
export declare class PluginConflictError extends PluginLoadError {
|
|
75
|
+
readonly other: string;
|
|
76
|
+
constructor(source: string, other: string, reason: string);
|
|
77
|
+
}
|
|
78
|
+
export declare class PluginNotInstalledError extends PluginLoadError {
|
|
79
|
+
readonly root: string;
|
|
80
|
+
constructor(source: string, root: string);
|
|
81
|
+
}
|
|
82
|
+
export declare class PluginSourceError extends PluginLoadError {
|
|
83
|
+
constructor(source: string, reason: string);
|
|
84
|
+
}
|
|
85
|
+
export declare class PluginFixerError extends MermaidLintError {
|
|
86
|
+
readonly rule: string;
|
|
87
|
+
constructor(rule: string, reason: string);
|
|
88
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const DIAGRAM_FILE_EXTS: Set<string>;
|
|
2
|
+
export declare const SKIP_DIRS: Set<string>;
|
|
3
|
+
export interface Collected {
|
|
4
|
+
files: string[];
|
|
5
|
+
unreadable: string[];
|
|
6
|
+
symlinks: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const collectFiles: (paths: string[]) => Collected;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RunFlags } from "../check.ts";
|
|
2
|
+
import { type Registry } from "../rules/registry.ts";
|
|
3
|
+
import type { Edit } from "../rules/types.ts";
|
|
4
|
+
export interface FixReport {
|
|
5
|
+
output: string;
|
|
6
|
+
applied: Edit[];
|
|
7
|
+
refused: Edit[];
|
|
8
|
+
}
|
|
9
|
+
export declare const fixSourceReport: (src: string, file: string, flags?: RunFlags, registry?: Registry) => Promise<FixReport>;
|
|
10
|
+
export declare const fixSource: (src: string, file: string, flags?: RunFlags, registry?: Registry) => Promise<string>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Node } from "mdast";
|
|
2
|
+
import type { DocModel } from "../model.ts";
|
|
3
|
+
import type { Edit } from "../rules/types.ts";
|
|
4
|
+
type Plain = {
|
|
5
|
+
[k: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
export declare const normalise: (node: Node, opts?: {
|
|
8
|
+
blankDiagrams?: boolean;
|
|
9
|
+
}) => Plain;
|
|
10
|
+
export type Verdict = "accept" | "refuse";
|
|
11
|
+
export declare const verify: (edit: Edit, before: DocModel, after: DocModel) => Promise<Verdict>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { checkMarkdown, checkModel, type RunFlags } from "./check.ts";
|
|
2
|
+
export { main } from "./cli.ts";
|
|
3
|
+
export { type Config, loadConfig, type PluginLoading, parseConfig, type RuleSetting, type Severity } from "./config.ts";
|
|
4
|
+
export * from "./errors.ts";
|
|
5
|
+
export { type Collected, collectFiles } from "./files.ts";
|
|
6
|
+
export { type FixReport, fixSource, fixSourceReport } from "./fix/engine.ts";
|
|
7
|
+
export { buildDocModel, DIAGRAM_EXTS, type Diagram, type DocModel, documentKind } from "./model.ts";
|
|
8
|
+
export type { ContrastAssessment, StyleDirective, ValueSpan } from "./parse/color.ts";
|
|
9
|
+
export { PRESETS, type Preset, type ThresholdConfig, thresholdsFrom } from "./parse/metrics.ts";
|
|
10
|
+
export { type DiagramStats, detectKeyword, extractStats } from "./parse/stats.ts";
|
|
11
|
+
export type { Plugin, PluginEdit, PluginFinding, PluginMeta, PluginRule } from "./plugins/contract.ts";
|
|
12
|
+
export type { SourceKind } from "./plugins/discover.ts";
|
|
13
|
+
export type { PluginFailure } from "./plugins/index.ts";
|
|
14
|
+
export { findProjectRoot, type Setup, type SetupOptions, setUp } from "./project.ts";
|
|
15
|
+
export { FIX_ORDER, RULES } from "./rules/index.ts";
|
|
16
|
+
export { BUILTIN, BUILTIN_CATEGORIES, buildRegistry, type LoadedSource, type Registry, type RegistryInput, } from "./rules/registry.ts";
|
|
17
|
+
export type { Category, CheckContext, CoreRuleId, Edit, Expectation, Finding, FixContext, Helpers, OptionSpec, OptionType, PluginRuleId, Rule, RuleContext, RuleId, RuleOptions, } from "./rules/types.ts";
|
|
18
|
+
export { CATEGORIES, RULE } from "./rules/types.ts";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Root } from "mdast";
|
|
2
|
+
import { type StyleDirective, type ValueSpan } from "./parse/color.ts";
|
|
3
|
+
import { type DiagramStats } from "./parse/stats.ts";
|
|
4
|
+
export interface Diagram {
|
|
5
|
+
index: number;
|
|
6
|
+
source: string;
|
|
7
|
+
keyword: string | null;
|
|
8
|
+
startLine: number;
|
|
9
|
+
endLine: number;
|
|
10
|
+
firstLine: number;
|
|
11
|
+
terminated: boolean;
|
|
12
|
+
endOffset: number;
|
|
13
|
+
directives: StyleDirective[];
|
|
14
|
+
offsetOf: (span: ValueSpan) => number | null;
|
|
15
|
+
stats: () => Promise<DiagramStats>;
|
|
16
|
+
}
|
|
17
|
+
export interface DocModel {
|
|
18
|
+
src: string;
|
|
19
|
+
kind: "markdown" | "diagram";
|
|
20
|
+
tree: Root;
|
|
21
|
+
diagrams: Diagram[];
|
|
22
|
+
}
|
|
23
|
+
export declare const parse: (src: string) => Root;
|
|
24
|
+
export declare const DIAGRAM_EXTS: readonly [".mmd", ".mermaid"];
|
|
25
|
+
export declare const documentKind: (file: string) => "markdown" | "diagram";
|
|
26
|
+
export declare const buildDocModel: (src: string, kind?: "markdown" | "diagram") => DocModel;
|
|
27
|
+
export declare const isMarkdownFile: (file: string) => boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type WcagRating = "AAA" | "AA" | "AA Large" | "Fail";
|
|
2
|
+
export interface ContrastAssessment {
|
|
3
|
+
foreground: string;
|
|
4
|
+
background: string;
|
|
5
|
+
ratio: number;
|
|
6
|
+
rating: WcagRating;
|
|
7
|
+
apca_lc: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const wcagRatio: (fg: string, bg: string) => number;
|
|
10
|
+
export declare const apcaLc: (fg: string, bg: string) => number;
|
|
11
|
+
export declare const alphaOf: (color: string) => number;
|
|
12
|
+
export declare const compositeOver: (fg: string, bg: string) => string;
|
|
13
|
+
export declare const wcagRating: (ratio: number) => WcagRating;
|
|
14
|
+
export declare const wcagAssess: (fg: string, bg: string) => ContrastAssessment;
|
|
15
|
+
export interface ValueSpan {
|
|
16
|
+
line: number;
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
}
|
|
20
|
+
export interface StyleDirective {
|
|
21
|
+
kind: "classDef" | "style" | "linkStyle" | "inlineClass";
|
|
22
|
+
selector: string;
|
|
23
|
+
class_name?: string;
|
|
24
|
+
properties: Record<string, string>;
|
|
25
|
+
spans: Record<string, ValueSpan>;
|
|
26
|
+
line: number;
|
|
27
|
+
}
|
|
28
|
+
export declare const extractStyleDirectives: (source: string) => StyleDirective[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ContrastAssessment, type StyleDirective } from "./color.ts";
|
|
2
|
+
export type Profile = "github" | "mkdocs-material";
|
|
3
|
+
export type Theme = "light" | "dark";
|
|
4
|
+
export type PairKind = "text" | "border";
|
|
5
|
+
export type ErRow = "odd" | "even";
|
|
6
|
+
export declare const PROFILES: readonly ["auto", "github", "mkdocs-material"];
|
|
7
|
+
export declare const edgeLabelBackground: (theme: Theme) => string;
|
|
8
|
+
export interface ContrastPair {
|
|
9
|
+
kind: PairKind;
|
|
10
|
+
selector: string;
|
|
11
|
+
directive: StyleDirective;
|
|
12
|
+
foreground: string;
|
|
13
|
+
background: string;
|
|
14
|
+
line: number;
|
|
15
|
+
assessment: ContrastAssessment;
|
|
16
|
+
passes: boolean;
|
|
17
|
+
theme?: Theme;
|
|
18
|
+
row?: ErRow;
|
|
19
|
+
advisory?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface SkippedDirective {
|
|
22
|
+
selector: string;
|
|
23
|
+
line: number;
|
|
24
|
+
reason: string;
|
|
25
|
+
blocking?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface ContrastResult {
|
|
28
|
+
pairs: ContrastPair[];
|
|
29
|
+
skipped: SkippedDirective[];
|
|
30
|
+
}
|
|
31
|
+
export declare const scoreGithub: (directives: StyleDirective[]) => ContrastResult;
|
|
32
|
+
export declare const scoreMkdocs: (directives: StyleDirective[]) => ContrastResult;
|
|
33
|
+
export declare const scoreEr: (directives: StyleDirective[]) => ContrastResult;
|
|
34
|
+
export declare const scoreForProfile: (directives: StyleDirective[], profile: Profile, keyword: string | null) => ContrastResult;
|
|
35
|
+
export declare const detectProfile: (filePath: string) => Profile;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { DiagramStats } from "./stats.ts";
|
|
2
|
+
export interface Preset {
|
|
3
|
+
node_ideal: number;
|
|
4
|
+
node_acceptable: number;
|
|
5
|
+
node_complex: number;
|
|
6
|
+
node_hard_limit: number;
|
|
7
|
+
vcs_ideal: number;
|
|
8
|
+
vcs_acceptable: number;
|
|
9
|
+
vcs_complex: number;
|
|
10
|
+
vcs_critical: number;
|
|
11
|
+
node_target: number;
|
|
12
|
+
vcs_target: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const PRESETS: Record<string, Preset>;
|
|
15
|
+
export declare const DEFAULT_PRESET = "high-density";
|
|
16
|
+
export interface ThresholdConfig extends Preset {
|
|
17
|
+
edge_weight: number;
|
|
18
|
+
subgraph_weight: number;
|
|
19
|
+
depth_weight: number;
|
|
20
|
+
preset_name: string;
|
|
21
|
+
}
|
|
22
|
+
export declare const THRESHOLD_OPTIONS: {
|
|
23
|
+
readonly nodeIdeal: "node_ideal";
|
|
24
|
+
readonly nodeAcceptable: "node_acceptable";
|
|
25
|
+
readonly nodeComplex: "node_complex";
|
|
26
|
+
readonly nodeHardLimit: "node_hard_limit";
|
|
27
|
+
readonly vcsIdeal: "vcs_ideal";
|
|
28
|
+
readonly vcsAcceptable: "vcs_acceptable";
|
|
29
|
+
readonly vcsComplex: "vcs_complex";
|
|
30
|
+
readonly vcsCritical: "vcs_critical";
|
|
31
|
+
readonly nodeTarget: "node_target";
|
|
32
|
+
readonly vcsTarget: "vcs_target";
|
|
33
|
+
readonly edgeWeight: "edge_weight";
|
|
34
|
+
readonly subgraphWeight: "subgraph_weight";
|
|
35
|
+
readonly depthWeight: "depth_weight";
|
|
36
|
+
};
|
|
37
|
+
export declare const PRESET_NAMES: string[];
|
|
38
|
+
export declare const canonicalPreset: (name: string) => string | null;
|
|
39
|
+
export declare const configFromPreset: (name: string) => ThresholdConfig;
|
|
40
|
+
export declare const thresholdsFrom: (options: Readonly<Record<string, unknown>>) => ThresholdConfig;
|
|
41
|
+
export interface ComplexityMetrics {
|
|
42
|
+
visual_complexity_score: number;
|
|
43
|
+
edge_density: number;
|
|
44
|
+
cyclomatic_complexity: number;
|
|
45
|
+
}
|
|
46
|
+
export declare const calculateComplexity: (stats: DiagramStats, config: ThresholdConfig) => ComplexityMetrics;
|
|
47
|
+
export declare const parseFailed: (content: string, stats: DiagramStats) => boolean;
|
|
48
|
+
export declare const sourceLines: (content: string) => number;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface DiagramStats {
|
|
2
|
+
nodes: number;
|
|
3
|
+
edges: number;
|
|
4
|
+
subgraphs: number;
|
|
5
|
+
max_subgraph_depth: number;
|
|
6
|
+
max_tree_depth: number;
|
|
7
|
+
node_ids: string[];
|
|
8
|
+
subgraph_names: string[];
|
|
9
|
+
parser_used: "langium" | "mermaid-core";
|
|
10
|
+
diagram_type: string;
|
|
11
|
+
error: string | null;
|
|
12
|
+
}
|
|
13
|
+
type MermaidStats = DiagramStats;
|
|
14
|
+
export declare function detectKeyword(content: string): string | null;
|
|
15
|
+
export declare function extractStats(content: string): Promise<MermaidStats>;
|
|
16
|
+
export declare const parserReason: (err: unknown) => string;
|
|
17
|
+
export declare function indentTreeStats(items: Array<{
|
|
18
|
+
label: string;
|
|
19
|
+
indent: number;
|
|
20
|
+
}>): {
|
|
21
|
+
labels: string[];
|
|
22
|
+
edges: number;
|
|
23
|
+
depth: number;
|
|
24
|
+
};
|
|
25
|
+
export {};
|