From d74b44588a5b07ebf099d66ebef8dac1a0cbe036 Mon Sep 17 00:00:00 2001 From: Tibor Blenessy Date: Mon, 13 Nov 2023 17:37:14 +0100 Subject: [PATCH] Create rule S6844 (`jsx-a11y/anchor-is-valid`): Anchor tags should not be used as buttons --- .../expected/jsts/Joust/typescript-S6844.json | 12 ++++ .../jsts/ant-design/javascript-S6844.json | 26 +++++++ .../jsts/ant-design/typescript-S6844.json | 19 ++++++ .../jsts/courselit/typescript-S6844.json | 14 ++++ .../jsts/desktop/typescript-S6844.json | 11 +++ .../react-cloud-music/javascript-S6844.json | 6 ++ .../expected/jsts/redux/javascript-S6844.json | 14 ++++ .../jsts/sonar-web/javascript-S6844.json | 67 +++++++++++++++++++ .../javascript/checks/AnchorIsValidCheck.java | 36 ++++++++++ .../sonar/javascript/checks/CheckList.java | 1 + .../javascript/rules/javascript/S6844.html | 46 +++++++++++++ .../javascript/rules/javascript/S6844.json | 30 +++++++++ .../rules/javascript/Sonar_way_profile.json | 3 +- 13 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 its/ruling/src/test/expected/jsts/Joust/typescript-S6844.json create mode 100644 its/ruling/src/test/expected/jsts/ant-design/javascript-S6844.json create mode 100644 its/ruling/src/test/expected/jsts/ant-design/typescript-S6844.json create mode 100644 its/ruling/src/test/expected/jsts/courselit/typescript-S6844.json create mode 100644 its/ruling/src/test/expected/jsts/desktop/typescript-S6844.json create mode 100644 its/ruling/src/test/expected/jsts/react-cloud-music/javascript-S6844.json create mode 100644 its/ruling/src/test/expected/jsts/redux/javascript-S6844.json create mode 100644 its/ruling/src/test/expected/jsts/sonar-web/javascript-S6844.json create mode 100644 sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/AnchorIsValidCheck.java create mode 100644 sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6844.html create mode 100644 sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6844.json diff --git a/its/ruling/src/test/expected/jsts/Joust/typescript-S6844.json b/its/ruling/src/test/expected/jsts/Joust/typescript-S6844.json new file mode 100644 index 00000000000..b51c8b1fea3 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/Joust/typescript-S6844.json @@ -0,0 +1,12 @@ +{ +"Joust:ts/components/GameWidget.tsx": [ +223 +], +"Joust:ts/components/GameWrapper.tsx": [ +64, +87 +], +"Joust:ts/components/Settings.tsx": [ +22 +] +} diff --git a/its/ruling/src/test/expected/jsts/ant-design/javascript-S6844.json b/its/ruling/src/test/expected/jsts/ant-design/javascript-S6844.json new file mode 100644 index 00000000000..89d64b584ea --- /dev/null +++ b/its/ruling/src/test/expected/jsts/ant-design/javascript-S6844.json @@ -0,0 +1,26 @@ +{ +"ant-design:components/breadcrumb/__tests__/router.test.js": [ +56, +57 +], +"ant-design:components/comment/__tests__/index.test.js": [ +15 +], +"ant-design:components/drawer/__tests__/DrawerEvent.test.js": [ +119 +], +"ant-design:components/dropdown/__tests__/index.test.js": [ +125 +], +"ant-design:components/list/__tests__/Item.test.js": [ +103, +125 +], +"ant-design:components/locale-provider/__tests__/config.test.js": [ +31, +34 +], +"ant-design:components/locale-provider/__tests__/index.test.js": [ +186 +] +} diff --git a/its/ruling/src/test/expected/jsts/ant-design/typescript-S6844.json b/its/ruling/src/test/expected/jsts/ant-design/typescript-S6844.json new file mode 100644 index 00000000000..10d9213b96e --- /dev/null +++ b/its/ruling/src/test/expected/jsts/ant-design/typescript-S6844.json @@ -0,0 +1,19 @@ +{ +"ant-design:components/badge/__tests__/index.test.tsx": [ +14, +107, +142, +145, +148 +], +"ant-design:components/modal/__tests__/Modal.test.tsx": [ +39 +], +"ant-design:components/pagination/Pagination.tsx": [ +62, +71 +], +"ant-design:components/typography/Base/index.tsx": [ +371 +] +} diff --git a/its/ruling/src/test/expected/jsts/courselit/typescript-S6844.json b/its/ruling/src/test/expected/jsts/courselit/typescript-S6844.json new file mode 100644 index 00000000000..2de090c5848 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/courselit/typescript-S6844.json @@ -0,0 +1,14 @@ +{ +"courselit:apps/web/components/admin/courses/course-editor/index.tsx": [ +562 +], +"courselit:apps/web/components/public/article.tsx": [ +117 +], +"courselit:apps/web/pages/profile/[id].tsx": [ +216 +], +"courselit:packages/components-library/src/course-item.tsx": [ +61 +] +} diff --git a/its/ruling/src/test/expected/jsts/desktop/typescript-S6844.json b/its/ruling/src/test/expected/jsts/desktop/typescript-S6844.json new file mode 100644 index 00000000000..9f5bde03c04 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/desktop/typescript-S6844.json @@ -0,0 +1,11 @@ +{ +"desktop:app/src/ui/banners/banner.tsx": [ +32 +], +"desktop:app/src/ui/dialog/header.tsx": [ +67 +], +"desktop:app/src/ui/history/commit-summary.tsx": [ +222 +] +} diff --git a/its/ruling/src/test/expected/jsts/react-cloud-music/javascript-S6844.json b/its/ruling/src/test/expected/jsts/react-cloud-music/javascript-S6844.json new file mode 100644 index 00000000000..82f24acf41a --- /dev/null +++ b/its/ruling/src/test/expected/jsts/react-cloud-music/javascript-S6844.json @@ -0,0 +1,6 @@ +{ +"react-cloud-music:src/application/User/Login/LoginForm/index.jsx": [ +40, +41 +] +} diff --git a/its/ruling/src/test/expected/jsts/redux/javascript-S6844.json b/its/ruling/src/test/expected/jsts/redux/javascript-S6844.json new file mode 100644 index 00000000000..a7596d8be59 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/redux/javascript-S6844.json @@ -0,0 +1,14 @@ +{ +"redux:examples/async/containers/App.js": [ +53 +], +"redux:examples/real-world/containers/App.js": [ +33 +], +"redux:examples/todomvc/components/Footer.js": [ +28 +], +"redux:examples/todos-with-undo/components/Footer.js": [ +10 +] +} diff --git a/its/ruling/src/test/expected/jsts/sonar-web/javascript-S6844.json b/its/ruling/src/test/expected/jsts/sonar-web/javascript-S6844.json new file mode 100644 index 00000000000..97ebe3a023f --- /dev/null +++ b/its/ruling/src/test/expected/jsts/sonar-web/javascript-S6844.json @@ -0,0 +1,67 @@ +{ +"sonar-web:src/main/js/apps/background-tasks/stats.js": [ +40, +62 +], +"sonar-web:src/main/js/apps/background-tasks/tasks.js": [ +88 +], +"sonar-web:src/main/js/apps/global-permissions/permission-groups.js": [ +10 +], +"sonar-web:src/main/js/apps/global-permissions/permission-users.js": [ +10 +], +"sonar-web:src/main/js/apps/overview/main/components.js": [ +27 +], +"sonar-web:src/main/js/apps/permission-templates/permission-template-set-defaults.js": [ +26, +38 +], +"sonar-web:src/main/js/apps/permission-templates/permission-template.js": [ +77, +85 +], +"sonar-web:src/main/js/apps/project-permissions/permissions-footer.js": [ +17 +], +"sonar-web:src/main/js/apps/project-permissions/project.js": [ +54, +62 +], +"sonar-web:src/main/js/components/shared/checkbox.js": [ +35 +], +"sonar-web:src/main/js/components/shared/favorite.js": [ +45 +], +"sonar-web:src/main/js/components/shared/list-footer.js": [ +31 +], +"sonar-web:src/main/js/main/nav/component/component-nav-menu.js": [ +57, +175 +], +"sonar-web:src/main/js/main/nav/global/global-nav-menu.js": [ +36, +127 +], +"sonar-web:src/main/js/main/nav/global/global-nav-search.js": [ +77 +], +"sonar-web:src/main/js/main/nav/global/global-nav-user.js": [ +9, +18, +28 +], +"sonar-web:src/main/js/main/nav/global/global-nav.js": [ +45 +], +"sonar-web:src/main/js/main/nav/settings/settings-nav.js": [ +20, +32, +45, +55 +] +} diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/AnchorIsValidCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/AnchorIsValidCheck.java new file mode 100644 index 00000000000..ecac25ffe9e --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/AnchorIsValidCheck.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; + +@JavaScriptRule +@TypeScriptRule +@Rule(key = "S6844") +public class AnchorIsValidCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "anchor-is-valid"; + } +} 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 ebc4d2d81eb..a8fb0609525 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 @@ -61,6 +61,7 @@ public static List> getAllChecks() { AlphabeticalSortCheck.class, AlwaysUseCurlyBracesCheck.class, AnchorHasContentCheck.class, + AnchorIsValidCheck.class, AnchorPrecedenceCheck.class, AngleBracketTypeAssertionCheck.class, ArgumentTypesCheck.class, diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6844.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6844.html new file mode 100644 index 00000000000..29f922294f7 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6844.html @@ -0,0 +1,46 @@ +

The <a> tag in HTML is designed to create hyperlinks, which can link to different sections of the same page, different pages, or +even different websites. However, sometimes developers misuse <a> tags as buttons, which can lead to accessibility issues and +unexpected behavior.

+

This rule checks that <a> tags are used correctly as hyperlinks and not misused as buttons. It verifies that each +<a> tag has a href attribute, which is necessary for it to function as a hyperlink. If an <a> tag +is used without a href attribute, it behaves like a button, which is not its intended use.

+

Using the correct HTML elements for their intended purpose is crucial for accessibility and usability. It ensures that the website behaves as +expected and can be used by all users, including those using assistive technologies. Misusing HTML elements can lead to a poor user experience and +potential accessibility violations.

+

Compliance with this rule will ensure that your HTML code is semantically correct, accessible, and behaves as expected.

+

Why is this an issue?

+

Misusing <a> tags as buttons can lead to several issues:

+ +

How to fix it

+

To fix this issue, you should use the appropriate HTML elements for their intended purposes. If you need to create a hyperlink, use the +<a> tag with a href attribute. If you need to create a button, use the <button> tag.

+

Code examples

+

Noncompliant code example

+
+<a href="javascript:void(0)" onClick={foo}>Perform action</a>
+<a href="#" onClick={foo}>Perform action</a>
+<a onClick={foo}>Perform action</a>
+
+

Compliant solution

+
+<button onClick={foo}>Perform action</button>
+
+

Resources

+

Documentation

+ + diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6844.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6844.json new file mode 100644 index 00000000000..1379f7883b8 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6844.json @@ -0,0 +1,30 @@ +{ + "title": "Anchor tags should not be used as buttons", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "accessibility", + "react" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6844", + "sqKey": "S6844", + "scope": "All", + "quickfix": "infeasible", + "code": { + "impacts": { + "MAINTAINABILITY": "HIGH", + "RELIABILITY": "MEDIUM", + "SECURITY": "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 eadc549bd45..8db4af7829c 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 @@ -310,6 +310,7 @@ "S6840", "S6841", "S6842", - "S6843" + "S6843", + "S6844" ] }