-
Notifications
You must be signed in to change notification settings - Fork 724
SONARJAVA-6824: Implemented rule S9358 - Conditional expressions should not duplicate operations in both branches #6001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
romainbrenguier
merged 15 commits into
master
from
romain/new-rule-s9358-sonarjava-6824
Aug 25, 2026
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
1972368
SONARJAVA-6824: Implemented rule S9358 - Conditional expressions shou…
romainbrenguier cef165e
SONARJAVA-6824: Add metadata for rule S9358
romainbrenguier 950bdb6
SONARJAVA-6824: Fix compilation errors and bugs in rule S9358
romainbrenguier d427045
Update ruling results
github-actions[bot] a3a988c
SONARJAVA-6824: Fix compilation error and merge ruling expectations
romainbrenguier bc72478
SONARJAVA-6824: Fix QG issues in rule S9358
romainbrenguier f8a3c0d
Update ruling results
github-actions[bot] de8e95b
SONARJAVA-6824: Fix hasExactlyOneArgumentDifference to count differen…
romainbrenguier 6541da8
Merge branch 'fix/update-ruling-for-romain/new-rule-s9358-sonarjava-6…
romainbrenguier db95caf
Update ruling results
github-actions[bot] 4c652ad
SONARJAVA-6824: Add S9358 ruling expectations for eclipse-jetty-simil…
romainbrenguier 0e2ef4b
SONARJAVA-6824: Compare resolved method symbols to avoid false positi…
romainbrenguier 4ebbb37
SONARJAVA-6824: Use SyntacticEquivalence for tree comparison and add …
romainbrenguier 74f544a
Update ruling results
github-actions[bot] ab58698
SONARJAVA-6824: Inline sameNullableTree to fix SonarQube always-false…
romainbrenguier File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
9 changes: 9 additions & 0 deletions
9
its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S9358.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ] | ||
| } |
15 changes: 15 additions & 0 deletions
15
its/ruling/src/test/resources/eclipse-jetty/java-S9358.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ] | ||
| } |
14 changes: 14 additions & 0 deletions
14
its/ruling/src/test/resources/sonar-server/java-S9358.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ] | ||
| } |
87 changes: 87 additions & 0 deletions
87
...urces/default/src/main/java/checks/TernaryOperatorSameOperationCheckNoSemanticSample.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) {} | ||
| } | ||
|
|
||
| } | ||
212 changes: 212 additions & 0 deletions
212
...ks-test-sources/default/src/main/java/checks/TernaryOperatorSameOperationCheckSample.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<String, String> 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) {} | ||
| } | ||
|
|
||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.