From dbe8cbdb2fa15bb51010ae5e9dd9398393c17aa6 Mon Sep 17 00:00:00 2001 From: Yassin Kammoun <52890329+yassin-kammoun-sonarsource@users.noreply.github.com> Date: Thu, 14 Sep 2023 09:22:58 +0200 Subject: [PATCH] Add rule S6767 (`no-unused-prop-types`): Unused React typed props should be removed (#4165) --- .../expected/ts/Joust/typescript-S6767.json | 5 + .../ts/ant-design/typescript-S6767.json | 53 +++++ .../expected/ts/console/typescript-S6767.json | 209 ++++++++++++++++++ .../ts/courselit/typescript-S6767.json | 61 +++++ .../expected/ts/desktop/typescript-S6767.json | 66 ++++++ .../expected/ts/eigen/typescript-S6767.json | 162 ++++++++++++++ .../sonar/javascript/checks/CheckList.java | 1 + .../checks/NoUnusedPropTypesCheck.java | 36 +++ .../javascript/rules/javascript/S6767.html | 81 +++++++ .../javascript/rules/javascript/S6767.json | 27 +++ .../rules/javascript/Sonar_way_profile.json | 3 +- 11 files changed, 703 insertions(+), 1 deletion(-) create mode 100644 its/ruling/src/test/expected/ts/Joust/typescript-S6767.json create mode 100644 its/ruling/src/test/expected/ts/ant-design/typescript-S6767.json create mode 100644 its/ruling/src/test/expected/ts/console/typescript-S6767.json create mode 100644 its/ruling/src/test/expected/ts/courselit/typescript-S6767.json create mode 100644 its/ruling/src/test/expected/ts/desktop/typescript-S6767.json create mode 100644 its/ruling/src/test/expected/ts/eigen/typescript-S6767.json create mode 100644 sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoUnusedPropTypesCheck.java create mode 100644 sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6767.html create mode 100644 sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6767.json diff --git a/its/ruling/src/test/expected/ts/Joust/typescript-S6767.json b/its/ruling/src/test/expected/ts/Joust/typescript-S6767.json new file mode 100644 index 00000000000..7d9872be21e --- /dev/null +++ b/its/ruling/src/test/expected/ts/Joust/typescript-S6767.json @@ -0,0 +1,5 @@ +{ +"Joust:ts/components/game/EndTurnButton.tsx": [ +8 +] +} diff --git a/its/ruling/src/test/expected/ts/ant-design/typescript-S6767.json b/its/ruling/src/test/expected/ts/ant-design/typescript-S6767.json new file mode 100644 index 00000000000..693d05f8ad8 --- /dev/null +++ b/its/ruling/src/test/expected/ts/ant-design/typescript-S6767.json @@ -0,0 +1,53 @@ +{ +"ant-design:components/anchor/Anchor.tsx": [ +70 +], +"ant-design:components/auto-complete/index.tsx": [ +43 +], +"ant-design:components/button/LoadingIcon.tsx": [ +41, +41 +], +"ant-design:components/config-provider/index.tsx": [ +60, +61, +65, +73, +76, +80 +], +"ant-design:components/descriptions/Row.tsx": [ +94, +96 +], +"ant-design:components/input/ClearableLabeledInput.tsx": [ +32, +40, +42, +43, +44, +45 +], +"ant-design:components/page-header/index.tsx": [ +19, +21, +22, +26, +28, +29, +30 +], +"ant-design:components/progress/Circle.tsx": [ +11 +], +"ant-design:components/skeleton/Element.tsx": [ +10 +], +"ant-design:components/transfer/ListBody.tsx": [ +16 +], +"ant-design:site/theme/template/Layout/Header/Navigation.tsx": [ +16 +] +} diff --git a/its/ruling/src/test/expected/ts/console/typescript-S6767.json b/its/ruling/src/test/expected/ts/console/typescript-S6767.json new file mode 100644 index 00000000000..06214e9e96d --- /dev/null +++ b/its/ruling/src/test/expected/ts/console/typescript-S6767.json @@ -0,0 +1,209 @@ +{ +"console:src/components/Header/Header.tsx": [ +8, +9, +10 +], +"console:src/components/InfiniteTable/InfiniteTable.tsx": [ +13, +17, +18 +], +"console:src/components/Links.tsx": [ +9, +12 +], +"console:src/components/NodeSelector/NodeSelector.tsx": [ +17 +], +"console:src/components/ProjectSelection/ProjectSelection.tsx": [ +32, +33, +34, +35, +36 +], +"console:src/components/ToggleButton/ToggleButton.tsx": [ +19 +], +"console:src/components/onboarding/OnboardingPopup/OnboardingPopup.tsx": [ +15 +], +"console:src/views/FunctionsView/FunctionPopup/Trigger.tsx": [ +14 +], +"console:src/views/FunctionsView/FunctionRow.tsx": [ +24 +], +"console:src/views/FunctionsView/FunctionsView.tsx": [ +10 +], +"console:src/views/Integrations/Algolia/AlgoliaView.tsx": [ +27 +], +"console:src/views/Integrations/Algolia/ConfirmOperationsPopup.tsx": [ +10, +13 +], +"console:src/views/Integrations/AlgoliaPopup/AlgoliaPopup.tsx": [ +22, +28 +], +"console:src/views/Integrations/AlgoliaPopup/AlgoliaPopupIndexes.tsx": [ +14, +16 +], +"console:src/views/PermissionsView/PermissionPopup/PermissionPopup.tsx": [ +34, +35, +39 +], +"console:src/views/PermissionsView/RelationPermissionPopup/RelationBaseSettings.tsx": [ +7 +], +"console:src/views/PermissionsView/RelationPermissionPopup/RelationPermissionPopup.tsx": [ +35, +36, +40 +], +"console:src/views/ProjectRootView/OnboardSideNav.tsx": [ +14, +15 +], +"console:src/views/ProjectRootView/Onboarding/FinalPopup.tsx": [ +12 +], +"console:src/views/ProjectRootView/Onboarding/SelectExample.tsx": [ +11 +], +"console:src/views/ProjectRootView/ProjectRootView.tsx": [ +46 +], +"console:src/views/ProjectRootView/SideNav.tsx": [ +34, +35, +40, +45 +], +"console:src/views/RelationsPopup/ConfirmPopup.tsx": [ +15, +16, +17 +], +"console:src/views/RelationsPopup/ModelSelection.tsx": [ +16, +18 +], +"console:src/views/RelationsPopup/SetMutation.tsx": [ +28, +29 +], +"console:src/views/SchemaView/EnumsOverview/AddEnum.tsx": [ +40, +41, +42 +], +"console:src/views/SchemaView/EnumsOverview/ConfirmEnum.tsx": [ +15 +], +"console:src/views/SchemaView/EnumsOverview/EnumBox.tsx": [ +23, +27, +28 +], +"console:src/views/SchemaView/EnumsOverview/EnumsOverview.tsx": [ +12 +], +"console:src/views/SchemaView/EnumsOverview/EnumsOverviewHeader.tsx": [ +5 +], +"console:src/views/SchemaView/SchemaEditor.tsx": [ +21, +27 +], +"console:src/views/SchemaView/SchemaOverview/ConfirmModel.tsx": [ +15 +], +"console:src/views/SchemaView/SchemaOverview/SchemaOverviewHeader.tsx": [ +8 +], +"console:src/views/SchemaView/SchemaOverview/TypeBox.tsx": [ +25 +], +"console:src/views/Settings/Billing/Billing.tsx": [ +41, +42, +46 +], +"console:src/views/Settings/Billing/ChangePricingPlan.tsx": [ +11 +], +"console:src/views/Settings/Billing/CreditCardBack.tsx": [ +9, +11 +], +"console:src/views/Settings/Billing/CurrentPlan.tsx": [ +14 +], +"console:src/views/account/AccountView/SettingsTab.tsx": [ +14 +], +"console:src/views/models/AuthProviderPopup/AuthProviderPopup.tsx": [ +16, +20 +], +"console:src/views/models/DatabrowserView/Cell.tsx": [ +44, +45, +51, +64 +], +"console:src/views/models/DatabrowserView/Cell/SelectNodesCell/SelectNodesCell.tsx": [ +34 +], +"console:src/views/models/DatabrowserView/CheckboxCell.tsx": [ +9 +], +"console:src/views/models/DatabrowserView/DataActionRow/NewNodeRow.tsx": [ +20 +], +"console:src/views/models/DatabrowserView/DatabrowserView.tsx": [ +66, +67, +68, +69, +70, +71, +72, +73, +78, +81, +82, +101, +102, +127 +], +"console:src/views/models/DatabrowserView/NewRow.tsx": [ +30, +31, +35 +], +"console:src/views/models/FieldPopup/ConfirmFieldPopup.tsx": [ +18 +], +"console:src/views/models/FieldPopup/FieldPopup.tsx": [ +53 +], +"console:src/views/models/ModelHeader.tsx": [ +35, +37 +], +"console:src/views/playground/PlaygroundView/CodeGenerationPopup/CodeGenerationPopup.tsx": [ +19 +], +"console:src/views/playground/PlaygroundView/PlaygroundView.tsx": [ +67, +70, +71 +] +} diff --git a/its/ruling/src/test/expected/ts/courselit/typescript-S6767.json b/its/ruling/src/test/expected/ts/courselit/typescript-S6767.json new file mode 100644 index 00000000000..0098316ec56 --- /dev/null +++ b/its/ruling/src/test/expected/ts/courselit/typescript-S6767.json @@ -0,0 +1,61 @@ +{ +"courselit:apps/web/components/admin/courses/course-editor/index.tsx": [ +74 +], +"courselit:apps/web/components/admin/courses/index.tsx": [ +62 +], +"courselit:apps/web/components/admin/courses/lesson-editor.tsx": [ +101 +], +"courselit:apps/web/components/admin/design/layout-manager/index.tsx": [ +109, +112 +], +"courselit:apps/web/components/admin/design/menus/links/index.tsx": [ +13, +15 +], +"courselit:apps/web/components/admin/design/menus/links/navigation-link-item.tsx": [ +52 +], +"courselit:apps/web/components/admin/media/index.tsx": [ +44 +], +"courselit:apps/web/components/admin/media/media-selector/media-manager-dialog.tsx": [ +19 +], +"courselit:apps/web/components/admin/settings.tsx": [ +94 +], +"courselit:apps/web/components/img.tsx": [ +7 +], +"courselit:apps/web/components/public/base-layout/component-scaffold.tsx": [ +144 +], +"courselit:apps/web/components/public/base-layout/scaffold/drawer-content.tsx": [ +37 +], +"courselit:apps/web/components/public/base-layout/scaffold/index.tsx": [ +102, +103, +105 +], +"courselit:apps/web/components/public/lesson-viewer.tsx": [ +72 +], +"courselit:apps/web/components/public/session-button.tsx": [ +14 +], +"courselit:apps/web/pages/post/[id]/[slug].tsx": [ +14 +], +"courselit:packages/common-widgets/src/branding/widget.tsx": [ +6 +], +"courselit:packages/components-library/src/image.tsx": [ +7, +8 +] +} diff --git a/its/ruling/src/test/expected/ts/desktop/typescript-S6767.json b/its/ruling/src/test/expected/ts/desktop/typescript-S6767.json new file mode 100644 index 00000000000..109135f4da6 --- /dev/null +++ b/its/ruling/src/test/expected/ts/desktop/typescript-S6767.json @@ -0,0 +1,66 @@ +{ +"desktop:app/src/ui/app-menu/app-menu.tsx": [ +41 +], +"desktop:app/src/ui/branches/branch-list.tsx": [ +30, +35, +45, +50 +], +"desktop:app/src/ui/branches/pull-request-list.tsx": [ +44 +], +"desktop:app/src/ui/changes/changes-list.tsx": [ +119 +], +"desktop:app/src/ui/changes/sidebar.tsx": [ +49, +54 +], +"desktop:app/src/ui/check-runs/ci-check-run-list-item.tsx": [ +18, +21 +], +"desktop:app/src/ui/commit-message/commit-message-dialog.tsx": [ +37, +77 +], +"desktop:app/src/ui/diff/seamless-diff-switcher.tsx": [ +43, +49, +55, +58, +61, +70, +76, +82, +88 +], +"desktop:app/src/ui/diff/side-by-side-diff.tsx": [ +79 +], +"desktop:app/src/ui/diff/text-diff.tsx": [ +147 +], +"desktop:app/src/ui/history/selected-commit.tsx": [ +41 +], +"desktop:app/src/ui/lib/filter-list.tsx": [ +62 +], +"desktop:app/src/ui/notifications/pull-request-checks-failed.tsx": [ +39, +40 +], +"desktop:app/src/ui/notifications/pull-request-review.tsx": [ +25, +29 +], +"desktop:app/src/ui/relative-time.tsx": [ +19 +], +"desktop:app/src/ui/welcome/welcome.tsx": [ +24 +] +} diff --git a/its/ruling/src/test/expected/ts/eigen/typescript-S6767.json b/its/ruling/src/test/expected/ts/eigen/typescript-S6767.json new file mode 100644 index 00000000000..77d25c769ba --- /dev/null +++ b/its/ruling/src/test/expected/ts/eigen/typescript-S6767.json @@ -0,0 +1,162 @@ +{ +"eigen:src/app/AppRegistry.tsx": [ +184 +], +"eigen:src/app/Components/ArtistAutosuggest/ArtistAutosuggestResults.tsx": [ +29 +], +"eigen:src/app/Components/ArtistListItem.tsx": [ +15, +16 +], +"eigen:src/app/Components/ArtworkFilter/ArtworkFilterNavigator.tsx": [ +50, +52, +57 +], +"eigen:src/app/Components/ArtworkGrids/SaleArtworkGridItem.tsx": [ +24, +25 +], +"eigen:src/app/Components/AuctionResultsList.tsx": [ +109 +], +"eigen:src/app/Components/Bidding/Components/Timer.tsx": [ +28, +29, +30, +31, +32, +33 +], +"eigen:src/app/Components/Bidding/Screens/SelectMaxBid.tsx": [ +25 +], +"eigen:src/app/Components/Buttons/BottomAlignedButtonWrapper.tsx": [ +6 +], +"eigen:src/app/Components/Countdown/CountdownTimer.tsx": [ +6, +7, +8 +], +"eigen:src/app/Components/LocationAutocomplete/LocationAutocomplete.tsx": [ +94 +], +"eigen:src/app/Components/PopoverMessage/PopoverMessage.tsx": [ +25, +26 +], +"eigen:src/app/Components/RetryErrorBoundary.tsx": [ +54 +], +"eigen:src/app/Components/ScrollableTabBar.tsx": [ +16, +17, +46 +], +"eigen:src/app/Components/Show/ShowArtworksPreview.tsx": [ +13 +], +"eigen:src/app/Components/States/ZeroState.tsx": [ +7 +], +"eigen:src/app/Components/TabBar.tsx": [ +56 +], +"eigen:src/app/Containers/Inbox.tsx": [ +70, +178 +], +"eigen:src/app/Containers/Inquiry.tsx": [ +91 +], +"eigen:src/app/Containers/RegistrationFlow.tsx": [ +9 +], +"eigen:src/app/Scenes/ArtistSeries/ArtistSeriesListItem.tsx": [ +21 +], +"eigen:src/app/Scenes/Artwork/Components/CommercialButtons/BidButton.tsx": [ +20 +], +"eigen:src/app/Scenes/Artwork/Components/CommercialInformation.tsx": [ +34, +37 +], +"eigen:src/app/Scenes/Artwork/Components/FollowArtistLink.tsx": [ +11 +], +"eigen:src/app/Scenes/Artwork/Components/ImageCarousel/FullScreen/ImageZoomView.tsx": [ +45 +], +"eigen:src/app/Scenes/Artwork/Components/ImageCarousel/ImageCarousel.tsx": [ +35 +], +"eigen:src/app/Scenes/Artwork/Components/OtherWorks/ContextGridCTA.tsx": [ +9 +], +"eigen:src/app/Scenes/City/Components/AllEvents.tsx": [ +15 +], +"eigen:src/app/Scenes/Favorites/FavoriteShows.tsx": [ +21 +], +"eigen:src/app/Scenes/Home/Components/FairsRail.tsx": [ +25, +26 +], +"eigen:src/app/Scenes/Inbox/Components/Conversations/Conversations.tsx": [ +20, +21 +], +"eigen:src/app/Scenes/Inbox/Components/Conversations/Message.tsx": [ +26 +], +"eigen:src/app/Scenes/Inbox/Screens/ConversationDetails.tsx": [ +22 +], +"eigen:src/app/Scenes/Map/MapContainer.tsx": [ +9 +], +"eigen:src/app/Scenes/MyAccount/MyAccountEditName.tsx": [ +15 +], +"eigen:src/app/Scenes/MyBids/MyBids.tsx": [ +172 +], +"eigen:src/app/Scenes/Onboarding/OnboardingPersonalization/OnboardingPersonalizationArtistListItem.tsx": [ +25, +26 +], +"eigen:src/app/Scenes/Partner/Components/PartnerFollowButton.tsx": [ +11 +], +"eigen:src/app/Scenes/Partner/Partner.tsx": [ +21 +], +"eigen:src/app/Scenes/Sale/Components/SaleArtworkList.tsx": [ +19, +20 +], +"eigen:src/app/Scenes/Sale/Components/SaleArtworkListItem.tsx": [ +21, +22 +], +"eigen:src/app/Scenes/SavedSearchAlert/Components/SavedSearchAlertSwitch.tsx": [ +9 +], +"eigen:src/app/Scenes/SavedSearchAlertsList/Components/SavedSearchesList.tsx": [ +25 +], +"eigen:src/app/Scenes/Tag/TagHeader.tsx": [ +8 +], +"eigen:src/app/navigation/NavStack.tsx": [ +59, +60 +], +"eigen:src/app/tests/MockRelayRendererFixtures.tsx": [ +62 +] +} 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 0b13ff52368..92ccb1381e1 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 @@ -306,6 +306,7 @@ public static List> getAllChecks() { NoUnstableNestedComponentsCheck.class, NoUnusedClassComponentMethodsCheck.class, NoUnusedPrivateClassMembersCheck.class, + NoUnusedPropTypesCheck.class, NoUselessCallCheck.class, NoUselessCatchCheck.class, NoUselessConstructorCheck.class, diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoUnusedPropTypesCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoUnusedPropTypesCheck.java new file mode 100644 index 00000000000..8f4f2eca4b6 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoUnusedPropTypesCheck.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 = "S6767") +public class NoUnusedPropTypesCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "no-unused-prop-types"; + } +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6767.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6767.html new file mode 100644 index 00000000000..4a2863df876 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6767.html @@ -0,0 +1,81 @@ +

Why is this an issue?

+

Leaving unused props in a React component can make the code harder to understand and maintain. Other developers may wonder why certain props are +passed to a component if they are not used. Unused props can also increase the size of the component’s memory footprint and impact performance. This +is especially true if the unused props are large objects or arrays. Furthermore, if a prop is unused, it may indicate that the developer did not +complete the implementation as he intended initially or made a mistake while writing the component.

+

To avoid these issues, you should remove any unused props from React components. This helps keep the codebase clean, improves performance, and +enhances code readability.

+

How to fix it in PropTypes

+

Code examples

+

Noncompliant code example

+
+import PropTypes from 'prop-types';
+import React from 'react';
+
+class Hello extends React.Component {
+  render() {
+    return (
+      <h1>Hello</h1>
+    );
+  }
+}
+
+Hello.propTypes = {
+  name: PropTypes.string
+};
+
+

Compliant solution

+
+import PropTypes from 'prop-types';
+import React from 'react';
+
+class Hello extends React.Component {
+  render() {
+    return (
+      <h1>Hello {this.props.name}</h1>
+    );
+  }
+}
+
+Hello.propTypes = {
+  name: PropTypes.string
+};
+
+

How to fix it in TypeScript

+

Code examples

+

Noncompliant code example

+
+import React from 'react';
+
+type Props = {
+  name: string;
+}
+
+class Hello extends React.Component<Props> {
+  render() {
+    return <div>Hello</div>;
+  }
+}
+
+

Compliant solution

+
+import React from 'react';
+
+type Props = {
+  name: string;
+};
+
+class Hello extends React.Component<Props> {
+  render() {
+    return <div>Hello {this.props.name}</div>;
+  }
+}
+
+

Resources

+

Documentation

+ + diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6767.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6767.json new file mode 100644 index 00000000000..cd2b07bc4e3 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6767.json @@ -0,0 +1,27 @@ +{ + "title": "React typed props should be used", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6767", + "sqKey": "S6767", + "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 ec36b9c20e6..c33a5b2f31b 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 @@ -284,6 +284,7 @@ "S6757", "S6759", "S6761", - "S6763" + "S6763", + "S6767" ] }