JS-1465 Fix S6819 false positive: SVG role="img" with accessible name#6856
Conversation
Tests cover the scenario where inline SVG elements with role="img" and a proper accessible name (via <title> child, aria-label, or aria-labelledby) are incorrectly flagged as violations suggesting <img> replacement. The tests verify that the decorator suppresses reports for these WCAG-compliant patterns while still flagging SVG elements with role="img" but no accessible name. Relates to JS-1465
The rule was incorrectly flagging inline SVG elements with role="img" that have proper accessible names via <title> child elements, aria-label, or aria-labelledby attributes. This is a WCAG-compliant pattern for icon components that require CSS class control, animation, or programmatic styling — capabilities that native <img> tags cannot provide. Extends decorator.ts with a new isSemanticSvgImg() helper that suppresses reports for SVG elements with role="img" when they have an accessible name. SVG elements with role="img" but no accessible name continue to be flagged as true positives. Also adds a compliant code example to the rule spec. Implementation follows the approved proposal guidelines from JS-1465. Relates to JS-1465
Rule Profile
A genuine violation — an SVG with <svg role="img" viewBox="0 0 24 24">
<path d="M5 12h14"/>
</svg>
// ^ S6819: Use <img alt=...> instead of the "img" roleFalse Positive Pattern
// FP 1: accessible name via <title> child
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>Nx</title>
<path d="M11.987 14.138l-3.132 4.923..." />
</svg>
// FP 2: accessible name via aria-label
<svg role="img" aria-label="GitHub" viewBox="0 0 24 24">
<path d="..." />
</svg>
// FP 3: accessible name via aria-labelledby
<svg role="img" aria-labelledby="icon-title" viewBox="0 0 24 24">
<title id="icon-title">Download</title>
<path d="..." />
</svg>False Negative RiskThe guard is conservative: suppression only fires when
The one truly conservative choice is dynamic expressions — Fix SummaryThe implementation adds two pure functions to
|
Ruling Report✅ No changes to ruling expected issues in this PR |
SummaryS6819 False Positive Fix: Semantic Inline SVG Icons The PR fixes a false positive in S6819 (prefer-tag-over-role), which was incorrectly flagging inline Changes:
Scope:
What reviewers should knowStart here: Look at the new Key implementation details:
Test coverage to review:
Edge cases handled:
Minor note: README.md has two unrelated description updates for security rules—these are cosmetic and do not affect behavior.
|
…rc/jsts/rules/S6819/decorator.ts Comment: `getProp` returns the prop AST node whenever the attribute is present, regardless of its value. This means `<svg role="img" aria-label="">` (explicit empty string — no accessible name) would still satisfy the guard and suppress the report, producing a false negative. Contrast with `isDecorativeSvg` just above, which correctly uses `getLiteralPropValue` to inspect the actual value before accepting the prop. For dynamic expressions (`aria-label={someVar}`), `getLiteralPropValue` returns `null`, which is fine — we can't statically determine the value so suppressing is the safe choice. Only the literal empty-string case is broken. ```suggestion const ariaLabelProp = getProp(attributes, 'aria-label'); if (ariaLabelProp && getLiteralPropValue(ariaLabelProp) !== '') { return true; } const ariaLabelledbyProp = getProp(attributes, 'aria-labelledby'); if (ariaLabelledbyProp && getLiteralPropValue(ariaLabelledbyProp) !== '') { return true; } ``` - [ ] Mark as noise
…rc/jsts/rules/S6819/unit.test.ts Comment: The `invalid` block only tests SVG with no accessible-name attributes at all. There is no test for `<svg role="img" aria-label="">` (explicit empty string), which is the edge case the guard currently mishandles. Adding it here would both document the expected behaviour and catch a regression if the guard is ever simplified back to a bare `getProp` check. ```suggestion invalid: [ // svg role="img" without an accessible name is still a true positive { code: `<svg role="img" viewBox="0 0 24 24"><path d="M5 12h14"/></svg>`, errors: 1, }, // svg role="img" with an explicit empty aria-label provides no accessible name { code: `<svg role="img" aria-label=""><path d="M5 12h14"/></svg>`, errors: 1, }, ], ``` - [ ] Mark as noise
|
…rc/jsts/rules/S6819/decorator.ts Comment: `hasTitleChild` checks for the *presence* of a `<title>` child but not whether it has any content. `<svg role="img"><title></title></svg>` passes the guard and suppresses the report even though an empty `<title>` provides no accessible name — the exact same false-negative class as the `aria-label=""` issue fixed in this commit. The fix should match the treatment of `aria-label`: accept a `<title>` only when it has at least one non-whitespace text child or a dynamic expression (which we can't statically evaluate, so we conservatively suppress). ```suggestion return parent.children.some( child => child.type === 'JSXElement' && child.openingElement.name.type === 'JSXIdentifier' && child.openingElement.name.name === 'title' && child.children.some( c => (c.type === 'JSXText' && c.value.trim() !== '') || c.type === 'JSXExpressionContainer', ), ); ``` - [ ] Mark as noise




Relates to JS-1465
S6819 was incorrectly flagging
<svg role="img">elements that carry a proper accessible name — a<title>child,aria-label, oraria-labelledbyattribute. These are valid WCAG-compliant patterns for inline SVG icons that need CSS control or animation that a native<img>cannot provide. The fix adds a tightly scopedisSemanticSvgImg()guard in the decorator that suppresses the report only when bothrole="img"and a non-empty accessible name are present; SVGs withrole="img"but no accessible name remain true positives.aria-label/aria-labelledbyare correctly treated as missing (no suppression), matching howisDecorativeSvgalready handles attribute values.<title>suppression requires at least one non-whitespace text child or a dynamic expression child — an empty<title>does not suppress.