rintenki 0.9.0 → 0.11.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 CHANGED
@@ -8,7 +8,7 @@ The name "rintenki" comes from the Japanese word "輪転機" (rintenki), meaning
8
8
 
9
9
  - Parsing by html5ever (Rust)
10
10
  - Node.js native binding via napi-rs
11
- - 58 built-in rules
11
+ - 78 built-in rules
12
12
  - CLI / API / VS Code extension / LSP server
13
13
  - Auto-fix with `--fix`
14
14
  - JSON output for CI integration
@@ -141,6 +141,8 @@ Parsers are auto-detected from installed packages, or can be explicitly configur
141
141
  | `deprecated-element` | error | Detect deprecated or obsolete elements |
142
142
  | `disallowed-element` | off | Detect disallowed elements |
143
143
  | `doctype` | error | Detect missing DOCTYPE declaration |
144
+ | `empty-title` | error | Detect empty title element |
145
+ | `form-dup-name` | warning | Detect duplicate form control names |
144
146
  | `header-footer-nesting` | error | Detect header/footer/main nesting inside header or footer |
145
147
  | `heading-levels` | error | Detect skipped heading levels |
146
148
  | `id-duplication` | error | Detect duplicate id attribute values |
@@ -151,6 +153,7 @@ Parsers are auto-detected from installed packages, or can be explicitly configur
151
153
  | `no-duplicate-dt` | error | Detect duplicate dt names in dl |
152
154
  | `no-empty-palpable-content` | warning | Detect empty palpable content elements |
153
155
  | `no-nested-forms` | error | Detect nested form elements |
156
+ | `no-non-scalable-viewport` | error | Detect user-scalable=no in viewport meta |
154
157
  | `no-nested-interactive` | error | Detect interactive content inside a or button |
155
158
  | `no-orphaned-end-tag` | error | Detect end tags without matching start tags |
156
159
  | `no-tabindex-on-dialog` | error | Detect tabindex on dialog elements |
@@ -159,26 +162,43 @@ Parsers are auto-detected from installed packages, or can be explicitly configur
159
162
  | `picture-structure` | error | Validate picture element structure |
160
163
  | `placeholder-label-option` | warning | Detect missing placeholder option in required select |
161
164
  | `require-datetime` | error | Detect missing datetime attribute on time element |
165
+ | `require-meta-charset` | off | Require meta charset declaration |
162
166
  | `required-attr` | error | Detect missing required attributes |
163
167
  | `required-element` | error | Detect missing required child elements |
164
168
  | `summary-first-child` | error | Require summary as first child of details |
165
169
  | `th-content-restrictions` | error | Detect disallowed elements inside th |
166
170
  | `unique-main` | error | Require at most one visible main element |
171
+ | `link-constraints` | error | Validate link element attribute constraints |
172
+ | `no-duplicate-in-head` | error | Detect duplicate charset/viewport/description meta |
173
+ | `no-implicit-button-type` | warning | Require explicit type on button elements |
174
+ | `script-type` | error | Validate script type attribute values |
175
+ | `src-not-empty` | error | Detect empty src or href attributes |
167
176
  | `valid-attr-value` | error | Validate enumerated attribute values |
168
177
  | `valid-autocomplete` | warning | Validate autocomplete attribute values |
178
+ | `valid-id` | error | Require valid id values (non-empty, no whitespace) |
169
179
  | `valid-rel` | error | Validate rel attribute values |
180
+ | `void-content` | error | Detect content inside void elements |
170
181
 
171
182
  ### Accessibility
172
183
 
173
184
  | Rule | Default | Description |
174
185
  |------|---------|-------------|
186
+ | `aria-attr-conflicts` | error | Detect conflicting ARIA and native HTML attributes |
175
187
  | `aria-attr-valid-values` | warning | Validate ARIA attribute values |
188
+ | `aria-hidden-focusable` | error | Detect aria-hidden on focusable elements |
189
+ | `aria-naming-prohibited` | error | Detect aria-label on elements where naming is prohibited |
176
190
  | `aria-role-conflicts` | warning | Detect conflicts between explicit role and implicit role |
