@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.
Files changed (57) hide show
  1. package/README.md +12 -9
  2. package/dist/bin.js +214 -64
  3. package/dist/index.js +217 -64
  4. package/dist/testing.js +99 -32
  5. package/dist/types/bin.d.ts +2 -0
  6. package/dist/types/check.d.ts +10 -0
  7. package/dist/types/cli.d.ts +9 -0
  8. package/dist/types/config.d.ts +20 -0
  9. package/dist/types/errors.d.ts +88 -0
  10. package/dist/types/files.d.ts +8 -0
  11. package/dist/types/fix/engine.d.ts +10 -0
  12. package/dist/types/fix/verify.d.ts +12 -0
  13. package/dist/types/index.d.ts +18 -0
  14. package/dist/types/model.d.ts +27 -0
  15. package/dist/types/parse/color.d.ts +28 -0
  16. package/dist/types/parse/contrast.d.ts +35 -0
  17. package/dist/types/parse/metrics.d.ts +48 -0
  18. package/dist/types/parse/stats.d.ts +25 -0
  19. package/dist/types/plugins/contract.d.ts +24 -0
  20. package/dist/types/plugins/discover.d.ts +20 -0
  21. package/dist/types/plugins/index.d.ts +19 -0
  22. package/dist/types/plugins/load.d.ts +14 -0
  23. package/dist/types/plugins/runtime.d.ts +6 -0
  24. package/dist/types/project.d.ts +18 -0
  25. package/dist/types/rules/complexity-node-count-acceptable-exceeded.d.ts +1 -0
  26. package/dist/types/rules/complexity-node-count-cognitive-limit.d.ts +1 -0
  27. package/dist/types/rules/complexity-node-count-hard-limit.d.ts +1 -0
  28. package/dist/types/rules/complexity-subgraph-nesting-too-deep.d.ts +2 -0
  29. package/dist/types/rules/complexity-visual-score-acceptable-exceeded.d.ts +1 -0
  30. package/dist/types/rules/complexity-visual-score-critical.d.ts +1 -0
  31. package/dist/types/rules/complexity.d.ts +12 -0
  32. package/dist/types/rules/contrast-border-below-ui-minimum.d.ts +2 -0
  33. package/dist/types/rules/contrast-directive-unauditable.d.ts +2 -0
  34. package/dist/types/rules/contrast-text-below-aa.d.ts +2 -0
  35. package/dist/types/rules/index.d.ts +3 -0
  36. package/dist/types/rules/parse-diagram-unparseable.d.ts +2 -0
  37. package/dist/types/rules/parse-fence-unterminated.d.ts +2 -0
  38. package/dist/types/rules/registry.d.ts +29 -0
  39. package/dist/types/rules/shared.d.ts +18 -0
  40. package/dist/types/rules/types.d.ts +67 -0
  41. package/dist/types/testing.d.ts +15 -0
  42. package/package.json +5 -4
  43. package/src/cli.ts +47 -15
  44. package/src/errors.ts +46 -0
  45. package/src/files.ts +21 -2
  46. package/src/fix/engine.ts +22 -10
  47. package/src/fix/verify.ts +31 -3
  48. package/src/index.ts +5 -3
  49. package/src/parse/metrics.ts +8 -1
  50. package/src/parse/stats.ts +72 -16
  51. package/src/plugins/contract.ts +4 -2
  52. package/src/plugins/discover.ts +38 -15
  53. package/src/plugins/index.ts +5 -3
  54. package/src/plugins/load.ts +32 -1
  55. package/src/project.ts +3 -1
  56. package/src/rules/complexity-subgraph-nesting-too-deep.ts +5 -0
  57. 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
- maxDepth = stats.depth;
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
- maxDepth = stats.depth;
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 { nodeIds: labels, edges: Math.max(0, labels.length - 1), subgraphNames: [], maxDepth };
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 { nodeIds: labels, edges: Math.max(0, labels.length - 1), subgraphNames: [], maxDepth };
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 quadrants = data?.quadrants ?? [];
551
- const labels = quadrants.map((q) => {
552
- const t = q.text;
553
- return typeof t === "string" ? t : t?.text ?? "";
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: 0
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.map((t) => t.task ?? ""),
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
- return { nodeIds: categories, edges: 0, subgraphNames: [], maxDepth: 0 };
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
- return sameTree(before.tree, after.tree, {}) ? "accept" : "refuse";
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
- break;
1723
- }
1724
- for (const edit of edits) {
1725
- if (await attempt([edit])) {
1726
- progressed = true;
1727
- break;
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
- throw new Error(`fix did not converge after ${MAX_PASSES} passes; a fixer keeps proposing edits`);
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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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 {};