diff --git a/its/ruling/src/test/resources/commons-beanutils/java-S9360.json b/its/ruling/src/test/resources/commons-beanutils/java-S9360.json new file mode 100644 index 00000000000..55309c93efd --- /dev/null +++ b/its/ruling/src/test/resources/commons-beanutils/java-S9360.json @@ -0,0 +1,8 @@ +{ +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java": [ +106, +154, +218, +267 +] +} diff --git a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9360.json b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9360.json new file mode 100644 index 00000000000..c2cf3131395 --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9360.json @@ -0,0 +1,59 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java": [ +343, +358, +373, +388, +403 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java": [ +1028 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java": [ +431, +436, +445, +453, +455, +459, +465, +471, +477, +483, +489, +495, +497, +501, +503, +514, +514, +515, +575, +615, +617, +622, +624, +628, +634, +640, +646, +652, +658, +664, +666, +670, +677, +684 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java": [ +93, +414 +], +"org.eclipse.jetty:jetty-project:jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java": [ +94 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java": [ +744, +835 +] +} diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S9360.json b/its/ruling/src/test/resources/eclipse-jetty/java-S9360.json new file mode 100644 index 00000000000..e4627a8f3bd --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S9360.json @@ -0,0 +1,75 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/HttpStatus.java": [ +343, +358, +373, +388, +403 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java": [ +1028 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java": [ +431, +436, +445, +453, +455, +459, +465, +471, +477, +483, +489, +495, +497, +501, +503, +514, +514, +515, +575, +615, +617, +622, +624, +628, +634, +640, +646, +652, +658, +664, +666, +670, +677, +684 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java": [ +93, +414 +], +"org.eclipse.jetty:jetty-project:jetty-io/src/test/java/org/eclipse/jetty/io/ArrayByteBufferPoolTest.java": [ +94 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java": [ +744, +835 +], +"org.eclipse.jetty:jetty-project:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java": [ +1275 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java": [ +901, +907, +925, +942, +957, +962, +978 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java": [ +117, +132 +] +} diff --git a/its/ruling/src/test/resources/guava/java-S9360.json b/its/ruling/src/test/resources/guava/java-S9360.json new file mode 100644 index 00000000000..5472011ddcc --- /dev/null +++ b/its/ruling/src/test/resources/guava/java-S9360.json @@ -0,0 +1,17 @@ +{ +"com.google.guava:guava:src/com/google/common/base/SmallCharMatcher.java": [ +61 +], +"com.google.guava:guava:src/com/google/common/io/LittleEndianDataInputStream.java": [ +82, +225 +], +"com.google.guava:guava:src/com/google/common/math/LongMath.java": [ +234 +], +"com.google.guava:guava:src/com/google/common/net/MediaType.java": [ +628, +631, +632 +] +} diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/YodaConditionCheckSample.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/YodaConditionCheckSample.java new file mode 100644 index 00000000000..6fc2172102d --- /dev/null +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/YodaConditionCheckSample.java @@ -0,0 +1,10 @@ +package checks; + +class YodaConditionCheckSample { + + void unknownLiteralType() { + Object x = new Object(); + if (UNKNOWN_LITERAL == x) { } // Compliant - UNKNOWN_LITERAL is not a valid literal + } + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/YodaConditionCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/YodaConditionCheckSample.java new file mode 100644 index 00000000000..f996d35538b --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/YodaConditionCheckSample.java @@ -0,0 +1,193 @@ +package checks; + +class YodaConditionCheckSample { + + void testIntLiteral() { + int count = 0; + int x = 5; + if (0 == count) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^ + if (5 != x) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^ + if (count == 0) { } // Compliant + if (x != 5) { } // Compliant + } + + void testNullLiteral() { + Object obj = null; + Object myObject = null; + if (null == obj) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^^^^ + if (null != myObject) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^^^^ + if (obj == null) { } // Compliant + if (myObject != null) { } // Compliant + if (null == null) { } // Compliant + } + + void testBooleanLiteral() { + boolean flag = true; + boolean result = false; + if (true == flag) { } // Compliant - boolean literal comparisons handled by S1125 + if (false != result) { } // Compliant - boolean literal comparisons handled by S1125 + if (flag == true) { } // Compliant + if (result != false) { } // Compliant + } + + void testStringLiteral() { + String str = "hello"; + String value = ""; + if ("hello" == str) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^^^^^^^ + if ("" != value) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^^ + if (str == "hello") { } // Compliant + if (value != "") { } // Compliant + } + + void testCharLiteral() { + char ch = 'a'; + if ('a' == ch) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^^^ + if (ch == 'a') { } // Compliant + } + + void testFloatingPointLiteral() { + double doubleValue = 0.0; + if (0.0 == doubleValue) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^^^ + if (doubleValue == 0.0) { } // Compliant + } + + void testNestedParentheses() { + int count = 0; + Object obj = new Object(); + if ((0) == count) { } // Noncompliant {{Put the variable on the left side of this comparison.}} + if (((null)) == obj) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^^^^ + } + + void testLessThanGreaterThan() { + int count = 0; + int x = 5; + if (0 < count) { } // Noncompliant {{Put the variable on the left side of this comparison and invert the operator.}} +// ^ + if (5 > x) { } // Noncompliant {{Put the variable on the left side of this comparison and invert the operator.}} +// ^ + if (count > 0) { } // Compliant + if (x < 5) { } // Compliant + } + + void testLessThanOrEqualGreaterThanOrEqual() { + int count = 0; + int x = 5; + if (0 <= count) { } // Noncompliant {{Put the variable on the left side of this comparison and invert the operator.}} +// ^ + if (5 >= x) { } // Noncompliant {{Put the variable on the left side of this comparison and invert the operator.}} +// ^ + if (count >= 0) { } // Compliant + if (x <= 5) { } // Compliant + } + + void testNonComparisonContexts() { + int count = 0; + int a = 1; + int b = 2; + count = 0; // Compliant - assignment + int sum = a + 5; // Compliant - arithmetic + int product = 5 * b; // Compliant - arithmetic + } + + void testTernaryOperator() { + boolean condition = true; + int result = condition ? 5 : 10; // Compliant + if (condition) { } // Compliant + } + + void testArrayAccess() { + int[] array = {1, 2, 3}; + if (0 == array[0]) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^ + if (array[0] == 0) { } // Compliant + } + + void testUnaryMinusPlusYoda() { + int index = 0; + if (-1 == index) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^^ + if (+1 == index) { } // Noncompliant {{Put the variable on the left side of this comparison.}} +// ^^ + if (index == -1) { } // Compliant + if (index == +1) { } // Compliant + } + + void testBothLiterals() { + if (0 == 0) { } // Compliant - both sides are literals + if (5 != 10) { } // Compliant - both sides are literals + if (true == false) { } // Compliant - both sides are literals + if (0 == -1) { } // Compliant - both sides are literals + if (-1 == 0) { } // Compliant - both sides are literals + if (-1 == -2) { } // Compliant - both sides are literals + } + + void testBothVariables() { + int count = 0; + int otherCount = 0; + Object obj1 = null; + Object obj2 = null; + if (count == otherCount) { } // Compliant - both are variables + if (obj1 == obj2) { } // Compliant - both are variables + } + + void testConstantMathCalls() { + int max = Math.max(5, 10); // Noncompliant {{Replace this call to "max" with the precomputed constant value.}} +// ^^^^^^^^ + int min = Math.min(3, 7); // Noncompliant {{Replace this call to "min" with the precomputed constant value.}} +// ^^^^^^^^ + double sqrt = Math.sqrt(16.0); // Noncompliant {{Replace this call to "sqrt" with the precomputed constant value.}} +// ^^^^^^^^^ + int abs = Math.abs(-5); // Noncompliant {{Replace this call to "abs" with the precomputed constant value.}} +// ^^^^^^^^ + long rounded = Math.round(3.14); // Noncompliant {{Replace this call to "round" with the precomputed constant value.}} +// ^^^^^^^^^^ + double floor = Math.floor(3.7); // Noncompliant {{Replace this call to "floor" with the precomputed constant value.}} +// ^^^^^^^^^^ + double ceil = Math.ceil(3.2); // Noncompliant {{Replace this call to "ceil" with the precomputed constant value.}} +// ^^^^^^^^^ + } + + void testConstantMathCallsCompliant() { + int x = 5; + int y = 10; + int max = Math.max(x, 10); // Compliant - x is not a literal + int min = Math.min(3, y); // Compliant - y is not a literal + double sqrt = Math.sqrt(x); // Compliant - x is not a literal + int abs = Math.abs(x); // Compliant - x is not a literal + int maxVar = Math.max(x, y); // Compliant - neither is a literal + } + + void testConstantMathCallsWithUnaryMinus() { + int abs = Math.abs(-10); // Noncompliant {{Replace this call to "abs" with the precomputed constant value.}} +// ^^^^^^^^ + int max = Math.max(-5, -3); // Noncompliant {{Replace this call to "max" with the precomputed constant value.}} +// ^^^^^^^^ + double sqrt = Math.sqrt(+4.0); // Noncompliant {{Replace this call to "sqrt" with the precomputed constant value.}} +// ^^^^^^^^^ + } + + void testTranscendentalMathCallsCompliant() { + double pow = Math.pow(2.0, 3.0); // Compliant - transcendental functions excluded (result may vary across JVMs) + double sin = Math.sin(0.5); // Compliant + double cos = Math.cos(0.5); // Compliant + double log = Math.log(2.0); // Compliant + double exp = Math.exp(1.0); // Compliant + double cbrt = Math.cbrt(8.0); // Compliant - not exactly specified + double deg = Math.toDegrees(1.0); // Compliant - not exactly specified + double rad = Math.toRadians(90.0); // Compliant - not exactly specified + } + + void testNonMathMethodCalls() { + String result = String.valueOf(5); // Compliant - not a Math method + int hash = Integer.hashCode(42); // Compliant - not a Math method + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/YodaConditionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/YodaConditionCheck.java new file mode 100644 index 00000000000..21aafb7e2b1 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/YodaConditionCheck.java @@ -0,0 +1,158 @@ +/* + * 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.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.tree.BinaryExpressionTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.UnaryExpressionTree; + +@Rule(key = "S9360") +public class YodaConditionCheck extends IssuableSubscriptionVisitor { + + private static final String JAVA_LANG_MATH = "java.lang.Math"; + private static final String FLOAT = "float"; + private static final String DOUBLE = "double"; + + private static final MethodMatchers CONSTANT_MATH_METHODS = MethodMatchers.or( + MethodMatchers.create().ofTypes(JAVA_LANG_MATH) + .names("max", "min") + .addParametersMatcher("int", "int") + .addParametersMatcher("long", "long") + .addParametersMatcher(FLOAT, FLOAT) + .addParametersMatcher(DOUBLE, DOUBLE) + .build(), + MethodMatchers.create().ofTypes(JAVA_LANG_MATH) + .names("abs", "absExact", "negateExact", "incrementExact", "decrementExact") + .addParametersMatcher("int") + .addParametersMatcher("long") + .build(), + MethodMatchers.create().ofTypes(JAVA_LANG_MATH) + .names("abs") + .addParametersMatcher(FLOAT) + .addParametersMatcher(DOUBLE) + .build(), + MethodMatchers.create().ofTypes(JAVA_LANG_MATH) + .names("sqrt", "ceil", "floor", "rint", "signum") + .addParametersMatcher(DOUBLE) + .build(), + MethodMatchers.create().ofTypes(JAVA_LANG_MATH) + .names("round") + .addParametersMatcher(FLOAT) + .addParametersMatcher(DOUBLE) + .build(), + MethodMatchers.create().ofTypes(JAVA_LANG_MATH) + .names("copySign") + .addParametersMatcher(DOUBLE, DOUBLE) + .build(), + MethodMatchers.create().ofTypes(JAVA_LANG_MATH) + .names("addExact", "subtractExact", "multiplyExact", "floorDiv", "floorMod") + .addParametersMatcher("int", "int") + .addParametersMatcher("long", "long") + .build(), + MethodMatchers.create().ofTypes(JAVA_LANG_MATH) + .names("toIntExact") + .addParametersMatcher("long") + .build(), + MethodMatchers.create().ofTypes(JAVA_LANG_MATH) + .names("signum") + .addParametersMatcher(FLOAT) + .build() + ); + + @Override + public List nodesToVisit() { + return List.of( + Tree.Kind.EQUAL_TO, + Tree.Kind.NOT_EQUAL_TO, + Tree.Kind.LESS_THAN, + Tree.Kind.GREATER_THAN, + Tree.Kind.LESS_THAN_OR_EQUAL_TO, + Tree.Kind.GREATER_THAN_OR_EQUAL_TO, + Tree.Kind.METHOD_INVOCATION + ); + } + + @Override + public void visitNode(Tree tree) { + if (tree.is(Tree.Kind.METHOD_INVOCATION)) { + checkConstantMathCall((MethodInvocationTree) tree); + } else { + checkYodaCondition((BinaryExpressionTree) tree); + } + } + + private void checkYodaCondition(BinaryExpressionTree binaryExpression) { + ExpressionTree left = ExpressionUtils.skipParentheses(binaryExpression.leftOperand()); + ExpressionTree right = ExpressionUtils.skipParentheses(binaryExpression.rightOperand()); + + if (isLiteral(left) && !isLiteral(right)) { + if (binaryExpression.is(Tree.Kind.LESS_THAN, Tree.Kind.GREATER_THAN, + Tree.Kind.LESS_THAN_OR_EQUAL_TO, Tree.Kind.GREATER_THAN_OR_EQUAL_TO)) { + reportIssue(left, "Put the variable on the left side of this comparison and invert the operator."); + } else { + reportIssue(left, "Put the variable on the left side of this comparison."); + } + } + } + + private void checkConstantMathCall(MethodInvocationTree methodInvocation) { + if (CONSTANT_MATH_METHODS.matches(methodInvocation) && allArgumentsAreLiterals(methodInvocation)) { + reportIssue(methodInvocation.methodSelect(), + String.format("Replace this call to \"%s\" with the precomputed constant value.", methodInvocation.methodSymbol().name())); + } + } + + private static boolean allArgumentsAreLiterals(MethodInvocationTree methodInvocation) { + return methodInvocation.arguments().stream().allMatch(YodaConditionCheck::isNumericLiteral); + } + + private static boolean isNumericLiteral(ExpressionTree tree) { + ExpressionTree expr = ExpressionUtils.skipParentheses(tree); + if (expr.is(Tree.Kind.UNARY_MINUS, Tree.Kind.UNARY_PLUS)) { + expr = ExpressionUtils.skipParentheses(((UnaryExpressionTree) expr).expression()); + } + return expr.is( + Tree.Kind.INT_LITERAL, + Tree.Kind.LONG_LITERAL, + Tree.Kind.FLOAT_LITERAL, + Tree.Kind.DOUBLE_LITERAL + ); + } + + private static boolean isLiteral(ExpressionTree tree) { + ExpressionTree expr = tree; + if (expr.is(Tree.Kind.UNARY_MINUS, Tree.Kind.UNARY_PLUS)) { + expr = ExpressionUtils.skipParentheses(((UnaryExpressionTree) expr).expression()); + } + return expr.is( + Tree.Kind.INT_LITERAL, + Tree.Kind.LONG_LITERAL, + Tree.Kind.FLOAT_LITERAL, + Tree.Kind.DOUBLE_LITERAL, + Tree.Kind.CHAR_LITERAL, + Tree.Kind.STRING_LITERAL, + Tree.Kind.NULL_LITERAL + ); + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/YodaConditionCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/YodaConditionCheckTest.java new file mode 100644 index 00000000000..f010f56d4c9 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/YodaConditionCheckTest.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; +import static org.sonar.java.checks.verifier.TestUtils.nonCompilingTestSourcesPath; + +class YodaConditionCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/YodaConditionCheckSample.java")) + .withCheck(new YodaConditionCheck()) + .verifyIssues(); + } + + @Test + void test_non_compiling() { + CheckVerifier.newVerifier() + .onFile(nonCompilingTestSourcesPath("checks/YodaConditionCheckSample.java")) + .withCheck(new YodaConditionCheck()) + .verifyNoIssues(); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.html new file mode 100644 index 00000000000..f4245e049a2 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.html @@ -0,0 +1,77 @@ +

This rule raises an issue when code contains operations on constants that can be precomputed, or when comparisons place constants on the left side +(Yoda conditions).

+

Why is this an issue?

+

Code that works with constants can often be simplified to improve readability and, in some cases, performance.

+

Constant Function Calls

+

When all arguments to a deterministic function are compile-time constants, the result is also a constant. For example, a function that returns the +maximum of two numbers when called with literal values 5 and 10 always returns 10. Computing this value at +runtime is unnecessary and makes the code less clear.

+

Mathematical functions that compute maximum values, minimum values, square roots, and absolute values produce predictable results when given +constant inputs. Replacing these calls with their precomputed values eliminates function call overhead and makes the intended value immediately +visible to readers.

+

Yoda Conditions

+

Yoda conditions place the constant on the left side of a comparison: 0 == count instead of count == 0. This pattern +originated in C programming to prevent accidental assignment (single equals operator) instead of comparison (double equals operator).

+

In Java, this defensive technique is largely unnecessary. The compiler rejects assignment in conditional contexts for non-boolean types (for example, +if (count = 0) does not compile when count is an int). While if (flag = true) does compile for +boolean variables, that case is better addressed by removing the redundant boolean comparison entirely.

+

Placing constants on the left reduces readability. Natural language flows from subject to comparison: "Is the count zero?" translates more +naturally to count == 0 than to 0 == count.

+

By following conventional comparison order, code becomes more intuitive for developers to read and maintain.

+

What is the potential impact?

+

The impact on code quality is primarily related to maintainability:

+ +

How to fix it

+

For constant method calls, replace the method invocation with the precomputed result. For Yoda conditions, reverse the comparison to place the +variable on the left and the constant on the right.

+

Code examples

+

Noncompliant code example

+
+public class Example {
+    public void calculate() {
+        int max = Math.max(5, 10); // Noncompliant
+        double result = Math.sqrt(16.0) + Math.abs(-5); // Noncompliant
+
+        int count = 0;
+        if (0 == count) { // Noncompliant
+            return;
+        }
+
+        Object obj = null;
+        if (null == obj) { // Noncompliant
+            System.out.println("Condition met");
+        }
+    }
+}
+
+

Compliant solution

+
+public class Example {
+    public void calculate() {
+        int max = 10; // Precomputed constant
+        double result = 4.0 + 5; // Precomputed constants
+
+        int count = 0;
+        if (count == 0) { // Natural comparison order
+            return;
+        }
+
+        Object obj = null;
+        if (obj == null) { // Natural comparison order
+            System.out.println("Condition met");
+        }
+    }
+}
+
+

Resources

+

Documentation

+ + diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.json new file mode 100644 index 00000000000..a5b09922431 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9360.json @@ -0,0 +1,25 @@ +{ + "title": "Constant expressions and comparisons should be simplified", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5 min" + }, + "tags": [ + "confusing", + "convention", + "clarity" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-9360", + "sqKey": "S9360", + "scope": "All", + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "MEDIUM" + }, + "attribute": "CLEAR" + } +} diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9360 b/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9360 new file mode 100644 index 00000000000..e69de29bb2d