-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SONARHTML-191 Create rule S6850: Heading elements should have accessi…
…ble content (#269) * Create rule S6850: Heading elements should have accessible content * Fix formatting according to SonarQube Quality Gate * Support descendants content * Fix violations * Improve code coverage * Add updated rule manifest * Remove calls to parent implementations * Fix issues detected by PR review
- Loading branch information
1 parent
4de7dfe
commit b913dbc
Showing
14 changed files
with
641 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"project:voten/resources/assets/js/components/pages/Credits.vue": [ | ||
30, | ||
44, | ||
62, | ||
80, | ||
98, | ||
134, | ||
148, | ||
165, | ||
182 | ||
], | ||
"project:voten/resources/views/backend/server-controls.blade.php": [ | ||
142 | ||
] | ||
} |
69 changes: 69 additions & 0 deletions
69
sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/BufferStack.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
* 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.api; | ||
|
||
import java.util.ArrayDeque; | ||
import java.util.Deque; | ||
|
||
public class BufferStack { | ||
Deque<StringBuffer> buffers = new ArrayDeque<>(); | ||
|
||
public int getLevel() { | ||
return buffers.size(); | ||
} | ||
|
||
public void start() { | ||
buffers.push(new StringBuffer()); | ||
} | ||
|
||
public void write(String data) { | ||
if (!buffers.isEmpty()) { | ||
StringBuffer active = buffers.getFirst(); | ||
|
||
active.append(data); | ||
} | ||
} | ||
|
||
public String getAndFlush() { | ||
String content = getContents(); | ||
|
||
endAndFlush(); | ||
|
||
return content; | ||
} | ||
|
||
public String getContents() { | ||
StringBuffer active = buffers.getFirst(); | ||
|
||
return active != null ? active.toString() : ""; | ||
} | ||
|
||
public void endAndFlush() { | ||
flush(); | ||
|
||
buffers.pop(); | ||
} | ||
|
||
public void flush() { | ||
StringBuffer active = buffers.pop(); | ||
|
||
write(active.toString()); | ||
|
||
buffers.push(new StringBuffer()); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/Helpers.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* 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.api; | ||
|
||
import org.sonar.plugins.html.node.TagNode; | ||
|
||
public class Helpers { | ||
private Helpers() { | ||
} | ||
|
||
public static boolean isHeadingTag(TagNode node) { | ||
return node.getNodeName().length() == 2 && | ||
Character.toUpperCase(node.getNodeName().charAt(0)) == 'H' && | ||
node.getNodeName().charAt(1) >= '1' && | ||
node.getNodeName().charAt(1) <= '6'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
125 changes: 125 additions & 0 deletions
125
...in/java/org/sonar/plugins/html/checks/accessibility/HeadingHasAccessibleContentCheck.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* | ||
* 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.Helpers; | ||
import org.sonar.plugins.html.api.BufferStack; | ||
import org.sonar.plugins.html.api.HtmlConstants; | ||
import org.sonar.plugins.html.checks.AbstractPageCheck; | ||
import org.sonar.plugins.html.node.DirectiveNode; | ||
import org.sonar.plugins.html.node.ExpressionNode; | ||
import org.sonar.plugins.html.node.TagNode; | ||
import org.sonar.plugins.html.node.TextNode; | ||
import org.sonar.plugins.html.node.Attribute; | ||
|
||
import java.util.ArrayDeque; | ||
import java.util.Deque; | ||
import java.util.List; | ||
|
||
@Rule(key = "S6850") | ||
public class HeadingHasAccessibleContentCheck extends AbstractPageCheck { | ||
private final List<String> invalidAttributes = List.of( | ||
"aria-hidden" | ||
); | ||
|
||
private final List<String> vueJsContentLikeAttributes = List.of( | ||
"v-html", | ||
"v-text" | ||
); | ||
|
||
private final BufferStack bufferStack = new BufferStack(); | ||
|
||
private final Deque<TagNode> openingHeadingTags = new ArrayDeque<>(); | ||
|
||
@Override | ||
public void startElement(TagNode node) { | ||
if (Helpers.isHeadingTag(node)) { | ||
bufferStack.start(); | ||
openingHeadingTags.push(node); | ||
|
||
if (hasAnInvalidAttribute(node)) { | ||
createViolation(node); | ||
} | ||
} else { | ||
String nodeName = node.getNodeName(); | ||
|
||
// tags that are not part of the known HTML tags list are considered as content | ||
if (!HtmlConstants.KNOWN_HTML_TAGS.contains(nodeName)) { | ||
bufferStack.write(nodeName); | ||
} | ||
} | ||
|
||
// vueJS attributes that maps to content are considered as content | ||
vueJsContentLikeAttributes.forEach(attributeName -> { | ||
String nodeAttribute = node.getAttribute(attributeName); | ||
|
||
if (nodeAttribute != null && !nodeAttribute.isBlank() && bufferStack.getLevel() > 0) { | ||
bufferStack.write(nodeAttribute); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public void endElement(TagNode node) { | ||
if (Helpers.isHeadingTag(node) && !openingHeadingTags.isEmpty()) { | ||
String content = bufferStack.getAndFlush(); | ||
TagNode openingTag = openingHeadingTags.pop(); | ||
|
||
if (content.isBlank()) { | ||
createViolation(openingTag); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void endDocument() { | ||
openingHeadingTags.clear(); | ||
} | ||
|
||
@Override | ||
public void characters(TextNode textNode) { | ||
if (bufferStack.getLevel() > 0) { | ||
bufferStack.write(textNode.toString()); | ||
} | ||
} | ||
|
||
@Override | ||
public void expression(ExpressionNode expressionNode) { | ||
if (bufferStack.getLevel() > 0) { | ||
bufferStack.write(expressionNode.toString()); | ||
} | ||
} | ||
|
||
@Override | ||
public void directive(DirectiveNode directiveNode) { | ||
if (bufferStack.getLevel() > 0) { | ||
bufferStack.write(directiveNode.toString()); | ||
} | ||
} | ||
|
||
private boolean hasAnInvalidAttribute(TagNode node) { | ||
return node.getAttributes().stream() | ||
.map(Attribute::getName) | ||
.anyMatch(invalidAttributes::contains); | ||
} | ||
|
||
private void createViolation(TagNode node) { | ||
super.createViolation(node.getStartLinePosition(), "Headings must have content and the content must be accessible by a screen reader."); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.