diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/Thymeleaf.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/Thymeleaf.java
index 24c9607c1..f7f389618 100644
--- a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/Thymeleaf.java
+++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/Thymeleaf.java
@@ -18,6 +18,10 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.sonar.plugins.html.node.Attribute;
import org.sonar.plugins.html.node.TagNode;
/**
@@ -32,9 +36,29 @@
*/
public final class Thymeleaf {
+ /**
+ * Attributes that ask Thymeleaf to render an external fragment in place of (or inside) the
+ * current element. Statically, the resulting markup is opaque — the rendered fragment can
+ * supply text, controls, or both — so checks should treat these as "do not flag".
+ */
+ public static final Set FRAGMENT_INSERTION_ATTRIBUTES = Set.of("th:insert", "th:include", "th:replace");
+
private Thymeleaf() {
}
+ /**
+ * Returns whether the node carries any Thymeleaf fragment-insertion attribute
+ * ({@code th:insert}, {@code th:include}, {@code th:replace}).
+ */
+ public static boolean hasFragmentInsertion(TagNode node) {
+ for (Attribute attribute : node.getAttributes()) {
+ if (FRAGMENT_INSERTION_ATTRIBUTES.contains(attribute.getName().toLowerCase(Locale.ROOT))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns whether {@code th:attr} on the node assigns a value to the given attribute.
* Presence check only — the assigned value may itself be empty.
@@ -84,6 +108,33 @@ public static boolean isEmptyAssignmentValue(String value) {
return false;
}
+ /**
+ * Returns whether the raw attribute value is absent, blank, or a quoted literal that resolves
+ * to whitespace. Combines a null/blank check with {@link #isEmptyAssignmentValue(String)} so
+ * callers do not have to trim and null-check separately.
+ */
+ public static boolean isEmptyValue(@Nullable String value) {
+ if (value == null) {
+ return true;
+ }
+ String trimmed = value.trim();
+ return trimmed.isEmpty() || isEmptyAssignmentValue(trimmed);
+ }
+
+ /**
+ * Returns whether the node sets {@code attributeName} via either the literal {@code th:NAME}
+ * attribute or a {@code th:attr=NAME=...} assignment, with a non-empty value. This is the
+ * Thymeleaf-only counterpart used to enrich attribute-presence checks; combine it with a
+ * plain property lookup at the call site when both forms should be accepted.
+ */
+ public static boolean hasNonEmptyThymeleafAttribute(TagNode node, String attributeName) {
+ String literalValue = node.getAttribute("th:" + attributeName.toLowerCase(Locale.ROOT));
+ if (!isEmptyValue(literalValue)) {
+ return true;
+ }
+ return !isEmptyValue(getAttrAssignmentValue(node, attributeName));
+ }
+
private static List splitAssignments(String thAttrValue) {
List assignments = new ArrayList<>();
StringBuilder currentAssignment = new StringBuilder();
diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/accessibility/AccessibilityUtils.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/accessibility/AccessibilityUtils.java
index dd13a4563..d5b3cd89c 100644
--- a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/accessibility/AccessibilityUtils.java
+++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/accessibility/AccessibilityUtils.java
@@ -16,16 +16,40 @@
*/
package org.sonar.plugins.html.api.accessibility;
+import java.util.Set;
+import org.sonar.plugins.html.api.Thymeleaf;
import org.sonar.plugins.html.node.TagNode;
import static org.sonar.plugins.html.api.HtmlConstants.isInteractiveElement;
public class AccessibilityUtils {
+ /**
+ * Attributes that template engines use to inject text content into an element at render time:
+ * Thymeleaf {@code th:text}/{@code th:utext} and Vue {@code v-text}/{@code v-html}. Centralized
+ * here so accessibility checks that ask "does this element get its text from a template?" all
+ * see the same definition.
+ */
+ public static final Set TEMPLATE_TEXT_ATTRIBUTES = Set.of("th:text", "th:utext", "v-text", "v-html");
+
private AccessibilityUtils() {
// utility class
}
+ /**
+ * Returns whether {@code element} carries any template-text attribute with a usable value,
+ * including {@code th:attr} assignments to {@code text} or {@code utext}.
+ */
+ public static boolean hasNonEmptyTemplateTextAttribute(TagNode element) {
+ for (String attributeName : TEMPLATE_TEXT_ATTRIBUTES) {
+ if (!Thymeleaf.isEmptyValue(element.getAttribute(attributeName))) {
+ return true;
+ }
+ }
+ return Thymeleaf.hasNonEmptyThymeleafAttribute(element, "text")
+ || Thymeleaf.hasNonEmptyThymeleafAttribute(element, "utext");
+ }
+
public static boolean isHiddenFromScreenReader(TagNode element) {
return (
(
diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/AnchorsHaveContentCheck.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/AnchorsHaveContentCheck.java
index 0ca75e810..ae96cdeeb 100644
--- a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/AnchorsHaveContentCheck.java
+++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/AnchorsHaveContentCheck.java
@@ -26,6 +26,7 @@
import org.sonar.plugins.html.node.TagNode;
import org.sonar.plugins.html.node.TextNode;
+import static org.sonar.plugins.html.api.accessibility.AccessibilityUtils.hasNonEmptyTemplateTextAttribute;
import static org.sonar.plugins.html.api.accessibility.AccessibilityUtils.isHiddenFromScreenReader;
@Rule(key = "S6827")
@@ -113,7 +114,7 @@ private static boolean hasContent(TagNode element) {
return true;
}
}
- return element.hasProperty("title") || element.hasProperty("aria-label") || element.hasAttribute("th:text")
- || element.hasAttribute("th:utext") || element.hasAttribute("v-text") || element.hasAttribute("v-html");
+ return element.hasProperty("title") || element.hasProperty("aria-label")
+ || hasNonEmptyTemplateTextAttribute(element);
}
}
diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/HeadingHasAccessibleContentCheck.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/HeadingHasAccessibleContentCheck.java
index 2c9c32337..ac651e3ae 100644
--- a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/HeadingHasAccessibleContentCheck.java
+++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/HeadingHasAccessibleContentCheck.java
@@ -19,6 +19,7 @@
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.accessibility.AccessibilityUtils;
import org.sonar.plugins.html.checks.AbstractPageCheck;
import org.sonar.plugins.html.node.DirectiveNode;
import org.sonar.plugins.html.node.ExpressionNode;
@@ -38,11 +39,6 @@ public class HeadingHasAccessibleContentCheck extends AbstractPageCheck {
"aria-hidden"
);
- private final List vueJsContentLikeAttributes = List.of(
- "v-html",
- "v-text"
- );
-
private final BufferStack bufferStack = new BufferStack();
private final Deque openingHeadingTags = new ArrayDeque<>();
@@ -65,8 +61,8 @@ public void startElement(TagNode node) {
}
}
- // vueJS attributes that maps to content are considered as content
- vueJsContentLikeAttributes.forEach(attributeName -> {
+ // template-text attributes (Thymeleaf th:text/th:utext, Vue v-text/v-html) are content
+ AccessibilityUtils.TEMPLATE_TEXT_ATTRIBUTES.forEach(attributeName -> {
String nodeAttribute = node.getAttribute(attributeName);
if (nodeAttribute != null && !nodeAttribute.isBlank() && bufferStack.getLevel() > 0) {
diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/LabelHasAssociatedControlCheck.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/LabelHasAssociatedControlCheck.java
index 844f63076..21ae0363d 100644
--- a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/LabelHasAssociatedControlCheck.java
+++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/LabelHasAssociatedControlCheck.java
@@ -21,8 +21,11 @@
import java.util.Set;
import java.util.regex.Pattern;
import org.sonar.check.Rule;
+import org.sonar.plugins.html.api.Helpers;
+import org.sonar.plugins.html.api.Thymeleaf;
+import org.sonar.plugins.html.api.accessibility.AccessibilityUtils;
import org.sonar.plugins.html.checks.AbstractPageCheck;
-import org.sonar.plugins.html.node.CommentNode;
+import org.sonar.plugins.html.node.Attribute;
import org.sonar.plugins.html.node.DirectiveNode;
import org.sonar.plugins.html.node.ExpressionNode;
import org.sonar.plugins.html.node.Node;
@@ -59,9 +62,14 @@ public void startDocument(List nodes) {
public void startElement(TagNode node) {
if (isLabel(node)) {
label = node;
- foundControl = hasForAttribute(label);
- foundAccessibleLabel = false;
+ foundControl = hasControlAssociationHint(label);
+ foundAccessibleLabel = hasAccessibleTextHint(label);
foundLabelBodyContent = false;
+ // A fragment-rendered label is opaque to static analysis — accept both axes.
+ if (Thymeleaf.hasFragmentInsertion(label)) {
+ foundControl = true;
+ foundAccessibleLabel = true;
+ }
} else {
if (label != null) {
foundLabelBodyContent = true;
@@ -69,32 +77,53 @@ public void startElement(TagNode node) {
if (isControl(node)) {
foundControl = true;
}
- }
- if (hasAccessibleLabel(node)) {
- foundAccessibleLabel = true;
+ if (label != null && hasAccessibleTextHint(node)) {
+ foundAccessibleLabel = true;
+ }
+ // Razor view-component or child can supply both text and control.
+ if (label != null && Helpers.isRazorFragmentTagHelper(node)) {
+ foundControl = true;
+ foundAccessibleLabel = true;
+ }
}
}
- private static boolean hasForAttribute(TagNode label) {
- return label.hasProperty("for") ||
- label.hasProperty("htmlFor") ||
- // Angular binding
- label.hasProperty("[for]") ||
- // Vue shorthand binding
- label.hasProperty(":for") ||
- // Vue full binding syntax
- label.hasProperty("v-bind:for") ||
- // ASP.NET Core Tag Helper
- label.hasProperty("asp-for");
+ private static boolean hasControlAssociationHint(TagNode label) {
+ return hasPropertyHint(label, "for")
+ || hasPropertyHint(label, "htmlFor")
+ || hasAttributeHint(label, "asp-for")
+ || Thymeleaf.hasNonEmptyThymeleafAttribute(label, "for");
}
- private static boolean hasAccessibleLabel(TagNode node) {
- return
- node.hasProperty("alt") ||
- node.hasProperty("aria-labelledby") ||
- node.hasProperty("aria-label") ||
+ private static boolean hasAccessibleTextHint(TagNode node) {
+ return hasPropertyHint(node, "alt")
+ || hasPropertyHint(node, "aria-labelledby")
+ || hasPropertyHint(node, "aria-label")
+ // Angular [innerText]/[innerHTML]/[textContent] write text content at runtime.
+ || hasPropertyHint(node, "innerText")
+ || hasPropertyHint(node, "innerHTML")
+ || hasPropertyHint(node, "textContent")
+ // Thymeleaf th:aria-label / th:attr="aria-label=..." (and aria-labelledby/alt variants).
+ || Thymeleaf.hasNonEmptyThymeleafAttribute(node, "aria-label")
+ || Thymeleaf.hasNonEmptyThymeleafAttribute(node, "aria-labelledby")
+ || Thymeleaf.hasNonEmptyThymeleafAttribute(node, "alt")
+ || AccessibilityUtils.hasNonEmptyTemplateTextAttribute(node)
// see https://sonarsource.github.io/rspec/#/rspec/S1926
- "FMT:MESSAGE".equalsIgnoreCase(node.getNodeName());
+ || "FMT:MESSAGE".equalsIgnoreCase(node.getNodeName());
+ }
+
+ // Property lookup that accepts Angular/Vue binding forms even with empty value — the binding name itself is the hint.
+ private static boolean hasPropertyHint(TagNode node, String propertyName) {
+ Attribute property = node.getProperty(propertyName);
+ return property != null && (isBindingForm(property, propertyName) || !Thymeleaf.isEmptyValue(property.getValue()));
+ }
+
+ private static boolean hasAttributeHint(TagNode node, String attributeName) {
+ return !Thymeleaf.isEmptyValue(node.getAttribute(attributeName));
+ }
+
+ private static boolean isBindingForm(Attribute attribute, String canonicalName) {
+ return !canonicalName.equalsIgnoreCase(attribute.getName());
}
private static boolean isLabel(TagNode node) {
@@ -116,13 +145,11 @@ public void characters(TextNode textNode) {
if (RAZOR_CONTROL_PATTERN.matcher(textNode.getCode()).find()) {
foundControl = true;
}
- }
- }
-
- @Override
- public void comment(CommentNode node) {
- if (label != null) {
- foundLabelBodyContent = true;
+ // Razor fragment rendering (@Html.PartialAsync, @RenderBody, ...) is opaque.
+ if (Helpers.isRazorFile(getHtmlSourceCode()) && Helpers.containsRazorFragmentRendering(textNode.getCode())) {
+ foundControl = true;
+ foundAccessibleLabel = true;
+ }
}
}
@@ -146,7 +173,7 @@ public void expression(ExpressionNode node) {
@Override
public void endElement(TagNode node) {
if (isLabel(node)) {
- if (label != null && label.hasProperty("asp-for") && !foundLabelBodyContent) {
+ if (label != null && hasAttributeHint(label, "asp-for") && !foundLabelBodyContent) {
foundAccessibleLabel = true;
}
if ((!foundAccessibleLabel || !foundControl) && label != null) {
diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/sonar/ImgWithoutAltCheck.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/sonar/ImgWithoutAltCheck.java
index e2ad9c762..d0f95484a 100644
--- a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/sonar/ImgWithoutAltCheck.java
+++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/sonar/ImgWithoutAltCheck.java
@@ -16,7 +16,6 @@
*/
package org.sonar.plugins.html.checks.sonar;
-import java.util.Locale;
import org.sonar.check.Rule;
import org.sonar.plugins.html.api.Thymeleaf;
import org.sonar.plugins.html.checks.AbstractPageCheck;
@@ -99,14 +98,7 @@ private static boolean hasNonEmptyAttribute(TagNode node, String attributeName)
if (value != null && !value.trim().isEmpty()) {
return true;
}
-
- String thymeleafValue = node.getAttribute("th:" + attributeName.toLowerCase(Locale.ROOT));
- if (thymeleafValue != null) {
- return !Thymeleaf.isEmptyAssignmentValue(thymeleafValue.trim());
- }
-
- String thymeleafAssignedValue = Thymeleaf.getAttrAssignmentValue(node, attributeName);
- return thymeleafAssignedValue != null && !Thymeleaf.isEmptyAssignmentValue(thymeleafAssignedValue);
+ return Thymeleaf.hasNonEmptyThymeleafAttribute(node, attributeName);
}
/**
diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/sonar/TableWithoutHeaderCheck.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/sonar/TableWithoutHeaderCheck.java
index 84e46cee6..dff3d2fbc 100644
--- a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/sonar/TableWithoutHeaderCheck.java
+++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/sonar/TableWithoutHeaderCheck.java
@@ -22,6 +22,7 @@
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.plugins.html.api.Helpers;
+import org.sonar.plugins.html.api.Thymeleaf;
import org.sonar.plugins.html.checks.AbstractPageCheck;
import org.sonar.plugins.html.node.Attribute;
import org.sonar.plugins.html.node.Node;
@@ -32,8 +33,6 @@
@Rule(key = "S5256")
public class TableWithoutHeaderCheck extends AbstractPageCheck {
- private static final Set THYMELEAF_FRAGMENT_INSERTION_KEYWORDS = Set.of("th:insert", "th:include", "th:replace");
-
private final Set tablesWithRazorFragmentRendering = new HashSet<>();
@Override
@@ -127,7 +126,7 @@ private static boolean hasThymeleafFragmentInsertionFromTableAttribute(List nodes) {
for (TagNode node : nodes) {
- if (node.getAttributes().stream().map(Attribute::getName).anyMatch(THYMELEAF_FRAGMENT_INSERTION_KEYWORDS::contains)
+ if (node.getAttributes().stream().map(Attribute::getName).anyMatch(Thymeleaf.FRAGMENT_INSERTION_ATTRIBUTES::contains)
|| hasThymeleafFragmentInsertionFromTableChildren(node.getChildren())) {
return true;
}
diff --git a/sonar-html-plugin/src/test/java/org/sonar/plugins/html/api/ThymeleafTest.java b/sonar-html-plugin/src/test/java/org/sonar/plugins/html/api/ThymeleafTest.java
index 20d0bf201..cdaf3df6d 100644
--- a/sonar-html-plugin/src/test/java/org/sonar/plugins/html/api/ThymeleafTest.java
+++ b/sonar-html-plugin/src/test/java/org/sonar/plugins/html/api/ThymeleafTest.java
@@ -106,6 +106,55 @@ void isEmptyAssignmentValue_nonEmptyValues() {
assertThat(Thymeleaf.isEmptyAssignmentValue("foo")).isFalse();
}
+ @Test
+ void isEmptyValue_nullOrBlank() {
+ assertThat(Thymeleaf.isEmptyValue(null)).isTrue();
+ assertThat(Thymeleaf.isEmptyValue("")).isTrue();
+ assertThat(Thymeleaf.isEmptyValue(" ")).isTrue();
+ }
+
+ @Test
+ void isEmptyValue_emptyQuotedLiteralWithPadding() {
+ assertThat(Thymeleaf.isEmptyValue(" '' ")).isTrue();
+ assertThat(Thymeleaf.isEmptyValue("\" \"")).isTrue();
+ }
+
+ @Test
+ void isEmptyValue_nonEmptyValues() {
+ assertThat(Thymeleaf.isEmptyValue("foo")).isFalse();
+ assertThat(Thymeleaf.isEmptyValue("'foo'")).isFalse();
+ assertThat(Thymeleaf.isEmptyValue("#{logo}")).isFalse();
+ }
+
+ @Test
+ void hasNonEmptyThymeleafAttribute_returnsFalse_whenNeitherFormIsPresent() {
+ assertThat(Thymeleaf.hasNonEmptyThymeleafAttribute(tagWithoutThAttr(), "alt")).isFalse();
+ }
+
+ @Test
+ void hasNonEmptyThymeleafAttribute_returnsTrue_whenLiteralIsSet() {
+ TagNode node = new TagNode();
+ node.getAttributes().add(new Attribute("th:alt", "#{logo}"));
+ assertThat(Thymeleaf.hasNonEmptyThymeleafAttribute(node, "alt")).isTrue();
+ }
+
+ @Test
+ void hasNonEmptyThymeleafAttribute_returnsFalse_whenLiteralIsEmpty() {
+ TagNode node = new TagNode();
+ node.getAttributes().add(new Attribute("th:alt", "''"));
+ assertThat(Thymeleaf.hasNonEmptyThymeleafAttribute(node, "alt")).isFalse();
+ }
+
+ @Test
+ void hasNonEmptyThymeleafAttribute_returnsTrue_whenAssignedViaThAttr() {
+ assertThat(Thymeleaf.hasNonEmptyThymeleafAttribute(tagWithThAttr("alt=#{logo}"), "alt")).isTrue();
+ }
+
+ @Test
+ void hasNonEmptyThymeleafAttribute_returnsFalse_whenAssignmentIsEmpty() {
+ assertThat(Thymeleaf.hasNonEmptyThymeleafAttribute(tagWithThAttr("alt=''"), "alt")).isFalse();
+ }
+
private static TagNode tagWithThAttr(String thAttrValue) {
TagNode node = new TagNode();
node.getAttributes().add(new Attribute("th:attr", thAttrValue));
diff --git a/sonar-html-plugin/src/test/java/org/sonar/plugins/html/checks/accessibility/LabelHasAssociatedControlCheckTest.java b/sonar-html-plugin/src/test/java/org/sonar/plugins/html/checks/accessibility/LabelHasAssociatedControlCheckTest.java
index 419a9258b..c004ae0bd 100644
--- a/sonar-html-plugin/src/test/java/org/sonar/plugins/html/checks/accessibility/LabelHasAssociatedControlCheckTest.java
+++ b/sonar-html-plugin/src/test/java/org/sonar/plugins/html/checks/accessibility/LabelHasAssociatedControlCheckTest.java
@@ -40,6 +40,7 @@ void nesting() {
.next().atLine(9)
.next().atLine(11)
.next().atLine(16)
+ .next().atLine(30)
.noMore();
}
@@ -57,6 +58,8 @@ void forAttribute() {
.next().atLine(10)
.next().atLine(12)
.next().atLine(14)
+ .next().atLine(15)
+ .next().atLine(16)
.noMore();
}
@@ -111,7 +114,20 @@ void aspFor() {
checkMessagesVerifier.verify(sourceCode.getIssues())
.next().atLine(11).withMessage("A form label must be associated with a control and have accessible text.")
.next().atLine(12)
- .next().atLine(13)
+ .noMore();
+ }
+
+ @Test
+ void templateGeneratedLabels() {
+ HtmlSourceCode sourceCode = TestHelper.scan(
+ new File("src/test/resources/checks/LabelHasAssociatedControlCheck/templateGenerated.html"),
+ new LabelHasAssociatedControlCheck());
+ checkMessagesVerifier.verify(sourceCode.getIssues())
+ .next().atLine(18).withMessage("A form label must be associated with a control and have accessible text.")
+ .next().atLine(21)
+ .next().atLine(22)
+ .next().atLine(25)
+ .next().atLine(26)
.noMore();
}
diff --git a/sonar-html-plugin/src/test/resources/checks/HeadingHasAccessibleContentCheck/file.html b/sonar-html-plugin/src/test/resources/checks/HeadingHasAccessibleContentCheck/file.html
index ae0eb5d8e..194f37250 100644
--- a/sonar-html-plugin/src/test/resources/checks/HeadingHasAccessibleContentCheck/file.html
+++ b/sonar-html-plugin/src/test/resources/checks/HeadingHasAccessibleContentCheck/file.html
@@ -89,4 +89,7 @@
-
\ No newline at end of file
+
+
+
+
\ No newline at end of file
diff --git a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/aspFor.cshtml b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/aspFor.cshtml
index 5f99950f2..e92d7db47 100644
--- a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/aspFor.cshtml
+++ b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/aspFor.cshtml
@@ -10,4 +10,6 @@
+
+
diff --git a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/binding.html b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/binding.html
index 35e07165c..6c97075f5 100644
--- a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/binding.html
+++ b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/binding.html
@@ -14,3 +14,8 @@
+
+
+
+
+
diff --git a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/for.html b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/for.html
index d29c17231..d0f80e1e0 100644
--- a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/for.html
+++ b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/for.html
@@ -12,3 +12,5 @@
+
+
diff --git a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/nesting.html b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/nesting.html
index e0bea358b..58b0647be 100644
--- a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/nesting.html
+++ b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/nesting.html
@@ -26,3 +26,5 @@
+
+
diff --git a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/razor.cshtml b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/razor.cshtml
index c7901dd64..990320d8c 100644
--- a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/razor.cshtml
+++ b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/razor.cshtml
@@ -15,3 +15,11 @@
+
+
+
+
+
+
+
+
diff --git a/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/templateGenerated.html b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/templateGenerated.html
new file mode 100644
index 000000000..b2163de0d
--- /dev/null
+++ b/sonar-html-plugin/src/test/resources/checks/LabelHasAssociatedControlCheck/templateGenerated.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+