asciidoclint 0.5.0 → 1.0.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 +67 -118
- package/dist/api/fixes.js +1 -1
- package/dist/api/lint.js +7 -1
- package/dist/api/rules.d.ts +17 -9
- package/dist/api/rules.js +116 -22
- package/dist/api/waivers.d.ts +2 -0
- package/dist/api/waivers.js +191 -0
- package/dist/cli/explain.d.ts +2 -0
- package/dist/cli/explain.js +43 -0
- package/dist/cli/index.js +59 -12
- package/dist/cli/init-rule.d.ts +2 -1
- package/dist/cli/init-rule.js +81 -11
- package/dist/cli/install-skill.d.ts +12 -0
- package/dist/cli/install-skill.js +42 -5
- package/dist/formatters/json.js +30 -17
- package/dist/formatters/pretty.js +9 -4
- package/dist/rules/ADW01.d.ts +1 -0
- package/dist/rules/ADW01.js +2 -0
- package/dist/rules/ADW02.d.ts +1 -0
- package/dist/rules/ADW02.js +2 -0
- package/dist/rules/ADW03.d.ts +1 -0
- package/dist/rules/ADW03.js +2 -0
- package/dist/rules/ADW04.d.ts +1 -0
- package/dist/rules/ADW04.js +2 -0
- package/dist/rules/ADW05.d.ts +1 -0
- package/dist/rules/ADW05.js +2 -0
- package/dist/rules/ADW06.d.ts +1 -0
- package/dist/rules/ADW06.js +2 -0
- package/dist/rules/ADW07.d.ts +1 -0
- package/dist/rules/ADW07.js +2 -0
- package/dist/rules/ADW08.d.ts +1 -0
- package/dist/rules/ADW08.js +2 -0
- package/dist/rules/builtin.js +16 -0
- package/dist/rules/waiverRule.d.ts +104 -0
- package/dist/rules/waiverRule.js +108 -0
- package/dist/types.d.ts +12 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +1296 -0
- package/docs/configuration.md +126 -0
- package/docs/custom-rules.md +162 -0
- package/docs/release-workflow.md +630 -0
- package/docs/rule-architecture.md +94 -0
- package/docs/rules/AD000.md +43 -0
- package/docs/rules/AD001.md +36 -0
- package/docs/rules/AD002.md +48 -0
- package/docs/rules/AD003.md +36 -0
- package/docs/rules/AD004.md +54 -0
- package/docs/rules/AD005.md +87 -0
- package/docs/rules/AD006.md +88 -0
- package/docs/rules/AD007.md +59 -0
- package/docs/rules/AD008.md +48 -0
- package/docs/rules/AD010.md +45 -0
- package/docs/rules/AD011.md +48 -0
- package/docs/rules/AD012.md +50 -0
- package/docs/rules/AD013.md +75 -0
- package/docs/rules/AD016.md +52 -0
- package/docs/rules/AD017.md +51 -0
- package/docs/rules/AD019.md +53 -0
- package/docs/rules/AD020.md +54 -0
- package/docs/rules/AD022.md +33 -0
- package/docs/rules/AD023.md +59 -0
- package/docs/rules/AD024.md +48 -0
- package/docs/rules/AD025.md +64 -0
- package/docs/rules/AD026.md +43 -0
- package/docs/rules/AD027.md +50 -0
- package/docs/rules/AD028.md +48 -0
- package/docs/rules/AD029.md +57 -0
- package/docs/rules/AD030.md +42 -0
- package/docs/rules/AD031.md +48 -0
- package/docs/rules/AD032.md +65 -0
- package/docs/rules/AD034.md +48 -0
- package/docs/rules/AD035.md +62 -0
- package/docs/rules/AD036.md +51 -0
- package/docs/rules/AD037.md +49 -0
- package/docs/rules/AD039.md +59 -0
- package/docs/rules/AD040.md +49 -0
- package/docs/rules/AD041.md +56 -0
- package/docs/rules/AD042.md +42 -0
- package/docs/rules/AD043.md +54 -0
- package/docs/rules/AD044.md +47 -0
- package/docs/rules/AD045.md +62 -0
- package/docs/rules/ADW01.md +31 -0
- package/docs/rules/ADW02.md +31 -0
- package/docs/rules/ADW03.md +31 -0
- package/docs/rules/ADW04.md +31 -0
- package/docs/rules/ADW05.md +32 -0
- package/docs/rules/ADW06.md +33 -0
- package/docs/rules/ADW07.md +34 -0
- package/docs/rules/ADW08.md +31 -0
- package/docs/rules/rule-necessity.md +82 -0
- package/docs/waiver.md +201 -0
- package/package.json +2 -1
- package/skills/asciidoclint/SKILL.md +27 -60
- package/skills/asciidoclint/references/agentic-fix.md +29 -0
- package/skills/asciidoclint/references/feedback.md +79 -0
- package/skills/asciidoclint/references/lint-summary.md +45 -0
- package/skills/asciidoclint/references/result-schema.md +4 -0
- package/skills/asciidoclint/references/rule-create.md +136 -0
- package/skills/asciidoclint/references/rule-review.md +68 -0
- package/skills/asciidoclint/references/waivers.md +42 -0
- package/skills/asciidoclint/references/ai-fix-policy.md +0 -11
package/README.md
CHANGED
|
@@ -1,45 +1,32 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<img src="assets/logo.svg" width="160" height="160" alt="asciidoclint logo">
|
|
3
3
|
</p>
|
|
4
|
-
<p align="center">
|
|
5
|
-
<sub><code>assets/logo.svg</code> and <code>assets/icon.svg</code> were created with <a href="https://inkscape.org/">Inkscape</a>. The <strong>lint</strong> label uses <a href="https://www.jetbrains.com/lp/mono/">JetBrains Mono</a> (SIL Open Font License).</sub>
|
|
6
|
-
</p>
|
|
7
4
|
|
|
8
5
|
# asciidoclint
|
|
9
6
|
|
|
10
7
|
`asciidoclint` is an AsciiDoc syntax, structure, and document-policy linter for
|
|
11
8
|
CLI, AI-agent, and editor workflows.
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
under the MIT License since June 1, 2026.
|
|
15
|
-
|
|
16
|
-
Design goals:
|
|
17
|
-
|
|
18
|
-
- Provide a library-first, typed, plugin-friendly rule model.
|
|
19
|
-
- Use Asciidoctor-backed diagnostics, source mapping, include awareness, and a
|
|
20
|
-
safe/unsafe fix model.
|
|
21
|
-
- Keep generic AsciiDoc syntax/structure rules separate from project or
|
|
22
|
-
organization policy rules.
|
|
23
|
-
|
|
24
|
-
Start with the architecture proposal:
|
|
10
|
+
## Install the npm package
|
|
25
11
|
|
|
26
|
-
|
|
12
|
+
```bash
|
|
13
|
+
npm install --save-dev asciidoclint
|
|
14
|
+
```
|
|
27
15
|
|
|
28
|
-
##
|
|
16
|
+
## Use the CLI
|
|
29
17
|
|
|
30
|
-
|
|
18
|
+
Run lint:
|
|
31
19
|
|
|
32
20
|
```bash
|
|
33
|
-
|
|
21
|
+
npx asciidoclint index.adoc
|
|
22
|
+
npx asciidoclint --format json index.adoc
|
|
34
23
|
```
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
Apply deterministic fixes:
|
|
37
26
|
|
|
38
27
|
```bash
|
|
39
|
-
npx asciidoclint
|
|
40
|
-
npx asciidoclint --
|
|
41
|
-
npx asciidoclint --fix docs/**/*.adoc
|
|
42
|
-
npx asciidoclint --fix --unsafe docs/**/*.adoc
|
|
28
|
+
npx asciidoclint --fix index.adoc
|
|
29
|
+
npx asciidoclint --fix --unsafe index.adoc
|
|
43
30
|
```
|
|
44
31
|
|
|
45
32
|
Inspect rules:
|
|
@@ -48,122 +35,80 @@ Inspect rules:
|
|
|
48
35
|
npx asciidoclint --list-rules
|
|
49
36
|
npx asciidoclint --explain AD001
|
|
50
37
|
npx asciidoclint --explain heading-level-progression
|
|
38
|
+
npx asciidoclint --explain AD001 --format json
|
|
51
39
|
```
|
|
52
40
|
|
|
53
|
-
|
|
41
|
+
Use a project config file when the same lint settings should be reused:
|
|
54
42
|
|
|
55
43
|
```yaml
|
|
44
|
+
# .asciidoclint/config.yaml
|
|
56
45
|
extends:
|
|
57
46
|
- asciidoclint:recommended
|
|
47
|
+
```
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
- build/**
|
|
49
|
+
Use a global config file for settings that should apply across projects:
|
|
61
50
|
|
|
51
|
+
```yaml
|
|
52
|
+
# ~/.asciidoclint/config.yaml
|
|
62
53
|
customRules:
|
|
63
|
-
-
|
|
64
|
-
- ./lint-rules/ORG002-section-policy.js
|
|
54
|
+
- "@example/asciidoclint-rules"
|
|
65
55
|
```
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
npx asciidoclint init-rule --pack my-org --id ORG001 --alias no-todo
|
|
71
|
-
```
|
|
57
|
+
See [docs/configuration.md](docs/configuration.md) for configuration fields and
|
|
58
|
+
merge order. See [docs/waiver.md](docs/waiver.md) for source waiver syntax and
|
|
59
|
+
reporting.
|
|
72
60
|
|
|
73
61
|
## Install the AI skill
|
|
74
62
|
|
|
75
|
-
The repository ships an `asciidoclint` skill
|
|
76
|
-
|
|
77
|
-
explicit unsafe fixes, and use reported `fixHelper` guidance for focused
|
|
78
|
-
AI-assisted repairs.
|
|
79
|
-
|
|
80
|
-
Install the skill directly from GitHub with the open skills CLI:
|
|
63
|
+
The repository ships an `asciidoclint` skill for AI agents. Install it from the
|
|
64
|
+
npm package:
|
|
81
65
|
|
|
82
66
|
```bash
|
|
83
|
-
npx
|
|
67
|
+
npx asciidoclint install-skill
|
|
84
68
|
```
|
|
85
69
|
|
|
86
|
-
|
|
87
|
-
shorter form installs the public `asciidoclint` skill too:
|
|
70
|
+
Or install it directly from GitHub with the open skills CLI:
|
|
88
71
|
|
|
89
72
|
```bash
|
|
90
|
-
npx skills add f33lgood/asciidoclint
|
|
73
|
+
npx skills add f33lgood/asciidoclint --skill asciidoclint -a codex -g
|
|
91
74
|
```
|
|
92
75
|
|
|
93
|
-
|
|
94
|
-
|
|
76
|
+
Remove the installed skill when you want to use `asciidoclint` without AI skill
|
|
77
|
+
assistance:
|
|
95
78
|
|
|
96
79
|
```bash
|
|
97
|
-
npx asciidoclint
|
|
80
|
+
npx asciidoclint uninstall-skill
|
|
98
81
|
```
|
|
99
82
|
|
|
100
|
-
|
|
83
|
+
The public skill exposes these user-facing workflows:
|
|
101
84
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
85
|
+
| Workflow | Purpose |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `lint-summary` | Run lint and summarize findings by severity, rule, file, waiver status, and fixability. |
|
|
88
|
+
| `agentic-fix` | Use lint guidance and local source context to repair findings that deterministic fixes cannot safely handle. |
|
|
89
|
+
| `waivers` | Add narrow source waivers and verify waiver syntax. |
|
|
90
|
+
| `rule-create` | Create project-local or shared custom rules. |
|
|
91
|
+
| `rule-review` | Review rule behavior, overlap, documentation, and tests. |
|
|
92
|
+
| `feedback` | Prepare a sanitized, paste-ready GitHub issue message. |
|
|
106
93
|
|
|
107
|
-
## VS Code
|
|
94
|
+
## VS Code / Open VSX-Compatible Extension
|
|
108
95
|
|
|
109
|
-
`asciidoclint` is also available as a VS Code/
|
|
110
|
-
|
|
111
|
-
including diagnostics mapped back to
|
|
96
|
+
`asciidoclint` is also available as a VS Code / Open VSX-compatible extension.
|
|
97
|
+
Editors compatible with VS Code's diagnostic model can use it to show lint
|
|
98
|
+
issues in the editor and Problems panel, including diagnostics mapped back to
|
|
99
|
+
included source files.
|
|
112
100
|
|
|
113
101
|
The extension can import CLI diagnostics written by:
|
|
114
102
|
|
|
115
103
|
```bash
|
|
116
104
|
npx asciidoclint --format json \
|
|
117
105
|
--output-diagnostics .asciidoclint/diagnostics.json \
|
|
118
|
-
|
|
106
|
+
index.adoc
|
|
119
107
|
```
|
|
120
108
|
|
|
121
109
|
See [packages/vscode-asciidoclint](packages/vscode-asciidoclint/README.md) for
|
|
122
110
|
extension commands and settings.
|
|
123
111
|
|
|
124
|
-
## Use this repository
|
|
125
|
-
|
|
126
|
-
Install dependencies:
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
npm install
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Run the check loop:
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
npm run check
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Run coverage only:
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
npm run test:coverage
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Coverage thresholds and the latest metrics are documented in
|
|
145
|
-
[docs/reports/report-coverage.md](docs/reports/report-coverage.md).
|
|
146
|
-
|
|
147
|
-
Run the CLI from source:
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
npx tsx src/cli/index.ts test/fixtures/api/structural_errors.adoc
|
|
151
|
-
npx tsx src/cli/index.ts --format json test/fixtures/api/structural_errors.adoc
|
|
152
|
-
npx tsx src/cli/index.ts --fix test/fixtures/api/structure_only.adoc
|
|
153
|
-
npx tsx src/cli/index.ts install-skill --dest ./tmp/skills --force
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Build and package the VS Code/Cursor extension:
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
npm run build:extension
|
|
160
|
-
npm test -w vscode-asciidoclint
|
|
161
|
-
npm run package -w vscode-asciidoclint
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Install the generated `.vsix` in Cursor with **Extensions: Install from VSIX**,
|
|
165
|
-
then run **asciidoclint: Lint Current File** or **asciidoclint: Lint Workspace**.
|
|
166
|
-
|
|
167
112
|
## Built-in Rules
|
|
168
113
|
|
|
169
114
|
| ID / Alias | Short description |
|
|
@@ -207,13 +152,19 @@ then run **asciidoclint: Lint Current File** or **asciidoclint: Lint Workspace**
|
|
|
207
152
|
| `AD043/section-title-start-left` | Section title syntax should start at the beginning of the line |
|
|
208
153
|
| `AD044/local-adoc-link` | Local AsciiDoc files should be referenced with xref, not link |
|
|
209
154
|
| `AD045/markdown-heading-mix` | Markdown-compatible headings should not be mixed with AsciiDoc headings |
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
155
|
+
| `ADW01/unknown-waiver-directive` | Waiver directive names should be known |
|
|
156
|
+
| `ADW02/missing-waiver-rule-list` | Waiver directives should include a rule list |
|
|
157
|
+
| `ADW03/malformed-waiver-rule-list` | Waiver rule lists should use comma-separated rule IDs |
|
|
158
|
+
| `ADW04/unknown-waiver-rule-id` | Waiver rule IDs should be defined rules |
|
|
159
|
+
| `ADW05/unpaired-waiver-enable-block` | Waiver enable-block directives should have a preceding disable-block |
|
|
160
|
+
| `ADW06/unpaired-waiver-disable-block` | Waiver disable-block directives should have a following enable-block |
|
|
161
|
+
| `ADW07/mismatched-waiver-block-rule-list` | Waiver block delimiters should use matching rule lists |
|
|
162
|
+
| `ADW08/waiver-targets-waiver-rule` | Source waivers should not target ADW waiver diagnostics |
|
|
213
163
|
|
|
214
164
|
## Tags
|
|
215
165
|
|
|
216
|
-
Tags group related rules and can be used to enable or disable classes of
|
|
166
|
+
Tags group related rules and can be used to enable or disable classes of normal
|
|
167
|
+
rules. `ADW##` waiver diagnostics use tags for discovery, but remain always on.
|
|
217
168
|
|
|
218
169
|
| Group | IDs |
|
|
219
170
|
|---|---|
|
|
@@ -232,6 +183,7 @@ Tags group related rules and can be used to enable or disable classes of rules.
|
|
|
232
183
|
| `lists` | `AD008`, `AD036` |
|
|
233
184
|
| `parser` | `AD000` |
|
|
234
185
|
| `table` | `AD004`, `AD010`, `AD017`, `AD030` |
|
|
186
|
+
| `waiver` | `ADW01`, `ADW02`, `ADW03`, `ADW04`, `ADW05`, `ADW06`, `ADW07`, `ADW08` |
|
|
235
187
|
| `whitespace` | `AD034` |
|
|
236
188
|
| `references` | `AD023` |
|
|
237
189
|
| `links` | `AD027`, `AD031`, `AD042`, `AD044` |
|
|
@@ -240,19 +192,16 @@ Tags group related rules and can be used to enable or disable classes of rules.
|
|
|
240
192
|
| `maintainability` | `AD045` |
|
|
241
193
|
| `xref` | `AD026`, `AD042`, `AD044` |
|
|
242
194
|
|
|
243
|
-
##
|
|
244
|
-
|
|
245
|
-
Built-in rule IDs use one reserved namespace:
|
|
246
|
-
|
|
247
|
-
- `AD###` - all built-in `asciidoclint` rules.
|
|
195
|
+
## Documentation
|
|
248
196
|
|
|
249
|
-
|
|
250
|
-
`dependencies`, `policy`, and `cleanup`, not through multiple built-in ID
|
|
251
|
-
prefixes. This keeps built-in IDs predictable as the rule set grows.
|
|
252
|
-
|
|
253
|
-
Company, product, or template-specific rules should not be built-ins. Use a
|
|
254
|
-
three-letter custom prefix such as `ORG`, `ABC`, or a team-owned namespace. The
|
|
255
|
-
registry rejects duplicate IDs and aliases across built-in and custom rules.
|
|
197
|
+
Start with the architecture proposal:
|
|
256
198
|
|
|
257
|
-
|
|
258
|
-
[
|
|
199
|
+
- [Architecture](docs/architecture.md)
|
|
200
|
+
- [Configuration](docs/configuration.md)
|
|
201
|
+
- [Rule architecture](docs/rule-architecture.md)
|
|
202
|
+
- [Waivers](docs/waiver.md)
|
|
203
|
+
- [Custom rules](docs/custom-rules.md)
|
|
204
|
+
|
|
205
|
+
Detailed per-rule docs live under [docs/rules](docs/rules/). The CLI exposes
|
|
206
|
+
the rule catalog through `--list-rules`, readable rule help through `--explain`,
|
|
207
|
+
and structured rule metadata through `--explain <rule> --format json`.
|
package/dist/api/fixes.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
export function applyFixes(findings, unsafeFixes) {
|
|
3
3
|
const allowed = unsafeFixes ? ["safe", "unsafe"] : ["safe"];
|
|
4
4
|
const edits = findings.flatMap((finding) => {
|
|
5
|
-
if (!finding.fix || !allowed.includes(finding.fix.applicability)) {
|
|
5
|
+
if (finding.waived || !finding.fix || !allowed.includes(finding.fix.applicability)) {
|
|
6
6
|
return [];
|
|
7
7
|
}
|
|
8
8
|
return finding.fix.edits;
|
package/dist/api/lint.js
CHANGED
|
@@ -6,6 +6,7 @@ import { helpers } from "../rules/helpers.js";
|
|
|
6
6
|
import { getVersion } from "../version.js";
|
|
7
7
|
import { applyFixes } from "./fixes.js";
|
|
8
8
|
import { loadRules } from "./rules.js";
|
|
9
|
+
import { applyWaivers } from "./waivers.js";
|
|
9
10
|
export async function lintFiles(patterns, options = {}) {
|
|
10
11
|
return lintFilesInternal(patterns, options, false);
|
|
11
12
|
}
|
|
@@ -14,9 +15,14 @@ async function lintFilesInternal(patterns, options, afterFix) {
|
|
|
14
15
|
const { config, rules } = await loadRules(options);
|
|
15
16
|
const files = await expandFiles(patterns, cwd, config);
|
|
16
17
|
const enabledRules = filterEnabledRules(rules, config);
|
|
18
|
+
const knownRuleIds = new Set(["AD000", ...rules.map((rule) => rule.id)]);
|
|
17
19
|
const findings = [];
|
|
20
|
+
const parsedFiles = new Map();
|
|
18
21
|
for (const file of files) {
|
|
19
22
|
const document = parseDocument(file);
|
|
23
|
+
for (const parsedFile of document.files) {
|
|
24
|
+
parsedFiles.set(path.resolve(parsedFile.file), parsedFile);
|
|
25
|
+
}
|
|
20
26
|
mergeAsciidoctorBlocks(document, await collectParserBlocks(file));
|
|
21
27
|
mergeAsciidoctorReferenceTargets(document, await collectParserReferenceTargets(file));
|
|
22
28
|
resolveDocumentXrefs(document);
|
|
@@ -45,7 +51,7 @@ async function lintFilesInternal(patterns, options, afterFix) {
|
|
|
45
51
|
});
|
|
46
52
|
}
|
|
47
53
|
}
|
|
48
|
-
const result = { files, findings: sortFindings(findings) };
|
|
54
|
+
const result = { files, findings: sortFindings(applyWaivers(findings, [...parsedFiles.values()], knownRuleIds)) };
|
|
49
55
|
if (options.fix && !afterFix) {
|
|
50
56
|
applyFixes(result.findings, options.unsafeFixes ?? false);
|
|
51
57
|
return lintFilesInternal(patterns, { ...options, fix: false }, true);
|
package/dist/api/rules.d.ts
CHANGED
|
@@ -5,29 +5,37 @@ export interface Config {
|
|
|
5
5
|
customRules?: string[];
|
|
6
6
|
ignores?: string[];
|
|
7
7
|
rules?: Record<string, RuleSetting>;
|
|
8
|
-
editor?: EditorConfig;
|
|
9
|
-
baseDir?: string;
|
|
10
8
|
}
|
|
11
9
|
export type RuleSetting = boolean | {
|
|
12
10
|
severity?: "error" | "warning" | "info";
|
|
13
11
|
enabled?: boolean;
|
|
14
12
|
};
|
|
15
|
-
export interface EditorConfig {
|
|
16
|
-
defaultScope?: "file" | "document" | "workspace";
|
|
17
|
-
lintOnSave?: boolean;
|
|
18
|
-
followSymlinks?: boolean;
|
|
19
|
-
importCliDiagnostics?: boolean;
|
|
20
|
-
}
|
|
21
13
|
export interface RuleLoadOptions {
|
|
22
14
|
configFile?: string;
|
|
23
15
|
customRules?: string[];
|
|
24
16
|
cwd?: string;
|
|
17
|
+
homeDir?: string;
|
|
18
|
+
noGlobalConfig?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ConfigSource {
|
|
21
|
+
kind: "global" | "project" | "explicit";
|
|
22
|
+
file: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ConfigLoadOptions {
|
|
25
|
+
configFile?: string;
|
|
26
|
+
cwd?: string;
|
|
27
|
+
homeDir?: string;
|
|
28
|
+
noGlobalConfig?: boolean;
|
|
25
29
|
}
|
|
26
30
|
export declare function loadRules(options?: RuleLoadOptions): Promise<{
|
|
27
31
|
config: Config;
|
|
28
32
|
rules: Rule[];
|
|
29
33
|
}>;
|
|
30
34
|
export declare function ruleMetadata(rule: Rule): object;
|
|
31
|
-
export declare function loadConfig(configFile: string | undefined, cwd: string): Config;
|
|
35
|
+
export declare function loadConfig(configFile: string | undefined, cwd: string, options?: ConfigLoadOptions): Config;
|
|
36
|
+
export declare function loadConfigDetails(options?: ConfigLoadOptions): {
|
|
37
|
+
config: Config;
|
|
38
|
+
sources: ConfigSource[];
|
|
39
|
+
};
|
|
32
40
|
export declare function loadCustomRules(references: string[], cwd: string): Promise<Rule[]>;
|
|
33
41
|
export declare function normalizeConfig(config: Config | undefined): Config;
|
package/dist/api/rules.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { pathToFileURL } from "node:url";
|
|
4
5
|
import yaml from "js-yaml";
|
|
@@ -6,9 +7,9 @@ import { builtInRules } from "../rules/builtin.js";
|
|
|
6
7
|
import { validateRules } from "../rules/registry.js";
|
|
7
8
|
export async function loadRules(options = {}) {
|
|
8
9
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
9
|
-
const config = loadConfig(options.configFile, cwd);
|
|
10
|
+
const config = loadConfig(options.configFile, cwd, options);
|
|
10
11
|
const customRules = [
|
|
11
|
-
...await loadCustomRules(config.customRules ?? [],
|
|
12
|
+
...await loadCustomRules(config.customRules ?? [], cwd),
|
|
12
13
|
...await loadCustomRules(options.customRules ?? [], cwd),
|
|
13
14
|
];
|
|
14
15
|
const rules = [...builtInRules, ...customRules];
|
|
@@ -26,18 +27,19 @@ export function ruleMetadata(rule) {
|
|
|
26
27
|
docs: rule.docs,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
|
-
export function loadConfig(configFile, cwd) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
30
|
+
export function loadConfig(configFile, cwd, options = {}) {
|
|
31
|
+
return loadConfigDetails({ ...options, configFile, cwd }).config;
|
|
32
|
+
}
|
|
33
|
+
export function loadConfigDetails(options = {}) {
|
|
34
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
35
|
+
const sources = configSources({
|
|
36
|
+
configFile: options.configFile,
|
|
37
|
+
cwd,
|
|
38
|
+
homeDir: options.homeDir,
|
|
39
|
+
noGlobalConfig: options.noGlobalConfig,
|
|
40
|
+
});
|
|
41
|
+
const config = sources.reduce((base, source) => (mergeConfig(base, configFromFile(source.file))), {});
|
|
42
|
+
return { config, sources };
|
|
41
43
|
}
|
|
42
44
|
export async function loadCustomRules(references, cwd) {
|
|
43
45
|
const rules = [];
|
|
@@ -63,20 +65,71 @@ export function normalizeConfig(config) {
|
|
|
63
65
|
function mergeConfig(base, override) {
|
|
64
66
|
return {
|
|
65
67
|
extends: override.extends ?? base.extends,
|
|
66
|
-
baseDir: override.baseDir ?? base.baseDir,
|
|
67
68
|
documents: [...(base.documents ?? []), ...(override.documents ?? [])],
|
|
68
69
|
customRules: [...(base.customRules ?? []), ...(override.customRules ?? [])],
|
|
69
70
|
ignores: [...(base.ignores ?? []), ...(override.ignores ?? [])],
|
|
70
|
-
editor: {
|
|
71
|
-
...(base.editor ?? {}),
|
|
72
|
-
...(override.editor ?? {}),
|
|
73
|
-
},
|
|
74
71
|
rules: {
|
|
75
72
|
...(base.rules ?? {}),
|
|
76
73
|
...(override.rules ?? {}),
|
|
77
74
|
},
|
|
78
75
|
};
|
|
79
76
|
}
|
|
77
|
+
function configSources(options) {
|
|
78
|
+
const sources = [];
|
|
79
|
+
if (!options.noGlobalConfig) {
|
|
80
|
+
const global = path.join(path.resolve(options.homeDir ?? os.homedir()), ".asciidoclint", "config.yaml");
|
|
81
|
+
if (fs.existsSync(global)) {
|
|
82
|
+
sources.push({ kind: "global", file: global });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (options.configFile) {
|
|
86
|
+
const explicit = path.resolve(options.cwd, options.configFile);
|
|
87
|
+
if (fs.existsSync(explicit)) {
|
|
88
|
+
sources.push({ kind: "explicit", file: explicit });
|
|
89
|
+
}
|
|
90
|
+
return sources;
|
|
91
|
+
}
|
|
92
|
+
const project = findProjectConfig(options.cwd);
|
|
93
|
+
if (project) {
|
|
94
|
+
sources.push({ kind: "project", file: project });
|
|
95
|
+
}
|
|
96
|
+
return sources;
|
|
97
|
+
}
|
|
98
|
+
function findProjectConfig(cwd) {
|
|
99
|
+
let directory = path.resolve(cwd);
|
|
100
|
+
while (true) {
|
|
101
|
+
const candidate = path.join(directory, ".asciidoclint", "config.yaml");
|
|
102
|
+
if (fs.existsSync(candidate)) {
|
|
103
|
+
return candidate;
|
|
104
|
+
}
|
|
105
|
+
const parent = path.dirname(directory);
|
|
106
|
+
if (parent === directory) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
directory = parent;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function configFromFile(file) {
|
|
113
|
+
const baseDir = configReferenceBaseDir(file);
|
|
114
|
+
const loaded = normalizeConfig(yaml.load(fs.readFileSync(file, "utf8")));
|
|
115
|
+
return {
|
|
116
|
+
...loaded,
|
|
117
|
+
customRules: loaded.customRules?.map((reference) => resolveConfigReference(reference, baseDir)),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function configReferenceBaseDir(file) {
|
|
121
|
+
const directory = path.dirname(file);
|
|
122
|
+
if (path.basename(file) === "config.yaml" && path.basename(directory) === ".asciidoclint") {
|
|
123
|
+
return path.dirname(directory);
|
|
124
|
+
}
|
|
125
|
+
return directory;
|
|
126
|
+
}
|
|
127
|
+
function resolveConfigReference(reference, baseDir) {
|
|
128
|
+
if (reference.startsWith(".") || reference.startsWith("/")) {
|
|
129
|
+
return path.resolve(baseDir, reference);
|
|
130
|
+
}
|
|
131
|
+
return reference;
|
|
132
|
+
}
|
|
80
133
|
function presetConfig(name) {
|
|
81
134
|
const rules = {};
|
|
82
135
|
const enableByTag = (tag) => {
|
|
@@ -102,10 +155,51 @@ function presetConfig(name) {
|
|
|
102
155
|
}
|
|
103
156
|
}
|
|
104
157
|
function resolveImport(reference, cwd) {
|
|
105
|
-
if (
|
|
106
|
-
return
|
|
158
|
+
if (!isLocalReference(reference)) {
|
|
159
|
+
return reference;
|
|
107
160
|
}
|
|
108
|
-
|
|
161
|
+
const absolute = path.resolve(cwd, reference);
|
|
162
|
+
if (fs.existsSync(absolute) && fs.statSync(absolute).isDirectory()) {
|
|
163
|
+
return pathToFileURL(resolveRulePackageEntry(absolute)).href;
|
|
164
|
+
}
|
|
165
|
+
return pathToFileURL(absolute).href;
|
|
166
|
+
}
|
|
167
|
+
function isLocalReference(reference) {
|
|
168
|
+
return reference.startsWith(".") || reference.startsWith("/") || /\.(ts|mts|cts|m?js|cjs)$/.test(reference);
|
|
169
|
+
}
|
|
170
|
+
function resolveRulePackageEntry(directory) {
|
|
171
|
+
const packageJson = path.join(directory, "package.json");
|
|
172
|
+
const packageEntry = fs.existsSync(packageJson) ? packageEntryFromJson(packageJson) : undefined;
|
|
173
|
+
const candidates = [
|
|
174
|
+
packageEntry && path.resolve(directory, packageEntry),
|
|
175
|
+
path.join(directory, "dist", "index.js"),
|
|
176
|
+
path.join(directory, "dist", "index.mjs"),
|
|
177
|
+
path.join(directory, "src", "index.js"),
|
|
178
|
+
path.join(directory, "src", "index.mjs"),
|
|
179
|
+
path.join(directory, "src", "index.ts"),
|
|
180
|
+
path.join(directory, "src", "index.mts"),
|
|
181
|
+
path.join(directory, "index.js"),
|
|
182
|
+
path.join(directory, "index.mjs"),
|
|
183
|
+
path.join(directory, "index.ts"),
|
|
184
|
+
].filter((candidate) => Boolean(candidate));
|
|
185
|
+
const entry = candidates.find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile());
|
|
186
|
+
if (!entry) {
|
|
187
|
+
throw new Error(`Custom rule package has no loadable entry: ${directory}`);
|
|
188
|
+
}
|
|
189
|
+
return entry;
|
|
190
|
+
}
|
|
191
|
+
function packageEntryFromJson(file) {
|
|
192
|
+
const packageJson = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
193
|
+
if (typeof packageJson.exports === "string") {
|
|
194
|
+
return packageJson.exports;
|
|
195
|
+
}
|
|
196
|
+
if (packageJson.exports && typeof packageJson.exports["."] === "string") {
|
|
197
|
+
return packageJson.exports["."];
|
|
198
|
+
}
|
|
199
|
+
if (packageJson.exports && typeof packageJson.exports["."] === "object") {
|
|
200
|
+
return packageJson.exports["."].import ?? packageJson.exports["."].default;
|
|
201
|
+
}
|
|
202
|
+
return packageJson.module ?? packageJson.main;
|
|
109
203
|
}
|
|
110
204
|
function asArray(value) {
|
|
111
205
|
if (!value) {
|