rintenki 0.14.4 → 0.15.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
@@ -15,7 +15,7 @@ The name "rintenki" comes from the Japanese word "輪転機" (rintenki), meaning
15
15
  - LSP server for editor integration
16
16
  - Vue / JSX / eRuby / Astro support via parser plugins
17
17
  - Per-rule severity customization and inline disable comments
18
- - JSON Schema for `.rintenkirc.json` editor completion
18
+ - Type-safe configuration via `rintenki.config.ts` with `defineRintenkiConfig`
19
19
 
20
20
  ## Install
21
21
 
@@ -37,7 +37,7 @@ npx rintenki --watch "src/**/*.html"
37
37
  ```
38
38
  rintenki [options] <files...>
39
39
 
40
- -c, --config <path> Path to config file (default: .rintenkirc.json)
40
+ -c, --config <path> Path to config file (default: rintenki.config.ts)
41
41
  -f, --format <format> Output format: stylish (default), json, sarif
42
42
  --fix Auto-fix fixable rules
43
43
  -w, --watch Watch files for changes and re-lint
@@ -53,25 +53,30 @@ const { lint, fix } = require("rintenki");
53
53
  const result = lint("<html><body><img></body></html>");
54
54
  console.log(result.diagnostics);
55
55
 
56
- const fixed = fix('<DIV Class="foo">text</DIV>');
57
- console.log(fixed.output); // <div class="foo">text</div>
56
+ const fixed = fix('<!DOCTYPE html><DIV Class="foo">text</DIV>');
57
+ console.log(fixed.output); // <!DOCTYPE html><div class="foo">text</div>
58
58
  ```
59
59
 
60
60
  ## Configuration
61
61
 
62
- Place `.rintenkirc.json` in your project root:
62
+ Place `rintenki.config.ts` in your project root:
63
63
 