191
+ | `empty-heading` | warning | Detect empty heading elements |
177
192
  | `label-has-control` | error | Detect label elements without associated control |
178
193
  | `landmark-roles` | warning | Detect nested landmark roles |
179
194
  | `neighbor-popovers` | off | Detect non-adjacent popover triggers and targets |
195
+ | `no-abstract-role` | error | Detect abstract ARIA roles |
180
196
  | `no-ambiguous-navigable-target-names` | warning | Detect invalid target name keywords |
197
+ | `no-aria-hidden-body` | error | Detect aria-hidden on body element |
181
198
  | `no-consecutive-br` | warning | Detect consecutive br elements |
199
+ | `no-dup-class` | warning | Detect duplicate class names |
200
+ | `no-redundant-role` | warning | Detect redundant explicit ARIA roles |
201
+ | `no-role-on-meta-elements` | error | Detect role/aria-* on meta elements |
182
202
  | `no-positive-tabindex` | warning | Detect positive tabindex values |
183
203
  | `no-refer-to-non-existent-id` | error | Detect references to non-existent ids |
184
204
  | `require-accessible-name` | error | Detect missing accessible names |
package/bin/rintenki.js CHANGED
@@ -258,12 +258,14 @@ const PARALLEL_THRESHOLD = 20;
258
258
  function lintOneFile(file, config, args) {
259
259
  let source = readFileSync(file, "utf-8");
260
260
  let lineOffset = 0;
261
+ let isFragment = false;
261
262
 
262
263
  const parser = loadParser(file, config?.parser);
263
264
  if (parser) {
264
265
  const preprocessed = parser.preprocess(source);
265
266
  source = preprocessed.html;
266
267
  lineOffset = preprocessed.lineOffset;
268
+ isFragment = preprocessed.isFragment ?? false;
267
269
  if (!source) return null;
268
270
  }
269
271
 
@@ -279,7 +281,7 @@ function lintOneFile(file, config, args) {
279
281
  }
280
282
  }
281
283
 
282
- const result = lint(html, config);
284
+ const result = lint(html, config, isFragment);
283
285
 
