asciidoclint 0.5.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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +258 -0
  3. package/assets/README.md +12 -0
  4. package/assets/icon.svg +198 -0
  5. package/assets/logo.svg +203 -0
  6. package/dist/api/fixes.d.ts +6 -0
  7. package/dist/api/fixes.js +61 -0
  8. package/dist/api/lint.d.ts +2 -0
  9. package/dist/api/lint.js +191 -0
  10. package/dist/api/rules.d.ts +33 -0
  11. package/dist/api/rules.js +115 -0
  12. package/dist/cli/index.d.ts +2 -0
  13. package/dist/cli/index.js +86 -0
  14. package/dist/cli/init-rule.d.ts +7 -0
  15. package/dist/cli/init-rule.js +74 -0
  16. package/dist/cli/install-skill.d.ts +10 -0
  17. package/dist/cli/install-skill.js +37 -0
  18. package/dist/formatters/json.d.ts +2 -0
  19. package/dist/formatters/json.js +30 -0
  20. package/dist/formatters/pretty.d.ts +2 -0
  21. package/dist/formatters/pretty.js +41 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.js +3 -0
  24. package/dist/parsers/asciidoctor.d.ts +4 -0
  25. package/dist/parsers/asciidoctor.js +444 -0
  26. package/dist/parsers/tolerant.d.ts +4 -0
  27. package/dist/parsers/tolerant.js +528 -0
  28. package/dist/rules/AD001.d.ts +2 -0
  29. package/dist/rules/AD001.js +28 -0
  30. package/dist/rules/AD002.d.ts +2 -0
  31. package/dist/rules/AD002.js +30 -0
  32. package/dist/rules/AD003.d.ts +2 -0
  33. package/dist/rules/AD003.js +28 -0
  34. package/dist/rules/AD004.d.ts +2 -0
  35. package/dist/rules/AD004.js +58 -0
  36. package/dist/rules/AD005.d.ts +2 -0
  37. package/dist/rules/AD005.js +31 -0
  38. package/dist/rules/AD006.d.ts +2 -0
  39. package/dist/rules/AD006.js +53 -0
  40. package/dist/rules/AD007.d.ts +2 -0
  41. package/dist/rules/AD007.js +39 -0
  42. package/dist/rules/AD008.d.ts +2 -0
  43. package/dist/rules/AD008.js +88 -0
  44. package/dist/rules/AD010.d.ts +2 -0
  45. package/dist/rules/AD010.js +39 -0
  46. package/dist/rules/AD011.d.ts +2 -0
  47. package/dist/rules/AD011.js +31 -0
  48. package/dist/rules/AD012.d.ts +2 -0
  49. package/dist/rules/AD012.js +28 -0
  50. package/dist/rules/AD013.d.ts +2 -0
  51. package/dist/rules/AD013.js +43 -0
  52. package/dist/rules/AD016.d.ts +2 -0
  53. package/dist/rules/AD016.js +83 -0
  54. package/dist/rules/AD017.d.ts +2 -0
  55. package/dist/rules/AD017.js +53 -0
  56. package/dist/rules/AD019.d.ts +2 -0
  57. package/dist/rules/AD019.js +58 -0
  58. package/dist/rules/AD020.d.ts +2 -0
  59. package/dist/rules/AD020.js +40 -0
  60. package/dist/rules/AD022.d.ts +2 -0
  61. package/dist/rules/AD022.js +55 -0
  62. package/dist/rules/AD023.d.ts +2 -0
  63. package/dist/rules/AD023.js +59 -0
  64. package/dist/rules/AD024.d.ts +2 -0
  65. package/dist/rules/AD024.js +30 -0
  66. package/dist/rules/AD025.d.ts +2 -0
  67. package/dist/rules/AD025.js +32 -0
  68. package/dist/rules/AD026.d.ts +2 -0
  69. package/dist/rules/AD026.js +26 -0
  70. package/dist/rules/AD027.d.ts +2 -0
  71. package/dist/rules/AD027.js +31 -0
  72. package/dist/rules/AD028.d.ts +2 -0
  73. package/dist/rules/AD028.js +113 -0
  74. package/dist/rules/AD029.d.ts +2 -0
  75. package/dist/rules/AD029.js +46 -0
  76. package/dist/rules/AD030.d.ts +2 -0
  77. package/dist/rules/AD030.js +33 -0
  78. package/dist/rules/AD031.d.ts +2 -0
  79. package/dist/rules/AD031.js +66 -0
  80. package/dist/rules/AD032.d.ts +2 -0
  81. package/dist/rules/AD032.js +81 -0
  82. package/dist/rules/AD034.d.ts +2 -0
  83. package/dist/rules/AD034.js +50 -0
  84. package/dist/rules/AD035.d.ts +2 -0
  85. package/dist/rules/AD035.js +77 -0
  86. package/dist/rules/AD036.d.ts +2 -0
  87. package/dist/rules/AD036.js +34 -0
  88. package/dist/rules/AD037.d.ts +2 -0
  89. package/dist/rules/AD037.js +34 -0
  90. package/dist/rules/AD039.d.ts +2 -0
  91. package/dist/rules/AD039.js +58 -0
  92. package/dist/rules/AD040.d.ts +2 -0
  93. package/dist/rules/AD040.js +56 -0
  94. package/dist/rules/AD041.d.ts +2 -0
  95. package/dist/rules/AD041.js +66 -0
  96. package/dist/rules/AD042.d.ts +2 -0
  97. package/dist/rules/AD042.js +62 -0
  98. package/dist/rules/AD043.d.ts +2 -0
  99. package/dist/rules/AD043.js +30 -0
  100. package/dist/rules/AD044.d.ts +2 -0
  101. package/dist/rules/AD044.js +54 -0
  102. package/dist/rules/AD045.d.ts +2 -0
  103. package/dist/rules/AD045.js +66 -0
  104. package/dist/rules/builtin.d.ts +3 -0
  105. package/dist/rules/builtin.js +81 -0
  106. package/dist/rules/helpers.d.ts +2 -0
  107. package/dist/rules/helpers.js +11 -0
  108. package/dist/rules/registry.d.ts +3 -0
  109. package/dist/rules/registry.js +34 -0
  110. package/dist/rules/utils.d.ts +42 -0
  111. package/dist/rules/utils.js +274 -0
  112. package/dist/types.d.ts +166 -0
  113. package/dist/types.js +1 -0
  114. package/dist/version.d.ts +2 -0
  115. package/dist/version.js +4 -0
  116. package/package.json +70 -0
  117. package/skills/asciidoclint/SKILL.md +84 -0
  118. package/skills/asciidoclint/references/ai-fix-policy.md +11 -0
  119. package/skills/asciidoclint/references/result-schema.md +22 -0
