Skip to content

Commit 38db436

Browse files
SONARJAVA-5454 Implement S7467: replace Unused exception parameter with the unnamed pattern (#5087)
1 parent 2aecab8 commit 38db436

8 files changed

Lines changed: 334 additions & 1 deletion

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"ruleKey": "S7467",
3+
"hasTruePositives": true,
4+
"falseNegatives": 0,
5+
"falsePositives": 0
6+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package checks;
2+
3+
import io.restassured.exception.PathException;
4+
import java.util.List;
5+
import java.util.function.Supplier;
6+
7+
public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample {
8+
public void simpleNonCompliant() {
9+
10+
List<String> elements = List.of();
11+
int value = 0;
12+
try {
13+
var elem = elements.get(10);
14+
value = Integer.parseInt(elem);
15+
} catch (NumberFormatException nfe) { // Noncompliant {{Replace "nfe" with an unnamed pattern.}}
16+
// ^^^
17+
// fix@qf1 {{Replace "nfe" with "_"}}
18+
// edit@qf1 [[sc=36;ec=39]] {{_}}
19+
System.err.println("Wrong number format");
20+
} catch (IndexOutOfBoundsException ioob) { // Noncompliant {{Replace "ioob" with an unnamed pattern.}}
21+
// ^^^^
22+
// fix@qf2 {{Replace "ioob" with "_"}}
23+
// edit@qf2 [[sc=40;ec=44]] {{_}}
24+
System.err.println("No such element");
25+
}
26+
}
27+
28+
public void simpleCompliant() {
29+
List<String> elements = List.of();
30+
int value = 0;
31+
try {
32+
var elem = elements.get(10);
33+
value = Integer.parseInt(elem);
34+
} catch (NumberFormatException _) { // compliant
35+
System.err.println("Wrong number format");
36+
} catch (IndexOutOfBoundsException _) { // compliant
37+
System.err.println("No such element");
38+
}
39+
}
40+
41+
public void severalExceptionNonCompliant() {
42+
try {
43+
44+
} catch (NumberFormatException | IndexOutOfBoundsException e) { // Noncompliant
45+
46+
} catch (PathException | ClassCastException e) { // Noncompliant
47+
48+
}
49+
}
50+
51+
public void severalExceptionCompliant() {
52+
try {
53+
54+
} catch (NumberFormatException | IndexOutOfBoundsException _) { // compliant
55+
56+
} catch ( PathException | ClassCastException _) { // compliant
57+
58+
}
59+
}
60+
61+
public void nestedExceptionNonCompliant() {
62+
try {
63+
} catch (IndexOutOfBoundsException e) { // Noncompliant
64+
try {
65+
66+
} catch (NumberFormatException k) { // Noncompliant
67+
try {
68+
69+
} catch (PathException _) {
70+
71+
}
72+
}
73+
}
74+
}
75+
76+
public void codeInsideCatchBlockNonCompliant() {
77+
try {
78+
} catch (Exception e) { // Noncompliant
79+
int x = 0;
80+
foo(new RuntimeException());
81+
e();
82+
}
83+
}
84+
85+
public void useExceptionCompliant() {
86+
try {
87+
} catch (Exception e) {
88+
var x = e;
89+
}
90+
91+
try {
92+
} catch (Exception e) {
93+
foo(e);
94+
}
95+
96+
try {
97+
} catch (Exception e) {
98+
throw e;
99+
}
100+
101+
try {
102+
} catch (Exception e) {
103+
throw new RuntimeException(e);
104+
}
105+
106+
try {
107+
} catch (Exception e) {
108+
var s = e.getStackTrace();
109+
}
110+
111+
try {
112+
} catch (Exception e) {
113+
Supplier<Integer> s =() -> {
114+
var x = e;
115+
return 0;
116+
};
117+
}
118+
}
119+
120+
public void foo(Exception e) {
121+
}
122+
123+
public void e(){}
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package checks;
2+
3+
import java.util.List;
4+
5+
public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSampleJava21 {
6+
public void simpleUnUsedParameter() {
7+
8+
List<String> elements = List.of();
9+
int value = 0;
10+
try {
11+
var elem = elements.get(10);
12+
value = Integer.parseInt(elem);
13+
} catch (NumberFormatException nfe) { // compliant java 21
14+
System.err.println("Wrong number format");
15+
} catch (IndexOutOfBoundsException ioob) { // compliant java 21
16+
System.err.println("No such element");
17+
}
18+
}
19+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import java.util.List;
20+
import org.sonar.check.Rule;
21+
import org.sonar.java.checks.helpers.QuickFixHelper;
22+
import org.sonar.java.reporting.JavaQuickFix;
23+
import org.sonar.java.reporting.JavaTextEdit;
24+
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
25+
import org.sonar.plugins.java.api.JavaVersion;
26+
import org.sonar.plugins.java.api.JavaVersionAwareVisitor;
27+
import org.sonar.plugins.java.api.tree.CatchTree;
28+
import org.sonar.plugins.java.api.tree.IdentifierTree;
29+
import org.sonar.plugins.java.api.tree.Tree;
30+
import org.sonar.plugins.java.api.tree.VariableTree;
31+
32+
@Rule(key = "S7467")
33+
public class ReplaceUnusedExceptionParameterWithUnnamedPatternCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor {
34+
35+
@Override
36+
public List<Tree.Kind> nodesToVisit() {
37+
return List.of(Tree.Kind.CATCH);
38+
}
39+
40+
@Override
41+
public void visitNode(Tree tree) {
42+
CatchTree catchTree = (CatchTree) tree;
43+
VariableTree v = catchTree.parameter();
44+
IdentifierTree ident = v.simpleName();
45+
46+
if (!ident.isUnnamedVariable() && v.symbol().usages().isEmpty()) {
47+
QuickFixHelper.newIssue(context)
48+
.forRule(this)
49+
.onTree(ident)
50+
.withMessage(String.format("Replace \"%s\" with an unnamed pattern.", ident.name()))
51+
.withQuickFix(() -> getQuickFix(ident))
52+
.report();
53+
}
54+
}
55+
56+
@Override
57+
public boolean isCompatibleWithJavaVersion(JavaVersion version) {
58+
return version.isJava22Compatible();
59+
}
60+
61+
private static JavaQuickFix getQuickFix(IdentifierTree ident) {
62+
return JavaQuickFix.newQuickFix(String.format("Replace \"%s\" with unnamed pattern \"_\"", ident.name()))
63+
.addTextEdit(JavaTextEdit.replaceTree(ident, "_"))
64+
.build();
65+
}
66+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.sonar.java.checks.verifier.CheckVerifier;
21+
22+
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
23+
24+
class ReplaceUnusedExceptionParameterWithUnnamedPatternCheckTest {
25+
26+
@Test
27+
void test_java22() {
28+
CheckVerifier.newVerifier()
29+
.onFile(mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
30+
.withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
31+
.withJavaVersion(22)
32+
.verifyIssues();
33+
}
34+
35+
@Test
36+
void test_java21() {
37+
CheckVerifier.newVerifier()
38+
.onFile(mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSampleJava21.java"))
39+
.withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
40+
.withJavaVersion(21)
41+
.verifyNoIssues();
42+
}
43+
44+
@Test
45+
void test_without_semantics() {
46+
CheckVerifier.newVerifier()
47+
.onFile(mainCodeSourcesPath("checks/ReplaceUnusedExceptionParameterWithUnnamedPatternCheckSample.java"))
48+
.withCheck(new ReplaceUnusedExceptionParameterWithUnnamedPatternCheck())
49+
.withoutSemantic()
50+
.withJavaVersion(22)
51+
.verifyIssues();
52+
}
53+
54+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<h2>Why is this an issue?</h2>
2+
<p>Good exception management is key to keeping a consistent application state in the face of errors and unexpected behaviors. However, in some cases,
3+
the information carried by the exception is not as important as the exception bubbling up itself. In such cases, developers may want to explicitly
4+
indicate that they have no use for the exception parameter. Java 22 introduces the unnamed variable pattern <code>_</code> which allows developers to
5+
free the catch clause from an unnecessary exception parameter name.</p>
6+
<h2>How to fix it</h2>
7+
<p>Replace exception parameter name with unnamed variable pattern <code>_</code>.</p>
8+
<h3>Code examples</h3>
9+
<h4>Noncompliant code example</h4>
10+
<pre data-diff-id="1" data-diff-type="noncompliant">
11+
List&lt;String&gt; elements = // ...
12+
int value = 0;
13+
try {
14+
var elem = elements.get(idx);
15+
value = Integer.parseInt(elem);
16+
} catch (NumberFormatException nfe) { // Noncompliant
17+
System.err.println("Wrong number format");
18+
} catch (IndexOutOfBoundsException ioob) { // Noncompliant
19+
System.err.println("No such element");
20+
}
21+
</pre>
22+
<h4>Compliant solution</h4>
23+
<pre data-diff-id="1" data-diff-type="compliant">
24+
List&lt;String&gt; elements = // ...
25+
int value = 0;
26+
try {
27+
var elem = elements.get(idx);
28+
value = Integer.parseInt(elem);
29+
} catch (NumberFormatException _) {
30+
System.err.println("Wrong number format");
31+
} catch (IndexOutOfBoundsException _) {
32+
System.err.println("No such element");
33+
}
34+
</pre>
35+
<h2>Resources</h2>
36+
<h3>Documentation</h3>
37+
<ul>
38+
<li> OpenJDK - <a href="https://openjdk.org/jeps/456">JEP 456: Unnamed Variables &amp; Patterns</a> </li>
39+
</ul>
40+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"title": "Unused exception parameter should use the unnamed variable pattern",
3+
"type": "CODE_SMELL",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "2min"
8+
},
9+
"tags": [
10+
"java22"
11+
],
12+
"defaultSeverity": "Minor",
13+
"ruleSpecification": "RSPEC-7467",
14+
"sqKey": "S7467",
15+
"scope": "All",
16+
"quickfix": "targeted",
17+
"code": {
18+
"impacts": {
19+
"MAINTAINABILITY": "LOW"
20+
},
21+
"attribute": "CLEAR"
22+
}
23+
}

sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,7 @@
504504
"S7190",
505505
"S7409",
506506
"S7435",
507-
"S7466"
507+
"S7466",
508+
"S7467"
508509
]
509510
}

0 commit comments

Comments
 (0)