feat(lint): noHeaderScope for HTML and JSX
ADD
internal/compiler/lint/rules/a11y/noHeaderScope.test.rjson
+[
+ {
+ filename: "file.tsx"
+ invalid: [
+ "<div scope={scope} />"
+ "<div scope='col' />"
+ ]
+ valid: [
+ "<th scope={scope}></th>"
+ "<th scope='col'></th>"
+ ]
+}
+ {
+ filename: "file.html"
+ invalid: [
+ "<div scope=\"col\" />"
+ ]
+ valid: [
+ "<th scope=\"col\"></th>"
+ ]
+}
+]

RENAME
internal/compiler/lint/rules/jsx-a11y/noHeaderScope.ts
internal/compiler/lint/rules/a11y/noHeaderScope.ts
@@ -2,9 +2,11 @@ import {descriptions} from "@internal/diagnostics";
import {createVisitor, signals} from "@internal/compiler";
import {doesNodeMatchPattern, hasJSXAttribute} from "@internal/js-ast-utils";
import {isJSXDOMElement} from "@internal/js-ast-utils/isJSXDOMElement";
+import isHTMLElement from "@internal/js-ast-utils/isHTMLElement";
+import hasHTMLAttribute from "@internal/js-ast-utils/hasHTMLAttribute";
export default createVisitor({
- name: "jsx-a11y/noHeaderScope",
+ name: "a11y/noHeaderScope",
enter(path) {
const {node} = path;
@@ -24,7 +26,24 @@ export default createVisitor({
),
}),
},
- descriptions.LINT.JSX_A11Y_NO_SCOPE,
+ descriptions.LINT.A11Y_NO_SCOPE,
+ );
+ } else if (
+ isHTMLElement(node) &&
+ hasHTMLAttribute(node, "scope") &&
+ node.name.name !== "th"
+ ) {
+ return path.addFixableDiagnostic(
+ {
+ fixed: signals.replace({
+ ...node,
+ attributes: node.attributes.filter((attribute) =>
+ attribute.type !== "HTMLAttribute" ||
+ attribute.name.name !== "scope"
+ ),
+ }),
+ },
+ descriptions.LINT.A11Y_NO_SCOPE,
);
}

