diff --git a/its/ruling/src/test/expected/jsts/ant-design/javascript-S6819.json b/its/ruling/src/test/expected/jsts/ant-design/javascript-S6819.json new file mode 100644 index 00000000000..ad2356119bb --- /dev/null +++ b/its/ruling/src/test/expected/jsts/ant-design/javascript-S6819.json @@ -0,0 +1,5 @@ +{ +"ant-design:components/grid/__tests__/gap.test.js": [ +16 +] +} diff --git a/its/ruling/src/test/expected/jsts/ant-design/typescript-S6819.json b/its/ruling/src/test/expected/jsts/ant-design/typescript-S6819.json new file mode 100644 index 00000000000..502936cd444 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/ant-design/typescript-S6819.json @@ -0,0 +1,20 @@ +{ +"ant-design:components/_util/transButton.tsx": [ +60 +], +"ant-design:components/alert/__tests__/index.test.tsx": [ +91 +], +"ant-design:components/divider/index.tsx": [ +57 +], +"ant-design:components/input/ClearableLabeledInput.tsx": [ +55 +], +"ant-design:components/table/hooks/useFilter/FilterDropdown.tsx": [ +466 +], +"ant-design:components/typography/Base/index.tsx": [ +399 +] +} diff --git a/its/ruling/src/test/expected/jsts/desktop/typescript-S6819.json b/its/ruling/src/test/expected/jsts/desktop/typescript-S6819.json new file mode 100644 index 00000000000..8944c7cde9c --- /dev/null +++ b/its/ruling/src/test/expected/jsts/desktop/typescript-S6819.json @@ -0,0 +1,14 @@ +{ +"desktop:app/src/ui/changes/commit-message.tsx": [ +753 +], +"desktop:app/src/ui/changes/undo-commit.tsx": [ +36 +], +"desktop:app/src/ui/dialog/header.tsx": [ +67 +], +"desktop:app/src/ui/lib/vertical-segmented-control/segmented-item.tsx": [ +67 +] +} diff --git a/its/ruling/src/test/expected/jsts/file-for-rules/javascript-S6819.json b/its/ruling/src/test/expected/jsts/file-for-rules/javascript-S6819.json new file mode 100644 index 00000000000..4edb0ae8166 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/file-for-rules/javascript-S6819.json @@ -0,0 +1,5 @@ +{ +"file-for-rules:S6807.js": [ +4 +] +} diff --git a/its/ruling/src/test/expected/jsts/fireact/javascript-S6819.json b/its/ruling/src/test/expected/jsts/fireact/javascript-S6819.json new file mode 100644 index 00000000000..de6d77494b5 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/fireact/javascript-S6819.json @@ -0,0 +1,5 @@ +{ +"fireact:src/components/Pagination/index.js": [ +40 +] +} diff --git a/its/ruling/src/test/expected/jsts/vuetify/typescript-S6819.json b/its/ruling/src/test/expected/jsts/vuetify/typescript-S6819.json new file mode 100644 index 00000000000..33dc4d6f16e --- /dev/null +++ b/its/ruling/src/test/expected/jsts/vuetify/typescript-S6819.json @@ -0,0 +1,44 @@ +{ +"vuetify:packages/vuetify/src/components/VBadge/VBadge.tsx": [ +112 +], +"vuetify:packages/vuetify/src/components/VBanner/VBanner.tsx": [ +84 +], +"vuetify:packages/vuetify/src/components/VIcon/VIcon.tsx": [ +53 +], +"vuetify:packages/vuetify/src/components/VImg/VImg.tsx": [ +292 +], +"vuetify:packages/vuetify/src/components/VList/VList.tsx": [ +225 +], +"vuetify:packages/vuetify/src/components/VList/VListGroup.tsx": [ +112 +], +"vuetify:packages/vuetify/src/components/VPagination/VPagination.tsx": [ +308 +], +"vuetify:packages/vuetify/src/components/VProgressCircular/VProgressCircular.tsx": [ +77 +], +"vuetify:packages/vuetify/src/components/VProgressLinear/VProgressLinear.tsx": [ +100 +], +"vuetify:packages/vuetify/src/components/VSlider/VSliderThumb.tsx": [ +123 +], +"vuetify:packages/vuetify/src/components/VSnackbar/VSnackbar.tsx": [ +130 +], +"vuetify:packages/vuetify/src/components/VTextField/VTextField.tsx": [ +182 +], +"vuetify:packages/vuetify/src/components/VTextarea/VTextarea.tsx": [ +226 +], +"vuetify:packages/vuetify/src/composables/icons.tsx": [ +116 +] +} diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java index 4cb3e0071f3..1c347003d0a 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java @@ -356,6 +356,7 @@ public static List> getAllChecks() { PreferReturnThisTypeCheck.class, PreferSpreadCheck.class, PreferStringStartsEndsWithCheck.class, + PreferTagOverRoleCheck.class, PreferTypeGuardCheck.class, PrimitiveWrappersCheck.class, ProcessArgvCheck.class, diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/PreferTagOverRoleCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/PreferTagOverRoleCheck.java new file mode 100644 index 00000000000..52b4104da8a --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/PreferTagOverRoleCheck.java @@ -0,0 +1,36 @@ +/** + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.javascript.checks; + +import org.sonar.check.Rule; +import org.sonar.plugins.javascript.api.EslintBasedCheck; +import org.sonar.plugins.javascript.api.JavaScriptRule; +import org.sonar.plugins.javascript.api.TypeScriptRule; + +@TypeScriptRule +@JavaScriptRule +@Rule(key = "S6819") +public class PreferTagOverRoleCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "prefer-tag-over-role"; + } +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6819.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6819.html new file mode 100644 index 00000000000..f1bf75579bb --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6819.html @@ -0,0 +1,33 @@ +

Why is this an issue?

+

ARIA (Accessible Rich Internet Applications) roles are used to make web content and web applications more accessible to people with disabilities. +However, you should not use ARIA role on a generic element (like span or div) if there is a semantic HTML tag with similar +functionality, just use that tag instead.

+

For example, instead of using a div element with a role of button (<div role="button">Click me</div>), you should just use +a button element (<button>Click me</button>).

+

Semantic HTML tags are generally preferred over ARIA roles for accessibility due to their built-in functionality, universal support by browsers and +assistive technologies, simplicity, and maintainability. They come with inherent behaviors and keyboard interactions, reducing the need for additional +JavaScript. Semantic HTML also enhances SEO by helping search engines better understand the content and structure of web pages. While ARIA roles are +useful, they should be considered a last resort when no suitable HTML element can provide the required behavior or semantics.

+

How to fix it in JSX

+

Replace the ARIA role with an appropriate HTML tag.

+

Code examples

+

Noncompliant code example

+
+<div role="button" tabindex="0" onClick={handleClick}>Click me</div>
+
+

Compliant solution

+
+<button onClick={handleClick}>Click me</button>
+
+

Resources

+

Documentation

+ +

Standards

+ + diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6819.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6819.json new file mode 100644 index 00000000000..d9ca4850f1c --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6819.json @@ -0,0 +1,28 @@ +{ + "title": "Prefer tag over ARIA role", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "a11y", + "react" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6819", + "sqKey": "S6819", + "scope": "All", + "quickfix": "infeasible", + "code": { + "impacts": { + "MAINTAINABILITY": "LOW" + }, + "attribute": "CONVENTIONAL" + }, + "compatibleLanguages": [ + "JAVASCRIPT", + "TYPESCRIPT" + ] +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json index 3d9c5ca6be0..687a17a6a1b 100644 --- a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json @@ -294,6 +294,7 @@ "S6793", "S6807", "S6811", + "S6819", "S6821" ] }