diff --git a/its/ruling/src/test/expected/jsts/Joust/typescript-S1077.json b/its/ruling/src/test/expected/jsts/Joust/typescript-S1077.json new file mode 100644 index 0000000000..3a99789ecd --- /dev/null +++ b/its/ruling/src/test/expected/jsts/Joust/typescript-S1077.json @@ -0,0 +1,11 @@ +{ +"Joust:ts/components/game/Deck.tsx": [ +33 +], +"Joust:ts/components/game/Player.tsx": [ +295 +], +"Joust:ts/components/game/visuals/CardArt.tsx": [ +97 +] +} diff --git a/its/ruling/src/test/expected/jsts/console/typescript-S1077.json b/its/ruling/src/test/expected/jsts/console/typescript-S1077.json new file mode 100644 index 0000000000..d8266a7f78 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/console/typescript-S1077.json @@ -0,0 +1,18 @@ +{ +"console:src/components/onboarding/OnboardingPopup/OnboardingPopup.tsx": [ +55 +], +"console:src/views/CLIAuthView/CLIAuthView/Left.tsx": [ +37 +], +"console:src/views/Integrations/Algolia/AlgoliaHeader.tsx": [ +42 +], +"console:src/views/ProjectRootView/OnboardSideNav.tsx": [ +95 +], +"console:src/views/models/AuthProviderPopup/AuthProviderPopup.tsx": [ +134, +151 +] +} diff --git a/its/ruling/src/test/expected/jsts/courselit/javascript-S1077.json b/its/ruling/src/test/expected/jsts/courselit/javascript-S1077.json new file mode 100644 index 0000000000..00593d7f6a --- /dev/null +++ b/its/ruling/src/test/expected/jsts/courselit/javascript-S1077.json @@ -0,0 +1,5 @@ +{ +"courselit:packages/rich-text/src/Renderers/Media.js": [ +12 +] +} diff --git a/its/ruling/src/test/expected/jsts/desktop/typescript-S1077.json b/its/ruling/src/test/expected/jsts/desktop/typescript-S1077.json new file mode 100644 index 0000000000..fedfb1456c --- /dev/null +++ b/its/ruling/src/test/expected/jsts/desktop/typescript-S1077.json @@ -0,0 +1,72 @@ +{ +"desktop:app/src/crash/crash-app.tsx": [ +204 +], +"desktop:app/src/ui/autocompletion/emoji-autocompletion-provider.tsx": [ +93 +], +"desktop:app/src/ui/branches/no-branches.tsx": [ +22 +], +"desktop:app/src/ui/branches/no-pull-requests.tsx": [ +36 +], +"desktop:app/src/ui/changes/multiple-selection.tsx": [ +21 +], +"desktop:app/src/ui/changes/no-changes.tsx": [ +717 +], +"desktop:app/src/ui/check-runs/ci-check-run-popover.tsx": [ +245 +], +"desktop:app/src/ui/check-runs/ci-check-run-rerun-dialog.tsx": [ +223 +], +"desktop:app/src/ui/diff/image-diffs/image-container.tsx": [ +23 +], +"desktop:app/src/ui/diff/index.tsx": [ +174, +191 +], +"desktop:app/src/ui/history/selected-commit.tsx": [ +303, +428 +], +"desktop:app/src/ui/no-repositories/no-repositories-view.tsx": [ +142, +146 +], +"desktop:app/src/ui/notifications/pull-request-checks-failed.tsx": [ +224, +241 +], +"desktop:app/src/ui/release-notes/release-notes-dialog.tsx": [ +172, +180 +], +"desktop:app/src/ui/repositories-list/repositories-list.tsx": [ +255 +], +"desktop:app/src/ui/thank-you/thank-you.tsx": [ +64, +69 +], +"desktop:app/src/ui/tutorial/done.tsx": [ +42 +], +"desktop:app/src/ui/tutorial/tutorial-panel.tsx": [ +103 +], +"desktop:app/src/ui/tutorial/welcome.tsx": [ +28, +34, +41 +], +"desktop:app/src/ui/welcome/welcome.tsx": [ +206, +207, +215 +] +} diff --git a/its/ruling/src/test/expected/jsts/redux/javascript-S1077.json b/its/ruling/src/test/expected/jsts/redux/javascript-S1077.json new file mode 100644 index 0000000000..786e747f44 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/redux/javascript-S1077.json @@ -0,0 +1,5 @@ +{ +"redux:examples/real-world/components/User.js": [ +11 +] +} diff --git a/its/ruling/src/test/expected/jsts/searchkit/javascript-S1077.json b/its/ruling/src/test/expected/jsts/searchkit/javascript-S1077.json new file mode 100644 index 0000000000..ee9237a648 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/searchkit/javascript-S1077.json @@ -0,0 +1,8 @@ +{ +"searchkit:docs/src/pages/index.js": [ +42 +], +"searchkit:examples/next/components/products.jsx": [ +106 +] +} diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/AltTextCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/AltTextCheck.java new file mode 100644 index 0000000000..bf4662c7ce --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/AltTextCheck.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 = "S1077") +public class AltTextCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "alt-text"; + } +} 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 f468309aed..3f942f68f8 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 @@ -59,6 +59,7 @@ public static List> getAllChecks() { AdjacentOverloadSignaturesCheck.class, AlertUseCheck.class, AlphabeticalSortCheck.class, + AltTextCheck.class, AlwaysUseCurlyBracesCheck.class, AnchorHasContentCheck.class, AnchorIsValidCheck.class, diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S1077.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S1077.html new file mode 100644 index 0000000000..a473b10723 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S1077.html @@ -0,0 +1,90 @@ +

Why is this an issue?

+

The alt, aria-label and aria-labelledby attributes provide a textual alternative to an image.

+

It is used whenever the actual image cannot be rendered.

+

Common reasons for that include:

+ +

It is also very important to not set an alternative text attribute to a non-informative value. For example <img ... alt="logo"> +is useless as it doesn’t give any information to the user. In this case, as for any other decorative image, it is better to use a CSS background image +instead of an <img> tag. If using CSS background-image is not possible, an empty alt="" is tolerated. See Exceptions +below.

+

This rule raises an issue when:

+ +

Noncompliant code example

+
+<img src="foo.png" /> <!-- missing `alt` attribute -->
+<input type="image" src="bar.png" /> <!-- missing alternative text attribute -->
+<input type="image" src="bar.png" alt="" /> <!-- empty alternative text attribute on <input> -->
+
+<img src="house.gif" usemap="#map1"
+    alt="rooms of the house." />
+<map id="map1" name="map1">
+  <area shape="rect" coords="0,0,42,42"
+    href="bedroom.html"/> <!-- missing alternative text attribute on <area> -->
+  <area shape="rect" coords="0,0,21,21"
+    href="lounge.html" alt=""/> <!-- empty `alt` attribute on <area> -->
+</map>
+
+<object {...props} />  <!-- missing alternative text attribute on <area> -->
+
+

Compliant solution

+
+<img src="foo.png" alt="Some textual description of foo.png" />
+<input type="image" src="bar.png" aria-labelledby="Textual description of bar.png" />
+
+<img src="house.gif" usemap="#map1"
+    alt="rooms of the house." />
+<map id="map1" name="map1">
+  <area shape="rect" coords="0,0,42,42"
+    href="bedroom.html" alt="Bedroom" />
+  <area shape="rect" coords="0,0,21,21"
+    href="lounge.html" aria-label="Lounge"/>
+</map>
+
+<object>My welcoming Bar</object>
+
+

Exceptions

+

<img> elements with an empty string alt="" attribute won’t raise any issue. However, this way should be used +in two cases only:

+

When the image is decorative and it is not possible to use a CSS background image. For example, when the decorative <img> is +generated via javascript with a source image coming from a database, it is better to use an <img alt=""> tag rather than generate +CSS code.

+
+<li *ngFor="let image of images">
+    <img [src]="image" alt="">
+</li>
+
+

When the image is not decorative but its alt text would repeat a nearby text. For example, images contained in links should not +duplicate the link’s text in their alt attribute, as it would make the screen reader repeat the text twice.

+
+<a href="flowers.html">
+    <img src="tulip.gif" alt="" />
+    A blooming tulip
+</a>
+
+

In all other cases, you should use CSS background images.

+

See W3C WAI Web Accessibility Tutorials for more information.

+

Resources

+ + diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S1077.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S1077.json new file mode 100644 index 0000000000..96ee325ae3 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S1077.json @@ -0,0 +1,29 @@ +{ + "title": "Image, area, button with image and object elements should have an alternative text", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "accessibility", + "wcag2-a", + "react" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-1077", + "sqKey": "S1077", + "scope": "All", + "quickfix": "infeasible", + "code": { + "impacts": { + "RELIABILITY": "LOW" + }, + "attribute": "COMPLETE" + }, + "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 656839083b..0cb91e51d5 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 @@ -11,6 +11,7 @@ "S905", "S930", "S1068", + "S1077", "S1082", "S1119", "S1121",