Skip to content

Commit 91dd9be

Browse files
SONARJAVA-5928 Provide quickfixes to insert an empty constructor in S1118
1 parent cd377c1 commit 91dd9be

File tree

3 files changed

+53
-2
lines changed

3 files changed

+53
-2
lines changed

java-checks-test-sources/default/src/main/java/checks/UtilityClassWithPublicConstructorCheckSample.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,4 +243,18 @@ public static void foo() {
243243
}
244244
}
245245

246+
// fix@qf1 {{Add an empty private constructor as the first member of the class.}}
247+
// edit@qf1 [[sl=+0;el=+0;sc=29;ec=29]] {{\n private StringUtils() {\n /* This utility class should not be instantiated */\n }\n}}
248+
// fix@qf2 {{Add an empty private constructor as the last member of the class.}}
249+
// edit@qf2 [[sl=+5;el=+5;sc=6;ec=6]] {{\n\n private StringUtils() {\n /* This utility class should not be instantiated */\n }}}
250+
// fix@qf3 {{Add an empty private constructor before the first method in the class.}}
251+
// edit@qf3 [[sl=+1;el=+1;sc=49;ec=49]] {{\n\n private StringUtils() {\n /* This utility class should not be instantiated */\n }}}
252+
public class StringUtils { // Noncompliant [[sc=16;ec=27;quickfixes=qf1,qf2,qf3]]
253+
public static String HELLO = "Hello world!";
254+
255+
public static String concatenate(String s1, String s2) {
256+
return s1 + s2;
257+
}
258+
}
259+
246260
}

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@
1616
*/
1717
package org.sonar.java.checks;
1818

19+
import java.util.ArrayList;
1920
import java.util.Collections;
2021
import java.util.List;
2122
import java.util.Set;
2223
import java.util.stream.Collectors;
2324
import org.sonar.check.Rule;
2425
import org.sonar.java.checks.helpers.AnnotationsHelper;
2526
import org.sonar.java.checks.helpers.ClassPatternsUtils;
27+
import org.sonar.java.checks.helpers.QuickFixHelper;
2628
import org.sonar.java.model.ModifiersUtils;
29+
import org.sonar.java.reporting.JavaQuickFix;
30+
import org.sonar.java.reporting.JavaTextEdit;
2731
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
2832
import org.sonar.plugins.java.api.semantic.Type;
2933
import org.sonar.plugins.java.api.tree.AnnotationTree;
@@ -71,7 +75,12 @@ public void visitNode(Tree tree) {
7175
}
7276
}
7377
if (hasImplicitPublicConstructor && !hasCompliantGeneratedConstructors(classTree)) {
74-
reportIssue(classTree.simpleName(), "Add a private constructor to hide the implicit public one.");
78+
QuickFixHelper.newIssue(context)
79+
.forRule(this)
80+
.onTree(classTree.simpleName())
81+
.withMessage("Add a private constructor to hide the implicit public one.")
82+
.withQuickFixes(() -> computeQuickFixes(classTree))
83+
.report();
7584
}
7685
}
7786

@@ -126,4 +135,32 @@ private static boolean isAccessLevelNotPublic(ExpressionTree tree) {
126135
return !"PUBLIC".equals(valueName);
127136
}
128137

138+
private static List<JavaQuickFix> computeQuickFixes(ClassTree classTree) {
139+
int firstMemberColumnOffset = classTree.members().get(0).firstToken().range().start().columnOffset();
140+
int columnOffsetDiff = firstMemberColumnOffset - classTree.firstToken().range().start().columnOffset();
141+
142+
String leftPadding = " ".repeat(firstMemberColumnOffset);
143+
String constructor = leftPadding + "private " + classTree.simpleName() + "() {\n" + leftPadding + " ".repeat(columnOffsetDiff)
144+
+ "/* This utility class should not be instantiated */\n" + leftPadding + "}";
145+
146+
List<JavaQuickFix> quickFixes = new ArrayList<>();
147+
quickFixes.add(JavaQuickFix.newQuickFix("Add an empty private constructor as the first member of the class.")
148+
.addTextEdit(JavaTextEdit.insertAfterTree(classTree.openBraceToken(), "\n" + constructor + "\n"))
149+
.build());
150+
quickFixes.add(JavaQuickFix.newQuickFix("Add an empty private constructor as the last member of the class.")
151+
.addTextEdit(JavaTextEdit.insertAfterTree(classTree.members().get(classTree.members().size() - 1), "\n\n" + constructor))
152+
.build());
153+
154+
if (classTree.members().stream().anyMatch(tree -> tree.is(Tree.Kind.METHOD))) {
155+
List<Tree> membersBeforeFirstMethod = classTree.members().stream().takeWhile(tree -> !tree.is(Tree.Kind.METHOD)).toList();
156+
if (!membersBeforeFirstMethod.isEmpty()) {
157+
quickFixes.add(JavaQuickFix.newQuickFix("Add an empty private constructor before the first method in the class.")
158+
.addTextEdit(JavaTextEdit.insertAfterTree(membersBeforeFirstMethod.get(membersBeforeFirstMethod.size() - 1), "\n\n" + constructor))
159+
.build());
160+
}
161+
}
162+
163+
return quickFixes;
164+
}
165+
129166
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@
1919
"ruleSpecification": "RSPEC-1118",
2020
"sqKey": "S1118",
2121
"scope": "Main",
22-
"quickfix": "unknown"
22+
"quickfix": "partial"
2323
}

0 commit comments

Comments
 (0)