This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.
The bug
A Biome user filed issue #11317: the noSvgWithoutTitle lint rule flagged <svg aria-hidden><rect /></svg>, even though that svg is hidden from the accessibility tree and does not need a title. Write it as <svg aria-hidden="true"> or <svg aria-hidden={true}> and the rule stays quiet. Write it with the JSX boolean shorthand <svg aria-hidden> and the rule reports "title element cannot be empty". Same meaning, different result.
The shorthand is not a niche style. In JSX, aria-hidden with no value is exactly aria-hidden={true}, the same way disabled is disabled={true} on an input. React users write it constantly, so the rule was punishing correct, accessible code.
Following the value
The rule lives in crates/biome_js_analyze/src/lint/a11y/no_svg_without_title.rs. The guard that is supposed to let a hidden svg through looked like this:
if let Some(aria_hidden_attr) = node.find_attribute_by_name("aria-hidden")
&& let Some(attr_static_val) = aria_hidden_attr.as_static_value()
{
let attr_text = attr_static_val.text();
if attr_text == "true" {
return None;
}
}
Read it top down. Find the aria-hidden attribute. Read its static value. If that value is the string "true", return None, which means no diagnostic. Reasonable for aria-hidden="true" and aria-hidden={true}. The trouble is the second line, as_static_value().
Here is what that helper does, in crates/biome_js_syntax/src/jsx_ext.rs:
pub fn as_static_value(&self) -> Option<StaticValue> {
self.initializer()?.value().ok()?.as_static_value()
}
The first thing it touches is self.initializer()?. An initializer is the = something part of an attribute. aria-hidden="true" has one. aria-hidden={true} has one. The bare shorthand aria-hidden has none. For the shorthand, initializer() returns None, the ? short circuits and as_static_value() returns None. The && let Some(..) in the guard fails, the whole block is skipped and the svg falls through to the title checks and gets flagged.
So the rule never actually decided the shorthand was not hidden. It just never got far enough to ask. The value was gone one line before the comparison that mattered.
The fix
The shorthand carries its truth in its presence, not in a value. The moment we know the attribute exists but has no initializer, we can treat it as true and stop:
if let Some(aria_hidden_attr) = node.find_attribute_by_name("aria-hidden") {
// In JSX the boolean shorthand `aria-hidden` (an attribute with no
// initializer) is equivalent to `aria-hidden={true}`, so it hides
// the svg from the accessibility tree and no title is required.
aria_hidden_attr.initializer()?;
if let Some(attr_static_val) = aria_hidden_attr.as_static_value()
&& attr_static_val.text() == "true"
{
return None;
}
}
aria_hidden_attr.initializer()? is the whole fix. If there is no initializer we are looking at the shorthand, so ? returns None from the rule and no diagnostic is raised. If there is an initializer, the existing "true" check runs exactly as before, so aria-hidden="true", aria-hidden={true} and aria-hidden={false} all keep the behavior they had. Clippy insists on the ? form here over an explicit if ... .is_none() { return None; }. Biome's CI denies warnings, so the terse version is the one that compiles clean.
Watch it fail, then pass
I added two valid cases to the rule's spec at crates/biome_js_analyze/tests/specs/a11y/noSvgWithoutTitle/valid.jsx:
<svg aria-hidden><rect /></svg>
<svg aria-hidden={true}><rect /></svg>
With the source reverted to the old guard but these cases in place, the spec test fails on exactly the shorthand:
× Alternative text title element cannot be empty
> 43 │ <svg aria-hidden><rect /></svg>
Line 44, <svg aria-hidden={true}>, does not fail, which matches the report: the value forms always worked, only the shorthand was broken. Put the one-line fix back and both cases pass while the invalid.jsx spec still flags every svg that genuinely has no title.
The rest of the gates are green too. cargo run -p rules_check passes, so the new valid example I added to the rule's documentation is checked the same way. cargo fmt --all --check is clean. cargo clippy -p biome_js_analyze --all-features --all-targets -- --deny warnings is clean. The snapshot regenerates with INSTA_UPDATE=always and produces no changes beyond the new input lines.
The change
This is a fix inside Biome itself, in the linter. Issue #11317, pull request biomejs/biome#11334, branch fix/no-svg-without-title-shorthand-aria-hidden at commit 5d92567. One statement of source in no_svg_without_title.rs, two spec cases, a documentation example and a changeset. A dead read of a value that was never there, fixed by asking the question one line earlier.
AI disclosure
AI assistance (Claude, Anthropic) was used to trace the root cause, write the fix and the test cases, then run the checks. I own the change, reviewed it and verified it locally before submitting. Verified: the rule spec test passes with the fix and fails on the shorthand case without it; cargo run -p rules_check, cargo fmt --all --check and cargo clippy -p biome_js_analyze --all-features --all-targets -- --deny warnings are clean; the snapshot regenerates with no changes.
Top comments (0)