Skip to content

Commit ee37634

Browse files
SONARJAVA-4683 Implement S6837: Superfluous @responsebody annotations… (#4512)
* SONARJAVA-4683 Implement S6837: Superfluous @responsebody annotations should be removed
1 parent 32f404b commit ee37634

9 files changed

Lines changed: 250 additions & 4 deletions

File tree

its/ruling/src/test/java/org/sonar/java/it/AutoScanTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,15 @@ public void javaCheckTestSources() throws Exception {
180180
softly.assertThat(newTotal).isEqualTo(knownTotal);
181181
softly.assertThat(rulesCausingFPs).hasSize(6);
182182
softly.assertThat(rulesNotReporting).hasSize(7);
183-
softly.assertThat(rulesSilenced).hasSize(75);
183+
softly.assertThat(rulesSilenced).hasSize(76);
184184

185185
/**
186186
* 4. Check total number of differences (FPs + FNs)
187187
*
188188
* No differences would mean that we find the same issues with and without the bytecode and libraries
189189
*/
190190
String differences = Files.readString(pathFor(TARGET_ACTUAL + PROJECT_KEY + "-no-binaries_differences"));
191-
softly.assertThat(differences).isEqualTo("Issues differences: 3478");
191+
softly.assertThat(differences).isEqualTo("Issues differences: 3481");
192192

193193
softly.assertAll();
194194
}

its/ruling/src/test/resources/autoscan/autoscan-diff-by-rules.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,7 @@
17181718
{
17191719
"ruleKey": "S3751",
17201720
"hasTruePositives": false,
1721-
"falseNegatives": 12,
1721+
"falseNegatives": 13,
17221722
"falsePositives": 0
17231723
},
17241724
{
@@ -2920,5 +2920,11 @@
29202920
"hasTruePositives": false,
29212921
"falseNegatives": 5,
29222922
"falsePositives": 0
2923+
},
2924+
{
2925+
"ruleKey": "S6837",
2926+
"hasTruePositives": false,
2927+
"falseNegatives": 2,
2928+
"falsePositives": 0
29232929
}
29242930
]
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package checks.spring;
2+
3+
import org.springframework.stereotype.Controller;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.ResponseBody;
6+
import org.springframework.web.bind.annotation.RestController;
7+
8+
@RestController
9+
public class SuperfluousResponseBodyAnnotationCheckSample {
10+
@ResponseBody // Noncompliant [[sc=3;ec=16]] {{Remove this superfluous "@ResponseBody" annotation.}}
11+
@GetMapping("foo")
12+
public String get() {
13+
return "Hello world!";
14+
}
15+
16+
@ResponseBody // Noncompliant
17+
@GetMapping("bar")
18+
private String get2() {
19+
return "Hello world!";
20+
}
21+
22+
@GetMapping("baz")
23+
public String get3() {
24+
return "Hello world!";
25+
}
26+
27+
class InnerClass {
28+
@ResponseBody // Compliant
29+
@GetMapping("foo")
30+
public String get() {
31+
return "Hello world!";
32+
}
33+
}
34+
}
35+
36+
@Controller
37+
class RegularController {
38+
@ResponseBody // Compliant
39+
@GetMapping("foo")
40+
public String get() {
41+
return "Hello world!";
42+
}
43+
44+
@GetMapping("baz")
45+
public String get3() {
46+
return "Hello world!";
47+
}
48+
}
49+
50+
class NotAController {
51+
@ResponseBody // Compliant
52+
@GetMapping("foo")
53+
public String get() {
54+
return "Hello world!";
55+
}
56+
57+
@GetMapping("baz")
58+
public String get3() {
59+
return "Hello world!";
60+
}
61+
}

