From 22d78bf676add4c72f269444eea5bb6abb3b1084 Mon Sep 17 00:00:00 2001 From: Alban Auzeill Date: Thu, 29 Feb 2024 16:44:50 +0100 Subject: [PATCH] SONARJAVA-4827 S6877 SequencedCollection reversed view should be used instead of Collections.reverse (#4694) --- .../resources/autoscan/diffs/diff_S6877.json | 6 + ...ReverseSequencedCollectionCheckSample.java | 196 +++++++++++++ .../ReverseSequencedCollectionCheck.java | 268 ++++++++++++++++++ .../ReverseSequencedCollectionCheckTest.java | 47 +++ .../org/sonar/plugins/java/CheckList.java | 2 + .../org/sonar/l10n/java/rules/java/S6877.html | 62 ++++ .../org/sonar/l10n/java/rules/java/S6877.json | 23 ++ .../java/rules/java/Sonar_way_profile.json | 1 + 8 files changed, 605 insertions(+) create mode 100644 its/autoscan/src/test/resources/autoscan/diffs/diff_S6877.json create mode 100644 java-checks-test-sources/default/src/main/java/checks/ReverseSequencedCollectionCheckSample.java create mode 100644 java-checks/src/main/java/org/sonar/java/checks/ReverseSequencedCollectionCheck.java create mode 100644 java-checks/src/test/java/org/sonar/java/checks/ReverseSequencedCollectionCheckTest.java create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6877.html create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6877.json diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6877.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6877.json new file mode 100644 index 00000000000..5ddd097363c --- /dev/null +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6877.json @@ -0,0 +1,6 @@ +{ + "ruleKey": "S6877", + "hasTruePositives": true, + "falseNegatives": 0, + "falsePositives": 0 +} \ No newline at end of file diff --git a/java-checks-test-sources/default/src/main/java/checks/ReverseSequencedCollectionCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/ReverseSequencedCollectionCheckSample.java new file mode 100644 index 00000000000..06f23b5ce06 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/ReverseSequencedCollectionCheckSample.java @@ -0,0 +1,196 @@ +package checks; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +class ReverseSequencedCollectionCheckSample { + + List field; + + List test_read_only_usage_as_argument(List source) { + var list = new ArrayList<>(source); + Collections.reverse(list); // Noncompliant, only readonly access after + var list2 = new ArrayList<>(list); + list2.addAll(list); + return List.copyOf(list); + } + + void test_noncompliant_for(List source) { + var list = new ArrayList<>(source); + list.add("a"); + Collections.reverse(list); // Noncompliant {{Remove this "reverse" statement and replace "list" with "list.reversed()" after.}} + for (var e : list) { + System.out.println(e); + } + } + + void test_external_list() { + var list = externalList(); + Collections.reverse(list); // Compliant, side effect on externalList() + list.forEach(System.out::println); + } + + void test_unknown_usage_before(List source) { + var list = new ArrayList<>(source); + unknownUsage(list); + Collections.reverse(list); // Compliant, unknown usage before + list.forEach(System.out::println); + } + + void test_unknown_usage_after(List source) { + var list = new ArrayList<>(source); + Collections.reverse(list); // Compliant, unknown usage after + unknownUsage(list); + list.forEach(System.out::println); + } + + void test_non_supported_constructor(List source) { + var list = new MyList(source); + Collections.reverse(list); // Compliant, not supported constructor MyList + list.forEach(System.out::println); + } + + void test_write_after(List source) { + var list = new ArrayList<>(source); + Collections.reverse(list); // Compliant, write after + list.add("a"); + list.forEach(System.out::println); + } + + void test_compliant_field(List source) { + field = new ArrayList<>(source); + Collections.reverse(field); // Compliant, field could be used outside of this method + field.forEach(System.out::println); + } + + void test_argument_and_reverse(List list) { + Collections.reverse(list); // Compliant, the caller needs the "list" argument to be reversed + } + + void test_argument_and_for(List list) { + Collections.reverse(list); // Compliant, we don't know if the caller needs the "list" argument to be reversed + for (var e : list) { + System.out.println(e); + } + } + + + List test_return_list(List source) { + var list = new ArrayList<>(source); + Collections.reverse(list); // Compliant, we don't know if the caller can work with the "reversed()" view of the list + return list; + } + + List test_non_supported_initializer(List source) { + var list = new ArrayList<>(source); + var spy = list; + Collections.reverse(list); // Compliant, list is assigned to another variable + return spy; + } + + List test_non_supported_assigment(List source) { + var list = new ArrayList<>(source); + List spy; + spy = list; + Collections.reverse(list); // Compliant, list is assigned to another variable + return spy; + } + + String[] test_return_array(List source) { + var list = new ArrayList<>(source); + list.add("A"); + if (!list.isEmpty()) { // not a loop + Collections.reverse(list); // Noncompliant + } + return list.toArray(String[]::new); + } + + void test_noncompliant_fori_loop(List source, int count) { + var list = new ArrayList<>(source); + list.add("A"); + for (int i = 0; i < count; i++) { + Collections.reverse(list); // Compliant, reverse is in a loop + } + list.forEach(System.out::println); + } + + void test_noncompliant_while_loop(List source, int count) { + List list; + list = new ArrayList<>(source); + while(--count >= 0) { + Collections.reverse(list); // Compliant, reverse is in a loop + } + list.forEach(System.out::println); + } + + void test_compliant_usage(List list) { + for (var e : list.reversed()) { + System.out.println(e); + } + } + + void test_noncompliant_stream(List source) { + var copy = new ArrayList<>(source); + copy.add("a"); + Collections.reverse(copy); // Noncompliant {{Remove this "reverse" statement and replace "copy" with "copy.reversed()" after.}} + copy.forEach(System.out::println); + copy.stream().forEach(System.out::println); + } + + Object[] test_no_initializer(List source) { + List list; + if (source.isEmpty()) { + list = new ArrayList<>(); + } else { + list = new ArrayList<>(source); + } + list.add("A"); + if (!list.isEmpty()) { + Collections.reverse(list); // Noncompliant + } + return list.toArray(); + } + + void test_null_initializer(List source) { + List list = null; + if (source.isEmpty()) { + list = null; + } else if (!source.isEmpty()) { + list = new ArrayList<>(source); + } + list.add("A"); + if (!source.isEmpty()) { + Collections.reverse(list); // Noncompliant + list.forEach(System.out::println); + } + } + + void test_reverse_non_identifier(List source) { + List list = new ArrayList<>(source); + Collections.reverse(list.subList(0,3)); // Compliant, partial reverse + list.forEach(System.out::println); + } + + void test_reverse_foreach_initialization(List> sources) { + for (List list : sources) { + Collections.reverse(list);// Compliant, foreach initialization is not supported + list.forEach(System.out::println); + } + } + + List externalList() { + return new ArrayList<>(); + } + void unknownUsage(List list) { + } + + class MyList extends ArrayList { + public MyList(@NotNull Collection c) { + super(c); + } + } + +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/ReverseSequencedCollectionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ReverseSequencedCollectionCheck.java new file mode 100644 index 00000000000..d549d795cf7 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/ReverseSequencedCollectionCheck.java @@ -0,0 +1,268 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2024 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.java.checks; + +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nullable; +import org.sonar.check.Rule; +import org.sonar.java.checks.methods.AbstractMethodDetection; +import org.sonar.java.model.ExpressionUtils; +import org.sonar.plugins.java.api.JavaVersion; +import org.sonar.plugins.java.api.JavaVersionAwareVisitor; +import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol; +import org.sonar.plugins.java.api.tree.Arguments; +import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.ForEachStatement; +import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.NewClassTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.VariableTree; + +@Rule(key = "S6877") +public class ReverseSequencedCollectionCheck extends AbstractMethodDetection implements JavaVersionAwareVisitor { + + private static final String MESSAGE = "Remove this \"reverse\" statement and replace \"%s\" with \"%s.reversed()\" after."; + + private static final String ADD_ALL = "addAll"; + + private static final MethodMatchers LIST_CONSTRUCTORS = MethodMatchers.create() + .ofTypes( + "java.util.ArrayList", + "java.util.LinkedList", + "java.util.Vector", + "java.util.Stack", + "java.util.concurrent.CopyOnWriteArrayList", + "javax.management.AttributeList", + "javax.management.relation.RoleList", + "javax.management.relation.RoleUnresolvedList") + .constructor() + .withAnyParameters() + .build(); + + private static final MethodMatchers LIST_READONLY_CONSUMERS = MethodMatchers.create() + .ofSubTypes("java.util.List") + .names(ADD_ALL, "copyOf") + .addParametersMatcher("java.util.Collection") + .build(); + + private static final MethodMatchers LIST_READ_ACCESSORS = MethodMatchers.or( + MethodMatchers.create() + .ofAnyType() + .names("clone", "getClass", "getFirst", "getLast", "hashCode", "isEmpty", "listIterator", "parallelStream", + "size", "spliterator", "stream", "toArray", "toString", "wait", "notify", "notifyAll") + .addWithoutParametersMatcher() + .build(), + MethodMatchers.create() + .ofAnyType() + .names("contains", "containsAll", "equals", "forEach", "get", "indexOf", "lastIndexOf", "listIterator", "toArray") + .addParametersMatcher(MethodMatchers.ANY) + .build()); + + private static final MethodMatchers LIST_WRITE_ACCESSORS = MethodMatchers.or( + MethodMatchers.create() + .ofAnyType() + .names("clear", "removeFirst", "removeLast") + .addWithoutParametersMatcher() + .build(), + MethodMatchers.create() + .ofAnyType() + .names("add", ADD_ALL, "addFirst", "addLast", "remove", "removeAll", "replaceAll", "retainAll", "sort") + .addParametersMatcher(MethodMatchers.ANY) + .build(), + MethodMatchers.create() + .ofAnyType() + .names("add", ADD_ALL, "set") + .addParametersMatcher("int", MethodMatchers.ANY) + .build()); + + private static final Set SUPPORTED_REVERSE_SCOPE_CHILD_KINDS = EnumSet.of( + Tree.Kind.IDENTIFIER, + Tree.Kind.ARGUMENTS, + Tree.Kind.METHOD_INVOCATION, + Tree.Kind.EXPRESSION_STATEMENT, + Tree.Kind.MEMBER_SELECT, + Tree.Kind.BLOCK, + Tree.Kind.IF_STATEMENT); + + private static final Set SUPPORTED_USAGE_SCOPE_CHILD_KINDS = EnumSet.of( + Tree.Kind.IDENTIFIER, + Tree.Kind.MEMBER_SELECT, + Tree.Kind.METHOD_INVOCATION, + Tree.Kind.ASSIGNMENT, + Tree.Kind.EXPRESSION_STATEMENT, + Tree.Kind.BLOCK, + Tree.Kind.IF_STATEMENT); + + @Override + public boolean isCompatibleWithJavaVersion(JavaVersion version) { + return version.isJava21Compatible(); + } + + @Override + protected MethodMatchers getMethodInvocationMatchers() { + return MethodMatchers.create() + .ofTypes("java.util.Collections") + .names("reverse") + .addParametersMatcher("java.util.List") + .build(); + } + + @Override + protected void onMethodInvocationFound(MethodInvocationTree methodInvocation) { + Arguments reverseMethodArguments = methodInvocation.arguments(); + if (reverseMethodArguments.isEmpty() || !reverseMethodArguments.get(0).is(Tree.Kind.IDENTIFIER)) { + return; + } + IdentifierTree reverseMethodArgument = (IdentifierTree) reverseMethodArguments.get(0); + Symbol symbol = reverseMethodArgument.symbol(); + // Only support local variables. "Collections.reverse(list)" mutates "list" and return void. If "list" was a + // field or a method parameter, we don't know if the mutation of the "list" is used outside of this method body. So + // we don't raise issue. + if (!symbol.isLocalVariable() || symbol.isParameter()) { + return; + } + VariableSymbol reverseMethodArgumentSymbol = (VariableSymbol) symbol; + if (areUsagesCompatibleWithReversed(reverseMethodArgument, reverseMethodArgumentSymbol)) { + String message = String.format(MESSAGE, reverseMethodArgumentSymbol.name(), reverseMethodArgumentSymbol.name()); + reportIssue(ExpressionUtils.methodName(methodInvocation), message); + } + } + + private static boolean areUsagesCompatibleWithReversed(IdentifierTree reverseArgument, VariableSymbol listSymbol) { + VariableTree declaration = listSymbol.declaration(); + // We are checking that there are no write modifiers of "list" after "Collections.reverse(list)". + // Because we don't use symbolic execution, we will know that a usage is after by using its position in the file. + // This strategy only works if there are no loops or lambdas. To ensure this, we will check that "Collections.reverse(list)" + // and its write usages share the same "reverseParentScope". + Tree reverseParentScope = findParentScope(reverseArgument, SUPPORTED_REVERSE_SCOPE_CHILD_KINDS); + if (declaration == null || reverseParentScope == null || + !isInitializerCompatibleWithReversed(declaration, reverseParentScope)) { + return false; + } + for (IdentifierTree usage : listSymbol.usages()) { + if (!isUsageCompatibleWithReversed(usage, reverseArgument, reverseParentScope)) { + return false; + } + } + return true; + } + + private static boolean isInitializerCompatibleWithReversed(VariableTree declaration, Tree reverseParentScope) { + if (declaration.parent() instanceof ForEachStatement) { + return false; + } + ExpressionTree initializer = declaration.initializer(); + if (initializer == null) { + return true; + } + return isNullOrListConstructor(initializer) && + matchReverseParentSafeScope(declaration.parent(), reverseParentScope); + } + + private static boolean isUsageCompatibleWithReversed(IdentifierTree usage, IdentifierTree reverseArgument, Tree reverseParentScope) { + if (usage == reverseArgument || isCompatibleReadUsage(usage)) { + return true; + } + if (isAfter(usage, reverseArgument)) { + return false; + } + return isCompatibleWriteUsage(usage) && matchReverseParentSafeScope(usage, reverseParentScope); + } + + private static boolean isCompatibleWriteUsage(IdentifierTree usage) { + if (matchAccessor(usage, LIST_WRITE_ACCESSORS)) { + return true; + } + return usage.parent() instanceof AssignmentExpressionTree assignmentExpression && + isNullOrListConstructor(assignmentExpression.expression()); + } + + private static boolean isCompatibleReadUsage(IdentifierTree usage) { + if (matchAccessor(usage, LIST_READ_ACCESSORS)) { + return true; + } + Tree parent = usage.parent(); + if (parent instanceof Arguments arguments) { + return isListReadOnlyConsumer(arguments.parent()); + } else { + return parent instanceof ForEachStatement forEachStatement && forEachStatement.expression() == usage; + } + } + + /** + * @return true if there is a method call on the given "usage" identifier, e.g. "list.getFirst()", and if the + * method call matches the given "methodMatchers". + */ + private static boolean matchAccessor(IdentifierTree usage, MethodMatchers methodMatchers) { + Tree parent = usage.parent(); + if (parent instanceof MemberSelectExpressionTree memberSelect && memberSelect.expression() == usage) { + Tree grandParent = parent.parent(); + if (grandParent instanceof MethodInvocationTree methodInvocation && methodInvocation.methodSelect() == parent) { + return methodMatchers.matches(methodInvocation); + } + } + return false; + } + + private static boolean isAfter(IdentifierTree a, IdentifierTree b) { + return a.identifierToken().range().start().isAfter(b.identifierToken().range().start()); + } + + private static boolean isNullOrListConstructor(ExpressionTree expression) { + if (expression.is(Tree.Kind.NULL_LITERAL)) { + return true; + } + return isListConstructor(expression); + } + + private static boolean isListReadOnlyConsumer(@Nullable Tree tree) { + if (tree instanceof MethodInvocationTree methodInvocation) { + return LIST_READONLY_CONSUMERS.matches(methodInvocation); + } + return isListConstructor(tree); + } + + private static boolean isListConstructor(@Nullable Tree tree) { + return tree instanceof NewClassTree newClassTree && LIST_CONSTRUCTORS.matches(newClassTree); + } + + private static Tree findParentScope(@Nullable Tree tree, Set supportedChildKinds) { + while (tree != null && supportedChildKinds.contains(tree.kind())) { + tree = tree.parent(); + } + return tree; + } + + /** + * @return true if we can reach "reverseParentScope" following "tree" parents and continuing only for + * the given Tree.Kind list. This will ensure that between the given tree and the "Collections.reverse(list)" call, + * there are no loops or lambdas. + */ + private static boolean matchReverseParentSafeScope(@Nullable Tree tree, Tree reverseParentScope) { + return findParentScope(tree, SUPPORTED_USAGE_SCOPE_CHILD_KINDS) == reverseParentScope; + } + +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/ReverseSequencedCollectionCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ReverseSequencedCollectionCheckTest.java new file mode 100644 index 00000000000..51887f02ed8 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/ReverseSequencedCollectionCheckTest.java @@ -0,0 +1,47 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2024 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.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class ReverseSequencedCollectionCheckTest { + + @Test + void test_with_java21() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/ReverseSequencedCollectionCheckSample.java")) + .withCheck(new ReverseSequencedCollectionCheck()) + .withJavaVersion(21) + .verifyIssues(); + } + + @Test + void test_before_java21() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/ReverseSequencedCollectionCheckSample.java")) + .withCheck(new ReverseSequencedCollectionCheck()) + .withJavaVersion(20) + .verifyNoIssues(); + } + +} diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/CheckList.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/CheckList.java index 3722f0e5a08..cc19a1e9298 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/CheckList.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/CheckList.java @@ -328,6 +328,7 @@ import org.sonar.java.checks.ReturnInFinallyCheck; import org.sonar.java.checks.ReturnOfBooleanExpressionsCheck; import org.sonar.java.checks.ReuseRandomCheck; +import org.sonar.java.checks.ReverseSequencedCollectionCheck; import org.sonar.java.checks.RightCurlyBraceDifferentLineAsNextBlockCheck; import org.sonar.java.checks.RightCurlyBraceSameLineAsNextBlockCheck; import org.sonar.java.checks.RightCurlyBraceStartLineCheck; @@ -1041,6 +1042,7 @@ public final class CheckList { ReturnEmptyArrayNotNullCheck.class, ReturnOfBooleanExpressionsCheck.class, ReuseRandomCheck.class, + ReverseSequencedCollectionCheck.class, RightCurlyBraceDifferentLineAsNextBlockCheck.class, RightCurlyBraceSameLineAsNextBlockCheck.class, RightCurlyBraceStartLineCheck.class, diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6877.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6877.html new file mode 100644 index 00000000000..d800fd54c0b --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6877.html @@ -0,0 +1,62 @@ +

Why is this an issue?

+

Java 21 introduces the new Sequenced Collections API, which applies to all collections with a defined sequence on their elements, such as +LinkedList, TreeSet, and others (see JEP 431). For projects using Java 21 and +onwards, use this API instead of workaround implementations that were necessary before Java 21. One of the features of the new Sequenced Collections +API is SequencedCollection.reversed() which returns a lightweight view of the original collection, in the reverse order.

+

This rule reports when reverse view would have been sufficient instead of a reverse copy of a sequenced collection created using a list constructor +plus a Collections.reverse(collection); call.

+

If feasible, a view should be preferred over a copy because a view is a lightweight iterator without modification of the list itself.

+

How to fix it

+

Remove Collections.reverse(list); and replace list with list.reversed() after.

+

Code examples

+

Noncompliant code example

+
+void foo() {
+  var list = new ArrayList<String>();
+  list.add("A");
+  list.add("B");
+  Collections.reverse(list); // Noncompliant
+  for (var e : list) {
+    // ...
+  }
+}
+
+

Compliant solution

+
+void foo() {
+  var list = new ArrayList<String>();
+  list.add("A");
+  list.add("B");
+  for (var e : list.reversed()) {  // Compliant
+    // ...
+  }
+}
+
+

Noncompliant code example

+
+void foo(List<String> list) {
+  var copy = new ArrayList<String>(list);
+  Collections.reverse(copy); // Noncompliant
+  for (var e : copy) {
+    // ...
+  }
+}
+
+

Compliant solution

+
+void foo(List<String> list) {
+  for (var e : list.reversed()) {  // Compliant
+    // ...
+  }
+}
+
+

Resources

+

Documentation

+ + diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6877.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6877.json new file mode 100644 index 00000000000..7553a21f71c --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6877.json @@ -0,0 +1,23 @@ +{ + "title": "Reverse view should be used instead of reverse copy in read-only cases", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "java21" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6877", + "sqKey": "S6877", + "scope": "All", + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "HIGH" + }, + "attribute": "CONVENTIONAL" + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json index 7d15abce98a..37d486d8959 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json @@ -499,6 +499,7 @@ "S6857", "S6862", "S6863", + "S6877", "S6889", "S6901", "S6905",