Skip to content

feat: add jsx-a11y accessibility lint rules (content/attribute cluster) - #1500

Open
bartlomieju wants to merge 1 commit into
mainfrom
feat/jsx-a11y-content-rules
Open

feat: add jsx-a11y accessibility lint rules (content/attribute cluster)#1500
bartlomieju wants to merge 1 commit into
mainfrom
feat/jsx-a11y-content-rules

Conversation

@bartlomieju

Copy link
Copy Markdown
Member

First cluster of accessibility rules ported from oxlint / eslint-plugin-jsx-a11y, implemented in Rust against deno_ast::view using the existing Handler/Traverse pattern.

Four rules are added:

  • jsx-a11y-anchor-has-content — an <a> anchor must have content accessible to screen readers (text, a non-hidden child, or dangerouslySetInnerHTML / title / aria-label).
  • jsx-a11y-heading-has-content — heading elements <h1><h6> must have content.
  • jsx-a11y-html-has-lang — the <html> element must have a non-empty lang attribute.
  • jsx-a11y-iframe-has-title<iframe> must have a non-empty title attribute.

A new a11y tag is introduced. These rules are opt-in: they are tagged a11y, jsx, react, fresh but intentionally not recommended, so they only run when a user enables them. Individual rules can be promoted to recommended later.

The configurable components option (custom-component name mapping) and the autofixers from the reference implementations are intentionally deferred for now; only built-in DOM element names are handled. Test fixtures are ported from the oxlint reference (pass cases → assert_lint_ok!, fail cases → assert_lint_err!).

This is the start of the jsx-a11y porting track (35 rules total); see the project notes for the full plan.

Ports four accessibility rules from oxlint/eslint-plugin-jsx-a11y, in Rust:

- jsx-a11y-anchor-has-content: <a> must have accessible content
- jsx-a11y-heading-has-content: <h1>-<h6> must have content
- jsx-a11y-html-has-lang: <html> must have a non-empty lang attribute
- jsx-a11y-iframe-has-title: <iframe> must have a non-empty title

Adds a new `a11y` tag. These rules are opt-in (tagged a11y/jsx/react/fresh,
not recommended). The configurable `components` option and autofixers from
the reference implementations are intentionally deferred.
@bartlomieju

Copy link
Copy Markdown
Member Author

Verdict: LGTM on the code — needs maintainer sign-off (new rule cluster) + a structural cleanup suggestion. Not auto-merging (new rules).

Clean port of 4 jsx-a11y rules using the standard Handler/Traverse pattern, correctly opt-in (a11y/jsx/react/fresh tags, not recommended). Logic and tests read as faithful to the oxlint/eslint-plugin-jsx-a11y reference.

Correctness — verified

  • anchor-has-content: the child/attr/aria-hidden logic matches the reference. I checked the whitespace edge (<a>\n</a>!text.value().is_empty() treats whitespace text as content, so it's not flagged) — this matches eslint-plugin-jsx-a11y's Boolean(value) behavior, so it's faithful, not a bug. If you ever want stricter behavior, .trim().is_empty() would flag whitespace-only anchors, but keeping parity with the reference is the right call here.
  • expr_to_boolean / is_hidden_from_screen_reader correctly handle aria-hidden with bare/string/expr values and input type="hidden". Good test coverage ported from oxlint (pass→ok, fail→err).

Structural — worth doing now, before the track grows

  • Helper duplication. get_attr_ignore_case, is_hidden_from_screen_reader, expr_to_boolean, and object_has_accessible_child are copy-pasted verbatim between jsx_a11y_anchor_has_content.rs and jsx_a11y_heading_has_content.rs (and the attr helpers recur in the html/iframe rules). Since this is rule 1–4 of a stated 35-rule port, extract these into a shared jsx_a11y/util.rs (or swc_util) module now — otherwise the duplication multiplies across 35 files and drift between copies becomes a real risk.

Minor

  • Expression-container children like {''}, {0}, {false} fall through to _ => true and count as "content". That also mirrors the reference's leniency; just noting it in case a stricter interpretation is wanted later.

Perf: fine — per-jsx_element visits with linear attr scans; no concerns.

Housekeeping: new a11y tag + 4 opt-in rules is a product decision (your own PR, so presumably intended). mergeable: UNKNOWN → rebase.

No behavioral changes requested; recommend extracting the shared helpers before landing so the rest of the a11y track builds on a common util.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant