feat(linter): add noAutofocus rule for HTML#8641
Conversation
Port the noAutofocus a11y rule to HTML. The rule enforces that the autofocus attribute is not used on elements, with exceptions for dialog elements and elements with the popover attribute. Part of biomejs#8155
🦋 Changeset detectedLatest commit: d83126c The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
CodSpeed Performance ReportMerging #8641 will not alter performanceComparing Summary
Footnotes
|
WalkthroughThis change adds a new HTML accessibility lint rule Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (2 passed)
✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ematipico
left a comment
There was a problem hiding this comment.
Looks good. The code can be improved
|
|
||
| /// Check if the element is inside an allowed context (dialog or popover) | ||
| /// | ||
| /// Note: We skip the first element (the one containing the autofocus attribute) |
There was a problem hiding this comment.
| /// Note: We skip the first element (the one containing the autofocus attribute) | |
| /// Note: We skip the first [HtmlElement] (the one containing the autofocus attribute) |
| /// Note: We skip the first element (the one containing the autofocus attribute) | ||
| /// because we only want to check if it's *inside* a dialog/popover, not if | ||
| /// it *is* the dialog/popover itself. | ||
| fn is_inside_allowed_context(attr: &HtmlAttribute) -> bool { |
There was a problem hiding this comment.
Return Option<bool>, so that you can convert all let Ok() in .ok()?
| if let Ok(opening) = element.opening_element() { | ||
| if let Ok(name) = opening.name() | ||
| && let Ok(token) = name.value_token() | ||
| && token.text_trimmed().eq_ignore_ascii_case("dialog") | ||
| { | ||
| return true; | ||
| } | ||
| // Check if element has popover attribute | ||
| if opening.find_attribute_by_name("popover").is_some() { | ||
| return true; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
We're repeating these checks, which means this code isn't well-maintainable. Instead, let's do something more generic.
We already have AnyHtmlTagElement from biome_html_syntax, let's use that so we can extract the name of the element and then do one single check
Address code review feedback: - Add [HtmlElement] link in doc comment for better rustdoc - Change is_inside_allowed_context to return Option<bool> - Use AnyHtmlTagElement to eliminate duplicate HtmlElement/HtmlSelfClosingElement checks - Extract helper functions for better maintainability
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs (2)
71-71: Options type is unused.
NoAutofocusOptionsis declared but never accessed in the implementation. If options aren't currently needed, consider removing lines 9 and 71 to simplify the code.🔎 Proposed change to remove unused Options
Remove the import on line 9:
-use biome_rule_options::no_autofocus::NoAutofocusOptions;And change line 71 to:
- type Options = NoAutofocusOptions; + type Options = ();
126-146: Consider simplifying return type tobool.The function returns
Option<bool>but always returnsSome(...)(lines 141, 145), neverNone. This means the.unwrap_or(false)on line 82 never uses its default value.Whilst a past review suggested
Option<bool>, the current implementation doesn't have any operations that would benefit from early-returningNone. Consider simplifying tofn is_inside_allowed_context(attr: &HtmlAttribute) -> booland removing theSome(...)wrappers.🔎 Proposed simplification
-fn is_inside_allowed_context(attr: &HtmlAttribute) -> Option<bool> { +fn is_inside_allowed_context(attr: &HtmlAttribute) -> bool { let mut skip_first_element = true; // Walk up the ancestors to find if we're inside a dialog or popover for ancestor in attr.syntax().ancestors() { let Some(tag_element) = get_tag_element(&ancestor) else { continue; }; if skip_first_element { skip_first_element = false; continue; } if is_dialog_or_popover(&tag_element) { - return Some(true); + return true; } } - Some(false) + false }And update line 82:
- if is_inside_allowed_context(node).unwrap_or(false) { + if is_inside_allowed_context(node) {
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
**/*.rs: Use inline rustdoc documentation for rules, assists, and their options
Use thedbg!()macro for debugging output in Rust tests and code
Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests
Files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
🧠 Learnings (14)
📓 Common learnings
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set rule severity to `error` for rules in correctness, security, and a11y groups
📚 Learning: 2025-12-22T09:26:56.943Z
Learnt from: ematipico
Repo: biomejs/biome PR: 8537
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:167-210
Timestamp: 2025-12-22T09:26:56.943Z
Learning: When defining lint rules (declare_lint_rule!), only specify fix_kind if the rule implements an action(...) function. Rules that only emit diagnostics without a code fix should omit fix_kind. This applies to all Rust lint rule definitions under crates/.../src/lint (e.g., crates/biome_js_analyze/src/lint/...).
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should perform static analysis of source code to detect invalid or error-prone patterns and emit diagnostics with proposed fixes
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Specify `fix_kind: FixKind::Unsafe` in `declare_lint_rule!` for unsafe code actions
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Specify `fix_kind: FixKind::Safe` in `declare_lint_rule!` for safe code actions
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `declare_lint_rule!` macro to declare analyzer rule types and implement the RuleMeta trait
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Add `sources` field with `RuleSource` to cite ESLint or other rules that inspired the implementation
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `domains` field in `declare_lint_rule!` to tag rules that belong to specific concepts like testing or frameworks
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Assist rules should detect refactoring opportunities and emit code action signals
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set rule severity to `error` for rules in correctness, security, and a11y groups
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Add `deprecated` field to `declare_lint_rule!` macro when deprecating a rule
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use try operator `?` when `run` function returns `Option` to transform `Result` into `Option`
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).same()` when implementing a rule that matches the behavior of an ESLint rule
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).inspired()` when implementing a rule inspired by but with different behavior than an ESLint rule
Applied to files:
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Check Dependencies
- GitHub Check: End-to-end tests
- GitHub Check: Documentation
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: autofix
- GitHub Check: Bench (biome_configuration)
🔇 Additional comments (3)
crates/biome_html_analyze/src/lint/a11y/no_autofocus.rs (3)
13-65: Well-structured rule declaration.The metadata is correct: Error severity for a11y group, fix_kind specified with an action implementation, and proper source attribution. Documentation is comprehensive with clear examples.
113-119: Correct attribute check.Case-insensitive comparison is appropriate for HTML attribute names.
148-167: Clean helper functions.Good use of
AnyHtmlTagElementto handle bothHtmlElementandHtmlSelfClosingElementuniformly. Theis_dialog_or_popoverlogic correctly checks both the tag name and the presence of a popover attribute.
hi, thank you for your review, i fix these issue already |
| fn get_tag_element(node: &biome_html_syntax::HtmlSyntaxNode) -> Option<AnyHtmlTagElement> { | ||
| HtmlElement::cast_ref(node) | ||
| .and_then(|e| e.opening_element().ok()) | ||
| .map(AnyHtmlTagElement::from) | ||
| .or_else(|| { | ||
| HtmlSelfClosingElement::cast_ref(node).map(|e| AnyHtmlTagElement::from(e.clone())) | ||
| }) | ||
| } |
There was a problem hiding this comment.
I don't understand this function. You can do AnyHtmlTagElement::cast_ref(node) and it will work. Technically, this function isn't needed, and you can use the cast_ref inside the loop.
|
Thanks! I did try For normal tags like That’s why I keep |
|
I understand now, thank you |
Summary
Port the
noAutofocusa11y rule to HTML. This rule enforces that theautofocusattribute is not used on elements, as it can cause usability issues for sighted and non-sighted users.The rule allows
autofocusin the following contexts:<dialog>elementspopoverattributePart of #8155
Test plan
invalid.htmltest with 5 cases that should trigger the rulevalid.htmltest with dialog and popover exception casescargo test -p biome_html_analyze no_autofocusAI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.