MODIFY
internal/compiler/lint/rules/index.ts
internal/compiler/lint/rules/index.ts
-/* GENERATED:START(hash:3774c59c86fc021085c0b4397097fb4e97ef3077,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
+/* GENERATED:START(hash:9c388925bca49f9173be3730919424147c234a36,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
import noAriaUnsupportedElements from "./a11y/noAriaUnsupportedElements";
import noAutofocus from "./a11y/noAutofocus";
import noDistractingElements from "./a11y/noDistractingElements";
+import noHeaderScope from "./a11y/noHeaderScope";
import noNoninteractiveElementToInteractiveRole from "./a11y/noNoninteractiveElementToInteractiveRole";
import noNoninteractiveTabindex from "./a11y/noNoninteractiveTabindex";
import noPositiveTabindex from "./a11y/noPositiveTabindex";
@@ -64,7 +65,6 @@ import useSortedSpecifiers from "./js/useSortedSpecifiers";
import useTemplate from "./js/useTemplate";
import useWhile from "./js/useWhile";
import noAccessKey from "./jsx-a11y/noAccessKey";
-import noHeaderScope from "./jsx-a11y/noHeaderScope";
import noOnChange from "./jsx-a11y/noOnChange";
import noRedundantRoles from "./jsx-a11y/noRedundantRoles";
import noTargetBlank from "./jsx-a11y/noTargetBlank";
@@ -120,6 +120,7 @@ export const lintTransforms: Map<LintRuleName, AnyVisitor> = new Map();
lintTransforms.set("a11y/noAriaUnsupportedElements", noAriaUnsupportedElements);
lintTransforms.set("a11y/noAutofocus", noAutofocus);
lintTransforms.set("a11y/noDistractingElements", noDistractingElements);
+lintTransforms.set("a11y/noHeaderScope", noHeaderScope);
lintTransforms.set(
"a11y/noNoninteractiveElementToInteractiveRole",
noNoninteractiveElementToInteractiveRole,
@@ -191,7 +192,6 @@ lintTransforms.set("js/useSortedSpecifiers", useSortedSpecifiers);
lintTransforms.set("js/useTemplate", useTemplate);
lintTransforms.set("js/useWhile", useWhile);
lintTransforms.set("jsx-a11y/noAccessKey", noAccessKey);
-lintTransforms.set("jsx-a11y/noHeaderScope", noHeaderScope);
lintTransforms.set("jsx-a11y/noOnChange", noOnChange);
lintTransforms.set("jsx-a11y/noRedundantRoles", noRedundantRoles);
lintTransforms.set("jsx-a11y/noTargetBlank", noTargetBlank);
@@ -267,6 +267,7 @@ export const lintRuleNames: LintRuleName[] = [
"a11y/noAriaUnsupportedElements",
"a11y/noAutofocus",
"a11y/noDistractingElements",
+ "a11y/noHeaderScope",
"a11y/noNoninteractiveElementToInteractiveRole",
"a11y/noNoninteractiveTabindex",
"a11y/noPositiveTabindex",
@@ -329,7 +330,6 @@ export const lintRuleNames: LintRuleName[] = [
"js/useTemplate",
"js/useWhile",
"jsx-a11y/noAccessKey",
- "jsx-a11y/noHeaderScope",
"jsx-a11y/noOnChange",
"jsx-a11y/noRedundantRoles",
"jsx-a11y/noTargetBlank",
@@ -385,6 +385,7 @@ export type LintRuleName =
| "a11y/noAriaUnsupportedElements"
| "a11y/noAutofocus"
| "a11y/noDistractingElements"
+ | "a11y/noHeaderScope"
| "a11y/noNoninteractiveElementToInteractiveRole"
| "a11y/noNoninteractiveTabindex"
| "a11y/noPositiveTabindex"
@@ -447,7 +448,6 @@ export type LintRuleName =
| "js/useTemplate"
| "js/useWhile"
| "jsx-a11y/noAccessKey"
- | "jsx-a11y/noHeaderScope"
| "jsx-a11y/noOnChange"
| "jsx-a11y/noRedundantRoles"
| "jsx-a11y/noTargetBlank"

DELETE
internal/compiler/lint/rules/jsx-a11y/noHeaderScope.test.rjson
-filename: "file.tsx"
-invalid: [
- "<div scope={scope} />"
- "<div scope='col' />"
-]
-valid: [
- "<th scope={scope}></th>"
- "<th scope='col'></th>"
-]

MODIFY
internal/compiler/lint/rules/tests.ts
internal/compiler/lint/rules/tests.ts
@@ -12,7 +12,7 @@ type Tests = Dict<{
cases: Test | Test[] | Test[][];
}>;
-/* GENERATED:START(hash:124fbbcd7e0f60c464ad722c48a0b4ba6a5b3465,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
+/* GENERATED:START(hash:12f8b3c328bfa10b4414b90b0090964a6b3670ba,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
// @ts-ignore
import noAriaUnsupportedElements from "./a11y/noAriaUnsupportedElements.test.rjson";
// @ts-ignore
@@ -20,6 +20,8 @@ import noAutofocus from "./a11y/noAutofocus.test.rjson";
// @ts-ignore
import noDistractingElements from "./a11y/noDistractingElements.test.rjson";
// @ts-ignore
+import noHeaderScope from "./a11y/noHeaderScope.test.rjson";
+// @ts-ignore
import noNoninteractiveElementToInteractiveRole from "./a11y/noNoninteractiveElementToInteractiveRole.test.rjson";
// @ts-ignore
import noNoninteractiveTabindex from "./a11y/noNoninteractiveTabindex.test.rjson";
@@ -142,8 +144,6 @@ import useWhile from "./js/useWhile.test.rjson";
// @ts-ignore
import noAccessKey from "./jsx-a11y/noAccessKey.test.rjson";
// @ts-ignore
-import noHeaderScope from "./jsx-a11y/noHeaderScope.test.rjson";
-// @ts-ignore
import noOnChange from "./jsx-a11y/noOnChange.test.rjson";
// @ts-ignore
import noRedundantRoles from "./jsx-a11y/noRedundantRoles.test.rjson";
@@ -247,6 +247,10 @@ export const tests: Tests = {
category: ["lint", "a11y", "noDistractingElements"],
cases: noDistractingElements,
},
+ "a11y/noHeaderScope": {
+ category: ["lint", "a11y", "noHeaderScope"],
+ cases: noHeaderScope,
+ },
"a11y/noNoninteractiveElementToInteractiveRole": {
category: ["lint", "a11y", "noNoninteractiveElementToInteractiveRole"],
cases: noNoninteractiveElementToInteractiveRole,
@@ -491,10 +495,6 @@ export const tests: Tests = {
category: ["lint", "jsx-a11y", "noAccessKey"],
cases: noAccessKey,
},
- "jsx-a11y/noHeaderScope": {
- category: ["lint", "jsx-a11y", "noHeaderScope"],
- cases: noHeaderScope,
- },
"jsx-a11y/noOnChange": {
category: ["lint", "jsx-a11y", "noOnChange"],
cases: noOnChange,

MODIFY
internal/diagnostics/categories.ts
internal/diagnostics/categories.ts
@@ -103,11 +103,12 @@ type DiagnosticCategoryString = StringConverter<DiagnosticCategory>;
type DiagnosticLintCategoryString = StringConverter<DiagnosticLintCategory>;
-/* GENERATED:START(hash:8c79eca77e24f506913417658216274b69da26bc,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
+/* GENERATED:START(hash:8d18d4384d752827b06477907c1ebae9e61b5c78,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
export type DiagnosticLintCategory =
| ["lint", "a11y", "noAriaUnsupportedElements"]
| ["lint", "a11y", "noAutofocus"]
| ["lint", "a11y", "noDistractingElements"]
+ | ["lint", "a11y", "noHeaderScope"]
| ["lint", "a11y", "noNoninteractiveElementToInteractiveRole"]
| ["lint", "a11y", "noNoninteractiveTabindex"]
| ["lint", "a11y", "noPositiveTabindex"]
@@ -170,7 +171,6 @@ export type DiagnosticLintCategory =
| ["lint", "js", "useTemplate"]
| ["lint", "js", "useWhile"]
| ["lint", "jsx-a11y", "noAccessKey"]
- | ["lint", "jsx-a11y", "noHeaderScope"]
| ["lint", "jsx-a11y", "noOnChange"]
| ["lint", "jsx-a11y", "noRedundantRoles"]
| ["lint", "jsx-a11y", "noTargetBlank"]
@@ -230,6 +230,7 @@ const lintCategoryNameMap: {
],
"lint/a11y/noAutofocus": ["lint", "a11y", "noAutofocus"],
"lint/a11y/noDistractingElements": ["lint", "a11y", "noDistractingElements"],
+ "lint/a11y/noHeaderScope": ["lint", "a11y", "noHeaderScope"],
"lint/a11y/noNoninteractiveElementToInteractiveRole": [
"lint",
"a11y",
@@ -308,7 +309,6 @@ const lintCategoryNameMap: {
"lint/js/useTemplate": ["lint", "js", "useTemplate"],
"lint/js/useWhile": ["lint", "js", "useWhile"],
"lint/jsx-a11y/noAccessKey": ["lint", "jsx-a11y", "noAccessKey"],
- "lint/jsx-a11y/noHeaderScope": ["lint", "jsx-a11y", "noHeaderScope"],
"lint/jsx-a11y/noOnChange": ["lint", "jsx-a11y", "noOnChange"],
"lint/jsx-a11y/noRedundantRoles": ["lint", "jsx-a11y", "noRedundantRoles"],
"lint/jsx-a11y/noTargetBlank": ["lint", "jsx-a11y", "noTargetBlank"],

MODIFY
internal/diagnostics/descriptions/lint.ts
internal/diagnostics/descriptions/lint.ts
@@ -144,6 +144,17 @@ export const lint = createDiagnosticsCategory({
},
],
},
+ A11Y_NO_SCOPE: {
+ category: DIAGNOSTIC_CATEGORIES["lint/a11y/noHeaderScope"],
+ message: markup`Avoid using the <emphasis>scope</emphasis> attribute on elements other than <emphasis>th</emphasis> elements.`,
+ advice: [
+ {
+ type: "log",
+ category: "info",
+ text: markup`Using the scope attribute incorrectly on tables makes them difficult to navigate using the keyboard.`,
+ },
+ ],
+ },
JS_NO_SINGLE_CHAR_REGEX_ALTERNATIVES: {
category: DIAGNOSTIC_CATEGORIES["lint/js/noSingleCharRegexAlternatives"],
message: markup`No single character alternations in regular expressions. Use a character class instead.`,
@@ -502,17 +513,6 @@ export const lint = createDiagnosticsCategory({
},
],
},
- JSX_A11Y_NO_SCOPE: {
- category: DIAGNOSTIC_CATEGORIES["lint/jsx-a11y/noHeaderScope"],
- message: markup`Avoid using the <emphasis>scope</emphasis> attribute on elements other than <emphasis>th</emphasis> elements.`,
- advice: [
- {
- type: "log",
- category: "info",
- text: markup`Using the scope attribute incorrectly on tables makes them difficult to navigate using the keyboard.`,
- },
- ],
- },
JSX_A11Y_ROLE_HAS_REQUIRED_ARIA_PROPS: (
roleName: string,
missingAttributes: string[],

MODIFY
website/src/docs/lint/rules/index.md
website/src/docs/lint/rules/index.md
@@ -11,7 +11,7 @@ eleventyNavigation:
# Rules
-<!-- GENERATED:START(hash:56af266e1ab1240d4b087d1f1404f6ba7c80a6ec,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. -->
+<!-- GENERATED:START(hash:00a7f027d25ef616d877c04665d6d5d3efae7d25,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. -->
<section>
<h2>JavaScript</h2>
<p>Rule semantics and descriptions taken from <a href="https://eslint.org/">ESLint</a>. See individual rule docs for direct references.</p>
@@ -409,6 +409,13 @@ discourage the usage of `autoFocus`
enforce distracting elements are not used
</div>
<div class="rule">
+<h3 data-toc-exclude id="noHeaderScope">
+ <a href="/docs/lint/rules/a11y/noHeaderScope">noHeaderScope</a>
+ <a class="header-anchor" href="#noHeaderScope"></a>
+</h3>
+enforce scope prop is only used on `th` elements
+</div>
+<div class="rule">
<h3 data-toc-exclude id="noNoninteractiveElementToInteractiveRole">
<a href="/docs/lint/rules/a11y/noNoninteractiveElementToInteractiveRole">noNoninteractiveElementToInteractiveRole</a>
<a class="header-anchor" href="#noNoninteractiveElementToInteractiveRole"></a>
@@ -504,13 +511,6 @@ check if `lang` attribute is valid
enforce that the `accessKey` prop is not used on any element to avoid complications with keyboard commands used by a screenreader
</div>
<div class="rule">
-<h3 data-toc-exclude id="noHeaderScope">
- <a href="/docs/lint/rules/jsx-a11y/noHeaderScope">noHeaderScope</a>
- <a class="header-anchor" href="#noHeaderScope"></a>
-</h3>
-enforce scope prop is only used on `th` elements
-</div>
-<div class="rule">
<h3 data-toc-exclude id="noOnChange">
<a href="/docs/lint/rules/jsx-a11y/noOnChange">noOnChange</a>
<a class="header-anchor" href="#noOnChange"></a>

feat(lint): noHeaderScope for HTML and JSX (#1416)