java-checks/src/main/java/org/sonar/java/checks/CheckList.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
import org.sonar.java.checks.spring.SpringScanDefaultPackageCheck;
159159
import org.sonar.java.checks.spring.SpringSecurityDisableCSRFCheck;
160160
import org.sonar.java.checks.spring.SpringSessionFixationCheck;
161+
import org.sonar.java.checks.spring.SuperfluousResponseBodyAnnotationCheck;
161162
import org.sonar.java.checks.spring.TransactionalMethodVisibilityCheck;
162163
import org.sonar.java.checks.spring.ValueAnnotationShouldInjectPropertyOrSpELCheck;
163164
import org.sonar.java.checks.synchronization.DoubleCheckedLockingCheck;
@@ -644,6 +645,7 @@ public final class CheckList {
644645
StrongCipherAlgorithmCheck.class,
645646
SubClassStaticReferenceCheck.class,
646647
SuperfluousCurlyBraceCheck.class,
648+
SuperfluousResponseBodyAnnotationCheck.class,
647649
SuppressWarningsCheck.class,
648650
SuspiciousListRemoveCheck.class,
649651
SwitchCaseTooBigCheck.class,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2023 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 GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.java.checks.spring;
21+
22+
import java.util.List;
23+
import org.sonar.check.Rule;
24+
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
25+
import org.sonar.plugins.java.api.tree.ClassTree;
26+
import org.sonar.plugins.java.api.tree.MethodTree;
27+
import org.sonar.plugins.java.api.tree.Tree;
28+
29+
@Rule(key = "S6837")
30+
public class SuperfluousResponseBodyAnnotationCheck extends IssuableSubscriptionVisitor {
31+
@Override
32+
public List<Tree.Kind> nodesToVisit() {
33+
return List.of(Tree.Kind.CLASS);
34+
}
35+
36+
@Override
37+
public void visitNode(Tree tree) {
38+
var ct = (ClassTree) tree;
39+
if (!ct.symbol().metadata().isAnnotatedWith("org.springframework.web.bind.annotation.RestController")) {
40+
return;
41+
}
42+
43+
ct.members().stream().filter(member -> member.is(Tree.Kind.METHOD)).forEach(member -> {
44+
var mt = (MethodTree) member;
45+
mt.modifiers().annotations().stream()
46+
.filter(annotationTree -> annotationTree.symbolType().is("org.springframework.web.bind.annotation.ResponseBody"))
47+
.findFirst()
48+
.ifPresent(annotationTree -> reportIssue(annotationTree, "Remove this superfluous \"@ResponseBody\" annotation."));
49+
});
50+
}
51+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<h2>Why is this an issue?</h2>
2+
<p>The Spring framework’s <code>@RestController</code> annotation is equivalent to using the <code>@Controller</code> and <code>@ResponseBody</code>
3+
annotations together. As such, it is redundant to add a <code>@ResponseBody</code> annotation when the class is already annotated with
4+
<code>@RestController</code>.</p>
5+
<h2>How to fix it</h2>
6+
<p>Remove the <code>@ResponseBody</code> annotation from the class or method.</p>
7+
<h3>Code examples</h3>
8+
<h4>Noncompliant code example</h4>
9+
<pre data-diff-id="1" data-diff-type="noncompliant">
10+
@RestController
11+
public class MyController {
12+
@ResponseBody // Noncompliant, the @RestController annotation already implies @ResponseBody
13+
@RequestMapping("/hello")
14+
public String hello() {
15+
return "Hello World!";
16+
}
17+
}
18+
</pre>
19+
<h4>Compliant solution</h4>
20+
<pre data-diff-id="1" data-diff-type="compliant">
21+
@RestController
22+
public class MyController {
23+
@RequestMapping("/hello")
24+
public String hello() {
25+
return "Hello World!";
26+
}
27+
}
28+
</pre>
29+
<h4>Noncompliant code example</h4>
30+
<pre data-diff-id="2" data-diff-type="noncompliant">
31+
@RestController
32+
@ResponseBody // Noncompliant, the @RestController annotation already implies @ResponseBody
33+
public class MyController {
34+
@RequestMapping("/hello")
35+
public String hello() {
36+
return "Hello World!";
37+
}
38+
}
39+
</pre>
40+
<h4>Compliant solution</h4>
41+
<pre data-diff-id="2" data-diff-type="compliant">
42+
@RestController
43+
public class MyController {
44+
@RequestMapping("/hello")
45+
public String hello() {
46+
return "Hello World!";
47+
}
48+
}
49+
</pre>
50+
<h2>Resources</h2>
51+
<h3>Articles &amp; blog posts</h3>
52+
<ul>
53+
<li> Spring Guides - <a href="https://spring.io/guides/gs/rest-service/">Building a RESTful Web Service</a> </li>
54+
<li> Baeldung - <a href="https://www.baeldung.com/spring-controller-vs-restcontroller">The Spring @Controller and @RestController Annotations</a>
55+
</li>
56+
<li> Baeldung - <a href="https://www.baeldung.com/spring-request-response-body">Spring’s RequestBody and ResponseBody Annotations</a> </li>
57+
</ul>
58+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"title": "Superfluous \"@ResponseBody\" annotations should be removed",
3+
"type": "CODE_SMELL",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "5min"
8+
},
9+
"tags": [
10+
"spring"
11+
],
12+
"defaultSeverity": "Major",
13+
"ruleSpecification": "RSPEC-6837",
14+
"sqKey": "S6837",
15+
"scope": "All",
16+
"quickfix": "targeted",
17+
"code": {
18+
"impacts": {
19+
"MAINTAINABILITY": "LOW"
20+
},
21+
"attribute": "CLEAR"
22+
}
23+
}

java-checks/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
@@ -487,6 +487,7 @@
487487
"S6813",
488488
"S6814",
489489
"S6818",
490-
"S6829"
490+
"S6829",
491+
"S6837"
491492
]
492493
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2023 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 GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.java.checks.spring;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.sonar.java.checks.verifier.CheckVerifier;
24+
25+
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
26+
27+
class SuperfluousResponseBodyAnnotationCheckTest {
28+
@Test
29+
void test() {
30+
CheckVerifier.newVerifier()
31+
.onFile(mainCodeSourcesPath("checks/spring/SuperfluousResponseBodyAnnotationCheckSample.java"))
32+
.withCheck(new SuperfluousResponseBodyAnnotationCheck())
33+
.verifyIssues();
34+
}
35+
36+
@Test
37+
void test_no_semantics() {
38+
CheckVerifier.newVerifier()
39+
.onFile(mainCodeSourcesPath("checks/spring/SuperfluousResponseBodyAnnotationCheckSample.java"))
40+
.withCheck(new SuperfluousResponseBodyAnnotationCheck())
41+
.withoutSemantic()
42+
.verifyNoIssues();
43+
}
44+
}

0 commit comments

Comments
 (0)