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.
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/assets/README.md +12 -0
- package/assets/icon.svg +198 -0
- package/assets/logo.svg +203 -0
- package/dist/api/fixes.d.ts +6 -0
- package/dist/api/fixes.js +61 -0
- package/dist/api/lint.d.ts +2 -0
- package/dist/api/lint.js +191 -0
- package/dist/api/rules.d.ts +33 -0
- package/dist/api/rules.js +115 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +86 -0
- package/dist/cli/init-rule.d.ts +7 -0
- package/dist/cli/init-rule.js +74 -0
- package/dist/cli/install-skill.d.ts +10 -0
- package/dist/cli/install-skill.js +37 -0
- package/dist/formatters/json.d.ts +2 -0
- package/dist/formatters/json.js +30 -0
- package/dist/formatters/pretty.d.ts +2 -0
- package/dist/formatters/pretty.js +41 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/parsers/asciidoctor.d.ts +4 -0
- package/dist/parsers/asciidoctor.js +444 -0
- package/dist/parsers/tolerant.d.ts +4 -0
- package/dist/parsers/tolerant.js +528 -0
- package/dist/rules/AD001.d.ts +2 -0
- package/dist/rules/AD001.js +28 -0
- package/dist/rules/AD002.d.ts +2 -0
- package/dist/rules/AD002.js +30 -0
- package/dist/rules/AD003.d.ts +2 -0
- package/dist/rules/AD003.js +28 -0
- package/dist/rules/AD004.d.ts +2 -0
- package/dist/rules/AD004.js +58 -0
- package/dist/rules/AD005.d.ts +2 -0
- package/dist/rules/AD005.js +31 -0
- package/dist/rules/AD006.d.ts +2 -0
- package/dist/rules/AD006.js +53 -0
- package/dist/rules/AD007.d.ts +2 -0
- package/dist/rules/AD007.js +39 -0
- package/dist/rules/AD008.d.ts +2 -0
- package/dist/rules/AD008.js +88 -0
- package/dist/rules/AD010.d.ts +2 -0
- package/dist/rules/AD010.js +39 -0
- package/dist/rules/AD011.d.ts +2 -0
- package/dist/rules/AD011.js +31 -0
- package/dist/rules/AD012.d.ts +2 -0
- package/dist/rules/AD012.js +28 -0
- package/dist/rules/AD013.d.ts +2 -0
- package/dist/rules/AD013.js +43 -0
- package/dist/rules/AD016.d.ts +2 -0
- package/dist/rules/AD016.js +83 -0
- package/dist/rules/AD017.d.ts +2 -0
- package/dist/rules/AD017.js +53 -0
- package/dist/rules/AD019.d.ts +2 -0
- package/dist/rules/AD019.js +58 -0
- package/dist/rules/AD020.d.ts +2 -0
- package/dist/rules/AD020.js +40 -0
- package/dist/rules/AD022.d.ts +2 -0
- package/dist/rules/AD022.js +55 -0
- package/dist/rules/AD023.d.ts +2 -0
- package/dist/rules/AD023.js +59 -0
- package/dist/rules/AD024.d.ts +2 -0
- package/dist/rules/AD024.js +30 -0
- package/dist/rules/AD025.d.ts +2 -0
- package/dist/rules/AD025.js +32 -0
- package/dist/rules/AD026.d.ts +2 -0
- package/dist/rules/AD026.js +26 -0
- package/dist/rules/AD027.d.ts +2 -0
- package/dist/rules/AD027.js +31 -0
- package/dist/rules/AD028.d.ts +2 -0
- package/dist/rules/AD028.js +113 -0
- package/dist/rules/AD029.d.ts +2 -0
- package/dist/rules/AD029.js +46 -0
- package/dist/rules/AD030.d.ts +2 -0
- package/dist/rules/AD030.js +33 -0
- package/dist/rules/AD031.d.ts +2 -0
- package/dist/rules/AD031.js +66 -0
- package/dist/rules/AD032.d.ts +2 -0
- package/dist/rules/AD032.js +81 -0
- package/dist/rules/AD034.d.ts +2 -0
- package/dist/rules/AD034.js +50 -0
- package/dist/rules/AD035.d.ts +2 -0
- package/dist/rules/AD035.js +77 -0
- package/dist/rules/AD036.d.ts +2 -0
- package/dist/rules/AD036.js +34 -0
- package/dist/rules/AD037.d.ts +2 -0
- package/dist/rules/AD037.js +34 -0
- package/dist/rules/AD039.d.ts +2 -0
- package/dist/rules/AD039.js +58 -0
- package/dist/rules/AD040.d.ts +2 -0
- package/dist/rules/AD040.js +56 -0
- package/dist/rules/AD041.d.ts +2 -0
- package/dist/rules/AD041.js +66 -0
- package/dist/rules/AD042.d.ts +2 -0
- package/dist/rules/AD042.js +62 -0
- package/dist/rules/AD043.d.ts +2 -0
- package/dist/rules/AD043.js +30 -0
- package/dist/rules/AD044.d.ts +2 -0
- package/dist/rules/AD044.js +54 -0
- package/dist/rules/AD045.d.ts +2 -0
- package/dist/rules/AD045.js +66 -0
- package/dist/rules/builtin.d.ts +3 -0
- package/dist/rules/builtin.js +81 -0
- package/dist/rules/helpers.d.ts +2 -0
- package/dist/rules/helpers.js +11 -0
- package/dist/rules/registry.d.ts +3 -0
- package/dist/rules/registry.js +34 -0
- package/dist/rules/utils.d.ts +42 -0
- package/dist/rules/utils.js +274 -0
- package/dist/types.d.ts +166 -0
- package/dist/types.js +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/package.json +70 -0
- package/skills/asciidoclint/SKILL.md +84 -0
- package/skills/asciidoclint/references/ai-fix-policy.md +11 -0
- package/skills/asciidoclint/references/result-schema.md +22 -0
package/assets/logo.svg
ADDED
|
@@ -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,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
|
+
}
|
package/dist/api/lint.js
ADDED
|
@@ -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
|
+
}
|