@@ -0,0 +1,203 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ viewBox="0 0 128 128"
4
+ role="img"
5
+ aria-labelledby="title desc"
6
+ version="1.1"
7
+ id="svg4"
8
+ sodipodi:docname="logo.svg"
9
+ xml:space="preserve"
10
+ inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
11
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
15
+ id="defs4"><inkscape:path-effect
16
+ effect="bspline"
17
+ id="path-effect14"
18
+ is_visible="true"
19
+ lpeversion="1.3"
20
+ weight="33.333333"
21
+ steps="2"
22
+ helper_size="0"
23
+ apply_no_weight="true"
24
+ apply_with_weight="true"
25
+ only_selected="false"
26
+ uniform="false" /><linearGradient
27
+ id="swatch13"
28
+ inkscape:swatch="solid"><stop
29
+ style="stop-color:#000000;stop-opacity:1;"
30
+ offset="0"
31
+ id="stop13" /></linearGradient><inkscape:path-effect
32
+ effect="bspline"
33
+ id="path-effect13"
34
+ is_visible="true"
35
+ lpeversion="1.3"
36
+ weight="33.333333"
37
+ steps="2"
38
+ helper_size="0"
39
+ apply_no_weight="true"
40
+ apply_with_weight="true"
41
+ only_selected="false"
42
+ uniform="false" /><inkscape:path-effect
43
+ effect="bspline"
44
+ id="path-effect12"
45
+ is_visible="true"
46
+ lpeversion="1.3"
47
+ weight="33.333333"
48
+ steps="2"
49
+ helper_size="0"
50
+ apply_no_weight="true"
51
+ apply_with_weight="true"
52
+ only_selected="false"
53
+ uniform="false" /><inkscape:path-effect
54
+ effect="bspline"
55
+ id="path-effect11"
56
+ is_visible="true"
57
+ lpeversion="1.3"
58
+ weight="33.333333"
59
+ steps="2"
60
+ helper_size="0"
61
+ apply_no_weight="true"
62
+ apply_with_weight="true"
63
+ only_selected="false"
64
+ uniform="false" /><inkscape:path-effect
65
+ effect="bspline"
66
+ id="path-effect10"
67
+ is_visible="true"
68
+ lpeversion="1.3"
69
+ weight="33.333333"
70
+ steps="2"
71
+ helper_size="0"
72
+ apply_no_weight="true"
73
+ apply_with_weight="true"
74
+ only_selected="false"
75
+ uniform="false" /><inkscape:path-effect
76
+ effect="bspline"
77
+ id="path-effect9"
78
+ is_visible="true"
79
+ lpeversion="1.3"
80
+ weight="33.333333"
81
+ steps="2"
82
+ helper_size="0"
83
+ apply_no_weight="true"
84
+ apply_with_weight="true"
85
+ only_selected="false"
86
+ uniform="false" /><inkscape:path-effect
87
+ effect="spiro"
88
+ id="path-effect8"
89
+ is_visible="true"
90
+ lpeversion="1" /><inkscape:path-effect
91
+ effect="bspline"
92
+ id="path-effect3"
93
+ is_visible="true"
94
+ lpeversion="1.3"
95
+ weight="33.333333"
96
+ steps="2"
97
+ helper_size="0"
98
+ apply_no_weight="true"
99
+ apply_with_weight="true"
100
+ only_selected="false"
101
+ uniform="false" /><inkscape:path-effect
102
+ effect="bspline"
103
+ id="path-effect7"
104
+ is_visible="true"
105
+ lpeversion="1.3"
106
+ weight="33.333333"
107
+ steps="2"
108
+ helper_size="0"
109
+ apply_no_weight="true"
110
+ apply_with_weight="true"
111
+ only_selected="false"
112
+ uniform="false" /><inkscape:path-effect
113
+ effect="bspline"
114
+ id="path-effect6"
115
+ is_visible="true"
116
+ lpeversion="1.3"
117
+ weight="33.333333"
118
+ steps="2"
119
+ helper_size="0"
120
+ apply_no_weight="true"
121
+ apply_with_weight="true"
122
+ only_selected="false"
123
+ uniform="false" /><inkscape:path-effect
124
+ effect="bspline"
125
+ id="path-effect5"
126
+ is_visible="true"
127
+ lpeversion="1.3"
128
+ weight="33.333333"
129
+ steps="2"
130
+ helper_size="0"
131
+ apply_no_weight="true"
132
+ apply_with_weight="true"
133
+ only_selected="false"
134
+ uniform="false" /><inkscape:path-effect
135
+ effect="bspline"
136
+ id="path-effect4"
137
+ is_visible="true"
138
+ lpeversion="1.3"
139
+ weight="33.333333"
140
+ steps="2"
141
+ helper_size="0"
142
+ apply_no_weight="true"
143
+ apply_with_weight="true"
144
+ only_selected="false"
145
+ uniform="false" /></defs><sodipodi:namedview
146
+ id="namedview4"
147
+ pagecolor="#ffffff"
148
+ bordercolor="#000000"
149
+ borderopacity="0.25"
150
+ inkscape:showpageshadow="2"
151
+ inkscape:pageopacity="0.0"
152
+ inkscape:pagecheckerboard="0"
153
+ inkscape:deskcolor="#d1d1d1"
154
+ showgrid="true"
155
+ inkscape:zoom="2.298097"
156
+ inkscape:cx="2.8284271"
157
+ inkscape:cy="52.434687"
158
+ inkscape:window-width="1440"
159
+ inkscape:window-height="726"
160
+ inkscape:window-x="0"
161
+ inkscape:window-y="33"
162
+ inkscape:window-maximized="0"
163
+ inkscape:current-layer="svg4"><inkscape:grid
164
+ id="grid4"
165
+ units="px"
166
+ originx="0"
167
+ originy="0"
168
+ spacingx="1"
169
+ spacingy="1"
170
+ empcolor="#0099e5"
171
+ empopacity="0.30196078"
172
+ color="#0099e5"
173
+ opacity="0.14901961"
174
+ empspacing="5"
175
+ enabled="true"
176
+ visible="true" /></sodipodi:namedview><title
177
+ id="title">asciidoclint</title><desc
178
+ id="desc">Full logo: Asciidoctor-style A, Lint label, diagnostic squiggle</desc><rect
179
+ width="128"
180
+ height="128"
181
+ rx="28"
182
+ fill="#e40046"
183
+ id="rect1" /><g
184
+ fill="none"
185
+ stroke="#ffffff"
186
+ stroke-width="10"
187
+ stroke-linecap="round"
188
+ stroke-linejoin="round"
189
+ id="g3"
190
+ transform="matrix(0.8004134,0,0,0.8004134,13.923007,-1.0663627)"><path
191
+ d="M 36.153846,94.307692 64,28"
192
+ id="path1"
193
+ sodipodi:nodetypes="cc" /><path
194
+ d="M 92.461538,94.307692 64,28"
195
+ id="path2"
196
+ sodipodi:nodetypes="cc" /></g><path
197
+ style="font-weight:bold;font-size:22px;line-height:6.66667px;font-family:'JetBrains Mono';-inkscape-font-specification:'JetBrains Mono Bold';letter-spacing:0.5;text-anchor:middle;fill:#ffffff;stroke-width:5"
198
+ d="m 46.458513,113.01054 q -1.804,0 -2.904,-1.078 -1.078,-1.078 -1.078,-2.838 v -9.658004 h -3.982 v -2.486 h 6.732 v 12.078004 q 0,0.682 0.374,1.1 0.396,0.396 1.056,0.396 h 3.542 v 2.486 z m 6.814008,0 v -2.486 h 4.136 v -7.128 h -3.696 v -2.486 h 6.446 v 9.614 h 3.718 v 2.486 z m 5.324,-14.124004 q -0.836,0 -1.32,-0.418 -0.484,-0.44 -0.484,-1.166 0,-0.726 0.484,-1.144 0.484,-0.44 1.32,-0.44 0.836,0 1.32,0.44 0.484,0.418 0.484,1.144 0,0.726 -0.484,1.166 -0.484,0.418 -1.32,0.418 z m 8.398008,14.124004 v -12.1 h 2.684 v 2.266 q 0.22,-1.144 1.078,-1.804 0.858,-0.682 2.178,-0.682 1.782,0 2.838,1.188 1.078,1.188 1.078,3.19 v 7.942 h -2.75 v -7.612 q 0,-1.122 -0.594,-1.716 -0.572,-0.616 -1.562,-0.616 -1.034,0 -1.628,0.616 -0.572,0.616 -0.572,1.76 v 7.568 z m 19.816009,0 q -1.694,0 -2.706,-0.968 -0.99,-0.99 -0.99,-2.662 v -5.984 h -3.344 v -2.486 h 3.344 v -3.410004 h 2.772 v 3.410004 h 4.818 v 2.486 h -4.818 v 5.918 q 0,0.528 0.286,0.88 0.308,0.33 0.836,0.33 h 3.586 v 2.486 z"
199
+ id="text3"
200
+ aria-label="lint" /><path
201
+ id="path14"
202
+ style="fill:none;fill-opacity:1;stroke:#ffff00;stroke-width:4.45706;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
203
+ d="m 41.675229,89.375676 c 1.340122,-1.303113 2.643607,-3.268472 3.888134,-3.346229 1.641041,-0.102533 3.179471,3.076889 4.717948,2.974289 1.538477,-0.102591 3.076909,-3.487142 4.820518,-3.435824 1.743609,0.05132 3.692287,3.538424 5.435892,3.640954 1.743604,0.10254 3.282037,-3.179451 4.871798,-3.128135 1.58976,0.05132 3.230754,3.435865 4.871793,3.384545 1.64104,-0.05131 3.282035,-3.538425 4.97436,-3.640956 1.692326,-0.102531 3.435882,3.179456 5.128206,3.179426 1.692325,-4.1e-5 3.333316,-3.282024 4.974359,-3.230708 1.379819,0.04315 2.759604,2.442813 4.139412,3.754388" /></svg>
@@ -0,0 +1,6 @@
1
+ import type { LintFinding } from "../types.js";
2
+ export interface FixSummary {
3
+ applied: number;
4
+ skipped: number;
5
+ }
6
+ export declare function applyFixes(findings: LintFinding[], unsafeFixes: boolean): FixSummary;
@@ -0,0 +1,61 @@
1
+ import fs from "node:fs";
2
+ export function applyFixes(findings, unsafeFixes) {
3
+ const allowed = unsafeFixes ? ["safe", "unsafe"] : ["safe"];
4
+ const edits = findings.flatMap((finding) => {
5
+ if (!finding.fix || !allowed.includes(finding.fix.applicability)) {
6
+ return [];
7
+ }
8
+ return finding.fix.edits;
9
+ });
10
+ const byFile = new Map();
11
+ for (const edit of edits) {
12
+ const list = byFile.get(edit.file) ?? [];
13
+ list.push(edit);
14
+ byFile.set(edit.file, list);
15
+ }
16
+ let applied = 0;
17
+ let skipped = 0;
18
+ for (const [file, fileEdits] of byFile.entries()) {
19
+ const text = fs.readFileSync(file, "utf8");
20
+ const normalized = fileEdits.map((edit) => ({
21
+ edit,
22
+ start: offsetFor(text, edit.range.start.line, edit.range.start.column),
23
+ end: offsetFor(text, edit.range.end?.line ?? edit.range.start.line, edit.range.end?.column ?? edit.range.start.column),
24
+ })).sort((a, b) => b.start - a.start);
25
+ const accepted = [];
26
+ for (const candidate of normalized) {
27
+ const overlaps = accepted.some((existing) => rangesOverlap(candidate.start, candidate.end, existing.start, existing.end));
28
+ if (overlaps) {
29
+ skipped += 1;
30
+ }
31
+ else {
32
+ accepted.push(candidate);
33
+ }
34
+ }
35
+ let output = text;
36
+ for (const { edit, start, end } of accepted) {
37
+ output = `${output.slice(0, start)}${edit.replacement}${output.slice(end)}`;
38
+ applied += 1;
39
+ }
40
+ if (output !== text) {
41
+ fs.writeFileSync(file, output);
42
+ }
43
+ }
44
+ return { applied, skipped };
45
+ }
46
+ function rangesOverlap(aStart, aEnd, bStart, bEnd) {
47
+ return aStart < bEnd && bStart < aEnd;
48
+ }
49
+ function offsetFor(text, line, column) {
50
+ let offset = 0;
51
+ let currentLine = 1;
52
+ while (currentLine < line && offset < text.length) {
53
+ const next = text.indexOf("\n", offset);
54
+ if (next === -1) {
55
+ return text.length;
56
+ }
57
+ offset = next + 1;
58
+ currentLine += 1;
59
+ }
60
+ return Math.min(offset + Math.max(column - 1, 0), text.length);
61
+ }
@@ -0,0 +1,2 @@
1
+ import type { LintOptions, LintResult } from "../types.js";
2
+ export declare function lintFiles(patterns: string[], options?: LintOptions): Promise<LintResult>;
@@ -0,0 +1,191 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import fg from "fast-glob";
4
+ import { parseDocument, resolveDocumentXrefs } from "../parsers/tolerant.js";
5
+ import { helpers } from "../rules/helpers.js";
6
+ import { getVersion } from "../version.js";
7
+ import { applyFixes } from "./fixes.js";
8
+ import { loadRules } from "./rules.js";
9
+ export async function lintFiles(patterns, options = {}) {
10
+ return lintFilesInternal(patterns, options, false);
11
+ }
12
+ async function lintFilesInternal(patterns, options, afterFix) {
13
+ const cwd = path.resolve(options.cwd ?? process.cwd());
14
+ const { config, rules } = await loadRules(options);
15
+ const files = await expandFiles(patterns, cwd, config);
16
+ const enabledRules = filterEnabledRules(rules, config);
17
+ const findings = [];
18
+ for (const file of files) {
19
+ const document = parseDocument(file);
20
+ mergeAsciidoctorBlocks(document, await collectParserBlocks(file));
21
+ mergeAsciidoctorReferenceTargets(document, await collectParserReferenceTargets(file));
22
+ resolveDocumentXrefs(document);
23
+ const parserDiagnostics = options.parserDiagnostics === false ? [] : await collectParserDiagnostics(file);
24
+ findings.push(...parserDiagnostics);
25
+ for (const rule of enabledRules) {
26
+ await rule.function({
27
+ file: document.file,
28
+ lines: document.lines,
29
+ document,
30
+ dependencies: document.dependencies,
31
+ parserDiagnostics,
32
+ config: config.rules?.[rule.id] ?? (rule.alias ? config.rules?.[rule.alias] : undefined),
33
+ version: getVersion(),
34
+ helpers,
35
+ }, (finding) => {
36
+ const configuredSeverity = getConfiguredSeverity(rule, config);
37
+ const fixHelper = finding.fixHelper ?? rule.docs?.fixHelper;
38
+ findings.push({
39
+ ...finding,
40
+ ruleId: finding.ruleId ?? rule.id,
41
+ alias: finding.alias ?? rule.alias,
42
+ severity: configuredSeverity ?? finding.severity,
43
+ fixHelper,
44
+ });
45
+ });
46
+ }
47
+ }
48
+ const result = { files, findings: sortFindings(findings) };
49
+ if (options.fix && !afterFix) {
50
+ applyFixes(result.findings, options.unsafeFixes ?? false);
51
+ return lintFilesInternal(patterns, { ...options, fix: false }, true);
52
+ }
53
+ if (options.outputDiagnosticsFile) {
54
+ writeDiagnosticsFile(result, options.outputDiagnosticsFile, cwd, patterns, options.configFile);
55
+ }
56
+ return result;
57
+ }
58
+ async function collectParserDiagnostics(file) {
59
+ const { collectAsciidoctorDiagnostics } = await import("../parsers/asciidoctor.js");
60
+ return collectAsciidoctorDiagnostics(file);
61
+ }
62
+ async function collectParserBlocks(file) {
63
+ const { collectAsciidoctorBlocks } = await import("../parsers/asciidoctor.js");
64
+ return collectAsciidoctorBlocks(file);
65
+ }
66
+ async function collectParserReferenceTargets(file) {
67
+ const { collectAsciidoctorReferenceTargets } = await import("../parsers/asciidoctor.js");
68
+ return collectAsciidoctorReferenceTargets(file);
69
+ }
70
+ function mergeAsciidoctorBlocks(document, blocks) {
71
+ if (!blocks.length) {
72
+ return;
73
+ }
74
+ const authoritativeTypes = new Set(blocks.map((block) => block.type));
75
+ const authoritativeFiles = new Set(blocks.map((block) => path.resolve(block.range.start.file)));
76
+ document.blocks = [
77
+ ...document.blocks.filter((block) => (!authoritativeTypes.has(block.type)
78
+ || !authoritativeFiles.has(path.resolve(block.range.start.file)))),
79
+ ...blocks,
80
+ ].sort((a, b) => (a.range.start.file.localeCompare(b.range.start.file)
81
+ || a.range.start.line - b.range.start.line
82
+ || a.range.start.column - b.range.start.column));
83
+ }
84
+ function mergeAsciidoctorReferenceTargets(document, targets) {
85
+ const byKey = new Map();
86
+ for (const target of document.referenceTargets) {
87
+ byKey.set(referenceTargetKey(target), target);
88
+ }
89
+ for (const target of targets) {
90
+ byKey.set(referenceTargetKey(target), target);
91
+ }
92
+ document.referenceTargets = [...byKey.values()].sort((a, b) => (a.file.localeCompare(b.file) || a.id.localeCompare(b.id)));
93
+ }
94
+ function referenceTargetKey(target) {
95
+ return `${path.resolve(target.file)}#${target.id}`;
96
+ }
97
+ async function expandFiles(patterns, cwd, config) {
98
+ const matches = await fg(patterns.length ? patterns : ["**/*.adoc"], {
99
+ cwd,
100
+ absolute: true,
101
+ onlyFiles: true,
102
+ followSymbolicLinks: false,
103
+ ignore: ["node_modules/**", "dist/**", ...(config.ignores ?? [])],
104
+ });
105
+ return [...new Set(matches)]
106
+ .filter((file) => !isIgnored(path.relative(cwd, file), config.ignores ?? []))
107
+ .sort();
108
+ }
109
+ function isIgnored(relativePath, ignores) {
110
+ const normalized = relativePath.split(path.sep).join("/");
111
+ return ignores.some((pattern) => matchesIgnorePattern(normalized, pattern));
112
+ }
113
+ function matchesIgnorePattern(file, pattern) {
114
+ const normalized = pattern.split(path.sep).join("/");
115
+ if (normalized.endsWith("/**")) {
116
+ return file.startsWith(normalized.slice(0, -3));
117
+ }
118
+ if (normalized.startsWith("**/")) {
119
+ return file.endsWith(normalized.slice(3));
120
+ }
121
+ if (normalized.includes("*")) {
122
+ const regex = new RegExp(`^${normalized.split("*").map(escapeRegex).join(".*")}$`);
123
+ return regex.test(file);
124
+ }
125
+ return file === normalized || file.startsWith(`${normalized}/`);
126
+ }
127
+ function escapeRegex(value) {
128
+ return value.replace(/[\\^$+?.()|[\]{}]/g, "\\$&");
129
+ }
130
+ function filterEnabledRules(rules, config) {
131
+ if (!config.rules) {
132
+ return rules;
133
+ }
134
+ return rules.filter((rule) => {
135
+ const byId = config.rules?.[rule.id];
136
+ const byAlias = rule.alias ? config.rules?.[rule.alias] : undefined;
137
+ const setting = byId ?? byAlias;
138
+ if (setting === false) {
139
+ return false;
140
+ }
141
+ if (typeof setting === "object" && setting.enabled === false) {
142
+ return false;
143
+ }
144
+ return true;
145
+ });
146
+ }
147
+ function getConfiguredSeverity(rule, config) {
148
+ const setting = config.rules?.[rule.id] ?? (rule.alias ? config.rules?.[rule.alias] : undefined);
149
+ if (!setting || typeof setting === "boolean") {
150
+ return undefined;
151
+ }
152
+ return setting.severity;
153
+ }
154
+ function sortFindings(findings) {
155
+ return findings.sort((a, b) => {
156
+ const file = a.range.start.file.localeCompare(b.range.start.file);
157
+ if (file !== 0) {
158
+ return file;
159
+ }
160
+ return a.range.start.line - b.range.start.line || a.range.start.column - b.range.start.column || a.ruleId.localeCompare(b.ruleId);
161
+ });
162
+ }
163
+ function writeDiagnosticsFile(result, outputFile, cwd, targets, configFile) {
164
+ const absolute = path.resolve(cwd, outputFile);
165
+ fs.mkdirSync(path.dirname(absolute), { recursive: true });
166
+ fs.writeFileSync(absolute, `${JSON.stringify({
167
+ version: 1,
168
+ source: "asciidoclint",
169
+ cwd,
170
+ generatedAt: new Date().toISOString(),
171
+ fingerprint: {
172
+ tool: {
173
+ name: "asciidoclint",
174
+ version: getVersion(),
175
+ },
176
+ command: {
177
+ targets,
178
+ configFile,
179
+ },
180
+ files: result.files.map((file) => {
181
+ const stat = fs.statSync(file);
182
+ return {
183
+ file,
184
+ mtimeMs: stat.mtimeMs,
185
+ size: stat.size,
186
+ };
187
+ }),
188
+ },
189
+ ...result,
190
+ }, null, 2)}\n`);
191
+ }
@@ -0,0 +1,33 @@
1
+ import type { Rule } from "../types.js";
2
+ export interface Config {
3
+ extends?: string | string[];
4
+ documents?: string[];
5
+ customRules?: string[];
6
+ ignores?: string[];
7
+ rules?: Record<string, RuleSetting>;
8
+ editor?: EditorConfig;
9
+ baseDir?: string;
10
+ }
11
+ export type RuleSetting = boolean | {
12
+ severity?: "error" | "warning" | "info";
13
+ enabled?: boolean;
14
+ };
15
+ export interface EditorConfig {
16
+ defaultScope?: "file" | "document" | "workspace";
17
+ lintOnSave?: boolean;
18
+ followSymlinks?: boolean;
19
+ importCliDiagnostics?: boolean;
20
+ }
21
+ export interface RuleLoadOptions {
22
+ configFile?: string;
23
+ customRules?: string[];
24
+ cwd?: string;
25
+ }
26
+ export declare function loadRules(options?: RuleLoadOptions): Promise<{
27
+ config: Config;
28
+ rules: Rule[];
29
+ }>;
30
+ export declare function ruleMetadata(rule: Rule): object;
31
+ export declare function loadConfig(configFile: string | undefined, cwd: string): Config;
32
+ export declare function loadCustomRules(references: string[], cwd: string): Promise<Rule[]>;
33
+ export declare function normalizeConfig(config: Config | undefined): Config;
@@ -0,0 +1,115 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import yaml from "js-yaml";
5
+ import { builtInRules } from "../rules/builtin.js";
6
+ import { validateRules } from "../rules/registry.js";
7
+ export async function loadRules(options = {}) {
8
+ const cwd = path.resolve(options.cwd ?? process.cwd());
9
+ const config = loadConfig(options.configFile, cwd);
10
+ const customRules = [
11
+ ...await loadCustomRules(config.customRules ?? [], config.baseDir ?? cwd),
12
+ ...await loadCustomRules(options.customRules ?? [], cwd),
13
+ ];
14
+ const rules = [...builtInRules, ...customRules];
15
+ validateRules(rules);
16
+ return { config, rules };
17
+ }
18
+ export function ruleMetadata(rule) {
19
+ return {
20
+ id: rule.id,
21
+ alias: rule.alias,
22
+ description: rule.description,
23
+ tags: rule.tags,
24
+ parser: rule.parser,
25
+ configSchema: rule.configSchema,
26
+ docs: rule.docs,
27
+ };
28
+ }
29
+ export function loadConfig(configFile, cwd) {
30
+ const candidates = configFile ? [configFile] : [".asciidoclint.yaml", ".asciidoclint.yml"];
31
+ for (const candidate of candidates) {
32
+ const absolute = path.resolve(cwd, candidate);
33
+ if (fs.existsSync(absolute)) {
34
+ return {
35
+ ...normalizeConfig(yaml.load(fs.readFileSync(absolute, "utf8"))),
36
+ baseDir: path.dirname(absolute),
37
+ };
38
+ }
39
+ }
40
+ return {};
41
+ }
42
+ export async function loadCustomRules(references, cwd) {
43
+ const rules = [];
44
+ for (const reference of references) {
45
+ const imported = await import(resolveImport(reference, cwd));
46
+ const exported = imported.default ?? imported.rules ?? imported.rule;
47
+ if (Array.isArray(exported)) {
48
+ rules.push(...exported);
49
+ }
50
+ else if (exported) {
51
+ rules.push(exported);
52
+ }
53
+ }
54
+ return rules;
55
+ }
56
+ export function normalizeConfig(config) {
57
+ const base = {};
58
+ for (const preset of asArray(config?.extends)) {
59
+ Object.assign(base, mergeConfig(base, presetConfig(preset)));
60
+ }
61
+ return mergeConfig(base, config ?? {});
62
+ }
63
+ function mergeConfig(base, override) {
64
+ return {
65
+ extends: override.extends ?? base.extends,
66
+ baseDir: override.baseDir ?? base.baseDir,
67
+ documents: [...(base.documents ?? []), ...(override.documents ?? [])],
68
+ customRules: [...(base.customRules ?? []), ...(override.customRules ?? [])],
69
+ ignores: [...(base.ignores ?? []), ...(override.ignores ?? [])],
70
+ editor: {
71
+ ...(base.editor ?? {}),
72
+ ...(override.editor ?? {}),
73
+ },
74
+ rules: {
75
+ ...(base.rules ?? {}),
76
+ ...(override.rules ?? {}),
77
+ },
78
+ };
79
+ }
80
+ function presetConfig(name) {
81
+ const rules = {};
82
+ const enableByTag = (tag) => {
83
+ for (const rule of builtInRules) {
84
+ rules[rule.id] = rule.tags.includes(tag);
85
+ }
86
+ };
87
+ switch (name) {
88
+ case "asciidoclint:all":
89
+ case "asciidoclint:recommended":
90
+ for (const rule of builtInRules) {
91
+ rules[rule.id] = true;
92
+ }
93
+ return { rules };
94
+ case "asciidoclint:core":
95
+ enableByTag("core");
96
+ return { rules };
97
+ case "asciidoclint:dependencies":
98
+ enableByTag("dependencies");
99
+ return { rules };
100
+ default:
101
+ throw new Error(`Unknown config preset: ${name}`);
102
+ }
103
+ }
104
+ function resolveImport(reference, cwd) {
105
+ if (reference.startsWith(".") || reference.startsWith("/") || /\.(ts|mts|cts|m?js|cjs)$/.test(reference)) {
106
+ return pathToFileURL(path.resolve(cwd, reference)).href;
107
+ }
108
+ return reference;
109
+ }
110
+ function asArray(value) {
111
+ if (!value) {
112
+ return [];
113
+ }
114
+ return Array.isArray(value) ? value : [value];
115
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};