diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/accessibility/ControlGroup.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/accessibility/ControlGroup.java new file mode 100644 index 000000000..e412d2400 --- /dev/null +++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/api/accessibility/ControlGroup.java @@ -0,0 +1,141 @@ +/* + * SonarSource HTML analyzer :: Sonar Plugin + * Copyright (c) 2010-2024 SonarSource SA and Matthijs Galesloot + * sonarqube@googlegroups.com + * + * 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.accessibility; + +import org.sonar.plugins.html.node.TagNode; + +public class ControlGroup { + public static boolean belongsToAutofillExpectationMantleControlGroup(TagNode node) { + if (!node.getNodeName().equalsIgnoreCase("input")) { + return true; + } + + var type = node.getAttribute("type"); + + return type == null || !type.equalsIgnoreCase("hidden"); + } + + public static boolean belongsToDateControlGroup(TagNode node) { + if (belongsToTextControlGroup(node)) { + return true; + } + + var type = node.getAttribute("type"); + + return type != null && type.equalsIgnoreCase("date"); + } + + public static boolean belongsToMonthControlGroup(TagNode node) { + var type = node.getAttribute("type"); + + if (type != null && type.equalsIgnoreCase("month")) { + return true; + } + + return belongsToTextControlGroup(node); + } + + public static boolean belongsToMultilineControlGroup(TagNode node) { + var nodeName = node.getNodeName(); + + if (nodeName.equalsIgnoreCase("textarea") || nodeName.equalsIgnoreCase("select")) { + return true; + } + + if (!nodeName.equalsIgnoreCase("input")) { + return false; + } + + var type = node.getAttribute("type"); + + return type != null && type.equalsIgnoreCase("hidden"); + } + + public static boolean belongsToNumericControlGroup(TagNode node) { + var type = node.getAttribute("type"); + + if (type != null && type.equalsIgnoreCase("number")) { + return true; + } + + return belongsToTextControlGroup(node); + } + + public static boolean belongsToPasswordControlGroup(TagNode node) { + var type = node.getAttribute("type"); + + if (type != null && type.equalsIgnoreCase("password")) { + return true; + } + + return belongsToTextControlGroup(node); + } + + public static boolean belongsToTelControlGroup(TagNode node) { + var type = node.getAttribute("type"); + + if (type != null && type.equalsIgnoreCase("tel")) { + return true; + } + + return belongsToTextControlGroup(node); + } + + public static boolean belongsToTextControlGroup(TagNode node) { + var nodeName = node.getNodeName(); + + if (nodeName.equalsIgnoreCase("textarea") || nodeName.equalsIgnoreCase("select")) { + return true; + } + + if (!nodeName.equalsIgnoreCase("input")) { + return false; + } + + var type = node.getAttribute("type"); + + if (type == null) { + return false; + } + + return type.equalsIgnoreCase("hidden") || type.equalsIgnoreCase("text") || type.equalsIgnoreCase("search"); + } + + public static boolean belongsToUrlControlGroup(TagNode node) { + var type = node.getAttribute("type"); + + if (type != null && type.equalsIgnoreCase("url")) { + return true; + } + + return belongsToTextControlGroup(node); + } + + public static boolean belongsToUsernameControlGroup(TagNode node) { + var type = node.getAttribute("type"); + + if (type != null && type.equalsIgnoreCase("email")) { + return true; + } + + return belongsToTextControlGroup(node); + } + + private ControlGroup() { + } +} diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/ValidAutocompleteCheck.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/ValidAutocompleteCheck.java new file mode 100644 index 000000000..00a583cea --- /dev/null +++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/checks/accessibility/ValidAutocompleteCheck.java @@ -0,0 +1,333 @@ +/* + * SonarSource HTML analyzer :: Sonar Plugin + * Copyright (c) 2010-2024 SonarSource SA and Matthijs Galesloot + * sonarqube@googlegroups.com + * + * 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.accessibility.ControlGroup; +import org.sonar.plugins.html.checks.AbstractPageCheck; +import org.sonar.plugins.html.node.TagNode; + +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +@Rule(key = "S6840") +public class ValidAutocompleteCheck extends AbstractPageCheck { + /** + * A very naive and straightforward implementation of a validator capable of testing a candidate against a series + * of predicates until either one is matched or none is. Should totally be replaced by a validation library would + * we eventually decide to use one. + */ + static class Validator { + protected List> predicates; + + public Validator(List> predicates) { + this.predicates = predicates; + } + + boolean isValid(TagNode tagNode) { + var autocomplete = tagNode.getAttribute("autocomplete"); + + if (autocomplete == null) { + return true; + } + + var tokens = isADirective(autocomplete) ? new String[]{autocomplete} : autocomplete.split("\\s+"); + var numberOfPredicates = predicates.size(); + + if (tokens.length != numberOfPredicates) { + return false; + } + + var isValid = true; + + for (int i = 0; i < numberOfPredicates; i++) { + var predicate = predicates.get(i); + var token = tokens[i]; + var candidate = new Candidate(token, tagNode); + + isValid = isValid && predicate.test(candidate); + } + + return isValid; + } + } + + /** + * A class representing an autocomplete token as defined by the HTML specification - i.e. a value and a control + * group this value belongs to. + * See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofilling-form-controls:-the-autocomplete-attribute + */ + static class Token { + private final String value; + + private final Predicate controlGroupPredicate; + + Token( + String value, + Predicate controlGroup + ) { + this.value = value; + this.controlGroupPredicate = controlGroup; + } + } + + /** + * A class representing an autocomplete value in the context of a tag - e.g. "cc-exp" in the context of an "input" - + * that can be validated as a whole. + */ + static class Candidate { + private final String value; + + private final TagNode tagNode; + + Candidate( + String value, + TagNode tagNode + ) { + this.value = value; + this.tagNode = tagNode; + } + + public boolean satisfies(Token token) { + /* + * Directives are considered as wildcards - i.e. they always match the passed token + */ + return (isADirective(this.value) || this.value.equalsIgnoreCase(token.value)) && token.controlGroupPredicate.test(this.tagNode); + } + } + + static List validators = List.of( + new Validator(List.of( + candidate -> candidate.value.isBlank() + )), + new Validator(List.of( + candidate -> isADirective(candidate.value) + )), + new Validator(List.of( + ValidAutocompleteCheck::isAOnOffToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isASection, + ValidAutocompleteCheck::isAnAutofillFieldNameToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isASection, + ValidAutocompleteCheck::isAnAutofillFieldNameToken, + ValidAutocompleteCheck::isAWebAuthnToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isASection, + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAnAutofillFieldNameToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isASection, + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAnAutofillFieldNameToken, + ValidAutocompleteCheck::isAWebAuthnToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isASection, + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAMediumValueToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isASection, + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAMediumValueToken, + ValidAutocompleteCheck::isAWebAuthnToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isASection, + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAMediumTypeToken, + ValidAutocompleteCheck::isAMediumValueToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isASection, + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAMediumTypeToken, + ValidAutocompleteCheck::isAMediumValueToken, + ValidAutocompleteCheck::isAWebAuthnToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAnAutofillFieldNameToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAnAutofillFieldNameToken, + ValidAutocompleteCheck::isAWebAuthnToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAMediumTypeToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAMediumTypeToken, + ValidAutocompleteCheck::isAWebAuthnToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAMediumTypeToken, + ValidAutocompleteCheck::isAMediumValueToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAnAddressType, + ValidAutocompleteCheck::isAMediumTypeToken, + ValidAutocompleteCheck::isAMediumValueToken, + ValidAutocompleteCheck::isAWebAuthnToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAnAutofillFieldNameToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAnAutofillFieldNameToken, + ValidAutocompleteCheck::isAWebAuthnToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAMediumValueToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAMediumValueToken, + ValidAutocompleteCheck::isAWebAuthnToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAMediumTypeToken, + ValidAutocompleteCheck::isAMediumValueToken + )), + new Validator(List.of( + ValidAutocompleteCheck::isAMediumTypeToken, + ValidAutocompleteCheck::isAMediumValueToken, + ValidAutocompleteCheck::isAWebAuthnToken + )) + ); + + static boolean isADirective(String value) { + return value.startsWith("<%=") || value.startsWith(" item.equalsIgnoreCase(candidate.value)); + } + + static boolean isAMediumValueToken(Candidate candidate) { + return Stream.of( + new Token("tel", ControlGroup::belongsToTelControlGroup), + new Token("tel-country-code", ControlGroup::belongsToTextControlGroup), + new Token("tel-national", ControlGroup::belongsToTextControlGroup), + new Token("tel-area-code", ControlGroup::belongsToTextControlGroup), + new Token("tel-local", ControlGroup::belongsToTextControlGroup), + new Token("tel-local-prefix", ControlGroup::belongsToTextControlGroup), + new Token("tel-local-suffix", ControlGroup::belongsToTextControlGroup), + new Token("tel-extension", ControlGroup::belongsToTextControlGroup), + new Token("email", ControlGroup::belongsToUsernameControlGroup), + new Token("impp", ControlGroup::belongsToUrlControlGroup) + ).anyMatch(candidate::satisfies); + } + + static boolean isASection(Candidate candidate) { + return candidate.value.toLowerCase().startsWith("section-"); + } + + static boolean isAnAddressType(Candidate candidate) { + return Stream.of( + "billing", + "shipping" + ).anyMatch(item -> item.equalsIgnoreCase(candidate.value)); + } + + static boolean isAnAutofillFieldNameToken(Candidate candidate) { + return Stream.of( + new Token("name", ControlGroup::belongsToTextControlGroup), + new Token("honorific-prefix", ControlGroup::belongsToTextControlGroup), + new Token("given-name", ControlGroup::belongsToTextControlGroup), + new Token("additional-name", ControlGroup::belongsToTextControlGroup), + new Token("family-name", ControlGroup::belongsToTextControlGroup), + new Token("honorific-suffix", ControlGroup::belongsToTextControlGroup), + new Token("nickname", ControlGroup::belongsToTextControlGroup), + new Token("username", ControlGroup::belongsToUsernameControlGroup), + new Token("new-password", ControlGroup::belongsToPasswordControlGroup), + new Token("current-password", ControlGroup::belongsToPasswordControlGroup), + new Token("one-time-code", ControlGroup::belongsToPasswordControlGroup), + new Token("organization-title", ControlGroup::belongsToTextControlGroup), + new Token("organization", ControlGroup::belongsToTextControlGroup), + new Token("street-address", ControlGroup::belongsToMultilineControlGroup), + new Token("address-line1", ControlGroup::belongsToTextControlGroup), + new Token("address-line2", ControlGroup::belongsToTextControlGroup), + new Token("address-line3", ControlGroup::belongsToTextControlGroup), + new Token("address-level4", ControlGroup::belongsToTextControlGroup), + new Token("address-level3", ControlGroup::belongsToTextControlGroup), + new Token("address-level2", ControlGroup::belongsToTextControlGroup), + new Token("address-level1", ControlGroup::belongsToTextControlGroup), + new Token("country", ControlGroup::belongsToTextControlGroup), + new Token("country-name", ControlGroup::belongsToTextControlGroup), + new Token("postal-code", ControlGroup::belongsToTextControlGroup), + new Token("cc-name", ControlGroup::belongsToTextControlGroup), + new Token("cc-given-name", ControlGroup::belongsToTextControlGroup), + new Token("cc-additional-name", ControlGroup::belongsToTextControlGroup), + new Token("cc-family-name", ControlGroup::belongsToTextControlGroup), + new Token("cc-number", ControlGroup::belongsToTextControlGroup), + new Token("cc-exp", ControlGroup::belongsToMonthControlGroup), + new Token("cc-exp-month", ControlGroup::belongsToNumericControlGroup), + new Token("cc-exp-year", ControlGroup::belongsToMultilineControlGroup), + new Token("cc-csc", ControlGroup::belongsToTextControlGroup), + new Token("cc-type", ControlGroup::belongsToTextControlGroup), + new Token("transaction-currency", ControlGroup::belongsToTextControlGroup), + new Token("transaction-amount", ControlGroup::belongsToMultilineControlGroup), + new Token("language", ControlGroup::belongsToTextControlGroup), + new Token("bday", ControlGroup::belongsToDateControlGroup), + new Token("bday-day", ControlGroup::belongsToMultilineControlGroup), + new Token("bday-month", ControlGroup::belongsToMultilineControlGroup), + new Token("bday-year", ControlGroup::belongsToMultilineControlGroup), + new Token("sex", ControlGroup::belongsToTextControlGroup), + new Token("url", ControlGroup::belongsToUrlControlGroup), + new Token("photo", ControlGroup::belongsToUrlControlGroup) + ).anyMatch(candidate::satisfies); + } + + static boolean isAWebAuthnToken(Candidate candidate) { + return Stream.of( + new Token("webauthn", node -> node.getNodeName().equalsIgnoreCase("input") || node.getNodeName().equalsIgnoreCase("textarea")) + ).anyMatch(candidate::satisfies); + } + + @Override + public void startElement(TagNode node) { + var isValid = validators.stream().anyMatch(validator -> validator.isValid(node)); + + if (!isValid) { + createViolation(node, "DOM elements should use the \"autocomplete\" attribute correctly."); + } + } +} diff --git a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/rules/CheckClasses.java b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/rules/CheckClasses.java index 2feb0a283..c8bf6d3e1 100644 --- a/sonar-html-plugin/src/main/java/org/sonar/plugins/html/rules/CheckClasses.java +++ b/sonar-html-plugin/src/main/java/org/sonar/plugins/html/rules/CheckClasses.java @@ -21,6 +21,7 @@ import org.sonar.plugins.html.checks.accessibility.AnchorsHaveContentCheck; import org.sonar.plugins.html.checks.accessibility.AriaProptypesCheck; +import org.sonar.plugins.html.checks.accessibility.ValidAutocompleteCheck; import org.sonar.plugins.html.checks.accessibility.ImgRedundantAltCheck; import org.sonar.plugins.html.checks.accessibility.LabelHasAssociatedControlCheck; import org.sonar.plugins.html.checks.accessibility.NoNoninteractiveElementToInteractiveRoleCheck; @@ -104,6 +105,7 @@ public final class CheckClasses { ChildElementRequiredCheck.class, ComplexityCheck.class, DeprecatedAttributesInHtml5Check.class, + ValidAutocompleteCheck.class, DoubleQuotesCheck.class, DynamicJspIncludeCheck.class, FileLengthCheck.class, diff --git a/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/S6840.html b/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/S6840.html new file mode 100644 index 000000000..2d17750c4 --- /dev/null +++ b/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/S6840.html @@ -0,0 +1,41 @@ +

Why is this an issue?

+

Not providing autocomplete values in form fields can lead to content inaccessibility. The function of each standard input field, which gathers a +person’s personal data, is systematically determined according to the list of 53 Input Purposes +for User Interface Components. If the necessary autocomplete attribute values are absent, screen readers will not be able to identify and read +these fields. This lack of information can hinder users, particularly those using screen readers, from properly navigating and interacting with +forms.

+

For screen readers to operate effectively, it is imperative that the autocomplete attribute values are not only valid but also correctly +applied.

+

How to fix it

+

Ensure the autocomplete attribute is correct and suitable for the form field it is used with:

+
    +
  • Identify the input type: The autocomplete attribute should be used with form elements like <input>, + <select>, and <textarea>. The type of input field should be clearly identified using the type + attribute, such as type="text", type="email", or type="tel".
  • +
  • Specify the autocomplete value: The value of the autocomplete attribute should be a string that specifies what kind of input the browser should + autofill. For example, autocomplete="name" would suggest that the browser autofill the user’s full name.
  • +
  • Use appropriate autocomplete values: The value you use should be appropriate for the type of input. For example, for a credit card field, you + might use autocomplete="cc-number". For a country field in an address form, you might use autocomplete="country".
  • +
+

For additional details, please refer to the guidelines provided in the HTML standard.

+

Code examples

+

Noncompliant code example

+
+<input type="text" autocomplete="foo" />
+
+

Compliant solution

+
+<input type="text" autocomplete="name" />
+
+

Resources

+

Documentation

+ + diff --git a/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/S6840.json b/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/S6840.json new file mode 100644 index 000000000..4d466429a --- /dev/null +++ b/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/S6840.json @@ -0,0 +1,24 @@ +{ + "title": "DOM elements should use the \"autocomplete\" attribute correctly", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6840", + "sqKey": "S6840", + "scope": "All", + "quickfix": "infeasible", + "code": { + "impacts": { + "MAINTAINABILITY": "LOW", + "RELIABILITY": "MEDIUM" + }, + "attribute": "CONVENTIONAL" + }, + "tags": [ + "accessibility" + ] +} diff --git a/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/Sonar_way_profile.json b/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/Sonar_way_profile.json index 48314a52e..ae2205f90 100644 --- a/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/Sonar_way_profile.json +++ b/sonar-html-plugin/src/main/resources/org/sonar/l10n/web/rules/Web/Sonar_way_profile.json @@ -28,6 +28,7 @@ "S5725", "S6793", "S6827", + "S6840", "S6841", "S6842", "S6846", diff --git a/sonar-html-plugin/src/test/java/org/sonar/plugins/html/checks/accessibility/ValidAutocompleteCheckTest.java b/sonar-html-plugin/src/test/java/org/sonar/plugins/html/checks/accessibility/ValidAutocompleteCheckTest.java new file mode 100644 index 000000000..064394e8b --- /dev/null +++ b/sonar-html-plugin/src/test/java/org/sonar/plugins/html/checks/accessibility/ValidAutocompleteCheckTest.java @@ -0,0 +1,118 @@ +/* + * SonarSource HTML analyzer :: Sonar Plugin + * Copyright (c) 2010-2024 SonarSource SA and Matthijs Galesloot + * sonarqube@googlegroups.com + * + * 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.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +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 ValidAutocompleteCheckTest { + @RegisterExtension + public CheckMessagesVerifierRule checkMessagesVerifier = new CheckMessagesVerifierRule(); + + @ParameterizedTest + @ValueSource(strings = { + "cc-exp", + "cc-exp-month", + "new-password", + "street-address", + "url", + "username" + }) + void html(String file) { + HtmlSourceCode sourceCode = TestHelper.scan(new File(String.format("src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/%s.html", file)), new ValidAutocompleteCheck()); + + checkMessagesVerifier.verify(sourceCode.getIssues()) + .next().atLine(1).withMessage("DOM elements should use the \"autocomplete\" attribute correctly.") + .next().atLine(2) + .next().atLine(3); + } + + @Test + void bday() { + ValidAutocompleteCheck check = new ValidAutocompleteCheck(); + HtmlSourceCode htmlSourceCode = TestHelper.scan(new File("src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.html"), check); + + checkMessagesVerifier.verify(htmlSourceCode.getIssues()) + .next().atLine(1).withMessage("DOM elements should use the \"autocomplete\" attribute correctly.") + .next().atLine(2) + .next().atLine(3); + + HtmlSourceCode jspSourceCode = TestHelper.scan(new File("src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.jsp"), check); + + checkMessagesVerifier.verify(jspSourceCode.getIssues()); + + HtmlSourceCode phtmlSourceCode = TestHelper.scan(new File("src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.phtml"), check); + + checkMessagesVerifier.verify(phtmlSourceCode.getIssues()); + + HtmlSourceCode vueSourceCode = TestHelper.scan(new File("src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.vue"), check); + + checkMessagesVerifier.verify(vueSourceCode.getIssues()) + .next().atLine(2).withMessage("DOM elements should use the \"autocomplete\" attribute correctly.") + .next().atLine(3) + .next().atLine(4); + } + + @Test + void on() { + HtmlSourceCode sourceCode = TestHelper.scan(new File("src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/on.html"), new ValidAutocompleteCheck()); + + checkMessagesVerifier.verify(sourceCode.getIssues()) + .next().atLine(1).withMessage("DOM elements should use the \"autocomplete\" attribute correctly."); + } + + @Test + void tel() { + HtmlSourceCode sourceCode = TestHelper.scan(new File("src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/tel.html"), new ValidAutocompleteCheck()); + + checkMessagesVerifier.verify(sourceCode.getIssues()) + .next().atLine(1).withMessage("DOM elements should use the \"autocomplete\" attribute correctly.") + .next().atLine(2) + .next().atLine(3) + .next().atLine(4); + } + + @Test + void withContext() { + HtmlSourceCode sourceCode = TestHelper.scan(new File("src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/with-context.html"), new ValidAutocompleteCheck()); + + checkMessagesVerifier.verify(sourceCode.getIssues()) + .next().atLine(1).withMessage("DOM elements should use the \"autocomplete\" attribute correctly.") + .next().atLine(2); + } + + @Test + void withWebauthn() { + HtmlSourceCode sourceCode = TestHelper.scan(new File("src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/with-webauthn.html"), new ValidAutocompleteCheck()); + + checkMessagesVerifier.verify(sourceCode.getIssues()) + .next().atLine(1).withMessage("DOM elements should use the \"autocomplete\" attribute correctly.") + .next().atLine(2) + .next().atLine(3) + .next().atLine(4) + .next().atLine(5); + } +} diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.html new file mode 100644 index 000000000..1ba270ba0 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.html @@ -0,0 +1,10 @@ +
+ + + + + + + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.jsp b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.jsp new file mode 100644 index 000000000..9eef5bd0a --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.jsp @@ -0,0 +1,8 @@ +
">
+"/> +"/> +"/> +"/> + + +"/> diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.phtml b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.phtml new file mode 100644 index 000000000..c630e0df2 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.phtml @@ -0,0 +1,18 @@ +
">
+"/> +
">
+"/> + +"/> +"/> +"/> + + +"/> + +"/> +"/> +"/> + + +"/> diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.vue b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.vue new file mode 100644 index 000000000..9abda3707 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/bday/file.vue @@ -0,0 +1,30 @@ + + \ No newline at end of file diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/cc-exp-month.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/cc-exp-month.html new file mode 100644 index 000000000..3604bc8d7 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/cc-exp-month.html @@ -0,0 +1,9 @@ +
+ + + + + + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/cc-exp.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/cc-exp.html new file mode 100644 index 000000000..75cf243d3 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/cc-exp.html @@ -0,0 +1,9 @@ +
+ + + + + + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/new-password.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/new-password.html new file mode 100644 index 000000000..e98a283f6 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/new-password.html @@ -0,0 +1,11 @@ + + +
+ + + + + + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/on.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/on.html new file mode 100644 index 000000000..1d40a486f --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/on.html @@ -0,0 +1,9 @@ + +
+ + + + + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/street-address.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/street-address.html new file mode 100644 index 000000000..3afde3c28 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/street-address.html @@ -0,0 +1,7 @@ + + +
+ + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/tel.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/tel.html new file mode 100644 index 000000000..969a489e2 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/tel.html @@ -0,0 +1,12 @@ +
+ + + + + + + + + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/url.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/url.html new file mode 100644 index 000000000..ac2c19e53 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/url.html @@ -0,0 +1,9 @@ +
+ + + + + + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/username.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/username.html new file mode 100644 index 000000000..7050843c9 --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/username.html @@ -0,0 +1,9 @@ +
+ + + + + + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/with-context.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/with-context.html new file mode 100644 index 000000000..3e7542d7e --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/with-context.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/with-webauthn.html b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/with-webauthn.html new file mode 100644 index 000000000..08127dd6c --- /dev/null +++ b/sonar-html-plugin/src/test/resources/checks/DomElementsShouldUseAutocompleteAttributeCorrectlyCheck/with-webauthn.html @@ -0,0 +1,10 @@ + + + + + + + + +