rintenki 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +274 -0
- package/bin/rintenki.js +227 -51
- package/bin/worker.js +51 -0
- package/index.d.ts +4 -0
- package/package.json +5 -2
- package/rintenki.darwin-arm64.node +0 -0
- package/rintenki.darwin-x64.node +0 -0
- package/rintenki.linux-arm64-gnu.node +0 -0
- package/rintenki.linux-x64-gnu.node +0 -0
- package/rintenki.win32-arm64-msvc.node +0 -0
- package/rintenki.win32-x64-msvc.node +0 -0
- package/rintenkirc.schema.json +336 -0
package/README.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# rintenki
|
|
2
|
+
|
|
3
|
+
A fast HTML linter powered by html5ever + napi-rs.
|
|
4
|
+
|
|
5
|
+
The name "rintenki" comes from the Japanese word "輪転機" (rintenki), meaning a rotary printing press — a machine that prints large volumes at high speed. The name was chosen because "lint" and "rint" (輪転) sound alike, and like a rotary press that rapidly inspects and produces printed pages, rintenki quickly scans and checks your HTML.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Parsing by html5ever (Rust)
|
|
10
|
+
- Node.js native binding via napi-rs
|
|
11
|
+
- 58 built-in rules
|
|
12
|
+
- CLI / API / VS Code extension / LSP server
|
|
13
|
+
- Auto-fix with `--fix`
|
|
14
|
+
- JSON output for CI integration
|
|
15
|
+
- Per-rule severity customization (error / warning / off)
|
|
16
|
+
- Vue / JSX / eRuby support via parser plugins
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install rintenki
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## CLI
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx rintenki "src/**/*.html"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Options
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
rintenki [options] <files...>
|
|
34
|
+
|
|
35
|
+
-c, --config <path> Path to config file (default: .rintenkirc.json)
|
|
36
|
+
-f, --format <format> Output format: stylish (default), json, sarif
|
|
37
|
+
--fix Auto-fix fixable rules
|
|
38
|
+
--max-warnings <number> Exit with error if warnings exceed this number
|
|
39
|
+
-h, --help Show help
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Examples
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Basic usage
|
|
46
|
+
rintenki "src/**/*.html"
|
|
47
|
+
|
|
48
|
+
# JSON output
|
|
49
|
+
rintenki --format json "src/**/*.html"
|
|
50
|
+
|
|
51
|
+
# Auto-fix
|
|
52
|
+
rintenki --fix "src/**/*.html"
|
|
53
|
+
|
|
54
|
+
# Custom config
|
|
55
|
+
rintenki --config custom.json "src/**/*.html"
|
|
56
|
+
|
|
57
|
+
# Vue files
|
|
58
|
+
rintenki "src/**/*.vue"
|
|
59
|
+
|
|
60
|
+
# JSX/TSX files
|
|
61
|
+
rintenki "src/**/*.tsx"
|
|
62
|
+
|
|
63
|
+
# eRuby files
|
|
64
|
+
rintenki "app/views/**/*.erb"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const { lint, fix } = require("rintenki");
|
|
71
|
+
|
|
72
|
+
const result = lint("<html><body><img></body></html>");
|
|
73
|
+
console.log(result.diagnostics);
|
|
74
|
+
|
|
75
|
+
const fixed = fix('<DIV Class="foo">text</DIV>');
|
|
76
|
+
console.log(fixed.output); // <div class="foo">text</div>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
Place `.rintenkirc.json` in your project root:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"parser": {
|
|
86
|
+
".vue": "@rintenki/vue-parser"
|
|
87
|
+
},
|
|
88
|
+
"rules": {
|
|
89
|
+
"doctype": "error",
|
|
90
|
+
"no-consecutive-br": "warning",
|
|
91
|
+
"no-hard-code-id": "off",
|
|
92
|
+
"required-attr": true,
|
|
93
|
+
"end-tag": false
|
|
94
|
+
},
|
|
95
|
+
"ignore": ["dist/**", "vendor/**"]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Severity
|
|
100
|
+
|
|
101
|
+
| Value | Description |
|
|
102
|
+
|-------|-------------|
|
|
103
|
+
| `"error"` | Report as error (exit code 1) |
|
|
104
|
+
| `"warning"` / `"warn"` | Report as warning |
|
|
105
|
+
| `"off"` / `false` | Disable the rule |
|
|
106
|
+
| `true` | Use default severity |
|
|
107
|
+
|
|
108
|
+
### Ignore
|
|
109
|
+
|
|
110
|
+
Use a `.rintenkiignore` file or the `ignore` field in config to exclude files by glob pattern. `node_modules` is always excluded automatically.
|
|
111
|
+
|
|
112
|
+
## Parser Plugins
|
|
113
|
+
|
|
114
|
+
rintenki supports non-HTML files via optional parser plugins.
|
|
115
|
+
|
|
116
|
+
| Plugin | Syntax | Install |
|
|
117
|
+
|--------|--------|---------|
|
|
118
|
+
| `@rintenki/vue-parser` | Vue SFC (`.vue`) | `npm install @rintenki/vue-parser` |
|
|
119
|
+
| `@rintenki/jsx-parser` | JSX/TSX (`.jsx`, `.tsx`) | `npm install @rintenki/jsx-parser` |
|
|
120
|
+
| `@rintenki/erb-parser` | eRuby (`.erb`) | `npm install @rintenki/erb-parser` |
|
|
121
|
+
|
|
122
|
+
Parsers are auto-detected from installed packages, or can be explicitly configured in `.rintenkirc.json`.
|
|
123
|
+
|
|
124
|
+
### How It Works
|
|
125
|
+
|
|
126
|
+
- **Vue**: Extracts `<template>` block via `@vue/compiler-sfc`, masks `{{ }}` interpolations
|
|
127
|
+
- **JSX/TSX**: Parses AST via `oxc-parser` (Rust), extracts HTML elements, maps React attributes (`className` → `class`)
|
|
128
|
+
- **eRuby**: Masks `<% %>` tags with same-length placeholders, preserving line numbers
|
|
129
|
+
|
|
130
|
+
## Rules
|
|
131
|
+
|
|
132
|
+
### Conformance Checking
|
|
133
|
+
|
|
134
|
+
| Rule | Default | Description |
|
|
135
|
+
|------|---------|-------------|
|
|
136
|
+
| `attr-duplication` | error | Detect duplicate attributes |
|
|
137
|
+
| `colspan-rowspan-range` | error | Validate colspan (1-1000) and rowspan (0-65534) ranges |
|
|
138
|
+
| `deprecated-attr` | error | Detect deprecated or obsolete attributes |
|
|
139
|
+
| `deprecated-element` | error | Detect deprecated or obsolete elements |
|
|
140
|
+
| `disallowed-element` | off | Detect disallowed elements |
|
|
141
|
+
| `doctype` | error | Detect missing DOCTYPE declaration |
|
|
142
|
+
| `header-footer-nesting` | error | Detect header/footer/main nesting inside header or footer |
|
|
143
|
+
| `heading-levels` | error | Detect skipped heading levels |
|
|
144
|
+
| `id-duplication` | error | Detect duplicate id attribute values |
|
|
145
|
+
| `input-attr-applicability` | warning | Detect attributes that do not apply to the input type |
|
|
146
|
+
| `invalid-attr` | error | Detect attributes not in the spec |
|
|
147
|
+
| `meta-constraints` | error | Validate meta element attribute constraints |
|
|
148
|
+
| `no-duplicate-base` | error | Detect multiple base or title elements |
|
|
149
|
+
| `no-duplicate-dt` | error | Detect duplicate dt names in dl |
|
|
150
|
+
| `no-empty-palpable-content` | warning | Detect empty palpable content elements |
|
|
151
|
+
| `no-nested-forms` | error | Detect nested form elements |
|
|
152
|
+
| `no-nested-interactive` | error | Detect interactive content inside a or button |
|
|
153
|
+
| `no-orphaned-end-tag` | error | Detect end tags without matching start tags |
|
|
154
|
+
| `no-tabindex-on-dialog` | error | Detect tabindex on dialog elements |
|
|
155
|
+
| `obsolete-but-conforming` | warning | Detect obsolete but conforming features |
|
|
156
|
+
| `permitted-contents` | error | Detect children not permitted by the spec |
|
|
157
|
+
| `picture-structure` | error | Validate picture element structure |
|
|
158
|
+
| `placeholder-label-option` | warning | Detect missing placeholder option in required select |
|
|
159
|
+
| `require-datetime` | error | Detect missing datetime attribute on time element |
|
|
160
|
+
| `required-attr` | error | Detect missing required attributes |
|
|
161
|
+
| `required-element` | error | Detect missing required child elements |
|
|
162
|
+
| `summary-first-child` | error | Require summary as first child of details |
|
|
163
|
+
| `th-content-restrictions` | error | Detect disallowed elements inside th |
|
|
164
|
+
| `unique-main` | error | Require at most one visible main element |
|
|
165
|
+
| `valid-attr-value` | error | Validate enumerated attribute values |
|
|
166
|
+
| `valid-autocomplete` | warning | Validate autocomplete attribute values |
|
|
167
|
+
| `valid-rel` | error | Validate rel attribute values |
|
|
168
|
+
|
|
169
|
+
### Accessibility
|
|
170
|
+
|
|
171
|
+
| Rule | Default | Description |
|
|
172
|
+
|------|---------|-------------|
|
|
173
|
+
| `aria-attr-valid-values` | warning | Validate ARIA attribute values |
|
|
174
|
+
| `aria-role-conflicts` | warning | Detect conflicts between explicit role and implicit role |
|
|
175
|
+
| `label-has-control` | error | Detect label elements without associated control |
|
|
176
|
+
| `landmark-roles` | warning | Detect nested landmark roles |
|
|
177
|
+
| `neighbor-popovers` | off | Detect non-adjacent popover triggers and targets |
|
|
178
|
+
| `no-ambiguous-navigable-target-names` | warning | Detect invalid target name keywords |
|
|
179
|
+
| `no-consecutive-br` | warning | Detect consecutive br elements |
|
|
180
|
+
| `no-positive-tabindex` | warning | Detect positive tabindex values |
|
|
181
|
+
| `no-refer-to-non-existent-id` | error | Detect references to non-existent ids |
|
|
182
|
+
| `require-accessible-name` | error | Detect missing accessible names |
|
|
183
|
+
| `required-h1` | error | Detect missing h1 element |
|
|
184
|
+
| `table-row-column-alignment` | warning | Detect inconsistent table column counts |
|
|
185
|
+
| `use-list` | warning | Suggest list elements for bullet-prefixed text |
|
|
186
|
+
| `wai-aria` | error | Detect invalid WAI-ARIA roles and attributes |
|
|
187
|
+
|
|
188
|
+
### Naming Convention
|
|
189
|
+
|
|
190
|
+
| Rule | Default | Description |
|
|
191
|
+
|------|---------|-------------|
|
|
192
|
+
| `class-naming` | off | Enforce class name conventions |
|
|
193
|
+
|
|
194
|
+
### Maintainability
|
|
195
|
+
|
|
196
|
+
| Rule | Default | Description |
|
|
197
|
+
|------|---------|-------------|
|
|
198
|
+
| `no-hard-code-id` | off | Detect hardcoded id attributes |
|
|
199
|
+
| `no-inline-style` | warning | Detect inline style attributes |
|
|
200
|
+
| `no-use-event-handler-attr` | warning | Detect event handler attributes |
|
|
201
|
+
|
|
202
|
+
### Style
|
|
203
|
+
|
|
204
|
+
| Rule | Default | Description |
|
|
205
|
+
|------|---------|-------------|
|
|
206
|
+
| `attr-value-quotes` | warning | Detect unquoted attribute values |
|
|
207
|
+
| `case-sensitive-attr-name` | warning | Detect uppercase attribute names |
|
|
208
|
+
| `case-sensitive-tag-name` | warning | Detect uppercase tag names |
|
|
209
|
+
| `character-reference` | warning | Detect unescaped `&` characters |
|
|
210
|
+
| `end-tag` | warning | Detect missing end tags |
|
|
211
|
+
| `ineffective-attr` | warning | Detect attributes with no effect on element |
|
|
212
|
+
| `no-boolean-attr-value` | warning | Detect values on boolean attributes |
|
|
213
|
+
| `no-default-value` | warning | Detect attributes set to their default value |
|
|
214
|
+
|
|
215
|
+
### Auto-fixable Rules
|
|
216
|
+
|
|
217
|
+
The following rules can be auto-fixed with `--fix`:
|
|
218
|
+
|
|
219
|
+
- `attr-value-quotes`
|
|
220
|
+
- `case-sensitive-attr-name`
|
|
221
|
+
- `case-sensitive-tag-name`
|
|
222
|
+
- `character-reference`
|
|
223
|
+
- `doctype`
|
|
224
|
+
- `no-boolean-attr-value`
|
|
225
|
+
- `no-default-value`
|
|
226
|
+
|
|
227
|
+
## VS Code Extension
|
|
228
|
+
|
|
229
|
+
The `packages/rintenki-vscode` package provides a VS Code extension with real-time linting and Quick Fix support via the LSP server.
|
|
230
|
+
|
|
231
|
+
### Development
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pnpm build # Build all packages
|
|
235
|
+
code . # Open project root in VS Code
|
|
236
|
+
# Press F5 to launch Extension Development Host
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Settings
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"rintenki.rules": {
|
|
244
|
+
"case-sensitive-attr-name": "off",
|
|
245
|
+
"no-hard-code-id": "warning"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Packages
|
|
251
|
+
|
|
252
|
+
| Package | Description |
|
|
253
|
+
|---------|-------------|
|
|
254
|
+
| `rintenki` | Linter core (Rust + napi-rs) |
|
|
255
|
+
| `rintenki-lsp-server` | LSP server |
|
|
256
|
+
| `rintenki-vscode` | VS Code extension |
|
|
257
|
+
| `@rintenki/parser-utils` | Shared parser interface |
|
|
258
|
+
| `@rintenki/vue-parser` | Vue SFC parser plugin |
|
|
259
|
+
| `@rintenki/jsx-parser` | JSX/TSX parser plugin |
|
|
260
|
+
| `@rintenki/erb-parser` | eRuby parser plugin |
|
|
261
|
+
|
|
262
|
+
## Supported Platforms
|
|
263
|
+
|
|
264
|
+
- macOS (arm64, x64)
|
|
265
|
+
- Linux (x64, arm64)
|
|
266
|
+
- Windows (x64, arm64)
|
|
267
|
+
|
|
268
|
+
## Inspired by
|
|
269
|
+
|
|
270
|
+
- [markuplint](https://markuplint.dev/) — The rule set design is inspired by markuplint.
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
MIT
|
package/bin/rintenki.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { existsSync, readFileSync, writeFileSync } = require("fs");
|
|
4
|
-
const { resolve, relative } = require("path");
|
|
3
|
+
const { existsSync, readFileSync, writeFileSync, watch } = require("fs");
|
|
4
|
+
const { resolve, relative, dirname, join } = require("path");
|
|
5
|
+
const { createHash } = require("crypto");
|
|
5
6
|
const { glob } = require("tinyglobby");
|
|
6
7
|
const { lint, fix } = require("../index");
|
|
7
8
|
|
|
@@ -85,11 +86,15 @@ function formatDiagnostic(diagnostic) {
|
|
|
85
86
|
? `${COLORS.red}error${COLORS.reset}`
|
|
86
87
|
: `${COLORS.yellow}warning${COLORS.reset}`;
|
|
87
88
|
const loc = diagnostic.line != null ? `${COLORS.gray}line ${diagnostic.line}${COLORS.reset} ` : "";
|
|
88
|
-
|
|
89
|
+
let line = ` ${loc}${severity} ${diagnostic.message} ${COLORS.gray}${diagnostic.rule}${COLORS.reset}`;
|
|
90
|
+
if (diagnostic.hint) {
|
|
91
|
+
line += `\n ${COLORS.gray}hint: ${diagnostic.hint}${COLORS.reset}`;
|
|
92
|
+
}
|
|
93
|
+
return line;
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
function parseArgs(argv) {
|
|
92
|
-
const args = { files: [], config: undefined, help: false, maxWarnings: -1, format: "stylish", fix: false };
|
|
97
|
+
const args = { files: [], config: undefined, help: false, maxWarnings: -1, format: "stylish", fix: false, watch: false };
|
|
93
98
|
let i = 0;
|
|
94
99
|
while (i < argv.length) {
|
|
95
100
|
const arg = argv[i];
|
|
@@ -103,6 +108,8 @@ function parseArgs(argv) {
|
|
|
103
108
|
args.format = argv[++i];
|
|
104
109
|
} else if (arg === "--fix") {
|
|
105
110
|
args.fix = true;
|
|
111
|
+
} else if (arg === "--watch" || arg === "-w") {
|
|
112
|
+
args.watch = true;
|
|
106
113
|
} else if (!arg.startsWith("-")) {
|
|
107
114
|
args.files.push(arg);
|
|
108
115
|
}
|
|
@@ -111,6 +118,69 @@ function parseArgs(argv) {
|
|
|
111
118
|
return args;
|
|
112
119
|
}
|
|
113
120
|
|
|
121
|
+
function buildSarif(jsonOutput) {
|
|
122
|
+
const ruleSet = new Map();
|
|
123
|
+
const results = [];
|
|
124
|
+
|
|
125
|
+
for (const { file, diagnostics } of jsonOutput) {
|
|
126
|
+
for (const d of diagnostics) {
|
|
127
|
+
if (!ruleSet.has(d.rule)) {
|
|
128
|
+
ruleSet.set(d.rule, {
|
|
129
|
+
id: d.rule,
|
|
130
|
+
shortDescription: { text: d.message },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const region = {};
|
|
135
|
+
if (d.line != null) {
|
|
136
|
+
region.startLine = d.line;
|
|
137
|
+
region.startColumn = (d.col ?? 0) + 1;
|
|
138
|
+
}
|
|
139
|
+
if (d.endLine != null) {
|
|
140
|
+
region.endLine = d.endLine;
|
|
141
|
+
region.endColumn = (d.endCol ?? 0) + 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const result = {
|
|
145
|
+
ruleId: d.rule,
|
|
146
|
+
level: d.severity === "error" ? "error" : "warning",
|
|
147
|
+
message: { text: d.hint ? `${d.message}\nhint: ${d.hint}` : d.message },
|
|
148
|
+
locations: [
|
|
149
|
+
{
|
|
150
|
+
physicalLocation: {
|
|
151
|
+
artifactLocation: { uri: file, uriBaseId: "%SRCROOT%" },
|
|
152
|
+
region,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (d.fixable) {
|
|
159
|
+
result.properties = { fixable: true };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
results.push(result);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
168
|
+
version: "2.1.0",
|
|
169
|
+
runs: [
|
|
170
|
+
{
|
|
171
|
+
tool: {
|
|
172
|
+
driver: {
|
|
173
|
+
name: "rintenki",
|
|
174
|
+
informationUri: "https://github.com/kzhrk/rintenki",
|
|
175
|
+
rules: [...ruleSet.values()],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
results,
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
114
184
|
async function main() {
|
|
115
185
|
const args = parseArgs(process.argv.slice(2));
|
|
116
186
|
|
|
@@ -119,8 +189,9 @@ async function main() {
|
|
|
119
189
|
|
|
120
190
|
Options:
|
|
121
191
|
-c, --config <path> Path to config file (default: .rintenkirc.json)
|
|
122
|
-
-f, --format <format> Output format: stylish (default), json
|
|
192
|
+
-f, --format <format> Output format: stylish (default), json, sarif
|
|
123
193
|
--fix Auto-fix fixable issues
|
|
194
|
+
-w, --watch Watch files for changes and re-lint
|
|
124
195
|
--max-warnings <number> Exit with error if warnings exceed this number
|
|
125
196
|
-h, --help Show help
|
|
126
197
|
|
|
@@ -144,68 +215,179 @@ Examples:
|
|
|
144
215
|
process.exit(1);
|
|
145
216
|
}
|
|
146
217
|
|
|
147
|
-
|
|
148
|
-
let totalWarnings = 0;
|
|
149
|
-
const jsonOutput = [];
|
|
218
|
+
const { totalErrors, totalWarnings } = await lintFiles(files, config, args);
|
|
150
219
|
|
|
151
|
-
|
|
220
|
+
if (args.watch) {
|
|
221
|
+
console.log(`\n${COLORS.gray}Watching for changes...${COLORS.reset}\n`);
|
|
222
|
+
|
|
223
|
+
const cache = new Map();
|
|
224
|
+
const dirs = [...new Set(files.map((f) => dirname(f)))];
|
|
225
|
+
let debounceTimer = null;
|
|
226
|
+
|
|
227
|
+
for (const dir of dirs) {
|
|
228
|
+
watch(dir, { recursive: true }, (_eventType, filename) => {
|
|
229
|
+
if (!filename) return;
|
|
230
|
+
const abs = resolve(dir, filename);
|
|
231
|
+
if (!files.includes(abs)) return;
|
|
152
232
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (!source) continue;
|
|
233
|
+
// Invalidate cache for changed file
|
|
234
|
+
cache.delete(abs);
|
|
235
|
+
|
|
236
|
+
clearTimeout(debounceTimer);
|
|
237
|
+
debounceTimer = setTimeout(async () => {
|
|
238
|
+
process.stdout.write("\x1Bc");
|
|
239
|
+
await lintFiles(files, config, args, cache);
|
|
240
|
+
console.log(`\n${COLORS.gray}Watching for changes...${COLORS.reset}\n`);
|
|
241
|
+
}, 100);
|
|
242
|
+
});
|
|
164
243
|
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
165
246
|
|
|
166
|
-
|
|
247
|
+
if (totalErrors > 0) {
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
167
250
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
251
|
+
if (args.maxWarnings >= 0 && totalWarnings > args.maxWarnings) {
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const PARALLEL_THRESHOLD = 20;
|
|
257
|
+
|
|
258
|
+
function lintOneFile(file, config, args) {
|
|
259
|
+
let source = readFileSync(file, "utf-8");
|
|
260
|
+
let lineOffset = 0;
|
|
261
|
+
|
|
262
|
+
const parser = loadParser(file, config?.parser);
|
|
263
|
+
if (parser) {
|
|
264
|
+
const preprocessed = parser.preprocess(source);
|
|
265
|
+
source = preprocessed.html;
|
|
266
|
+
lineOffset = preprocessed.lineOffset;
|
|
267
|
+
if (!source) return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let html = source;
|
|
271
|
+
let fixedCount = 0;
|
|
272
|
+
|
|
273
|
+
if (args.fix) {
|
|
274
|
+
const fixResult = fix(html, config);
|
|
275
|
+
if (fixResult.fixedCount > 0) {
|
|
276
|
+
writeFileSync(file, fixResult.output, "utf-8");
|
|
277
|
+
html = fixResult.output;
|
|
278
|
+
fixedCount = fixResult.fixedCount;
|
|
175
279
|
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const result = lint(html, config);
|
|
283
|
+
|
|
284
|
+
if (lineOffset > 0) {
|
|
285
|
+
for (const d of result.diagnostics) {
|
|
286
|
+
if (d.line != null) d.line += lineOffset;
|
|
287
|
+
if (d.endLine != null) d.endLine += lineOffset;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { diagnostics: result.diagnostics, fixedCount };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function fileHash(content) {
|
|
295
|
+
return createHash("md5").update(content).digest("hex");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function lintFilesParallel(files, config, args) {
|
|
299
|
+
const { Worker } = require("worker_threads");
|
|
300
|
+
const cpus = require("os").cpus().length;
|
|
301
|
+
const workerCount = Math.min(cpus, 4, files.length);
|
|
302
|
+
const workerPath = join(__dirname, "worker.js");
|
|
303
|
+
|
|
304
|
+
const chunks = Array.from({ length: workerCount }, () => []);
|
|
305
|
+
files.forEach((f, i) => chunks[i % workerCount].push(f));
|
|
306
|
+
|
|
307
|
+
const results = await Promise.all(
|
|
308
|
+
chunks.map(
|
|
309
|
+
(chunk) =>
|
|
310
|
+
new Promise((resolve, reject) => {
|
|
311
|
+
const worker = new Worker(workerPath, {
|
|
312
|
+
workerData: { files: chunk, config, fix: args.fix },
|
|
313
|
+
});
|
|
314
|
+
worker.on("message", resolve);
|
|
315
|
+
worker.on("error", reject);
|
|
316
|
+
})
|
|
317
|
+
)
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// Merge results: array of { file, diagnostics, fixedCount }[]
|
|
321
|
+
return results.flat();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function lintFiles(files, config, args, cache) {
|
|
325
|
+
let totalErrors = 0;
|
|
326
|
+
let totalWarnings = 0;
|
|
327
|
+
const jsonOutput = [];
|
|
328
|
+
let totalFixed = 0;
|
|
329
|
+
|
|
330
|
+
let fileResults;
|
|
331
|
+
|
|
332
|
+
if (!args.fix && !cache && files.length >= PARALLEL_THRESHOLD) {
|
|
333
|
+
// Parallel mode: no fix, no cache, many files
|
|
334
|
+
const parallel = await lintFilesParallel(files, config, args);
|
|
335
|
+
fileResults = parallel.map((r) => ({
|
|
336
|
+
file: r.file,
|
|
337
|
+
rel: relative(process.cwd(), r.file),
|
|
338
|
+
diagnostics: r.diagnostics,
|
|
339
|
+
fixedCount: r.fixedCount,
|
|
340
|
+
}));
|
|
341
|
+
} else {
|
|
342
|
+
// Sequential mode (with optional cache for watch)
|
|
343
|
+
fileResults = [];
|
|
344
|
+
for (const file of files) {
|
|
345
|
+
const source = readFileSync(file, "utf-8");
|
|
346
|
+
const hash = fileHash(source);
|
|
347
|
+
|
|
348
|
+
if (cache) {
|
|
349
|
+
const cached = cache.get(file);
|
|
350
|
+
if (cached && cached.hash === hash) {
|
|
351
|
+
fileResults.push({ file, rel: relative(process.cwd(), file), diagnostics: cached.diagnostics, fixedCount: 0 });
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
176
355
|
|
|
177
|
-
|
|
178
|
-
|
|
356
|
+
const result = lintOneFile(file, config, args);
|
|
357
|
+
if (!result) continue;
|
|
179
358
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
for (const d of result.diagnostics) {
|
|
183
|
-
if (d.line != null) d.line += lineOffset;
|
|
359
|
+
if (cache) {
|
|
360
|
+
cache.set(file, { hash, diagnostics: result.diagnostics });
|
|
184
361
|
}
|
|
362
|
+
|
|
363
|
+
fileResults.push({ file, rel: relative(process.cwd(), file), diagnostics: result.diagnostics, fixedCount: result.fixedCount });
|
|
185
364
|
}
|
|
365
|
+
}
|
|
186
366
|
|
|
187
|
-
|
|
367
|
+
for (const { rel, diagnostics, fixedCount } of fileResults) {
|
|
368
|
+
totalFixed += fixedCount;
|
|
369
|
+
|
|
370
|
+
for (const d of diagnostics) {
|
|
188
371
|
if (d.severity === "error") totalErrors++;
|
|
189
372
|
else totalWarnings++;
|
|
190
373
|
}
|
|
191
374
|
|
|
192
|
-
if (args.format === "json") {
|
|
193
|
-
if (
|
|
194
|
-
jsonOutput.push({
|
|
195
|
-
file: rel,
|
|
196
|
-
diagnostics: result.diagnostics,
|
|
197
|
-
});
|
|
375
|
+
if (args.format === "json" || args.format === "sarif") {
|
|
376
|
+
if (diagnostics.length > 0) {
|
|
377
|
+
jsonOutput.push({ file: rel, diagnostics });
|
|
198
378
|
}
|
|
199
379
|
} else {
|
|
200
|
-
if (
|
|
380
|
+
if (diagnostics.length === 0) continue;
|
|
201
381
|
console.log(`\n${COLORS.bold}${rel}${COLORS.reset}`);
|
|
202
|
-
for (const d of
|
|
382
|
+
for (const d of diagnostics) {
|
|
203
383
|
console.log(formatDiagnostic(d));
|
|
204
384
|
}
|
|
205
385
|
}
|
|
206
386
|
}
|
|
207
387
|
|
|
208
|
-
if (args.format === "
|
|
388
|
+
if (args.format === "sarif") {
|
|
389
|
+
console.log(JSON.stringify(buildSarif(jsonOutput), null, 2));
|
|
390
|
+
} else if (args.format === "json") {
|
|
209
391
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
210
392
|
} else if (totalErrors > 0 || totalWarnings > 0) {
|
|
211
393
|
const parts = [];
|
|
@@ -220,13 +402,7 @@ Examples:
|
|
|
220
402
|
console.log("No issues found.");
|
|
221
403
|
}
|
|
222
404
|
|
|
223
|
-
|
|
224
|
-
process.exit(1);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (args.maxWarnings >= 0 && totalWarnings > args.maxWarnings) {
|
|
228
|
-
process.exit(1);
|
|
229
|
-
}
|
|
405
|
+
return { totalErrors, totalWarnings };
|
|
230
406
|
}
|
|
231
407
|
|
|
232
408
|
main().catch((err) => {
|
package/bin/worker.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { workerData, parentPort } = require("worker_threads");
|
|
2
|
+
const { readFileSync, writeFileSync } = require("fs");
|
|
3
|
+
const { lint, fix } = require("../index");
|
|
4
|
+
|
|
5
|
+
let loadParser;
|
|
6
|
+
try {
|
|
7
|
+
loadParser = require("@rintenki/parser-utils").loadParser;
|
|
8
|
+
} catch {
|
|
9
|
+
loadParser = () => undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { files, config, fix: shouldFix } = workerData;
|
|
13
|
+
const results = [];
|
|
14
|
+
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
let source = readFileSync(file, "utf-8");
|
|
17
|
+
let lineOffset = 0;
|
|
18
|
+
|
|
19
|
+
const parser = loadParser(file, config?.parser);
|
|
20
|
+
if (parser) {
|
|
21
|
+
const preprocessed = parser.preprocess(source);
|
|
22
|
+
source = preprocessed.html;
|
|
23
|
+
lineOffset = preprocessed.lineOffset;
|
|
24
|
+
if (!source) continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let html = source;
|
|
28
|
+
let fixedCount = 0;
|
|
29
|
+
|
|
30
|
+
if (shouldFix) {
|
|
31
|
+
const fixResult = fix(html, config);
|
|
32
|
+
if (fixResult.fixedCount > 0) {
|
|
33
|
+
writeFileSync(file, fixResult.output, "utf-8");
|
|
34
|
+
html = fixResult.output;
|
|
35
|
+
fixedCount = fixResult.fixedCount;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = lint(html, config);
|
|
40
|
+
|
|
41
|
+
if (lineOffset > 0) {
|
|
42
|
+
for (const d of result.diagnostics) {
|
|
43
|
+
if (d.line != null) d.line += lineOffset;
|
|
44
|
+
if (d.endLine != null) d.endLine += lineOffset;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
results.push({ file, diagnostics: result.diagnostics, fixedCount });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
parentPort.postMessage(results);
|
package/index.d.ts
CHANGED
|
@@ -3,7 +3,11 @@ export interface LintDiagnostic {
|
|
|
3
3
|
message: string;
|
|
4
4
|
severity: "error" | "warning";
|
|
5
5
|
line: number | null;
|
|
6
|
+
col: number | null;
|
|
7
|
+
endLine: number | null;
|
|
8
|
+
endCol: number | null;
|
|
6
9
|
fixable: boolean;
|
|
10
|
+
hint: string | null;
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export interface LintResult {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rintenki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A fast HTML linter powered by html5ever + napi-rs",
|
|
5
5
|
"author": "Kazuhiro Kobayashi <https://github.com/kzhrk>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,7 +32,10 @@
|
|
|
32
32
|
"index.js",
|
|
33
33
|
"index.d.ts",
|
|
34
34
|
"bin/rintenki.js",
|
|
35
|
-
"
|
|
35
|
+
"bin/worker.js",
|
|
36
|
+
"rintenki.*.node",
|
|
37
|
+
"README.md",
|
|
38
|
+
"rintenkirc.schema.json"
|
|
36
39
|
],
|
|
37
40
|
"napi": {
|
|
38
41
|
"binaryName": "rintenki",
|
|
Binary file
|
package/rintenki.darwin-x64.node
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Rintenki Configuration",
|
|
4
|
+
"description": "Configuration file for the rintenki HTML linter",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"rules": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"description": "Rule severity overrides",
|
|
10
|
+
"properties": {
|
|
11
|
+
"aria-attr-valid-values": {
|
|
12
|
+
"$ref": "#/definitions/severity",
|
|
13
|
+
"description": "Validate ARIA attribute values",
|
|
14
|
+
"default": "warning"
|
|
15
|
+
},
|
|
16
|
+
"aria-role-conflicts": {
|
|
17
|
+
"$ref": "#/definitions/severity",
|
|
18
|
+
"description": "Detect conflicts between explicit role and element's implicit role",
|
|
19
|
+
"default": "warning"
|
|
20
|
+
},
|
|
21
|
+
"attr-duplication": {
|
|
22
|
+
"$ref": "#/definitions/severity",
|
|
23
|
+
"description": "Disallow duplicate attributes on the same element",
|
|
24
|
+
"default": "error"
|
|
25
|
+
},
|
|
26
|
+
"attr-value-quotes": {
|
|
27
|
+
"$ref": "#/definitions/severity",
|
|
28
|
+
"description": "Require attribute values to be quoted",
|
|
29
|
+
"default": "warning"
|
|
30
|
+
},
|
|
31
|
+
"case-sensitive-attr-name": {
|
|
32
|
+
"$ref": "#/definitions/severity",
|
|
33
|
+
"description": "Enforce lowercase attribute names (auto-fixable)",
|
|
34
|
+
"default": "warning"
|
|
35
|
+
},
|
|
36
|
+
"colspan-rowspan-range": {
|
|
37
|
+
"$ref": "#/definitions/severity",
|
|
38
|
+
"description": "Require colspan and rowspan values to be within valid ranges",
|
|
39
|
+
"default": "error"
|
|
40
|
+
},
|
|
41
|
+
"case-sensitive-tag-name": {
|
|
42
|
+
"$ref": "#/definitions/severity",
|
|
43
|
+
"description": "Enforce lowercase tag names (auto-fixable)",
|
|
44
|
+
"default": "warning"
|
|
45
|
+
},
|
|
46
|
+
"character-reference": {
|
|
47
|
+
"$ref": "#/definitions/severity",
|
|
48
|
+
"description": "Require unescaped & to be written as &",
|
|
49
|
+
"default": "warning"
|
|
50
|
+
},
|
|
51
|
+
"class-naming": {
|
|
52
|
+
"$ref": "#/definitions/severity",
|
|
53
|
+
"description": "Enforce class name conventions (opt-in)",
|
|
54
|
+
"default": "off"
|
|
55
|
+
},
|
|
56
|
+
"deprecated-attr": {
|
|
57
|
+
"$ref": "#/definitions/severity",
|
|
58
|
+
"description": "Disallow deprecated HTML attributes",
|
|
59
|
+
"default": "error"
|
|
60
|
+
},
|
|
61
|
+
"deprecated-element": {
|
|
62
|
+
"$ref": "#/definitions/severity",
|
|
63
|
+
"description": "Disallow deprecated HTML elements",
|
|
64
|
+
"default": "error"
|
|
65
|
+
},
|
|
66
|
+
"disallowed-element": {
|
|
67
|
+
"$ref": "#/definitions/severity",
|
|
68
|
+
"description": "Disallow specified elements (opt-in)",
|
|
69
|
+
"default": "off"
|
|
70
|
+
},
|
|
71
|
+
"doctype": {
|
|
72
|
+
"$ref": "#/definitions/severity",
|
|
73
|
+
"description": "Require DOCTYPE declaration",
|
|
74
|
+
"default": "error"
|
|
75
|
+
},
|
|
76
|
+
"end-tag": {
|
|
77
|
+
"$ref": "#/definitions/severity",
|
|
78
|
+
"description": "Require closing tags for non-void elements",
|
|
79
|
+
"default": "warning"
|
|
80
|
+
},
|
|
81
|
+
"header-footer-nesting": {
|
|
82
|
+
"$ref": "#/definitions/severity",
|
|
83
|
+
"description": "Disallow header/footer/main nesting inside header or footer",
|
|
84
|
+
"default": "error"
|
|
85
|
+
},
|
|
86
|
+
"heading-levels": {
|
|
87
|
+
"$ref": "#/definitions/severity",
|
|
88
|
+
"description": "Disallow skipping heading levels (e.g. h1 to h3)",
|
|
89
|
+
"default": "error"
|
|
90
|
+
},
|
|
91
|
+
"id-duplication": {
|
|
92
|
+
"$ref": "#/definitions/severity",
|
|
93
|
+
"description": "Disallow duplicate id attribute values",
|
|
94
|
+
"default": "error"
|
|
95
|
+
},
|
|
96
|
+
"input-attr-applicability": {
|
|
97
|
+
"$ref": "#/definitions/severity",
|
|
98
|
+
"description": "Detect attributes that do not apply to the input type",
|
|
99
|
+
"default": "warning"
|
|
100
|
+
},
|
|
101
|
+
"ineffective-attr": {
|
|
102
|
+
"$ref": "#/definitions/severity",
|
|
103
|
+
"description": "Disallow attributes that have no effect on the element",
|
|
104
|
+
"default": "warning"
|
|
105
|
+
},
|
|
106
|
+
"invalid-attr": {
|
|
107
|
+
"$ref": "#/definitions/severity",
|
|
108
|
+
"description": "Disallow non-standard attributes",
|
|
109
|
+
"default": "error"
|
|
110
|
+
},
|
|
111
|
+
"meta-constraints": {
|
|
112
|
+
"$ref": "#/definitions/severity",
|
|
113
|
+
"description": "Validate meta element attribute constraints",
|
|
114
|
+
"default": "error"
|
|
115
|
+
},
|
|
116
|
+
"label-has-control": {
|
|
117
|
+
"$ref": "#/definitions/severity",
|
|
118
|
+
"description": "Require label elements to have an associated control",
|
|
119
|
+
"default": "error"
|
|
120
|
+
},
|
|
121
|
+
"landmark-roles": {
|
|
122
|
+
"$ref": "#/definitions/severity",
|
|
123
|
+
"description": "Disallow nested landmark roles",
|
|
124
|
+
"default": "warning"
|
|
125
|
+
},
|
|
126
|
+
"neighbor-popovers": {
|
|
127
|
+
"$ref": "#/definitions/severity",
|
|
128
|
+
"description": "Require popover triggers to be adjacent to their targets (opt-in)",
|
|
129
|
+
"default": "off"
|
|
130
|
+
},
|
|
131
|
+
"no-ambiguous-navigable-target-names": {
|
|
132
|
+
"$ref": "#/definitions/severity",
|
|
133
|
+
"description": "Disallow ambiguous target attribute values",
|
|
134
|
+
"default": "warning"
|
|
135
|
+
},
|
|
136
|
+
"no-boolean-attr-value": {
|
|
137
|
+
"$ref": "#/definitions/severity",
|
|
138
|
+
"description": "Disallow redundant boolean attribute values (auto-fixable)",
|
|
139
|
+
"default": "warning"
|
|
140
|
+
},
|
|
141
|
+
"no-consecutive-br": {
|
|
142
|
+
"$ref": "#/definitions/severity",
|
|
143
|
+
"description": "Disallow consecutive br elements",
|
|
144
|
+
"default": "warning"
|
|
145
|
+
},
|
|
146
|
+
"no-default-value": {
|
|
147
|
+
"$ref": "#/definitions/severity",
|
|
148
|
+
"description": "Disallow default attribute values (auto-fixable)",
|
|
149
|
+
"default": "warning"
|
|
150
|
+
},
|
|
151
|
+
"no-duplicate-base": {
|
|
152
|
+
"$ref": "#/definitions/severity",
|
|
153
|
+
"description": "Disallow multiple base or title elements in the document",
|
|
154
|
+
"default": "error"
|
|
155
|
+
},
|
|
156
|
+
"no-duplicate-dt": {
|
|
157
|
+
"$ref": "#/definitions/severity",
|
|
158
|
+
"description": "Disallow duplicate dt elements in a dl",
|
|
159
|
+
"default": "error"
|
|
160
|
+
},
|
|
161
|
+
"no-empty-palpable-content": {
|
|
162
|
+
"$ref": "#/definitions/severity",
|
|
163
|
+
"description": "Disallow empty interactive or palpable content elements",
|
|
164
|
+
"default": "warning"
|
|
165
|
+
},
|
|
166
|
+
"no-hard-code-id": {
|
|
167
|
+
"$ref": "#/definitions/severity",
|
|
168
|
+
"description": "Disallow hardcoded id attributes (opt-in)",
|
|
169
|
+
"default": "off"
|
|
170
|
+
},
|
|
171
|
+
"no-nested-forms": {
|
|
172
|
+
"$ref": "#/definitions/severity",
|
|
173
|
+
"description": "Disallow nested form elements",
|
|
174
|
+
"default": "error"
|
|
175
|
+
},
|
|
176
|
+
"no-nested-interactive": {
|
|
177
|
+
"$ref": "#/definitions/severity",
|
|
178
|
+
"description": "Disallow interactive content inside a or button elements",
|
|
179
|
+
"default": "error"
|
|
180
|
+
},
|
|
181
|
+
"no-inline-style": {
|
|
182
|
+
"$ref": "#/definitions/severity",
|
|
183
|
+
"description": "Disallow inline style attributes",
|
|
184
|
+
"default": "warning"
|
|
185
|
+
},
|
|
186
|
+
"no-orphaned-end-tag": {
|
|
187
|
+
"$ref": "#/definitions/severity",
|
|
188
|
+
"description": "Disallow unmatched closing tags",
|
|
189
|
+
"default": "error"
|
|
190
|
+
},
|
|
191
|
+
"no-tabindex-on-dialog": {
|
|
192
|
+
"$ref": "#/definitions/severity",
|
|
193
|
+
"description": "Disallow tabindex attribute on dialog elements",
|
|
194
|
+
"default": "error"
|
|
195
|
+
},
|
|
196
|
+
"no-refer-to-non-existent-id": {
|
|
197
|
+
"$ref": "#/definitions/severity",
|
|
198
|
+
"description": "Disallow references to non-existent id values",
|
|
199
|
+
"default": "error"
|
|
200
|
+
},
|
|
201
|
+
"no-positive-tabindex": {
|
|
202
|
+
"$ref": "#/definitions/severity",
|
|
203
|
+
"description": "Disallow positive tabindex values",
|
|
204
|
+
"default": "warning"
|
|
205
|
+
},
|
|
206
|
+
"no-use-event-handler-attr": {
|
|
207
|
+
"$ref": "#/definitions/severity",
|
|
208
|
+
"description": "Disallow inline event handler attributes",
|
|
209
|
+
"default": "warning"
|
|
210
|
+
},
|
|
211
|
+
"obsolete-but-conforming": {
|
|
212
|
+
"$ref": "#/definitions/severity",
|
|
213
|
+
"description": "Warn about obsolete but conforming features",
|
|
214
|
+
"default": "warning"
|
|
215
|
+
},
|
|
216
|
+
"picture-structure": {
|
|
217
|
+
"$ref": "#/definitions/severity",
|
|
218
|
+
"description": "Validate picture element structure",
|
|
219
|
+
"default": "error"
|
|
220
|
+
},
|
|
221
|
+
"permitted-contents": {
|
|
222
|
+
"$ref": "#/definitions/severity",
|
|
223
|
+
"description": "Enforce HTML content model restrictions",
|
|
224
|
+
"default": "error"
|
|
225
|
+
},
|
|
226
|
+
"placeholder-label-option": {
|
|
227
|
+
"$ref": "#/definitions/severity",
|
|
228
|
+
"description": "Require placeholder option in select elements with required attribute",
|
|
229
|
+
"default": "warning"
|
|
230
|
+
},
|
|
231
|
+
"require-accessible-name": {
|
|
232
|
+
"$ref": "#/definitions/severity",
|
|
233
|
+
"description": "Require accessible names for interactive elements",
|
|
234
|
+
"default": "error"
|
|
235
|
+
},
|
|
236
|
+
"require-datetime": {
|
|
237
|
+
"$ref": "#/definitions/severity",
|
|
238
|
+
"description": "Require datetime attribute on time elements",
|
|
239
|
+
"default": "error"
|
|
240
|
+
},
|
|
241
|
+
"required-attr": {
|
|
242
|
+
"$ref": "#/definitions/severity",
|
|
243
|
+
"description": "Require specification-mandated attributes",
|
|
244
|
+
"default": "error"
|
|
245
|
+
},
|
|
246
|
+
"required-element": {
|
|
247
|
+
"$ref": "#/definitions/severity",
|
|
248
|
+
"description": "Require specification-mandated child elements",
|
|
249
|
+
"default": "error"
|
|
250
|
+
},
|
|
251
|
+
"required-h1": {
|
|
252
|
+
"$ref": "#/definitions/severity",
|
|
253
|
+
"description": "Require exactly one h1 element per document",
|
|
254
|
+
"default": "error"
|
|
255
|
+
},
|
|
256
|
+
"summary-first-child": {
|
|
257
|
+
"$ref": "#/definitions/severity",
|
|
258
|
+
"description": "Require summary to be the first child of details",
|
|
259
|
+
"default": "error"
|
|
260
|
+
},
|
|
261
|
+
"table-row-column-alignment": {
|
|
262
|
+
"$ref": "#/definitions/severity",
|
|
263
|
+
"description": "Require consistent column counts in table rows",
|
|
264
|
+
"default": "warning"
|
|
265
|
+
},
|
|
266
|
+
"th-content-restrictions": {
|
|
267
|
+
"$ref": "#/definitions/severity",
|
|
268
|
+
"description": "Disallow header, footer, sectioning, and heading elements inside th",
|
|
269
|
+
"default": "error"
|
|
270
|
+
},
|
|
271
|
+
"unique-main": {
|
|
272
|
+
"$ref": "#/definitions/severity",
|
|
273
|
+
"description": "Require at most one visible main element per document",
|
|
274
|
+
"default": "error"
|
|
275
|
+
},
|
|
276
|
+
"use-list": {
|
|
277
|
+
"$ref": "#/definitions/severity",
|
|
278
|
+
"description": "Suggest using list elements for bullet-like text patterns",
|
|
279
|
+
"default": "warning"
|
|
280
|
+
},
|
|
281
|
+
"valid-attr-value": {
|
|
282
|
+
"$ref": "#/definitions/severity",
|
|
283
|
+
"description": "Validate enumerated attribute values",
|
|
284
|
+
"default": "error"
|
|
285
|
+
},
|
|
286
|
+
"valid-autocomplete": {
|
|
287
|
+
"$ref": "#/definitions/severity",
|
|
288
|
+
"description": "Validate autocomplete attribute values on form elements",
|
|
289
|
+
"default": "warning"
|
|
290
|
+
},
|
|
291
|
+
"valid-rel": {
|
|
292
|
+
"$ref": "#/definitions/severity",
|
|
293
|
+
"description": "Validate rel attribute values for a, area, link, and form elements",
|
|
294
|
+
"default": "error"
|
|
295
|
+
},
|
|
296
|
+
"wai-aria": {
|
|
297
|
+
"$ref": "#/definitions/severity",
|
|
298
|
+
"description": "Validate WAI-ARIA roles and attributes",
|
|
299
|
+
"default": "error"
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
"additionalProperties": {
|
|
303
|
+
"$ref": "#/definitions/severity"
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"ignore": {
|
|
307
|
+
"type": "array",
|
|
308
|
+
"description": "Glob patterns for files to exclude from linting",
|
|
309
|
+
"items": {
|
|
310
|
+
"type": "string"
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
"parser": {
|
|
314
|
+
"type": "object",
|
|
315
|
+
"description": "File extension to parser package mapping (e.g. { \".vue\": \"@rintenki/vue-parser\" })",
|
|
316
|
+
"additionalProperties": {
|
|
317
|
+
"type": "string"
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
"additionalProperties": false,
|
|
322
|
+
"definitions": {
|
|
323
|
+
"severity": {
|
|
324
|
+
"oneOf": [
|
|
325
|
+
{
|
|
326
|
+
"type": "string",
|
|
327
|
+
"enum": ["error", "warning", "warn", "off"]
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"type": "boolean"
|
|
331
|
+
}
|
|
332
|
+
],
|
|
333
|
+
"description": "Rule severity: \"error\", \"warning\"/\"warn\", \"off\", true (default severity), or false (off)"
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|