@wistia/oxlint-config 1.1.1 → 1.3.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
@@ -29,6 +29,8 @@ yarn add -D oxlint-tsgolint
29
29
 
30
30
  `oxlint-tsgolint` is a separate Go-based tool that builds your TypeScript program using `typescript-go` and runs type-aware rules (e.g. detecting unhandled promises, unsafe assignments). Without it installed, type-aware rules will not run.
31
31
 
32
+ _Note: this will likely be unnecessary in the future when Typescript v7 is released._
33
+
32
34
  ## Quick start
33
35
 
34
36
  Create an `oxlint.config.ts` in your project root:
@@ -161,12 +163,6 @@ export default defineConfig({
161
163
  5. Add short description of rule and link to rule documentation in code comments
162
164
  6. Never delete a rule — set it to `off` with a comment explaining why
163
165
 
164
- ## ESLint parity
165
-
166
- Differences between `@wistia/oxlint-config` and `@wistia/eslint-config`.
167
-
168
- oxlint and ESLint use different rule name prefixes. This document uses the eslint-config names. See [Rule Name Mapping](#rule-name-mapping) at the bottom for prefix translations.
169
-
170
166
  ### Why some rules are ESLint-only
171
167
 
172
168
  oxlint's jsPlugins feature can load ESLint plugins that export a standard `{ rules }` object. This works for packages like `eslint-plugin-import-x`, `eslint-plugin-storybook`, etc.
@@ -179,4 +175,5 @@ Several categories of rules cannot use this approach:
179
175
 
180
176
  3. **Rules requiring module resolution** (e.g. `import-x/no-unresolved`, `import-x/extensions`, `import-x/no-rename-default`). In ESLint, these rules use `eslint-import-resolver-typescript` to resolve TypeScript path aliases, barrel re-exports, and extensionless imports. The resolver is configured via ESLint's `settings['import-x/resolver']` — but oxlint's jsPlugin runner does not pass ESLint settings to plugins. Without a resolver, these rules can't find modules and produce thousands of false positives on every import.
181
177
 
182
- 4. **Old-style ESLint plugins** (e.g. `eslint-plugin-filenames`). These export rules as plain functions instead of objects with a `create` method. oxlint's jsPlugin runner requires the modern format (`{ meta, create }`).
178
+ 4. **Old-style ESLint plugins** (e.g. `eslint-plugin-filenames`). These export rules as plain functions instead of objects with a `create` method, which oxlint's jsPlugin runner requiring the modern `{ meta, create }` format — cannot load. The package is also abandoned. Where a rule from such a plugin is worth keeping, it is reimplemented in our own custom local jsPlugin (`custom`) under [`plugins/custom.mjs`](./plugins/custom.mjs).
179
+ - `custom/match-exported` is a faithful port of `eslint-plugin-filenames`' `match-exported`, enabled in `javascriptConfig` + `typescriptConfig`, matching eslint-config. (Its rule ID is namespaced `custom/` rather than `filenames/` to signal it is our own rule, not the upstream plugin.) The upstream `match-regex` and `no-index` rules are not ported — eslint-config disables both.
@@ -3,16 +3,22 @@ import { baseRules } from '../rules/base.mjs';
3
3
  import { importRules } from '../rules/import.mjs';
4
4
  import { promiseRules } from '../rules/promise.mjs';
5
5
  import { barrelFilesRules } from '../rules/barrel-files.mjs';
6
+ import { customRules } from '../rules/custom.mjs';
6
7
  import { unicornRules } from '../rules/unicorn.mjs';
7
8
 
8
9
  export default defineConfig({
9
10
  plugins: [...baseRules.plugins, ...importRules.plugins, ...promiseRules.plugins],
10
- jsPlugins: [...(importRules.jsPlugins || []), ...(barrelFilesRules.jsPlugins || [])],
11
+ jsPlugins: [
12
+ ...(importRules.jsPlugins || []),
13
+ ...(barrelFilesRules.jsPlugins || []),
14
+ ...(customRules.jsPlugins || []),
15
+ ],
11
16
  rules: {
12
17
  ...baseRules.rules,
13
18
  ...importRules.rules,
14
19
  ...promiseRules.rules,
15
20
  ...barrelFilesRules.rules,
21
+ ...customRules.rules,
16
22
  ...unicornRules.rules,
17
23
  },
18
24
  });
@@ -1,12 +1,18 @@
1
1
  import { defineConfig } from 'oxlint';
2
2
  import { storybookRules } from '../rules/storybook.mjs';
3
+ import { customRules } from '../rules/custom.mjs';
3
4
 
4
5
  export default defineConfig({
5
6
  overrides: [
6
7
  {
7
8
  files: ['**/*.stories.ts', '**/*.stories.tsx', '**/*.stories.js', '**/*.stories.jsx'],
8
- jsPlugins: storybookRules.jsPlugins,
9
- rules: storybookRules.rules,
9
+ jsPlugins: [...(storybookRules.jsPlugins || []), ...(customRules.jsPlugins || [])],
10
+ rules: {
11
+ ...storybookRules.rules,
12
+ // Decision: stories always default-export an object named `meta`, so
13
+ // the filename can never match the exported name.
14
+ 'custom/match-exported': 'off',
15
+ },
10
16
  },
11
17
  ],
12
18
  });
@@ -4,6 +4,7 @@ import { importRules } from '../rules/import.mjs';
4
4
  import { promiseRules } from '../rules/promise.mjs';
5
5
  import { typescriptRules } from '../rules/typescript.mjs';
6
6
  import { barrelFilesRules } from '../rules/barrel-files.mjs';
7
+ import { customRules } from '../rules/custom.mjs';
7
8
  import { unicornRules } from '../rules/unicorn.mjs';
8
9
 
9
10
  export default defineConfig({
@@ -17,13 +18,18 @@ export default defineConfig({
17
18
  ...promiseRules.plugins,
18
19
  ...typescriptRules.plugins,
19
20
  ],
20
- jsPlugins: [...(importRules.jsPlugins || []), ...(barrelFilesRules.jsPlugins || [])],
21
+ jsPlugins: [
22
+ ...(importRules.jsPlugins || []),
23
+ ...(barrelFilesRules.jsPlugins || []),
24
+ ...(customRules.jsPlugins || []),
25
+ ],
21
26
  rules: {
22
27
  ...baseRules.rules,
23
28
  ...importRules.rules,
24
29
  ...promiseRules.rules,
25
30
  ...typescriptRules.rules,
26
31
  ...barrelFilesRules.rules,
32
+ ...customRules.rules,
27
33
  ...unicornRules.rules,
28
34
  },
29
35
  });
package/index.d.mts CHANGED
@@ -21,6 +21,7 @@ export declare const storybookRules: RuleFile;
21
21
  export declare const styledComponentsRules: RuleFile;
22
22
  export declare const testingLibraryRules: RuleFile;
23
23
  export declare const barrelFilesRules: RuleFile;
24
+ export declare const customRules: RuleFile;
24
25
 
25
26
  // Configs
26
27
  export declare const javascriptConfig: OxlintConfig;
package/index.mjs CHANGED
@@ -13,6 +13,7 @@ export { storybookRules } from './rules/storybook.mjs';
13
13
  export { styledComponentsRules } from './rules/styled-components.mjs';
14
14
  export { testingLibraryRules } from './rules/testing-library.mjs';
15
15
  export { barrelFilesRules } from './rules/barrel-files.mjs';
16
+ export { customRules } from './rules/custom.mjs';
16
17
 
17
18
  // Configs
18
19
  export { default as javascriptConfig } from './configs/javascript.mjs';
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@wistia/oxlint-config",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "Wistia's Oxlint configurations",
5
5
  "packageManager": "yarn@4.17.0",
6
6
  "type": "module",
7
7
  "files": [
8
8
  "configs",
9
9
  "rules",
10
+ "plugins",
10
11
  "index.mjs",
11
12
  "index.d.mts"
12
13
  ],
@@ -27,6 +28,7 @@
27
28
  "./testing-library": "./configs/testing-library.mjs",
28
29
  "./rules/barrel-files": "./rules/barrel-files.mjs",
29
30
  "./rules/base": "./rules/base.mjs",
31
+ "./rules/custom": "./rules/custom.mjs",
30
32
  "./rules/import": "./rules/import.mjs",
31
33
  "./rules/node": "./rules/node.mjs",
32
34
  "./rules/playwright": "./rules/playwright.mjs",
@@ -0,0 +1,188 @@
1
+ // `custom` — Wistia's custom oxlint jsPlugin, holding rules with no native
2
+ // oxlint or loadable-ESLint-plugin equivalent.
3
+ //
4
+ // `custom/match-exported` is a faithful port of `match-exported` from the
5
+ // abandoned eslint-plugin-filenames (https://github.com/selaux/eslint-plugin-filenames),
6
+ // rewritten in oxlint's modern `{ meta, create }` plugin format. The original
7
+ // ships its rules as plain functions, which oxlint's jsPlugin runner cannot
8
+ // load — see the ESLint-parity section of the README.
9
+ //
10
+ // The rule ensures a file's name matches the name of its default export
11
+ // (`export default ...`) or `module.exports = ...`. Files with no default
12
+ // export, or an anonymous/object default export, are ignored. `index.*` files
13
+ // are matched against their parent directory name instead.
14
+ import path from 'node:path';
15
+
16
+ // --- case transforms (lodash-equivalent for identifier-shaped input) ----------
17
+ const splitWords = (name) =>
18
+ name
19
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // camelCase boundary
20
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // ACRONYMCase boundary
21
+ .replace(/([a-zA-Z])([0-9])/g, '$1 $2') // letter→digit boundary (lodash treats digit runs as words)
22
+ .replace(/([0-9])([a-zA-Z])/g, '$1 $2') // digit→letter boundary
23
+ .split(/[^A-Za-z0-9]+/)
24
+ .filter(Boolean);
25
+
26
+ const upperFirst = (word) => word.charAt(0).toUpperCase() + word.slice(1);
27
+
28
+ const transforms = {
29
+ kebab: (name) =>
30
+ splitWords(name)
31
+ .map((w) => w.toLowerCase())
32
+ .join('-'),
33
+ snake: (name) =>
34
+ splitWords(name)
35
+ .map((w) => w.toLowerCase())
36
+ .join('_'),
37
+ camel: (name) =>
38
+ splitWords(name)
39
+ .map((w, i) => (i === 0 ? w.toLowerCase() : upperFirst(w.toLowerCase())))
40
+ .join(''),
41
+ pascal: (name) =>
42
+ splitWords(name)
43
+ .map((w) => upperFirst(w.toLowerCase()))
44
+ .join(''),
45
+ };
46
+
47
+ // --- helpers ported from the original plugin ----------------------------------
48
+ const ignoredFilenames = ['<text>', '<input>'];
49
+ const isIgnoredFilename = (filename) => ignoredFilenames.includes(filename);
50
+
51
+ const parseFilename = (filename) => {
52
+ const ext = path.extname(filename);
53
+ return {
54
+ dir: path.dirname(filename),
55
+ base: path.basename(filename),
56
+ ext,
57
+ name: path.basename(filename, ext),
58
+ };
59
+ };
60
+
61
+ const isIndexFile = (parsed) => parsed.name === 'index';
62
+
63
+ const getStringToCheckAgainstExport = (parsed, replacePattern) => {
64
+ const dirArray = parsed.dir.split(path.sep);
65
+ const lastDirectory = dirArray[dirArray.length - 1];
66
+
67
+ if (isIndexFile(parsed)) {
68
+ return lastDirectory;
69
+ }
70
+ return replacePattern ? parsed.name.replace(replacePattern, '') : parsed.name;
71
+ };
72
+
73
+ const getNodeName = (node, options) => {
74
+ const op = options || [];
75
+
76
+ if (node.type === 'Identifier') {
77
+ return node.name;
78
+ }
79
+ if (node.id && node.id.type === 'Identifier') {
80
+ return node.id.name;
81
+ }
82
+ if (op[2] && node.type === 'CallExpression' && node.callee.type === 'Identifier') {
83
+ return node.callee.name;
84
+ }
85
+ return undefined;
86
+ };
87
+
88
+ const getExportedName = (programNode, options) => {
89
+ for (const node of programNode.body) {
90
+ // export default ...
91
+ if (node.type === 'ExportDefaultDeclaration') {
92
+ return getNodeName(node.declaration, options);
93
+ }
94
+
95
+ // module.exports = ...
96
+ if (
97
+ node.type === 'ExpressionStatement' &&
98
+ node.expression.type === 'AssignmentExpression' &&
99
+ node.expression.left.type === 'MemberExpression' &&
100
+ node.expression.left.object.type === 'Identifier' &&
101
+ node.expression.left.object.name === 'module' &&
102
+ node.expression.left.property.type === 'Identifier' &&
103
+ node.expression.left.property.name === 'exports'
104
+ ) {
105
+ return getNodeName(node.expression.right, options);
106
+ }
107
+ }
108
+ return undefined;
109
+ };
110
+
111
+ const getTransformsFromOptions = (option) => {
112
+ const usedTransforms = Array.isArray(option) ? option : [option];
113
+ return usedTransforms.map((name) => transforms[name]);
114
+ };
115
+
116
+ const applyTransforms = (exportedName, usedTransforms) =>
117
+ usedTransforms.map((t) => (t ? t(exportedName) : exportedName));
118
+
119
+ const anyMatch = (expectedExport, transformedNames) =>
120
+ transformedNames.some((name) => name === expectedExport);
121
+
122
+ const getWhatToMatchMessage = (usedTransforms) => {
123
+ if (usedTransforms.length === 1 && !usedTransforms[0]) {
124
+ return 'the exported name';
125
+ }
126
+ if (usedTransforms.length > 1) {
127
+ return 'any of the exported and transformed names';
128
+ }
129
+ return 'the exported and transformed name';
130
+ };
131
+
132
+ const matchExported = {
133
+ meta: {
134
+ schema: [
135
+ {
136
+ oneOf: [
137
+ { enum: ['kebab', 'snake', 'camel', 'pascal', null] },
138
+ {
139
+ type: 'array',
140
+ items: { enum: ['kebab', 'snake', 'camel', 'pascal', null] },
141
+ minItems: 1,
142
+ },
143
+ ],
144
+ },
145
+ { type: ['string', 'null'] },
146
+ { type: ['boolean', 'null'] },
147
+ ],
148
+ },
149
+ create(context) {
150
+ const options = context.options || [];
151
+ const usedTransforms = getTransformsFromOptions(options[0]);
152
+ const replacePattern = options[1] ? new RegExp(options[1]) : null;
153
+ const filename =
154
+ context.physicalFilename ??
155
+ context.filename ??
156
+ (typeof context.getFilename === 'function' ? context.getFilename() : '');
157
+
158
+ return {
159
+ Program(node) {
160
+ if (isIgnoredFilename(filename)) return;
161
+
162
+ const parsed = parseFilename(path.resolve(filename));
163
+ const exportedName = getExportedName(node, options);
164
+ const isExporting = Boolean(exportedName);
165
+ if (!isExporting) return;
166
+
167
+ const expectedExport = getStringToCheckAgainstExport(parsed, replacePattern);
168
+ const transformedNames = applyTransforms(exportedName, usedTransforms);
169
+ const everythingIsIndex = exportedName === 'index' && parsed.name === 'index';
170
+ const matchesExported = anyMatch(expectedExport, transformedNames) || everythingIsIndex;
171
+
172
+ if (matchesExported) return;
173
+
174
+ const exportName = transformedNames.join("', '");
175
+ const message = isIndexFile(parsed)
176
+ ? `The directory '${expectedExport}' must be named '${exportName}', after the exported value of its index file.`
177
+ : `Filename '${expectedExport}' must match ${getWhatToMatchMessage(usedTransforms)} '${exportName}'.`;
178
+
179
+ context.report({ node, message });
180
+ },
181
+ };
182
+ },
183
+ };
184
+
185
+ export default {
186
+ meta: { name: 'custom' },
187
+ rules: { 'match-exported': matchExported },
188
+ };
@@ -0,0 +1,21 @@
1
+ import { fileURLToPath } from 'node:url';
2
+
3
+ // `customRules` configures Wistia's custom local jsPlugin (plugins/custom.mjs),
4
+ // which holds rules that have no native oxlint or loadable-ESLint-plugin
5
+ // equivalent. The plugin path is resolved to an absolute path so it loads both
6
+ // in this repo and from a consumer's node_modules.
7
+ //
8
+ // `custom/match-exported` ports `match-exported` from the abandoned
9
+ // eslint-plugin-filenames (it ships its rules in a legacy format oxlint cannot
10
+ // load). The upstream `match-regex` and `no-index` rules are intentionally not
11
+ // implemented — @wistia/eslint-config disables both.
12
+ const customPlugin = fileURLToPath(new URL('../plugins/custom.mjs', import.meta.url));
13
+
14
+ export const customRules = {
15
+ jsPlugins: [customPlugin],
16
+ rules: {
17
+ // Match Exported Values
18
+ // https://github.com/selaux/eslint-plugin-filenames#matching-exported-values-match-exported
19
+ 'custom/match-exported': 'error',
20
+ },
21
+ };
package/rules/import.mjs CHANGED
@@ -195,7 +195,7 @@ export const importRules = {
195
195
  'import-x-js/order': [
196
196
  'error',
197
197
  {
198
- groups: [['builtin', 'external', 'internal'], 'index', ['parent', 'sibling']],
198
+ groups: ['type', ['builtin', 'external', 'internal'], 'index', ['parent', 'sibling']],
199
199
  'newlines-between': 'never',
200
200
  },
201
201
  ],
@@ -2,15 +2,15 @@ export const reactA11yRules = {
2
2
  plugins: ['jsx-a11y'],
3
3
  rules: {
4
4
  // Enforce that all elements that require alternative text have meaningful information
5
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/alt-text.html
5
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/alt-text.html
6
6
  'jsx_a11y/alt-text': 'error',
7
7
 
8
8
  // Enforce that anchors have content
9
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/anchor-has-content.html
9
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/anchor-has-content.html
10
10
  'jsx_a11y/anchor-has-content': 'error',
11
11
 
12
12
  // Enforce all anchors are valid, navigable elements
13
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/anchor-is-valid.html
13
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/anchor-is-valid.html
14
14
  'jsx_a11y/anchor-is-valid': [
15
15
  'error',
16
16
  {
@@ -21,40 +21,40 @@ export const reactA11yRules = {
21
21
  ],
22
22
 
23
23
  // Enforce that elements with aria-activedescendant have tabindex
24
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/aria-activedescendant-has-tabindex.html
24
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/aria-activedescendant-has-tabindex.html
25
25
  'jsx_a11y/aria-activedescendant-has-tabindex': 'error',
26
26
 
27
27
  // Enforce that elements have valid aria-* props
28
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/aria-props.html
28
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/aria-props.html
29
29
  'jsx_a11y/aria-props': 'error',
30
30
 
31
31
  // Enforce that ARIA state and property values are valid
32
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/aria-proptypes.html
32
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/aria-proptypes.html
33
33
  'jsx_a11y/aria-proptypes': 'error',
34
34
 
35
35
  // Enforce that elements with ARIA roles have all required attributes for that role
36
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/role-has-required-aria-props.html
36
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/role-has-required-aria-props.html
37
37
  'jsx_a11y/role-has-required-aria-props': 'error',
38
38
 
39
39
  // Enforce that elements with explicit or implicit roles defined contain only aria-* properties supported by that role
40
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/role-supports-aria-props.html
40
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/role-supports-aria-props.html
41
41
  'jsx_a11y/role-supports-aria-props': 'error',
42
42
 
43
43
  // Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role
44
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/aria-role.html
44
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/aria-role.html
45
45
  'jsx_a11y/aria-role': 'error',
46
46
 
47
47
  // Enforce that certain elements don't have ARIA roles, states, or properties
48
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/aria-unsupported-elements.html
48
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/aria-unsupported-elements.html
49
49
  'jsx_a11y/aria-unsupported-elements': 'error',
50
50
 
51
51
  // Enforce that autocomplete attribute is correct
52
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/autocomplete-valid.html
52
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/autocomplete-valid.html
53
53
  // Decision: too opinionated for general use
54
54
  'jsx_a11y/autocomplete-valid': 'off',
55
55
 
56
56
  // Enforce that a control (an interactive element) has a text label
57
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/control-has-associated-label.html
57
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/control-has-associated-label.html
58
58
  'jsx_a11y/control-has-associated-label': [
59
59
  'error',
60
60
  {
@@ -78,27 +78,27 @@ export const reactA11yRules = {
78
78
  ],
79
79
 
80
80
  // Enforce a clickable non-interactive element has at least one keyboard event listener
81
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/click-events-have-key-events.html
81
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/click-events-have-key-events.html
82
82
  'jsx_a11y/click-events-have-key-events': 'error',
83
83
 
84
84
  // Enforce heading elements have content
85
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/heading-has-content.html
85
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/heading-has-content.html
86
86
  'jsx_a11y/heading-has-content': ['error', { components: [''] }],
87
87
 
88
88
  // Enforce <html> element has lang prop
89
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/html-has-lang.html
89
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/html-has-lang.html
90
90
  'jsx_a11y/html-has-lang': 'error',
91
91
 
92
92
  // Enforce iframe elements have a title attribute
93
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/iframe-has-title.html
93
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/iframe-has-title.html
94
94
  'jsx_a11y/iframe-has-title': 'error',
95
95
 
96
96
  // Enforce <img> alt prop does not contain the word "image", "picture", or "photo"
97
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/img-redundant-alt.html
97
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/img-redundant-alt.html
98
98
  'jsx_a11y/img-redundant-alt': 'error',
99
99
 
100
100
  // Enforce that a label tag has a text label and an associated control
101
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/label-has-associated-control.html
101
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/label-has-associated-control.html
102
102
  'jsx_a11y/label-has-associated-control': [
103
103
  'error',
104
104
  {
@@ -111,31 +111,31 @@ export const reactA11yRules = {
111
111
  ],
112
112
 
113
113
  // Enforce lang attribute has a valid value
114
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/lang.html
114
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/lang.html
115
115
  'jsx_a11y/lang': 'error',
116
116
 
117
117
  // Enforce that media elements have captions
118
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/media-has-caption.html
118
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/media-has-caption.html
119
119
  'jsx_a11y/media-has-caption': 'error',
120
120
 
121
121
  // Enforce that onMouseOver/onMouseOut are accompanied by onFocus/onBlur
122
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/mouse-events-have-key-events.html
122
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/mouse-events-have-key-events.html
123
123
  'jsx_a11y/mouse-events-have-key-events': 'error',
124
124
 
125
125
  // Enforce that the accessKey prop is not used on any element
126
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-access-key.html
126
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-access-key.html
127
127
  'jsx_a11y/no-access-key': 'error',
128
128
 
129
129
  // Enforce autoFocus prop is not used
130
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-autofocus.html
130
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-autofocus.html
131
131
  'jsx_a11y/no-autofocus': ['error', { ignoreNonDOM: true }],
132
132
 
133
133
  // Enforce distracting elements are not used
134
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-distracting-elements.html
134
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-distracting-elements.html
135
135
  'jsx_a11y/no-distracting-elements': 'error',
136
136
 
137
137
  // Enforce tabIndex value is not greater than zero
138
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/tabindex-no-positive.html
138
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/tabindex-no-positive.html
139
139
  'jsx_a11y/tabindex-no-positive': 'error',
140
140
 
141
141
  // Elements with an interactive role and interaction handlers must be focusable
@@ -143,7 +143,7 @@ export const reactA11yRules = {
143
143
  'jsx_a11y/interactive-supports-focus': 'error',
144
144
 
145
145
  // Ensure interactive elements are not assigned non-interactive roles
146
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-noninteractive-tabindex.html
146
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-noninteractive-tabindex.html
147
147
  'jsx_a11y/no-noninteractive-tabindex': [
148
148
  'error',
149
149
  {
@@ -153,21 +153,21 @@ export const reactA11yRules = {
153
153
  ],
154
154
 
155
155
  // Ensure interactive elements are not assigned non-interactive roles
156
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-interactive-element-to-noninteractive-role.html
156
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-interactive-element-to-noninteractive-role.html
157
157
  'jsx_a11y/no-interactive-element-to-noninteractive-role': [
158
158
  'error',
159
159
  { tr: ['none', 'presentation'] },
160
160
  ],
161
161
 
162
162
  // Enforce that non-interactive elements do not have interaction handlers
163
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-noninteractive-element-interactions.html
163
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-noninteractive-element-interactions.html
164
164
  'jsx_a11y/no-noninteractive-element-interactions': [
165
165
  'error',
166
166
  { handlers: ['onClick', 'onMouseDown', 'onMouseUp', 'onKeyPress', 'onKeyDown', 'onKeyUp'] },
167
167
  ],
168
168
 
169
169
  // Enforce that non-interactive elements are not assigned interactive roles
170
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-noninteractive-element-to-interactive-role.html
170
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-noninteractive-element-to-interactive-role.html
171
171
  'jsx_a11y/no-noninteractive-element-to-interactive-role': [
172
172
  'error',
173
173
  {
@@ -180,27 +180,27 @@ export const reactA11yRules = {
180
180
  ],
181
181
 
182
182
  // Enforce explicit role is not redundant with implicit role of the element
183
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-redundant-roles.html
183
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-redundant-roles.html
184
184
  'jsx_a11y/no-redundant-roles': 'error',
185
185
 
186
186
  // Enforce that non-interactive, visible elements with click handlers use the role attribute
187
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-static-element-interactions.html
187
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-static-element-interactions.html
188
188
  'jsx_a11y/no-static-element-interactions': 'error',
189
189
 
190
190
  // Enforce scope prop is only used on <th> elements
191
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/scope.html
191
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/scope.html
192
192
  'jsx_a11y/scope': 'error',
193
193
 
194
194
  // Enforce that anchor elements have non-ambiguous text content
195
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/anchor-ambiguous-text.html
195
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/anchor-ambiguous-text.html
196
196
  'jsx_a11y/anchor-ambiguous-text': 'error',
197
197
 
198
198
  // Enforce that aria-hidden="true" is not set on focusable elements
199
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/no-aria-hidden-on-focusable.html
199
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-aria-hidden-on-focusable.html
200
200
  'jsx_a11y/no-aria-hidden-on-focusable': 'error',
201
201
 
202
202
  // Prefer semantic HTML elements over role attributes
203
- // https://oxc.rs/docs/guide/usage/linter/rules/jsx-a11y/prefer-tag-over-role.html
203
+ // https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/prefer-tag-over-role.html
204
204
  // Decision: left to implementer
205
205
  'jsx_a11y/prefer-tag-over-role': 'off',
206
206
  },