284
286
  if (lineOffset > 0) {
285
287
  for (const d of result.diagnostics) {
package/bin/worker.js CHANGED
@@ -15,12 +15,14 @@ const results = [];
15
15
  for (const file of files) {
16
16
  let source = readFileSync(file, "utf-8");
17
17
  let lineOffset = 0;
18
+ let isFragment = false;
18
19
 
19
20
  const parser = loadParser(file, config?.parser);
20
21
  if (parser) {
21
22
  const preprocessed = parser.preprocess(source);
22
23
  source = preprocessed.html;
23
24
  lineOffset = preprocessed.lineOffset;
25
+ isFragment = preprocessed.isFragment ?? false;
24
26
  if (!source) continue;
25
27
  }
26
28
 
@@ -36,7 +38,7 @@ for (const file of files) {
36
38
  }
37
39
  }
38
40
 
39
- const result = lint(html, config);
41
+ const result = lint(html, config, isFragment);
40
42
 
41
43
  if (lineOffset > 0) {
42
44
  for (const d of result.diagnostics) {
package/index.d.ts CHANGED
@@ -32,5 +32,5 @@ export interface LintConfig {
32
32
  rules?: Record<string, RuleSeverity>;
33
33
  }
34
34
 
35
- export function lint(html: string, config?: LintConfig): LintResult;
35
+ export function lint(html: string, config?: LintConfig, isFragment?: boolean): LintResult;
36
36
  export function fix(html: string, config?: LintConfig): FixResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rintenki",
3
- "version": "0.9.0",
3
+ "version": "0.11.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",
@@ -52,7 +52,7 @@
52
52
  "tinyglobby": "0.2.15"
53
53
  },
54
54
  "devDependencies": {
55
- "@napi-rs/cli": "3.5.1",
55
+ "@napi-rs/cli": "3.6.0",
56
56
  "@types/node": "25.5.0",
57
57
  "typescript": "6.0.2",
58
58
  "vitest": "4.1.2"
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -8,11 +8,26 @@
8
8
  "type": "object",
9
9
  "description": "Rule severity overrides",
10
10
  "properties": {
11
+ "aria-attr-conflicts": {
12
+ "$ref": "#/definitions/severity",
13
+ "description": "Detect conflicting ARIA and native HTML attributes",
14
+ "default": "error"
15
+ },
11
16
  "aria-attr-valid-values": {
12
17
  "$ref": "#/definitions/severity",
13
18
  "description": "Validate ARIA attribute values",
14
19
  "default": "warning"
15
20
  },
21
+ "aria-hidden-focusable": {
22
+ "$ref": "#/definitions/severity",
23
+ "description": "Disallow aria-hidden on focusable elements",
24
+ "default": "error"
25
+ },
26
+ "aria-naming-prohibited": {
27
+ "$ref": "#/definitions/severity",
28
+ "description": "Disallow aria-label/aria-labelledby on elements where naming is prohibited",
29
+ "default": "error"
30
+ },
16
31
  "aria-role-conflicts": {
17
32
  "$ref": "#/definitions/severity",
18
33
  "description": "Detect conflicts between explicit role and element's implicit role",
@@ -73,11 +88,26 @@
73
88
  "description": "Require DOCTYPE declaration",
74
89
  "default": "error"
75
90
  },
91
+ "empty-heading": {
92
+ "$ref": "#/definitions/severity",
93
+ "description": "Detect empty heading elements",
94
+ "default": "warning"
95
+ },
96
+ "empty-title": {
97
+ "$ref": "#/definitions/severity",
98
+ "description": "Detect empty title element",
99
+ "default": "error"
100
+ },
76
101
  "end-tag": {
77
102
  "$ref": "#/definitions/severity",
78
103
  "description": "Require closing tags for non-void elements",
79
104
  "default": "warning"
80
105
  },
106
+ "form-dup-name": {
107
+ "$ref": "#/definitions/severity",
108
+ "description": "Detect duplicate form control names within a form",
109
+ "default": "warning"
110
+ },
81
111
  "header-footer-nesting": {
82
112
  "$ref": "#/definitions/severity",
83
113
  "description": "Disallow header/footer/main nesting inside header or footer",
@@ -113,6 +143,11 @@
113
143
  "description": "Validate meta element attribute constraints",
114
144
  "default": "error"
115
145
  },
146
+ "link-constraints": {
147
+ "$ref": "#/definitions/severity",
148
+ "description": "Validate link element attribute constraints (preload requires as, etc.)",
149
+ "default": "error"
150
+ },
116
151
  "label-has-control": {
117
152
  "$ref": "#/definitions/severity",
118
153
  "description": "Require label elements to have an associated control",
@@ -128,6 +163,16 @@
128
163
  "description": "Require popover triggers to be adjacent to their targets (opt-in)",
129
164
  "default": "off"
130
165
  },
166
+ "no-abstract-role": {
167
+ "$ref": "#/definitions/severity",
168
+ "description": "Disallow abstract ARIA roles",
169
+ "default": "error"
170
+ },
171
+ "no-aria-hidden-body": {
172
+ "$ref": "#/definitions/severity",
173
+ "description": "Disallow aria-hidden on body element",
174
+ "default": "error"
175
+ },
131
176
  "no-ambiguous-navigable-target-names": {
132
177
  "$ref": "#/definitions/severity",
133
178
  "description": "Disallow ambiguous target attribute values",
@@ -143,11 +188,21 @@
143
188
  "description": "Disallow consecutive br elements",
144
189
  "default": "warning"
145
190
  },
191
+ "no-dup-class": {
192
+ "$ref": "#/definitions/severity",
193
+ "description": "Detect duplicate class names in class attribute",
194
+ "default": "warning"
195
+ },
146
196
  "no-default-value": {
147
197
  "$ref": "#/definitions/severity",
148
198
  "description": "Disallow default attribute values (auto-fixable)",
149
199
  "default": "warning"
150
200
  },
201
+ "no-duplicate-in-head": {
202
+ "$ref": "#/definitions/severity",
203
+ "description": "Disallow duplicate charset, viewport, or description meta elements",
204
+ "default": "error"
205
+ },
151
206
  "no-duplicate-base": {
152
207
  "$ref": "#/definitions/severity",
153
208
  "description": "Disallow multiple base or title elements in the document",
@@ -178,11 +233,21 @@
178
233
  "description": "Disallow interactive content inside a or button elements",
179
234
  "default": "error"
180
235
  },
236
+ "no-implicit-button-type": {
237
+ "$ref": "#/definitions/severity",
238
+ "description": "Require explicit type attribute on button elements",
239
+ "default": "warning"
240
+ },
181
241
  "no-inline-style": {
182
242
  "$ref": "#/definitions/severity",
183
243
  "description": "Disallow inline style attributes",
184
244
  "default": "warning"
185
245
  },
246
+ "no-non-scalable-viewport": {
247
+ "$ref": "#/definitions/severity",
248
+ "description": "Disallow user-scalable=no or maximum-scale=1 in viewport meta",
249
+ "default": "error"
250
+ },
186
251
  "no-orphaned-end-tag": {
187
252
  "$ref": "#/definitions/severity",
188
253
  "description": "Disallow unmatched closing tags",
@@ -198,6 +263,16 @@
198
263
  "description": "Disallow references to non-existent id values",
199
264
  "default": "error"
200
265
  },
266
+ "no-redundant-role": {
267
+ "$ref": "#/definitions/severity",
268
+ "description": "Detect redundant explicit ARIA roles matching implicit role",
269
+ "default": "warning"
270
+ },
271
+ "no-role-on-meta-elements": {
272
+ "$ref": "#/definitions/severity",
273
+ "description": "Disallow role and aria-* attributes on meta elements",
274
+ "default": "error"
275
+ },
201
276
  "no-positive-tabindex": {
202
277
  "$ref": "#/definitions/severity",
203
278
  "description": "Disallow positive tabindex values",
@@ -213,6 +288,16 @@
213
288
  "description": "Warn about obsolete but conforming features",
214
289
  "default": "warning"
215
290
  },
291
+ "script-type": {
292
+ "$ref": "#/definitions/severity",
293
+ "description": "Validate script type attribute values",
294
+ "default": "error"
295
+ },
296
+ "src-not-empty": {
297
+ "$ref": "#/definitions/severity",
298
+ "description": "Disallow empty src or href attributes",
299
+ "default": "error"
300
+ },
216
301
  "picture-structure": {
217
302
  "$ref": "#/definitions/severity",
218
303
  "description": "Validate picture element structure",
@@ -248,6 +333,11 @@
248
333
  "description": "Require specification-mandated child elements",
249
334
  "default": "error"
250
335
  },
336
+ "require-meta-charset": {
337
+ "$ref": "#/definitions/severity",
338
+ "description": "Require meta charset declaration in the document",
339
+ "default": "off"
340
+ },
251
341
  "required-h1": {
252
342
  "$ref": "#/definitions/severity",
253
343
  "description": "Require exactly one h1 element per document",
@@ -278,6 +368,16 @@
278
368
  "description": "Suggest using list elements for bullet-like text patterns",
279
369
  "default": "warning"
280
370
  },
371
+ "valid-id": {
372
+ "$ref": "#/definitions/severity",
373
+ "description": "Require valid id attribute values (non-empty, no whitespace)",
374
+ "default": "error"
375
+ },
376
+ "void-content": {
377
+ "$ref": "#/definitions/severity",
378
+ "description": "Disallow content inside void elements",
379
+ "default": "error"
380
+ },
281
381
  "valid-attr-value": {
282
382
  "$ref": "#/definitions/severity",
283
383
  "description": "Validate enumerated attribute values",