64
- ```json
65
- {
66
- "rules": {
64
+ ```ts
65
+ // rintenki.config.ts
66
+ import { defineRintenkiConfig } from "rintenki";
67
+
68
+ export default defineRintenkiConfig({
69
+ rules: {
67
70
  "doctype": "error",
68
71
  "no-consecutive-br": "warning",
69
- "no-hard-code-id": "off"
72
+ "no-hard-code-id": "off",
70
73
  },
71
- "ignore": ["dist/**", "vendor/**"]
72
- }
74
+ ignore: ["dist/**", "vendor/**"],
75
+ });
73
76
  ```
74
77
 
78
+ `rintenki.config.js` is also supported. `defineRintenkiConfig` provides autocomplete and type checking for rule names and severity values.
79
+
75
80
  | Value | Description |
76
81
  |-------|-------------|
77
82
  | `"error"` | Report as error (exit code 1) |
@@ -81,7 +86,12 @@ Place `.rintenkirc.json` in your project root:
81
86
 
82
87
  ### Ignore Patterns
83
88
 
84
- Use a `.rintenkiignore` file or the `ignore` field in config. `node_modules` is always excluded.
89
+ Two equivalent ways to exclude files (both can be used together):
90
+
91
+ 1. **`ignore` field in `rintenki.config.ts`** — glob patterns inside the config
92
+ 2. **`.rintenkiignore` file** at the project root — one glob per line, `#` for comments, blank lines ignored (same format as `.gitignore`)
93
+
94
+ `**/node_modules/**` is always excluded automatically. See the [Configuration guide](https://rintenki.dev/guide/configuration.html#ignore-patterns) for details.
85
95
 
86
96
  ### Inline Disable Comments
87
97
 
@@ -103,12 +113,15 @@ Use a `.rintenkiignore` file or the `ignore` field in config. `node_modules` is
103
113
 
104
114
  Parsers are auto-detected from installed packages, or configured explicitly:
105
115
 
106
- ```json
107
- {
108
- "parser": {
109
- ".vue": "@rintenki/vue-parser"
110
- }
111
- }
116
+ ```ts
117
+ // rintenki.config.ts
118
+ import { defineRintenkiConfig } from "rintenki";
119
+
120
+ export default defineRintenkiConfig({
121
+ parser: {
122
+ ".vue": "@rintenki/vue-parser",
123
+ },
124
+ });
112
125
  ```
113
126
 
114
127
  ## Rules
package/bin/rintenki.js CHANGED
@@ -4,6 +4,7 @@ const { existsSync, readFileSync, writeFileSync, watch } = require("fs");
4
4
  const { resolve, relative, dirname, join } = require("path");
5
5
  const { createHash } = require("crypto");
6
6
  const { glob } = require("tinyglobby");
7
+ const { createJiti } = require("jiti");
7
8
  const { lint, fix } = require("../index");
8
9
 
9
10
  let loadParser;
@@ -21,14 +22,28 @@ const COLORS = {
21
22
  reset: "\x1b[0m",
22
23
  };
23
24
 
24
- const CONFIG_FILES = [".rintenkirc.json", ".rintenkirc"];
25
+ const CONFIG_FILES = ["rintenki.config.ts", "rintenki.config.js"];
25
26
  const IGNORE_FILE = ".rintenkiignore";
26
27
 
27
- function parseJsonFile(filePath) {
28
+ let jitiInstance;
29
+ function getJiti() {
30
+ if (!jitiInstance) {
31
+ jitiInstance = createJiti(__filename);
32
+ }
33
+ return jitiInstance;
34
+ }
35
+
36
+ /**
37
+ * Load a rintenki config file. Supports both TypeScript (`rintenki.config.ts`)
38
+ * and JavaScript (`rintenki.config.js`) configs. Both ES module (`export default`)
39
+ * and CommonJS (`module.exports`) styles work via the jiti runtime.
40
+ */
41
+ function loadConfigFile(filePath) {
28
42
  try {
29
- return JSON.parse(readFileSync(filePath, "utf-8"));
43
+ const mod = getJiti()(filePath);
44
+ return mod.default ?? mod;
30
45
  } catch (err) {
31
- console.error(`Failed to parse config file: ${filePath}`);
46
+ console.error(`Failed to load config file: ${filePath}`);
32
47
  console.error(err.message);
33
48
  process.exit(1);
34
49
  }
@@ -41,13 +56,13 @@ function loadConfig(configPath) {
41
56
  console.error(`Config file not found: ${configPath}`);
42
57
  process.exit(1);
43
58
  }
44
- return parseJsonFile(abs);
59
+ return loadConfigFile(abs);
45
60
  }
46
61
 
47
62
  for (const name of CONFIG_FILES) {
48
63
  const abs = resolve(name);
49
64
  if (existsSync(abs)) {
50
- return parseJsonFile(abs);
65
+ return loadConfigFile(abs);
51
66
  }
52
67
  }
53
68
 
@@ -184,11 +199,10 @@ function buildSarif(jsonOutput) {
184
199
  async function main() {
185
200
  const args = parseArgs(process.argv.slice(2));
186
201
 
187
- if (args.help || args.files.length === 0) {
188
- console.log(`Usage: rintenki [options] <files...>
202
+ const helpText = `Usage: rintenki [options] <files...>
189
203
 
190
204
  Options:
191
- -c, --config <path> Path to config file (default: .rintenkirc.json)
205
+ -c, --config <path> Path to config file (default: rintenki.config.ts)
192
206
  -f, --format <format> Output format: stylish (default), json, sarif
193
207
  --fix Auto-fix fixable issues
194
208
  -w, --watch Watch files for changes and re-lint
@@ -196,16 +210,34 @@ Options:
196
210
  -h, --help Show help
197
211
 
198
212
  Ignore patterns:
199
- Create a .rintenkiignore file or use "ignore" in config to exclude files.
200
- node_modules is always ignored.
213
+ Two equivalent options (both can be combined):
214
+ 1. ".rintenkiignore" file at the project root — one glob per line,
215
+ # for comments, blank lines ignored.
216
+ 2. "ignore" field in rintenki.config.ts — array of glob patterns.
217
+ node_modules is always excluded.
201
218
 
202
219
  Examples:
203
220
  rintenki index.html
204
221
  rintenki "src/**/*.html"
205
- rintenki --format json "src/**/*.html"`);
222
+ rintenki --format json "src/**/*.html"`;
223
+
224
+ if (args.help) {
225
+ console.log(helpText);
206
226
  process.exit(0);
207
227
  }
208
228
 
229
+ if (args.files.length === 0) {
230
+ console.error("Error: No files specified.\n");
231
+ console.error(helpText);
232
+ process.exit(1);
233
+ }
234
+
235
+ const validFormats = ["stylish", "json", "sarif"];
236
+ if (args.format && !validFormats.includes(args.format)) {
237
+ console.error(`Error: Invalid format "${args.format}". Valid formats: ${validFormats.join(", ")}`);
238
+ process.exit(1);
239
+ }
240
+
209
241
  const config = loadConfig(args.config);
210
242
  const ignorePatterns = loadIgnorePatterns(config);
211
243
  const files = await glob(args.files, { absolute: true, ignore: ignorePatterns });
@@ -255,8 +287,31 @@ Examples:
255
287
 
256
288
  const PARALLEL_THRESHOLD = 20;
257
289
 
290
+ function internalErrorDiagnostic(message) {
291
+ return {
292
+ rule: "internal-error",
293
+ message,
294
+ severity: "error",
295
+ line: 1,
296
+ col: null,
297
+ endLine: null,
298
+ endCol: null,
299
+ fixable: false,
300
+ hint: null,
301
+ };
302
+ }
303
+
258
304
  function lintOneFile(file, config, args) {
259
- let source = readFileSync(file, "utf-8");
305
+ let source;
306
+ try {
307
+ source = readFileSync(file, "utf-8");
308
+ } catch (err) {
309
+ return {
310
+ diagnostics: [internalErrorDiagnostic(`Failed to read file: ${err.message}`)],
311
+ fixedCount: 0,
312
+ };
313
+ }
314
+
260
315
  let lineOffset = 0;
261
316
  let isFragment = false;
262
317
 
@@ -344,7 +399,18 @@ async function lintFiles(files, config, args, cache) {
344
399
  // Sequential mode (with optional cache for watch)
345
400
  fileResults = [];
346
401
  for (const file of files) {
347
- const source = readFileSync(file, "utf-8");
402
+ let source;
403
+ try {
404
+ source = readFileSync(file, "utf-8");
405
+ } catch (err) {
406
+ fileResults.push({
407
+ file,
408
+ rel: relative(process.cwd(), file),
409
+ diagnostics: [internalErrorDiagnostic(`Failed to read file: ${err.message}`)],
410
+ fixedCount: 0,
411
+ });
412
+ continue;
413
+ }
348
414
  const hash = fileHash(source);
349
415
 
350
416
  if (cache) {
package/bin/worker.js CHANGED
@@ -13,41 +13,59 @@ const { files, config, fix: shouldFix } = workerData;
13
13
  const results = [];
14
14
 
15
15
  for (const file of files) {
16
- let source = readFileSync(file, "utf-8");
17
- let lineOffset = 0;
18
- let isFragment = false;
19
-
20
- const parser = loadParser(file, config?.parser);
21
- if (parser) {
22
- const preprocessed = parser.preprocess(source);
23
- source = preprocessed.html;
24
- lineOffset = preprocessed.lineOffset;
25
- isFragment = preprocessed.isFragment ?? false;
26
- if (!source) continue;
27
- }
16
+ try {
17
+ let source = readFileSync(file, "utf-8");
18
+ let lineOffset = 0;
19
+ let isFragment = false;
20
+
21
+ const parser = loadParser(file, config?.parser);
22
+ if (parser) {
23
+ const preprocessed = parser.preprocess(source);
24
+ source = preprocessed.html;
25
+ lineOffset = preprocessed.lineOffset;
26
+ isFragment = preprocessed.isFragment ?? false;
27
+ if (!source) continue;
28
+ }
28
29
 
29
- let html = source;
30
- let fixedCount = 0;
30
+ let html = source;
31
+ let fixedCount = 0;
31
32
 
32
- if (shouldFix) {
33
- const fixResult = fix(html, config);
34
- if (fixResult.fixedCount > 0) {
35
- writeFileSync(file, fixResult.output, "utf-8");
36
- html = fixResult.output;
37
- fixedCount = fixResult.fixedCount;
33
+ if (shouldFix) {
34
+ const fixResult = fix(html, config);
35
+ if (fixResult.fixedCount > 0) {
36
+ writeFileSync(file, fixResult.output, "utf-8");
37
+ html = fixResult.output;
38
+ fixedCount = fixResult.fixedCount;
39
+ }
38
40
  }
39
- }
40
41
 
41
- const result = lint(html, config, isFragment);
42
+ const result = lint(html, config, isFragment);
42
43
 
43
- if (lineOffset > 0) {
44
- for (const d of result.diagnostics) {
45
- if (d.line != null) d.line += lineOffset;
46
- if (d.endLine != null) d.endLine += lineOffset;
44
+ if (lineOffset > 0) {
45
+ for (const d of result.diagnostics) {
46
+ if (d.line != null) d.line += lineOffset;
47
+ if (d.endLine != null) d.endLine += lineOffset;
48
+ }
47
49
  }
48
- }
49
50
 
50
- results.push({ file, diagnostics: result.diagnostics, fixedCount });
51
+ results.push({ file, diagnostics: result.diagnostics, fixedCount });
52
+ } catch (err) {
53
+ results.push({
54
+ file,
55
+ diagnostics: [{
56
+ rule: "internal-error",
57
+ message: `Failed to process file: ${err.message}`,
58
+ severity: "error",
59
+ line: 1,
60
+ col: null,
61
+ endLine: null,
62
+ endCol: null,
63
+ fixable: false,
64
+ hint: null,
65
+ }],
66
+ fixedCount: 0,
67
+ });
68
+ }
51
69
  }
52
70
 
53
71
  parentPort.postMessage(results);
package/index.d.ts CHANGED
@@ -19,18 +19,154 @@ export interface FixResult {
19
19
  fixedCount: number;
20
20
  }
21
21
 
22
- export type RuleSeverity = "error" | "warning" | "off" | boolean;
22
+ export type RuleName =
23
+ | "address-content-model"
24
+ | "aria-attr-conflicts"
25
+ | "aria-attr-valid-values"
26
+ | "aria-hidden-focusable"
27
+ | "aria-naming-prohibited"
28
+ | "aria-role-conflicts"
29
+ | "attr-duplication"
30
+ | "attr-value-quotes"
31
+ | "base-before-urls"
32
+ | "base-has-href-or-target"
33
+ | "case-sensitive-attr-name"
34
+ | "case-sensitive-tag-name"
35
+ | "character-reference"
36
+ | "class-naming"
37
+ | "colspan-rowspan-range"
38
+ | "deprecated-attr"
39
+ | "deprecated-element"
40
+ | "dfn-no-dfn-descendants"
41
+ | "disallowed-element"
42
+ | "doctype"
43
+ | "empty-heading"
44
+ | "empty-title"
45
+ | "end-tag"
46
+ | "fieldset-has-legend"
47
+ | "figcaption-position"
48
+ | "form-dup-name"
49
+ | "header-footer-nesting"
50
+ | "heading-levels"
51
+ | "id-duplication"
52
+ | "ineffective-attr"
53
+ | "input-attr-applicability"
54
+ | "internal-error"
55
+ | "invalid-attr"
56
+ | "label-has-control"
57
+ | "landmark-roles"
58
+ | "link-constraints"
59
+ | "link-rel-or-itemprop"
60
+ | "media-has-captions"
61
+ | "meta-constraints"
62
+ | "neighbor-popovers"
63
+ | "no-abstract-role"
64
+ | "no-ambiguous-navigable-target-names"
65
+ | "no-aria-checked-mismatch"
66
+ | "no-aria-disabled-link"
67
+ | "no-aria-hidden-body"
68
+ | "no-autoplay-media"
69
+ | "no-boolean-attr-value"
70
+ | "no-consecutive-br"
71
+ | "no-default-value"
72
+ | "no-dup-class"
73
+ | "no-duplicate-accesskey"
74
+ | "no-duplicate-base"
75
+ | "no-duplicate-dt"
76
+ | "no-duplicate-in-head"
77
+ | "no-duplicate-landmark"
78
+ | "no-duplicate-track"
79
+ | "no-empty-palpable-content"
80
+ | "no-empty-track-label"
81
+ | "no-form-without-action"
82
+ | "no-generic-role"
83
+ | "no-hard-code-id"
84
+ | "no-implicit-button-type"
85
+ | "no-inline-style"
86
+ | "no-javascript-url"
87
+ | "no-multiple-default-track"
88
+ | "no-nested-forms"
89
+ | "no-nested-interactive"
90
+ | "no-non-scalable-viewport"
91
+ | "no-obsolete-doctype"
92
+ | "no-orphaned-end-tag"
93
+ | "no-positive-tabindex"
94
+ | "no-redundant-role"
95
+ | "no-refer-to-non-existent-id"
96
+ | "no-role-on-meta-elements"
97
+ | "no-tabindex-on-dialog"
98
+ | "no-target-blank-without-rel"
99
+ | "no-use-event-handler-attr"
100
+ | "obsolete-but-conforming"
101
+ | "parse-error"
102
+ | "permitted-contents"
103
+ | "picture-structure"
104
+ | "placeholder-label-option"
105
+ | "prefer-native-over-aria"
106
+ | "require-accessible-name"
107
+ | "require-datetime"
108
+ | "require-meta-charset"
109
+ | "required-attr"
110
+ | "required-element"
111
+ | "required-h1"
112
+ | "script-type"
113
+ | "src-not-empty"
114
+ | "summary-first-child"
115
+ | "table-has-caption"
116
+ | "table-row-column-alignment"
117
+ | "th-content-restrictions"
118
+ | "th-has-scope"
119
+ | "track-has-srclang"
120
+ | "unique-main"
121
+ | "use-list"
122
+ | "valid-attr-value"
123
+ | "valid-autocomplete"
124
+ | "valid-id"
125
+ | "valid-rel"
126
+ | "void-content"
127
+ | "wai-aria";
23
128
 
24
- export interface LintConfig {
129
+ export type RuleSeverity = "error" | "warning" | "warn" | "off" | boolean;
130
+
131
+ export type ParserSyntax = ".vue" | ".jsx" | ".tsx" | ".erb" | ".astro";
132
+
133
+ export interface RintenkiConfig {
25
134
  /**
26
135
  * Rule name -> severity.
27
- * - "error": report as error
136
+ * - "error": report as error (exit code 1)
28
137
  * - "warning" | "warn": report as warning
29
138
  * - "off" | false: disable the rule
30
139
  * - true: use default severity
31
140
  */
32
- rules?: Record<string, RuleSeverity>;
141
+ rules?: Partial<Record<RuleName, RuleSeverity>>;
142
+ /** Glob patterns to ignore. */
143
+ ignore?: string[];
144
+ /** File extension -> parser package mapping. */
145
+ parser?: Partial<Record<ParserSyntax, string>>;
33
146
  }
34
147
 
35
- export function lint(html: string, config?: LintConfig, isFragment?: boolean): LintResult;
36
- export function fix(html: string, config?: LintConfig): FixResult;
148
+ /** @deprecated Use `RintenkiConfig` instead. */
149
+ export type LintConfig = RintenkiConfig;
150
+
151
+ /**
152
+ * Define a type-safe rintenki configuration.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * // rintenki.config.ts
157
+ * import { defineRintenkiConfig } from "rintenki";
158
+ *
159
+ * export default defineRintenkiConfig({
160
+ * rules: {
161
+ * "doctype": "error",
162
+ * "no-consecutive-br": "warning",
163
+ * "no-hard-code-id": "off",
164
+ * },
165
+ * ignore: ["dist/**", "vendor/**"],
166
+ * });
167
+ * ```
168
+ */
169
+ export function defineRintenkiConfig(config: RintenkiConfig): RintenkiConfig;
170
+
171
+ export function lint(html: string, config?: RintenkiConfig, isFragment?: boolean): LintResult;
172
+ export function fix(html: string, config?: RintenkiConfig): FixResult;
package/index.js CHANGED
@@ -6,20 +6,14 @@ const { platform, arch } = process;
6
6
  function loadBinding() {
7
7
  const triples = {
8
8
  "darwin-arm64": "rintenki.darwin-arm64.node",
9
- "darwin-x64": "rintenki.darwin-x64.node",
10
9
  "linux-x64": "rintenki.linux-x64-gnu.node",
11
- "linux-arm64": "rintenki.linux-arm64-gnu.node",
12
10
  "win32-x64": "rintenki.win32-x64-msvc.node",
13
- "win32-arm64": "rintenki.win32-arm64-msvc.node",
14
11
  };
15
12
 
16
13
  const packages = {
17
14
  "darwin-arm64": "rintenki-darwin-arm64",
18
- "darwin-x64": "rintenki-darwin-x64",
19
15
  "linux-x64": "rintenki-linux-x64-gnu",
20
- "linux-arm64": "rintenki-linux-arm64-gnu",
21
16
  "win32-x64": "rintenki-win32-x64-msvc",
22
- "win32-arm64": "rintenki-win32-arm64-msvc",
23
17
  };
24
18
 
25
19
  const key = `${platform}-${arch}`;
@@ -47,3 +41,6 @@ function loadBinding() {
47
41
  const binding = loadBinding();
48
42
  module.exports.lint = binding.lint;
49
43
  module.exports.fix = binding.fix;
44
+ module.exports.defineRintenkiConfig = function defineRintenkiConfig(config) {
45
+ return config;
46
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rintenki",
3
- "version": "0.14.4",
3
+ "version": "0.15.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",
@@ -34,8 +34,7 @@
34
34
  "bin/rintenki.js",
35
35
  "bin/worker.js",
36
36
  "rintenki.*.node",
37
- "README.md",
38
- "rintenkirc.schema.json"
37
+ "README.md"
39
38
  ],
40
39
  "napi": {
41
40
  "binaryName": "rintenki",
@@ -46,13 +45,14 @@
46
45
  ]
47
46
  },
48
47
  "dependencies": {
49
- "tinyglobby": "0.2.15"
48
+ "jiti": "^2.7.0",
49
+ "tinyglobby": "0.2.16"
50
50
  },
51
51
  "devDependencies": {
52
- "@napi-rs/cli": "3.6.0",
53
- "@types/node": "25.5.0",
54
- "typescript": "6.0.2",
55
- "vitest": "4.1.2"
52
+ "@napi-rs/cli": "3.6.2",
53
+ "@types/node": "25.6.2",
54
+ "typescript": "6.0.3",
55
+ "vitest": "4.1.5"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "napi build --release --platform --no-js --dts .napi-output.d.ts && rm -f .napi-output.d.ts",
Binary file
Binary file
Binary file
@@ -1,561 +0,0 @@
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-conflicts": {
12
- "$ref": "#/definitions/severity",
13
- "description": "Detect conflicting ARIA and native HTML attributes",
14
- "default": "error"
15
- },
16
- "aria-attr-valid-values": {
17
- "$ref": "#/definitions/severity",
18
- "description": "Validate ARIA attribute values",
19
- "default": "warning"
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
- },
31
- "aria-role-conflicts": {
32
- "$ref": "#/definitions/severity",
33
- "description": "Detect conflicts between explicit role and element's implicit role",
34
- "default": "warning"
35
- },
36
- "attr-duplication": {
37
- "$ref": "#/definitions/severity",
38
- "description": "Disallow duplicate attributes on the same element",
39
- "default": "error"
40
- },
41
- "attr-value-quotes": {
42
- "$ref": "#/definitions/severity",
43
- "description": "Require attribute values to be quoted",
44
- "default": "warning"
45
- },
46
- "case-sensitive-attr-name": {
47
- "$ref": "#/definitions/severity",
48
- "description": "Enforce lowercase attribute names (auto-fixable)",
49
- "default": "warning"
50
- },
51
- "colspan-rowspan-range": {
52
- "$ref": "#/definitions/severity",
53
- "description": "Require colspan and rowspan values to be within valid ranges",
54
- "default": "error"
55
- },
56
- "case-sensitive-tag-name": {
57
- "$ref": "#/definitions/severity",
58
- "description": "Enforce lowercase tag names (auto-fixable)",
59
- "default": "warning"
60
- },
61
- "character-reference": {
62
- "$ref": "#/definitions/severity",
63
- "description": "Require unescaped & to be written as &amp;",
64
- "default": "warning"
65
- },
66
- "class-naming": {
67
- "$ref": "#/definitions/severity",
68
- "description": "Enforce class name conventions (opt-in)",
69
- "default": "off"
70
- },
71
- "dfn-no-dfn-descendants": {
72
- "$ref": "#/definitions/severity",
73
- "description": "Disallow dfn nested inside another dfn",
74
- "default": "error"
75
- },
76
- "deprecated-attr": {
77
- "$ref": "#/definitions/severity",
78
- "description": "Disallow deprecated HTML attributes",
79
- "default": "error"
80
- },
81
- "deprecated-element": {
82
- "$ref": "#/definitions/severity",
83
- "description": "Disallow deprecated HTML elements",
84
- "default": "error"
85
- },
86
- "disallowed-element": {
87
- "$ref": "#/definitions/severity",
88
- "description": "Disallow specified elements (opt-in)",
89
- "default": "off"
90
- },
91
- "doctype": {
92
- "$ref": "#/definitions/severity",
93
- "description": "Require DOCTYPE declaration",
94
- "default": "error"
95
- },
96
- "empty-heading": {
97
- "$ref": "#/definitions/severity",
98
- "description": "Detect empty heading elements",
99
- "default": "warning"
100
- },
101
- "empty-title": {
102
- "$ref": "#/definitions/severity",
103
- "description": "Detect empty title element",
104
- "default": "error"
105
- },
106
- "end-tag": {
107
- "$ref": "#/definitions/severity",
108
- "description": "Require closing tags for non-void elements",
109
- "default": "warning"
110
- },
111
- "figcaption-position": {
112
- "$ref": "#/definitions/severity",
113
- "description": "Require figcaption to be the first or last child of figure",
114
- "default": "warning"
115
- },
116
- "fieldset-has-legend": {
117
- "$ref": "#/definitions/severity",
118
- "description": "Require legend element in fieldset",
119
- "default": "warning"
120
- },
121
- "form-dup-name": {
122
- "$ref": "#/definitions/severity",
123
- "description": "Detect duplicate form control names within a form",
124
- "default": "warning"
125
- },
126
- "header-footer-nesting": {
127
- "$ref": "#/definitions/severity",
128
- "description": "Disallow header/footer/main nesting inside header or footer",
129
- "default": "error"
130
- },
131
- "heading-levels": {
132
- "$ref": "#/definitions/severity",
133
- "description": "Disallow skipping heading levels (e.g. h1 to h3)",
134
- "default": "error"
135
- },
136
- "id-duplication": {
137
- "$ref": "#/definitions/severity",
138
- "description": "Disallow duplicate id attribute values",
139
- "default": "error"
140
- },
141
- "input-attr-applicability": {
142
- "$ref": "#/definitions/severity",
143
- "description": "Detect attributes that do not apply to the input type",
144
- "default": "warning"
145
- },
146
- "ineffective-attr": {
147
- "$ref": "#/definitions/severity",
148
- "description": "Disallow attributes that have no effect on the element",
149
- "default": "warning"
150
- },
151
- "invalid-attr": {
152
- "$ref": "#/definitions/severity",
153
- "description": "Disallow non-standard attributes",
154
- "default": "error"
155
- },
156
- "media-has-captions": {
157
- "$ref": "#/definitions/severity",
158
- "description": "Require captions or subtitles track on video elements",
159
- "default": "warning"
160
- },
161
- "meta-constraints": {
162
- "$ref": "#/definitions/severity",
163
- "description": "Validate meta element attribute constraints",
164
- "default": "error"
165
- },
166
- "link-constraints": {
167
- "$ref": "#/definitions/severity",
168
- "description": "Validate link element attribute constraints (preload requires as, etc.)",
169
- "default": "error"
170
- },
171
- "label-has-control": {
172
- "$ref": "#/definitions/severity",
173
- "description": "Require label elements to have an associated control",
174
- "default": "error"
175
- },
176
- "landmark-roles": {
177
- "$ref": "#/definitions/severity",
178
- "description": "Disallow nested landmark roles",
179
- "default": "warning"
180
- },
181
- "neighbor-popovers": {
182
- "$ref": "#/definitions/severity",
183
- "description": "Require popover triggers to be adjacent to their targets (opt-in)",
184
- "default": "off"
185
- },
186
- "no-abstract-role": {
187
- "$ref": "#/definitions/severity",
188
- "description": "Disallow abstract ARIA roles",
189
- "default": "error"
190
- },
191
- "no-autoplay-media": {
192
- "$ref": "#/definitions/severity",
193
- "description": "Detect autoplay without muted on video/audio elements",
194
- "default": "warning"
195
- },
196
- "no-aria-hidden-body": {
197
- "$ref": "#/definitions/severity",
198
- "description": "Disallow aria-hidden on body element",
199
- "default": "error"
200
- },
201
- "no-ambiguous-navigable-target-names": {
202
- "$ref": "#/definitions/severity",
203
- "description": "Disallow ambiguous target attribute values",
204
- "default": "warning"
205
- },
206
- "no-boolean-attr-value": {
207
- "$ref": "#/definitions/severity",
208
- "description": "Disallow redundant boolean attribute values (auto-fixable)",
209
- "default": "warning"
210
- },
211
- "no-consecutive-br": {
212
- "$ref": "#/definitions/severity",
213
- "description": "Disallow consecutive br elements",
214
- "default": "warning"
215
- },
216
- "no-dup-class": {
217
- "$ref": "#/definitions/severity",
218
- "description": "Detect duplicate class names in class attribute",
219
- "default": "warning"
220
- },
221
- "no-default-value": {
222
- "$ref": "#/definitions/severity",
223
- "description": "Disallow default attribute values (auto-fixable)",
224
- "default": "warning"
225
- },
226
- "no-duplicate-landmark": {
227
- "$ref": "#/definitions/severity",
228
- "description": "Require aria-label on duplicate landmarks",
229
- "default": "warning"
230
- },
231
- "no-duplicate-in-head": {
232
- "$ref": "#/definitions/severity",
233
- "description": "Disallow duplicate charset, viewport, or description meta elements",
234
- "default": "error"
235
- },
236
- "no-duplicate-base": {
237
- "$ref": "#/definitions/severity",
238
- "description": "Disallow multiple base or title elements in the document",
239
- "default": "error"
240
- },
241
- "no-duplicate-dt": {
242
- "$ref": "#/definitions/severity",
243
- "description": "Disallow duplicate dt elements in a dl",
244
- "default": "error"
245
- },
246
- "no-empty-track-label": {
247
- "$ref": "#/definitions/severity",
248
- "description": "Disallow empty label attribute on track elements",
249
- "default": "warning"
250
- },
251
- "no-empty-palpable-content": {
252
- "$ref": "#/definitions/severity",
253
- "description": "Disallow empty interactive or palpable content elements",
254
- "default": "warning"
255
- },
256
- "no-javascript-url": {
257
- "$ref": "#/definitions/severity",
258
- "description": "Detect javascript: URLs in href attributes",
259
- "default": "error"
260
- },
261
- "no-hard-code-id": {
262
- "$ref": "#/definitions/severity",
263
- "description": "Disallow hardcoded id attributes (opt-in)",
264
- "default": "off"
265
- },
266
- "no-obsolete-doctype": {
267
- "$ref": "#/definitions/severity",
268
- "description": "Detect legacy XHTML/HTML4 doctypes",
269
- "default": "warning"
270
- },
271
- "no-nested-forms": {
272
- "$ref": "#/definitions/severity",
273
- "description": "Disallow nested form elements",
274
- "default": "error"
275
- },
276
- "no-nested-interactive": {
277
- "$ref": "#/definitions/severity",
278
- "description": "Disallow interactive content inside a or button elements",
279
- "default": "error"
280
- },
281
- "no-implicit-button-type": {
282
- "$ref": "#/definitions/severity",
283
- "description": "Require explicit type attribute on button elements",
284
- "default": "warning"
285
- },
286
- "no-inline-style": {
287
- "$ref": "#/definitions/severity",
288
- "description": "Disallow inline style attributes",
289
- "default": "warning"
290
- },
291
- "no-non-scalable-viewport": {
292
- "$ref": "#/definitions/severity",
293
- "description": "Disallow user-scalable=no or maximum-scale=1 in viewport meta",
294
- "default": "error"
295
- },
296
- "no-target-blank-without-rel": {
297
- "$ref": "#/definitions/severity",
298
- "description": "Require rel=\"noopener\" on target=\"_blank\" anchors",
299
- "default": "warning"
300
- },
301
- "no-orphaned-end-tag": {
302
- "$ref": "#/definitions/severity",
303
- "description": "Disallow unmatched closing tags",
304
- "default": "error"
305
- },
306
- "no-tabindex-on-dialog": {
307
- "$ref": "#/definitions/severity",
308
- "description": "Disallow tabindex attribute on dialog elements",
309
- "default": "error"
310
- },
311
- "no-refer-to-non-existent-id": {
312
- "$ref": "#/definitions/severity",
313
- "description": "Disallow references to non-existent id values",
314
- "default": "error"
315
- },
316
- "no-redundant-role": {
317
- "$ref": "#/definitions/severity",
318
- "description": "Detect redundant explicit ARIA roles matching implicit role",
319
- "default": "warning"
320
- },
321
- "no-role-on-meta-elements": {
322
- "$ref": "#/definitions/severity",
323
- "description": "Disallow role and aria-* attributes on meta elements",
324
- "default": "error"
325
- },
326
- "no-positive-tabindex": {
327
- "$ref": "#/definitions/severity",
328
- "description": "Disallow positive tabindex values",
329
- "default": "warning"
330
- },
331
- "no-use-event-handler-attr": {
332
- "$ref": "#/definitions/severity",
333
- "description": "Disallow inline event handler attributes",
334
- "default": "warning"
335
- },
336
- "obsolete-but-conforming": {
337
- "$ref": "#/definitions/severity",
338
- "description": "Warn about obsolete but conforming features",
339
- "default": "warning"
340
- },
341
- "script-type": {
342
- "$ref": "#/definitions/severity",
343
- "description": "Validate script type attribute values",
344
- "default": "error"
345
- },
346
- "src-not-empty": {
347
- "$ref": "#/definitions/severity",
348
- "description": "Disallow empty src or href attributes",
349
- "default": "error"
350
- },
351
- "picture-structure": {
352
- "$ref": "#/definitions/severity",
353
- "description": "Validate picture element structure",
354
- "default": "error"
355
- },
356
- "permitted-contents": {
357
- "$ref": "#/definitions/severity",
358
- "description": "Enforce HTML content model restrictions",
359
- "default": "error"
360
- },
361
- "placeholder-label-option": {
362
- "$ref": "#/definitions/severity",
363
- "description": "Require placeholder option in select elements with required attribute",
364
- "default": "warning"
365
- },
366
- "require-accessible-name": {
367
- "$ref": "#/definitions/severity",
368
- "description": "Require accessible names for interactive elements",
369
- "default": "error"
370
- },
371
- "require-datetime": {
372
- "$ref": "#/definitions/severity",
373
- "description": "Require datetime attribute on time elements",
374
- "default": "error"
375
- },
376
- "required-attr": {
377
- "$ref": "#/definitions/severity",
378
- "description": "Require specification-mandated attributes",
379
- "default": "error"
380
- },
381
- "required-element": {
382
- "$ref": "#/definitions/severity",
383
- "description": "Require specification-mandated child elements",
384
- "default": "error"
385
- },
386
- "require-meta-charset": {
387
- "$ref": "#/definitions/severity",
388
- "description": "Require meta charset declaration in the document",
389
- "default": "off"
390
- },
391
- "required-h1": {
392
- "$ref": "#/definitions/severity",
393
- "description": "Require exactly one h1 element per document",
394
- "default": "error"
395
- },
396
- "summary-first-child": {
397
- "$ref": "#/definitions/severity",
398
- "description": "Require summary to be the first child of details",
399
- "default": "error"
400
- },
401
- "table-has-caption": {
402
- "$ref": "#/definitions/severity",
403
- "description": "Require caption element on data tables",
404
- "default": "warning"
405
- },
406
- "table-row-column-alignment": {
407
- "$ref": "#/definitions/severity",
408
- "description": "Require consistent column counts in table rows",
409
- "default": "warning"
410
- },
411
- "th-content-restrictions": {
412
- "$ref": "#/definitions/severity",
413
- "description": "Disallow header, footer, sectioning, and heading elements inside th",
414
- "default": "error"
415
- },
416
- "track-has-srclang": {
417
- "$ref": "#/definitions/severity",
418
- "description": "Require srclang on subtitles track elements",
419
- "default": "error"
420
- },
421
- "unique-main": {
422
- "$ref": "#/definitions/severity",
423
- "description": "Require at most one visible main element per document",
424
- "default": "error"
425
- },
426
- "use-list": {
427
- "$ref": "#/definitions/severity",
428
- "description": "Suggest using list elements for bullet-like text patterns",
429
- "default": "warning"
430
- },
431
- "valid-id": {
432
- "$ref": "#/definitions/severity",
433
- "description": "Require valid id attribute values (non-empty, no whitespace)",
434
- "default": "error"
435
- },
436
- "void-content": {
437
- "$ref": "#/definitions/severity",
438
- "description": "Disallow content inside void elements",
439
- "default": "error"
440
- },
441
- "valid-attr-value": {
442
- "$ref": "#/definitions/severity",
443
- "description": "Validate enumerated attribute values",
444
- "default": "error"
445
- },
446
- "valid-autocomplete": {
447
- "$ref": "#/definitions/severity",
448
- "description": "Validate autocomplete attribute values on form elements",
449
- "default": "warning"
450
- },
451
- "valid-rel": {
452
- "$ref": "#/definitions/severity",
453
- "description": "Validate rel attribute values for a, area, link, and form elements",
454
- "default": "error"
455
- },
456
- "wai-aria": {
457
- "$ref": "#/definitions/severity",
458
- "description": "Validate WAI-ARIA roles and attributes",
459
- "default": "error"
460
- },
461
- "no-duplicate-track": {
462
- "$ref": "#/definitions/severity",
463
- "description": "Disallow duplicate track elements with same kind, srclang, and label",
464
- "default": "error"
465
- },
466
- "no-multiple-default-track": {
467
- "$ref": "#/definitions/severity",
468
- "description": "Disallow multiple default track elements in the same category",
469
- "default": "error"
470
- },
471
- "base-before-urls": {
472
- "$ref": "#/definitions/severity",
473
- "description": "Require base href to appear before elements with URL attributes",
474
- "default": "warning"
475
- },
476
- "address-content-model": {
477
- "$ref": "#/definitions/severity",
478
- "description": "Disallow heading, sectioning, header, footer, address inside address",
479
- "default": "error"
480
- },
481
- "link-rel-or-itemprop": {
482
- "$ref": "#/definitions/severity",
483
- "description": "Require link to have rel or itemprop attribute",
484
- "default": "error"
485
- },
486
- "base-has-href-or-target": {
487
- "$ref": "#/definitions/severity",
488
- "description": "Require base to have at least href or target attribute",
489
- "default": "error"
490
- },
491
- "no-aria-disabled-link": {
492
- "$ref": "#/definitions/severity",
493
- "description": "Disallow aria-disabled on anchor elements with href",
494
- "default": "warning"
495
- },
496
- "no-generic-role": {
497
- "$ref": "#/definitions/severity",
498
- "description": "Disallow explicit role=\"generic\"",
499
- "default": "warning"
500
- },
501
- "no-aria-checked-mismatch": {
502
- "$ref": "#/definitions/severity",
503
- "description": "Disallow aria-checked on checkbox/radio with checked attribute",
504
- "default": "warning"
505
- },
506
- "th-has-scope": {
507
- "$ref": "#/definitions/severity",
508
- "description": "Require scope attribute on th elements",
509
- "default": "warning"
510
- },
511
- "prefer-native-over-aria": {
512
- "$ref": "#/definitions/severity",
513
- "description": "Prefer native HTML elements over ARIA roles",
514
- "default": "warning"
515
- },
516
- "no-duplicate-accesskey": {
517
- "$ref": "#/definitions/severity",
518
- "description": "Disallow duplicate accesskey values in the document",
519
- "default": "warning"
520
- },
521
- "no-form-without-action": {
522
- "$ref": "#/definitions/severity",
523
- "description": "Warn when form element has no action attribute",
524
- "default": "warning"
525
- }
526
- },
527
- "additionalProperties": {
528
- "$ref": "#/definitions/severity"
529
- }
530
- },
531
- "ignore": {
532
- "type": "array",
533
- "description": "Glob patterns for files to exclude from linting",
534
- "items": {
535
- "type": "string"
536
- }
537
- },
538
- "parser": {
539
- "type": "object",
540
- "description": "File extension to parser package mapping (e.g. { \".vue\": \"@rintenki/vue-parser\" })",
541
- "additionalProperties": {
542
- "type": "string"
543
- }
544
- }
545
- },
546
- "additionalProperties": false,
547
- "definitions": {
548
- "severity": {
549
- "oneOf": [
550
- {
551
- "type": "string",
552
- "enum": ["error", "warning", "warn", "off"]
553
- },
554
- {
555
- "type": "boolean"
556
- }
557
- ],
558
- "description": "Rule severity: \"error\", \"warning\"/\"warn\", \"off\", true (default severity), or false (off)"
559
- }
560
- }
561
- }