diff --git a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9358.json b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9358.json new file mode 100644 index 00000000000..a0499db7273 --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9358.json @@ -0,0 +1,9 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java": [ +129 +], +"org.eclipse.jetty:jetty-project:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java": [ +362, +373 +] +} diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S9358.json b/its/ruling/src/test/resources/eclipse-jetty/java-S9358.json new file mode 100644 index 00000000000..da3fc3fea25 --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S9358.json @@ -0,0 +1,15 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java": [ +129 +], +"org.eclipse.jetty:jetty-project:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java": [ +362, +373 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/JavaVersion.java": [ +58 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/statistic/CounterStatisticTest.java": [ +91 +] +} diff --git a/its/ruling/src/test/resources/guava/java-S9358.json b/its/ruling/src/test/resources/guava/java-S9358.json new file mode 100644 index 00000000000..05f6667d541 --- /dev/null +++ b/its/ruling/src/test/resources/guava/java-S9358.json @@ -0,0 +1,12 @@ +{ +"com.google.guava:guava:src/com/google/common/collect/RegularImmutableTable.java": [ +151, +155 +], +"com.google.guava:guava:src/com/google/common/collect/TreeMultiset.java": [ +91 +], +"com.google.guava:guava:src/com/google/common/hash/MessageDigestHashFunction.java": [ +156 +] +} diff --git a/its/ruling/src/test/resources/sonar-server/java-S9358.json b/its/ruling/src/test/resources/sonar-server/java-S9358.json new file mode 100644 index 00000000000..550f9758876 --- /dev/null +++ b/its/ruling/src/test/resources/sonar-server/java-S9358.json @@ -0,0 +1,14 @@ +{ +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/ConditionEvaluator.java": [ +113 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java": [ +175 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/qualityprofile/RuleActivator.java": [ +200 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/setting/ws/ValuesAction.java": [ +247 +] +} diff --git a/java-checks-test-sources/default/src/main/java/checks/TernaryOperatorSameOperationCheckNoSemanticSample.java b/java-checks-test-sources/default/src/main/java/checks/TernaryOperatorSameOperationCheckNoSemanticSample.java new file mode 100644 index 00000000000..83e49878722 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/TernaryOperatorSameOperationCheckNoSemanticSample.java @@ -0,0 +1,87 @@ +package checks; + +import java.util.function.Function; + +class TernaryOperatorSameOperationCheckNoSemanticSample { + + boolean condition; + boolean other; + TernaryOperatorSameOperationCheckNoSemanticSample obj; + TernaryOperatorSameOperationCheckNoSemanticSample obj2; + + void testMethodInvocations() { + String a = "a"; + String b = "b"; + String x = "x"; + String y = "y"; + + // Method invocations - Noncompliant + String m1 = condition ? foo(a) : foo(b); // Noncompliant {{Move the conditional expression inside this operation.}} + + String m2 = condition ? this.foo(a) : this.foo(b); // Noncompliant + String m3 = condition ? obj.foo(a) : obj.foo(b); // Noncompliant + + // Method invocations with multiple args where some differ - Noncompliant + String m5 = condition ? foo(a, x) : foo(b, x); // Noncompliant + + // Method invocations with multiple args where all differ - Compliant + String m6 = condition ? foo(a, x) : foo(b, y); // Compliant - more than one argument differs + + // Method invocations - Compliant (different operations or same arguments) + String c1 = condition ? foo(a) : bar(b); // Compliant - different methods + String c2 = condition ? foo(a) : foo(a); // Compliant - same arguments + String c3 = condition ? foo(a) : foo(a, b); // Compliant - different number of arguments + } + + void testNewClass() { + String a = "a"; + String b = "b"; + + // New class - Noncompliant + Object n1 = condition ? new Foo(a) : new Foo(b); // Noncompliant {{Move the conditional expression inside this operation.}} + + // New class - Compliant + Object c4 = condition ? new Foo(a) : new Bar(b); // Compliant - different classes + Object c5 = condition ? new Foo(a) : new Foo(a); // Compliant - same arguments + Object c6 = condition ? new Foo(a) : new Foo(a, b); // Compliant - different arguments count + } + + void testArrayAccess() { + String[] arr = new String[10]; + String[] otherArr = new String[10]; + int i = 0; + int j = 1; + + // Array access - Noncompliant + String a1 = condition ? arr[i] : arr[j]; // Noncompliant {{Move the conditional expression inside this operation.}} + + // Array access - Compliant + String c7 = condition ? arr[i] : arr[i]; // Compliant - same index + String c8 = condition ? arr[i] : otherArr[j]; // Compliant - different arrays + } + + void testNewClassWithClassBody() { + String a = "a"; + String b = "b"; + + // Anonymous class body - Compliant even without semantics + Object ac1 = condition ? new Foo(a) { } : new Foo(b) { }; // Compliant - anonymous class bodies + Object ac2 = condition ? new Foo(a) { } : new Foo(b); // Compliant - one has class body + } + + // Private methods used in ternary + private String foo(String s) { return s; } + private String foo(String s, String t) { return s + t; } + private String bar(String s) { return s; } + private String noArg() { return ""; } + + private static class Foo { + Foo(String s) {} + Foo(String s, String t) {} + } + + private static class Bar { + Bar(String s) {} + } + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/TernaryOperatorSameOperationCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/TernaryOperatorSameOperationCheckSample.java new file mode 100644 index 00000000000..f0e3dc02894 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/TernaryOperatorSameOperationCheckSample.java @@ -0,0 +1,212 @@ +package checks; + +import java.util.function.Function; + +class TernaryOperatorSameOperationCheckSample { + + boolean condition; + boolean other; + TernaryOperatorSameOperationCheckSample obj; + TernaryOperatorSameOperationCheckSample obj2; + + void testMethodInvocations() { + String a = "a"; + String b = "b"; + String x = "x"; + String y = "y"; + String z = "z"; + + // Method invocations - Noncompliant + String m1 = condition ? foo(a) : foo(b); // Noncompliant {{Move the conditional expression inside this operation.}} + + String m2 = condition ? this.foo(a) : this.foo(b); // Noncompliant + String m3 = condition ? obj.foo(a) : obj.foo(b); // Noncompliant + String m4 = condition ? StaticClass.foo(a) : StaticClass.foo(b); // Noncompliant + + // Method invocations with multiple args where some differ - Noncompliant + String m5 = condition ? foo(a, x) : foo(b, x); // Noncompliant + + // Method invocations with multiple args where all differ - Compliant + String m6 = condition ? foo(a, x) : foo(b, y); // Compliant - more than one argument differs + + // Method invocations - Compliant (different operations or same arguments) + String c1 = condition ? foo(a) : bar(b); // Compliant - different methods + String c2 = condition ? foo(a) : foo(a); // Compliant - same arguments + String c3 = condition ? foo(a) : foo(a, b); // Compliant - different number of arguments + } + + void testMethodInvocationsEdgeCases() { + String a = "a"; + String b = "b"; + + // No-arg methods - Compliant (no arguments to differ) + String e1 = condition ? noArg() : noArg(); // Compliant + + // Different receivers - Compliant + String e2 = condition ? obj.foo(a) : obj2.foo(b); // Compliant - different receiver objects + + // Different kinds in true/false - Compliant + Object e3 = condition ? foo(a) : new Foo(b); // Compliant - method vs constructor + Object e4 = condition ? a : b; // Compliant - simple identifiers, not method/new/array + + // String literal - Compliant + String e5 = condition ? "hello" : "world"; // Compliant + + // Numeric literal - Compliant + int e6 = condition ? 1 : 2; // Compliant + } + + void testNewClass() { + String a = "a"; + String b = "b"; + + // New class - Noncompliant + Object n1 = condition ? new Foo(a) : new Foo(b); // Noncompliant {{Move the conditional expression inside this operation.}} + + // New class with multiple args where some differ - Noncompliant + Object n2 = condition ? new Foo(a, b) : new Foo(b, b); // Noncompliant + + // New class with multiple args where all differ - Compliant + Object n3 = condition ? new Foo(a, a) : new Foo(b, b); // Compliant - more than one argument differs + + // New class - Compliant + Object c4 = condition ? new Foo(a) : new Bar(b); // Compliant - different classes + Object c5 = condition ? new Foo(a) : new Foo(a); // Compliant - same arguments + Object c6 = condition ? new Foo(a) : new Foo(a, b); // Compliant - different arguments count + } + + void testArrayAccess() { + String[] arr = new String[10]; + String[] otherArr = new String[10]; + int i = 0; + int j = 1; + + // Array access - Noncompliant + String a1 = condition ? arr[i] : arr[j]; // Noncompliant {{Move the conditional expression inside this operation.}} + + // Array access - Compliant + String c7 = condition ? arr[i] : arr[i]; // Compliant - same index + String c8 = condition ? arr[i] : otherArr[j]; // Compliant - different arrays + } + + void testNestedTernary() { + String x = "x"; + String y = "y"; + String z = "z"; + + // Nested ternary - Noncompliant (outer ternary has same operation after parentheses skip) + String n3 = condition ? (other ? foo(x) : foo(y)) : foo(z); // Noncompliant {{Move the conditional expression inside this operation.}} + + // Nested ternary - Compliant (inner ternary is not same operation) + String c9 = condition ? (other ? foo(x) : bar(x)) : foo(z); // Compliant + } + + void testMemberSelectEdgeCases() { + String a = "a"; + String b = "b"; + + // Same receiver, different method names - Compliant + String ms1 = condition ? obj.foo(a) : obj.bar(b); // Compliant - different method names + + // Method invocation vs member select method invocation - Compliant + String ms2 = condition ? foo(a) : obj.foo(b); // Compliant - identifier vs member select + } + + void testOther() { + String a = "a"; + String b = "b"; + + // Method reference - Compliant + Function f1 = condition ? this::foo : this::method; // Compliant + + // Multiple different operations - Compliant + String c10 = condition ? foo(a) : bar(b); // Compliant + Object c11 = condition ? new Foo(a) : new Bar(b); // Compliant + + // Array access vs method invocation - Compliant + String[] arr = {a, b}; + Object o1 = condition ? arr[0] : foo(b); // Compliant - different expression kinds + + // New class vs array access - Compliant + Object o2 = condition ? new Foo(a) : arr[0]; // Compliant - different expression kinds + } + + void testOverloadedMethods() { + // Overloaded methods with different parameter types - Compliant (different method symbols) + Object ov1 = condition ? overloaded(1) : overloaded("x"); // Compliant - different overloads + Object ov2 = condition ? this.overloaded(1) : this.overloaded("x"); // Compliant - different overloads + + // Same overload, different arguments - Noncompliant + Object ov3 = condition ? overloaded(1) : overloaded(2); // Noncompliant + Object ov4 = condition ? overloaded("a") : overloaded("b"); // Noncompliant + } + + void testOverloadedConstructors() { + // Overloaded constructors with different parameter types - Compliant (different constructor symbols) + Object oc1 = condition ? new OverloadedCtor(1) : new OverloadedCtor("x"); // Compliant - different constructors + } + + void testNewClassWithClassBody() { + String a = "a"; + String b = "b"; + + // Anonymous class body - Compliant (class bodies make each instantiation unique) + Object ac1 = condition ? new Foo(a) { } : new Foo(b) { }; // Compliant - anonymous class bodies + Object ac2 = condition ? new Foo(a) { } : new Foo(b); // Compliant - one has class body + } + + void testQualifiedInstantiations() { + String a = "a"; + String b = "b"; + + // Qualified instantiation with same enclosing expression - Noncompliant + Object qi1 = condition ? obj.new Inner(a) : obj.new Inner(b); // Noncompliant + + // Qualified instantiation with different enclosing expression - Compliant + Object qi2 = condition ? obj.new Inner(a) : obj2.new Inner(b); // Compliant - different enclosing expressions + + // Mixed: unqualified vs qualified - Compliant + Object qi3 = condition ? new Inner(a) : obj.new Inner(b); // Compliant - one has enclosing, other doesn't + } + + void testNonIdentifierReceiver() { + String a = "a"; + String b = "b"; + + // Non-identifier receiver (method call as receiver) - Noncompliant + Object nir1 = condition ? getObj().foo(a) : getObj().foo(b); // Noncompliant + } + + // Private methods used in ternary + private String foo(String s) { return s; } + private String foo(String s, String t) { return s + t; } + private String bar(String s) { return s; } + private String method(String s) { return s; } + private String noArg() { return ""; } + private Object overloaded(int i) { return i; } + private Object overloaded(String s) { return s; } + private TernaryOperatorSameOperationCheckSample getObj() { return this; } + + private static class StaticClass { + static String foo(String s) { return s; } + } + + private static class Foo { + Foo(String s) {} + Foo(String s, String t) {} + } + + private static class Bar { + Bar(String s) {} + } + + private static class OverloadedCtor { + OverloadedCtor(int i) {} + OverloadedCtor(String s) {} + } + + class Inner { + Inner(String s) {} + } + +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/TernaryOperatorSameOperationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/TernaryOperatorSameOperationCheck.java new file mode 100644 index 00000000000..0861e01ad11 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/TernaryOperatorSameOperationCheck.java @@ -0,0 +1,148 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.List; +import org.sonar.check.Rule; +import org.sonar.java.model.ExpressionUtils; +import org.sonar.java.model.SyntacticEquivalence; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.tree.ArrayAccessExpressionTree; +import org.sonar.plugins.java.api.tree.ConditionalExpressionTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +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; + +@Rule(key = "S9358") +public class TernaryOperatorSameOperationCheck extends IssuableSubscriptionVisitor { + + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.CONDITIONAL_EXPRESSION); + } + + @Override + public void visitNode(Tree tree) { + var conditional = (ConditionalExpressionTree) tree; + var trueExpr = ExpressionUtils.skipParentheses(conditional.trueExpression()); + var falseExpr = ExpressionUtils.skipParentheses(conditional.falseExpression()); + + if (hasSameOperationStructure(trueExpr, falseExpr)) { + reportIssue(conditional, "Move the conditional expression inside this operation."); + } + } + + private static boolean hasSameOperationStructure(ExpressionTree left, ExpressionTree right) { + if (left.is(Tree.Kind.METHOD_INVOCATION) && right.is(Tree.Kind.METHOD_INVOCATION)) { + return sameMethodInvocation((MethodInvocationTree) left, (MethodInvocationTree) right); + } + if (left.is(Tree.Kind.NEW_CLASS) && right.is(Tree.Kind.NEW_CLASS)) { + return sameNewClass((NewClassTree) left, (NewClassTree) right); + } + if (left.is(Tree.Kind.ARRAY_ACCESS_EXPRESSION) && right.is(Tree.Kind.ARRAY_ACCESS_EXPRESSION)) { + return sameArrayAccess((ArrayAccessExpressionTree) left, (ArrayAccessExpressionTree) right); + } + return false; + } + + private static boolean sameMethodInvocation(MethodInvocationTree left, MethodInvocationTree right) { + if (!sameMethodSelect(left.methodSelect(), right.methodSelect())) { + return false; + } + if (!hasExactlyOneArgumentDifference(left.arguments(), right.arguments())) { + return false; + } + return sameMethodSymbol(left.methodSymbol(), right.methodSymbol()); + } + + private static boolean sameMethodSymbol(Symbol.MethodSymbol left, Symbol.MethodSymbol right) { + if (left.isUnknown() || right.isUnknown()) { + return true; + } + return left.equals(right); + } + + private static boolean sameMethodSelect(ExpressionTree left, ExpressionTree right) { + if (!left.is(right.kind())) { + return false; + } + if (left.is(Tree.Kind.MEMBER_SELECT)) { + var leftMember = (MemberSelectExpressionTree) left; + var rightMember = (MemberSelectExpressionTree) right; + return sameMethodSelect(leftMember.expression(), rightMember.expression()) + && sameIdentifier(leftMember.identifier(), rightMember.identifier()); + } + if (left.is(Tree.Kind.IDENTIFIER)) { + return sameIdentifier((IdentifierTree) left, (IdentifierTree) right); + } + return sameTree(left, right); + } + + private static boolean sameIdentifier(IdentifierTree left, IdentifierTree right) { + return left.name().equals(right.name()); + } + + private static boolean sameNewClass(NewClassTree left, NewClassTree right) { + if (!sameTree(left.identifier(), right.identifier())) { + return false; + } + if (!hasExactlyOneArgumentDifference(left.arguments(), right.arguments())) { + return false; + } + if (!sameMethodSymbol(left.methodSymbol(), right.methodSymbol())) { + return false; + } + if (left.classBody() != null || right.classBody() != null) { + return false; + } + var leftEnclosing = left.enclosingExpression(); + var rightEnclosing = right.enclosingExpression(); + if (leftEnclosing == null && rightEnclosing == null) { + return true; + } + if (leftEnclosing == null || rightEnclosing == null) { + return false; + } + return sameTree(leftEnclosing, rightEnclosing); + } + + private static boolean hasExactlyOneArgumentDifference(List leftArgs, List rightArgs) { + if (leftArgs.size() != rightArgs.size()) { + return false; + } + int differences = 0; + for (int i = 0; i < leftArgs.size(); i++) { + if (!sameTree(leftArgs.get(i), rightArgs.get(i))) { + differences++; + } + } + return differences == 1; + } + + private static boolean sameArrayAccess(ArrayAccessExpressionTree left, ArrayAccessExpressionTree right) { + return sameTree(left.expression(), right.expression()) + && !sameTree(left.dimension().expression(), right.dimension().expression()); + } + + private static boolean sameTree(Tree left, Tree right) { + return SyntacticEquivalence.areEquivalent(left, right); + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/TernaryOperatorSameOperationCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/TernaryOperatorSameOperationCheckTest.java new file mode 100644 index 00000000000..2dac4ea8a55 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/TernaryOperatorSameOperationCheckTest.java @@ -0,0 +1,42 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +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 TernaryOperatorSameOperationCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/TernaryOperatorSameOperationCheckSample.java")) + .withCheck(new TernaryOperatorSameOperationCheck()) + .verifyIssues(); + } + + @Test + void test_without_semantic() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/TernaryOperatorSameOperationCheckNoSemanticSample.java")) + .withCheck(new TernaryOperatorSameOperationCheck()) + .withoutSemantic() + .verifyIssues(); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9358.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9358.html new file mode 100644 index 00000000000..6cfc3cd3764 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9358.html @@ -0,0 +1,51 @@ +

An issue is raised when a conditional expression (with a condition determining which of two alternative expressions to evaluate) performs the same +operation on both branches, differing only in the arguments.

+

In Java, this refers to the ternary operator with the syntax condition ? trueExpr : falseExpr.

+

Why is this an issue?

+

When both branches of a ternary operator apply the same function call or operation to different values, the code contains unnecessary duplication. +This pattern makes the code harder to read because the common operation is stated twice instead of once.

+

Consider this example:

+
+message = isError ? formatMessage(errorText) : formatMessage(warningText)
+
+

Here, the message formatting function appears in both branches. This duplication obscures the actual difference between the branches (the input +value) and makes the code longer than necessary.

+

By moving the condition inside the function call, you highlight what actually varies:

+
+message = formatMessage(isError ? errorText : warningText)
+
+

This refactored version:

+ +

This pattern applies to function calls, object creation, array access, and other operations that are identical in both branches.

+

What is the potential impact?

+

This issue has a minor impact on code maintainability:

+ +

How to fix it

+

Move the ternary operator inside the common method call or operation. The condition should determine which argument is passed, not which method is +called.

+

Code examples

+

Noncompliant code example

+
+String result = condition ? foo(a) : foo(b); // Noncompliant
+
+

Compliant solution

+
+String result = foo(condition ? a : b);
+
+

Resources

+

Documentation

+ + diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9358.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9358.json new file mode 100644 index 00000000000..2967f9aa388 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9358.json @@ -0,0 +1,25 @@ +{ + "title": "Conditional expressions should not duplicate operations in both branches", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5 min" + }, + "tags": [ + "clumsy", + "redundant", + "confusing" + ], + "defaultSeverity": "Minor", + "ruleSpecification": "RSPEC-9358", + "sqKey": "S9358", + "scope": "All", + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "LOW" + }, + "attribute": "CLEAR" + } +} diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9358 b/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9358 new file mode 100644 index 00000000000..e69de29bb2d