Skip to content

Commit

Permalink
Create rule S6823 - DOM elements with the aria-activedescendant pro…
Browse files Browse the repository at this point in the history
…perty should be accessible via the tab key (#286)

DOM elements with the `aria-activedescendant` property should be accessible via the tab key
  • Loading branch information
ericmorand-sonarsource authored Feb 22, 2024
1 parent 94a0bf1 commit 4da3081
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,6 @@ public static boolean hasInteractiveRole(TagNode element) {
return INTERACTIVE_ROLES.stream().anyMatch(role -> role.equalsIgnoreCase(element.getAttribute("role")));
}

public static boolean isInteractiveElement(TagNode element) {
return INTERACTIVE_ELEMENTS.stream().anyMatch(tag -> tag.equalsIgnoreCase(element.getNodeName()));
}

public static boolean isNoninteractiveElement(TagNode element) {
return NON_INTERACTIVE_ELEMENTS.stream().anyMatch(tag -> tag.equalsIgnoreCase(element.getNodeName()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* SonarSource HTML analyzer :: Sonar Plugin
* Copyright (c) 2010-2024 SonarSource SA and Matthijs Galesloot
* [email protected]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sonar.plugins.html.checks.accessibility;

import org.sonar.check.Rule;
import org.sonar.plugins.html.api.HtmlConstants;
import org.sonar.plugins.html.checks.AbstractPageCheck;
import org.sonar.plugins.html.node.TagNode;

@Rule(key = "S6823")
public class AriaActiveDescendantHasTabIndexCheck extends AbstractPageCheck {
@Override
public void startElement(TagNode node) {
var ariaActiveDescendant = node.getAttribute("aria-activedescendant");

if (ariaActiveDescendant != null) {
var tabIndex = node.getAttribute("tabindex");

if ((tabIndex == null || tabIndex.isBlank()) && !HtmlConstants.isInteractiveElement(node)) {
createViolation(node, "An element that manages focus with `aria-activedescendant` must have a tabindex.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import org.sonar.plugins.html.checks.accessibility.AnchorsHaveContentCheck;
import org.sonar.plugins.html.checks.accessibility.AnchorsShouldNotBeUsedAsButtonsCheck;
import org.sonar.plugins.html.checks.accessibility.AriaActiveDescendantHasTabIndexCheck;
import org.sonar.plugins.html.checks.accessibility.AriaProptypesCheck;
import org.sonar.plugins.html.checks.accessibility.FocusableInteractiveElementsCheck;
import org.sonar.plugins.html.checks.accessibility.ValidAutocompleteCheck;
Expand Down Expand Up @@ -107,6 +108,7 @@ public final class CheckClasses {
AbsoluteURICheck.class,
AnchorsHaveContentCheck.class,
AnchorsShouldNotBeUsedAsButtonsCheck.class,
AriaActiveDescendantHasTabIndexCheck.class,
AriaProptypesCheck.class,
AvoidCommentedOutCodeCheck.class,
AvoidHtmlCommentCheck.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<h2>Why is this an issue?</h2>
<p>ARIA (Accessible Rich Internet Applications) attributes are used to enhance the accessibility of web content and web applications. These attributes
provide additional information about an element’s role, state, properties, and values to assistive technologies like screen readers.</p>
<p>The <code>aria-activedescendant</code> attribute is used to enhance the accessibility of composite widgets by managing focus within them. It allows
a parent element to retain active document focus while indicating which of its child elements has secondary focus. This attribute is particularly
useful in interactive components like search typeahead select lists, where the user can navigate through a list of options while continuing to type in
the input field.</p>
<p>This rule checks that DOM elements with the <code>aria-activedescendant</code> property either have an inherent tabIndex or declare one.</p>
<h2>How to fix it</h2>
<p>Make sure that DOM elements with the <code>aria-activedescendant</code> property have a <code>tabindex</code> property, or use an element with an
inherent one.</p>
<h3>Code examples</h3>
<h4>Noncompliant code example</h4>
<pre data-diff-id="1" data-diff-type="noncompliant">
&lt;div aria-activedescendant="descendantId"&gt;
&lt;div id="descendantId"&gt;&lt;/div&gt;
&lt;/div&gt;
</pre>
<h4>Compliant solution</h4>
<pre data-diff-id="1" data-diff-type="compliant">
&lt;div aria-activedescendant="descendantId" tabIndex="0"&gt;
&lt;div id="descendantId"&gt;&lt;/div&gt;
&lt;/div&gt;
</pre>
<h2>Resources</h2>
<h3>Documentation</h3>
<ul>
<li> MDN web docs - <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques">Using ARIA: Roles, states, and
properties</a> </li>
<li> MDN web docs - <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles">ARIA roles (Reference)</a> </li>
<li> MDN web docs - <a
href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-activedescendant"><code>aria-activedescendant</code>
attribute</a> </li>
<li> MDN web docs - <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex"><code>tabIndex</code> attribute</a> </li>
</ul>
<h3>Standards</h3>
<ul>
<li> W3C - <a href="https://www.w3.org/TR/wai-aria-1.2/">Accessible Rich Internet Applications (WAI-ARIA) 1.2</a> </li>
<li> W3C - <a href="https://www.w3.org/TR/wai-aria/#composite">Composite role</a> </li>
</ul>

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"title": "DOM elements with the `aria-activedescendant` property should be accessible via the tab key",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"accessibility"
],
"defaultSeverity": "Minor",
"ruleSpecification": "RSPEC-6823",
"sqKey": "S6823",
"scope": "All",
"quickfix": "targeted",
"code": {
"impacts": {
"RELIABILITY": "MEDIUM"
},
"attribute": "LOGICAL"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"S5264",
"S5725",
"S6793",
"S6823",
"S6827",
"S6840",
"S6841",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SonarSource HTML analyzer :: Sonar Plugin
* Copyright (c) 2010-2024 SonarSource SA and Matthijs Galesloot
* [email protected]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sonar.plugins.html.checks.accessibility;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.sonar.plugins.html.checks.CheckMessagesVerifierRule;
import org.sonar.plugins.html.checks.TestHelper;
import org.sonar.plugins.html.visitor.HtmlSourceCode;

import java.io.File;

class AriaActiveDescendantHasTabIndexCheckTest {
@RegisterExtension
public CheckMessagesVerifierRule checkMessagesVerifier = new CheckMessagesVerifierRule();

@Test
void html() {
HtmlSourceCode sourceCode = TestHelper.scan(
new File("src/test/resources/checks/AriaActiveDescendantHasTabIndexCheck/file.html"),
new AriaActiveDescendantHasTabIndexCheck());

checkMessagesVerifier.verify(sourceCode.getIssues())
.next().atLine(1).withMessage("An element that manages focus with `aria-activedescendant` must have a tabindex.")
.next().atLine(2)
.next().atLine(3)
.next().atLine(4)
.next().atLine(5);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div aria-activedescendant="foo"></div> <!-- Noncompliant -->
<span aria-activedescendant="foo"></span> <!-- Noncompliant -->
<table aria-activedescendant="foo"></table> <!-- Noncompliant -->
<div aria-activedescendant="foo" tabindex=""></div> <!-- Noncompliant -->
<div aria-activedescendant="descendantId"> <!-- Noncompliant -->
<div id="descendantId"></div>
</div>
<div></div>
<input/>
<div tabIndex="0"></div>
<div aria-activedescendant="foo" tabIndex="0"></div>
<div aria-activedescendant="foo" tabIndex="1"></div>
<div aria-activedescendant="foo" tabIndex="-1"></div>
<input aria-activedescendant="foo"/>
<input aria-activedescendant="foo" tabIndex="0"/>
<input aria-activedescendant="foo" tabIndex="-1"/>
<select aria-activedescendant="foo"></select>
<div aria-activedescendant="descendantId" tabIndex="0">
<div id="descendantId"></div>
</div>
<input aria-activedescendant="foo" tabIndex=""/>

0 comments on commit 4da3081

Please sign in to comment.