asciidoclint 0.5.0 → 1.1.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 +95 -127
- package/dist/api/fixes.js +1 -1
- package/dist/api/lint.js +48 -5
- 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/parsers/asciidoctor.d.ts +2 -1
- package/dist/parsers/asciidoctor.js +150 -1
- package/dist/parsers/tolerant.js +29 -5
- package/dist/rules/AD004.js +99 -0
- package/dist/rules/AD009.d.ts +2 -0
- package/dist/rules/AD009.js +80 -0
- package/dist/rules/AD010.js +17 -3
- package/dist/rules/AD011.js +20 -5
- package/dist/rules/AD012.js +14 -2
- package/dist/rules/AD013.js +2 -2
- package/dist/rules/AD016.js +16 -10
- package/dist/rules/AD017.js +4 -4
- package/dist/rules/AD020.js +41 -20
- package/dist/rules/AD025.js +2 -2
- package/dist/rules/AD028.js +77 -10
- package/dist/rules/AD029.js +2 -2
- package/dist/rules/AD039.js +2 -2
- package/dist/rules/AD046.d.ts +2 -0
- package/dist/rules/AD046.js +91 -0
- package/dist/rules/AD047.d.ts +2 -0
- package/dist/rules/AD047.js +47 -0
- package/dist/rules/AD048.d.ts +2 -0
- package/dist/rules/AD048.js +37 -0
- package/dist/rules/AD049.d.ts +2 -0
- package/dist/rules/AD049.js +46 -0
- package/dist/rules/AD050.d.ts +2 -0
- package/dist/rules/AD050.js +46 -0
- package/dist/rules/AD051.d.ts +2 -0
- package/dist/rules/AD051.js +52 -0
- package/dist/rules/AD052.d.ts +2 -0
- package/dist/rules/AD052.js +46 -0
- package/dist/rules/AD053.d.ts +2 -0
- package/dist/rules/AD053.js +46 -0
- package/dist/rules/AD054.d.ts +2 -0
- package/dist/rules/AD054.js +46 -0
- package/dist/rules/AD055.d.ts +2 -0
- package/dist/rules/AD055.js +41 -0
- package/dist/rules/AD056.d.ts +2 -0
- package/dist/rules/AD056.js +49 -0
- package/dist/rules/AD057.d.ts +2 -0
- package/dist/rules/AD057.js +79 -0
- package/dist/rules/AD058.d.ts +2 -0
- package/dist/rules/AD058.js +33 -0
- package/dist/rules/AD059.d.ts +2 -0
- package/dist/rules/AD059.js +99 -0
- 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 +46 -0
- package/dist/rules/specialSections.d.ts +13 -0
- package/dist/rules/specialSections.js +54 -0
- package/dist/rules/waiverRule.d.ts +104 -0
- package/dist/rules/waiverRule.js +108 -0
- package/dist/types.d.ts +17 -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/AD009.md +59 -0
- package/docs/rules/AD010.md +54 -0
- package/docs/rules/AD011.md +56 -0
- package/docs/rules/AD012.md +61 -0
- package/docs/rules/AD013.md +75 -0
- package/docs/rules/AD016.md +53 -0
- package/docs/rules/AD017.md +52 -0
- package/docs/rules/AD019.md +53 -0
- package/docs/rules/AD020.md +100 -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 +53 -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/AD046.md +118 -0
- package/docs/rules/AD047.md +46 -0
- package/docs/rules/AD048.md +43 -0
- package/docs/rules/AD049.md +43 -0
- package/docs/rules/AD050.md +41 -0
- package/docs/rules/AD051.md +49 -0
- package/docs/rules/AD052.md +39 -0
- package/docs/rules/AD053.md +41 -0
- package/docs/rules/AD054.md +40 -0
- package/docs/rules/AD055.md +60 -0
- package/docs/rules/AD056.md +36 -0
- package/docs/rules/AD057.md +44 -0
- package/docs/rules/AD058.md +28 -0
- package/docs/rules/AD059.md +68 -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 +97 -0
- package/docs/waiver.md +201 -0
- package/package.json +2 -1
- package/skills/asciidoclint/SKILL.md +52 -66
- package/skills/asciidoclint/references/agentic-fix.md +29 -0
- package/skills/asciidoclint/references/feedback.md +82 -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 +147 -0
- package/skills/asciidoclint/references/rule-review.md +75 -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 |
|
|
@@ -177,6 +122,7 @@ then run **asciidoclint: Lint Current File** or **asciidoclint: Lint Workspace**
|
|
|
177
122
|
| `AD006/included-document-title` | Included AsciiDoc files should not introduce a level-0 title without level offset |
|
|
178
123
|
| `AD007/heading-depth-limit` | Section headings should not exceed Asciidoctor's supported depth |
|
|
179
124
|
| `AD008/blank-before-list` | Lists should be separated from preceding paragraph text |
|
|
125
|
+
| `AD009/blank-after-list` | Lists should be separated from following structural blocks |
|
|
180
126
|
| `AD010/table-title` | Table blocks should have a title |
|
|
181
127
|
| `AD011/image-title` | Block images should have a title |
|
|
182
128
|
| `AD012/diagram-title` | Diagram blocks should have a title |
|
|
@@ -184,14 +130,14 @@ then run **asciidoclint: Lint Current File** or **asciidoclint: Lint Workspace**
|
|
|
184
130
|
| `AD016/malformed-figure-caption` | Figure captions should use AsciiDoc title syntax |
|
|
185
131
|
| `AD017/malformed-table-caption` | Table captions should use AsciiDoc title syntax |
|
|
186
132
|
| `AD019/content-after-include` | Text should not be attached directly after include directives |
|
|
187
|
-
| `AD020/appendix-
|
|
133
|
+
| `AD020/appendix-placement` | Appendix markers should apply to documented appendix section levels |
|
|
188
134
|
| `AD022/circular-include` | Include trees must not contain cycles |
|
|
189
135
|
| `AD023/empty-section` | Sections should contain body content or child sections |
|
|
190
136
|
| `AD024/missing-include` | Include targets should exist after attribute substitution |
|
|
191
137
|
| `AD025/missing-image` | Image targets should exist after attribute substitution |
|
|
192
138
|
| `AD026/missing-xref` | Cross-reference targets should resolve to an anchor or file |
|
|
193
139
|
| `AD027/missing-local-link` | Local link targets should resolve to existing files |
|
|
194
|
-
| `AD028/image-alt-text` | Images should
|
|
140
|
+
| `AD028/image-alt-text` | Images should provide meaningful alt text |
|
|
195
141
|
| `AD029/markdown-link-image-residue` | Markdown link and image residue should not render as text |
|
|
196
142
|
| `AD030/markdown-table-residue` | Markdown pipe table residue should not render as text |
|
|
197
143
|
| `AD031/no-nested-link-text` | Link text should not contain nested links or cross references |
|
|
@@ -207,52 +153,74 @@ then run **asciidoclint: Lint Current File** or **asciidoclint: Lint Workspace**
|
|
|
207
153
|
| `AD043/section-title-start-left` | Section title syntax should start at the beginning of the line |
|
|
208
154
|
| `AD044/local-adoc-link` | Local AsciiDoc files should be referenced with xref, not link |
|
|
209
155
|
| `AD045/markdown-heading-mix` | Markdown-compatible headings should not be mixed with AsciiDoc headings |
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
156
|
+
| `AD046/preface-placement` | Preface sections should match documented book placement |
|
|
157
|
+
| `AD047/abstract-placement` | Abstract sections should match documented article placement |
|
|
158
|
+
| `AD048/bibliography-placement` | Bibliography markers should apply to documented bibliography sections |
|
|
159
|
+
| `AD049/glossary-placement` | Glossary markers should apply to documented glossary section levels |
|
|
160
|
+
| `AD050/index-placement` | Index markers should apply to documented index section levels |
|
|
161
|
+
| `AD051/partintro-placement` | Part introduction markers should apply to the introductory block of a book part |
|
|
162
|
+
| `AD052/acknowledgments-placement` | Acknowledgments markers should apply to documented book section levels |
|
|
163
|
+
| `AD053/dedication-placement` | Dedication markers should apply to documented book section levels |
|
|
164
|
+
| `AD054/colophon-placement` | Colophon markers should apply to documented book section levels |
|
|
165
|
+
| `AD055/docx-anchor-caption-residue` | DOCX anchor caption residue should be converted to AsciiDoc titles |
|
|
166
|
+
| `AD056/dangling-blank-list-continuation` | Blank list continuations should not capture following content |
|
|
167
|
+
| `AD057/semantic-anchor-target` | Semantic anchors should attach to the matching block type |
|
|
168
|
+
| `AD058/docx-bookmark-hash-residue` | DOCX bookmark/hash residue should be cleaned up |
|
|
169
|
+
| `AD059/docx-nested-table-structure` | DOCX-converted nested tables should use valid nested table separators and shallow nesting |
|
|
170
|
+
| `ADW01/unknown-waiver-directive` | Waiver directive names should be known |
|
|
171
|
+
| `ADW02/missing-waiver-rule-list` | Waiver directives should include a rule list |
|
|
172
|
+
| `ADW03/malformed-waiver-rule-list` | Waiver rule lists should use comma-separated rule IDs |
|
|
173
|
+
| `ADW04/unknown-waiver-rule-id` | Waiver rule IDs should be defined rules |
|
|
174
|
+
| `ADW05/unpaired-waiver-enable-block` | Waiver enable-block directives should have a preceding disable-block |
|
|
175
|
+
| `ADW06/unpaired-waiver-disable-block` | Waiver disable-block directives should have a following enable-block |
|
|
176
|
+
| `ADW07/mismatched-waiver-block-rule-list` | Waiver block delimiters should use matching rule lists |
|
|
177
|
+
| `ADW08/waiver-targets-waiver-rule` | Source waivers should not target ADW waiver diagnostics |
|
|
213
178
|
|
|
214
179
|
## Tags
|
|
215
180
|
|
|
216
|
-
Tags group related rules and can be used to enable or disable classes of
|
|
181
|
+
Tags group related rules and can be used to enable or disable classes of normal
|
|
182
|
+
rules. `ADW##` waiver diagnostics use tags for discovery, but remain always on.
|
|
217
183
|
|
|
218
184
|
| Group | IDs |
|
|
219
185
|
|---|---|
|
|
220
|
-
| `
|
|
186
|
+
| `anchor` | `AD057` |
|
|
187
|
+
| `blank_lines` | `AD008`, `AD009` |
|
|
221
188
|
| `blocks` | `AD003`, `AD032`, `AD035` |
|
|
222
189
|
| `accessibility` | `AD028`, `AD042` |
|
|
223
|
-
| `cleanup` | `AD032`, `AD034`, `AD035`, `AD036`, `AD037`, `AD039`, `AD040`, `AD041` |
|
|
224
|
-
| `conversion` | `AD029`, `AD030`, `AD031`, `
|
|
190
|
+
| `cleanup` | `AD032`, `AD034`, `AD035`, `AD036`, `AD037`, `AD039`, `AD040`, `AD041`, `AD055`, `AD058`, `AD059` |
|
|
191
|
+
| `conversion` | `AD029`, `AD030`, `AD031`, `AD037`, `AD039`, `AD040`, `AD055`, `AD056`, `AD058`, `AD059` |
|
|
192
|
+
| `docx` | `AD016`, `AD017`, `AD028`, `AD055`, `AD056`, `AD058`, `AD059` |
|
|
225
193
|
| `dependencies` | `AD024`, `AD025`, `AD026`, `AD027` |
|
|
226
194
|
| `diagram` | `AD012` |
|
|
227
195
|
| `format` | `AD041` |
|
|
228
196
|
| `headings` | `AD001`, `AD002`, `AD005`, `AD006`, `AD007`, `AD043`, `AD045` |
|
|
229
|
-
| `image` | `AD011`, `AD013`, `AD016`, `AD025`, `AD028` |
|
|
197
|
+
| `image` | `AD011`, `AD013`, `AD016`, `AD025`, `AD028`, `AD057` |
|
|
230
198
|
| `include` | `AD006`, `AD019`, `AD022`, `AD024` |
|
|
231
199
|
| `inline` | `AD041` |
|
|
232
|
-
| `
|
|
200
|
+
| `list` | `AD056` |
|
|
201
|
+
| `lists` | `AD008`, `AD009`, `AD036` |
|
|
202
|
+
| `pandoc` | `AD059` |
|
|
233
203
|
| `parser` | `AD000` |
|
|
234
|
-
| `table` | `AD004`, `AD010`, `AD017`, `AD030` |
|
|
204
|
+
| `table` | `AD004`, `AD010`, `AD017`, `AD030`, `AD057`, `AD059` |
|
|
205
|
+
| `waiver` | `ADW01`, `ADW02`, `ADW03`, `ADW04`, `ADW05`, `ADW06`, `ADW07`, `ADW08` |
|
|
235
206
|
| `whitespace` | `AD034` |
|
|
236
207
|
| `references` | `AD023` |
|
|
237
208
|
| `links` | `AD027`, `AD031`, `AD042`, `AD044` |
|
|
238
|
-
| `structure` | `AD043` |
|
|
209
|
+
| `structure` | `AD009`, `AD019`, `AD020`, `AD022`, `AD023`, `AD043`, `AD046`, `AD047`, `AD048`, `AD049`, `AD050`, `AD051`, `AD052`, `AD053`, `AD054`, `AD056`, `AD057` |
|
|
239
210
|
| `markdown-compatibility` | `AD045` |
|
|
240
211
|
| `maintainability` | `AD045` |
|
|
241
212
|
| `xref` | `AD026`, `AD042`, `AD044` |
|
|
242
213
|
|
|
243
|
-
##
|
|
244
|
-
|
|
245
|
-
Built-in rule IDs use one reserved namespace:
|
|
246
|
-
|
|
247
|
-
- `AD###` - all built-in `asciidoclint` rules.
|
|
214
|
+
## Documentation
|
|
248
215
|
|
|
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.
|
|
216
|
+
Start with the architecture proposal:
|
|
256
217
|
|
|
257
|
-
|
|
258
|
-
[
|
|
218
|
+
- [Architecture](docs/architecture.md)
|
|
219
|
+
- [Configuration](docs/configuration.md)
|
|
220
|
+
- [Rule architecture](docs/rule-architecture.md)
|
|
221
|
+
- [Waivers](docs/waiver.md)
|
|
222
|
+
- [Custom rules](docs/custom-rules.md)
|
|
223
|
+
|
|
224
|
+
Detailed per-rule docs live under [docs/rules](docs/rules/). The CLI exposes
|
|
225
|
+
the rule catalog through `--list-rules`, readable rule help through `--explain`,
|
|
226
|
+
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,15 @@ 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
|
+
}
|
|
26
|
+
mergeAsciidoctorSections(document, await collectParserSections(file));
|
|
20
27
|
mergeAsciidoctorBlocks(document, await collectParserBlocks(file));
|
|
21
28
|
mergeAsciidoctorReferenceTargets(document, await collectParserReferenceTargets(file));
|
|
22
29
|
resolveDocumentXrefs(document);
|
|
@@ -45,7 +52,7 @@ async function lintFilesInternal(patterns, options, afterFix) {
|
|
|
45
52
|
});
|
|
46
53
|
}
|
|
47
54
|
}
|
|
48
|
-
const result = { files, findings: sortFindings(findings) };
|
|
55
|
+
const result = { files, findings: sortFindings(applyWaivers(findings, [...parsedFiles.values()], knownRuleIds)) };
|
|
49
56
|
if (options.fix && !afterFix) {
|
|
50
57
|
applyFixes(result.findings, options.unsafeFixes ?? false);
|
|
51
58
|
return lintFilesInternal(patterns, { ...options, fix: false }, true);
|
|
@@ -63,24 +70,60 @@ async function collectParserBlocks(file) {
|
|
|
63
70
|
const { collectAsciidoctorBlocks } = await import("../parsers/asciidoctor.js");
|
|
64
71
|
return collectAsciidoctorBlocks(file);
|
|
65
72
|
}
|
|
73
|
+
async function collectParserSections(file) {
|
|
74
|
+
const { collectAsciidoctorSections } = await import("../parsers/asciidoctor.js");
|
|
75
|
+
return collectAsciidoctorSections(file);
|
|
76
|
+
}
|
|
66
77
|
async function collectParserReferenceTargets(file) {
|
|
67
78
|
const { collectAsciidoctorReferenceTargets } = await import("../parsers/asciidoctor.js");
|
|
68
79
|
return collectAsciidoctorReferenceTargets(file);
|
|
69
80
|
}
|
|
81
|
+
function mergeAsciidoctorSections(document, sections) {
|
|
82
|
+
if (!sections.length) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const section of sections) {
|
|
86
|
+
const existing = document.sections.find((candidate) => (path.resolve(candidate.range.start.file) === path.resolve(section.range.start.file)
|
|
87
|
+
&& candidate.range.start.line === section.range.start.line
|
|
88
|
+
&& candidate.title === section.title));
|
|
89
|
+
if (!existing) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
existing.sectname = section.sectname;
|
|
93
|
+
existing.source = "asciidoctor";
|
|
94
|
+
if (!existing.style && section.style) {
|
|
95
|
+
existing.style = section.style;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
70
99
|
function mergeAsciidoctorBlocks(document, blocks) {
|
|
71
100
|
if (!blocks.length) {
|
|
72
101
|
return;
|
|
73
102
|
}
|
|
74
|
-
const authoritativeTypes = new Set(blocks
|
|
103
|
+
const authoritativeTypes = new Set(blocks
|
|
104
|
+
.filter((block) => ["table", "image", "diagram"].includes(block.type))
|
|
105
|
+
.map((block) => block.type));
|
|
75
106
|
const authoritativeFiles = new Set(blocks.map((block) => path.resolve(block.range.start.file)));
|
|
76
|
-
|
|
107
|
+
const merged = [
|
|
77
108
|
...document.blocks.filter((block) => (!authoritativeTypes.has(block.type)
|
|
78
109
|
|| !authoritativeFiles.has(path.resolve(block.range.start.file)))),
|
|
79
|
-
|
|
80
|
-
|
|
110
|
+
];
|
|
111
|
+
for (const block of blocks) {
|
|
112
|
+
if (!merged.some((candidate) => sameBlock(candidate, block))) {
|
|
113
|
+
merged.push(block);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
document.blocks = merged.sort((a, b) => (a.range.start.file.localeCompare(b.range.start.file)
|
|
81
117
|
|| a.range.start.line - b.range.start.line
|
|
82
118
|
|| a.range.start.column - b.range.start.column));
|
|
83
119
|
}
|
|
120
|
+
function sameBlock(a, b) {
|
|
121
|
+
return path.resolve(a.range.start.file) === path.resolve(b.range.start.file)
|
|
122
|
+
&& a.range.start.line === b.range.start.line
|
|
123
|
+
&& a.range.start.column === b.range.start.column
|
|
124
|
+
&& a.type === b.type
|
|
125
|
+
&& a.style === b.style;
|
|
126
|
+
}
|
|
84
127
|
function mergeAsciidoctorReferenceTargets(document, targets) {
|
|
85
128
|
const byKey = new Map();
|
|
86
129
|
for (const target of document.referenceTargets) {
|
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 && !sources.some((source) => path.resolve(source.file) === path.resolve(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) {
|