Skip to content

Commit ef2cf5c

Browse files
authored
Merge pull request #805 from solven-eu/fix/CTorThisCallFields
ThisEmptyInConstructor_notEmptyCtor
2 parents fa6402d + b8b6de7 commit ef2cf5c

File tree

10 files changed

+166
-41
lines changed

10 files changed

+166
-41
lines changed

CHANGES.MD

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,21 @@ This document is intended for CleanThat developers and users (e.g. through [Spot
22

33
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format
44

5-
## [2.18 - Unreleased]
5+
## [2.19 - Unreleased]
66

77
### Changes
88

99
* `RemoveExplicitCallToSuper` now removes `super();` in constructor only if it is not the only statement.
10+
* `RemoveExplicitCallToSuper` does not remove `this()` anymore.
11+
12+
### Fixed
13+
14+
* `ModifierOrder` shall not prevent further modifiers mutators to operate correctly.
15+
16+
## [2.18]
17+
18+
### Changes
19+
1020
* Spotless licenseHeader default YearMode has been updated to `UPDATE_TO_TODAY`, as CleanThat generally behave as if `ratchetFrom` is the base branch
1121
* `UnnecessaryBoxing` is not draft anymore
1222
* `AvoidMultipleUnaryOperators` is not draft anymore

java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/AJavaparserAstMutator.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 Benoit Lacelle - SOLVEN
2+
* Copyright 2023-2024 Benoit Lacelle - SOLVEN
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -64,8 +64,8 @@ public Optional<Node> walkAst(Node ast) {
6464
var astHasMutated = new AtomicBoolean();
6565

6666
ast.walk(node -> {
67-
boolean nodeHasMUtated = walkOneNode(node);
68-
if (nodeHasMUtated) {
67+
boolean nodeHasMutated = walkOneNode(node);
68+
if (nodeHasMutated) {
6969
astHasMutated.set(true);
7070
}
7171
});

java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/ModifierOrder.java

+30-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 Benoit Lacelle - SOLVEN
2+
* Copyright 2023-2024 Benoit Lacelle - SOLVEN
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
1515
*/
1616
package eu.solven.cleanthat.engine.java.refactorer.mutators;
1717

18+
import java.util.ArrayList;
1819
import java.util.Collections;
1920
import java.util.Comparator;
2021
import java.util.List;
@@ -35,6 +36,7 @@
3536
import eu.solven.cleanthat.engine.java.IJdkVersionConstants;
3637
import eu.solven.cleanthat.engine.java.refactorer.AJavaparserNodeMutator;
3738
import eu.solven.cleanthat.engine.java.refactorer.NodeAndSymbolSolver;
39+
import eu.solven.cleanthat.engine.java.refactorer.meta.ApplyBeforeMe;
3840

3941
/**
4042
* Order modifiers according the the Java specification.
@@ -43,6 +45,7 @@
4345
* @see https://github.com/checkstyle/checkstyle/blob/master/src/xdocs/checks/modifier/modifierorder.xml
4446
* @see
4547
*/
48+
@ApplyBeforeMe(UnnecessaryModifier.class)
4649
public class ModifierOrder extends AJavaparserNodeMutator {
4750
private static final Logger LOGGER = LoggerFactory.getLogger(ModifierOrder.class);
4851

@@ -108,19 +111,11 @@ protected boolean processNotRecursively(NodeAndSymbolSolver<?> nodeAndContext) {
108111
NodeWithModifiers<?> nodeWithModifiers = (NodeWithModifiers<?>) node;
109112
NodeList<Modifier> modifiers = nodeWithModifiers.getModifiers();
110113

111-
NodeList<Modifier> mutableModifiers = new NodeList<>(modifiers);
114+
// Do not rely on a `NodeList` to prevent transfer of modifiers ownership
115+
// https://github.com/solven-eu/cleanthat/issues/802
116+
List<Modifier> mutableModifiers = new ArrayList<>(modifiers);
112117

113-
Collections.sort(mutableModifiers, new Comparator<Modifier>() {
114-
115-
@Override
116-
public int compare(Modifier o1, Modifier o2) {
117-
return compare2(o1.getKeyword().asString(), o2.getKeyword().asString());
118-
}
119-
120-
private int compare2(String left, String right) {
121-
return Integer.compare(ORDERED_MODIFIERS.indexOf(left), ORDERED_MODIFIERS.indexOf(right));
122-
}
123-
});
118+
Collections.sort(mutableModifiers, modifiersComparator());
124119

125120
var changed = areSameReferences(modifiers, mutableModifiers);
126121

@@ -132,9 +127,23 @@ private int compare2(String left, String right) {
132127
return false;
133128
}
134129

130+
private Comparator<Modifier> modifiersComparator() {
131+
return new Comparator<Modifier>() {
132+
133+
@Override
134+
public int compare(Modifier o1, Modifier o2) {
135+
return compare2(o1.getKeyword().asString(), o2.getKeyword().asString());
136+
}
137+
138+
private int compare2(String left, String right) {
139+
return Integer.compare(ORDERED_MODIFIERS.indexOf(left), ORDERED_MODIFIERS.indexOf(right));
140+
}
141+
};
142+
}
143+
135144
private boolean applyModifiers(NodeWithModifiers<?> nodeWithModifiers,
136145
NodeList<Modifier> originalModifiers,
137-
NodeList<Modifier> sortedModifiers) {
146+
List<Modifier> sortedModifiers) {
138147
if (sortedModifiers.stream()
139148
.map(m -> m.getKeyword())
140149
.anyMatch(m -> m == Keyword.SEALED || m == Keyword.NON_SEALED)) {
@@ -149,13 +158,18 @@ private boolean applyModifiers(NodeWithModifiers<?> nodeWithModifiers,
149158
nodeWithModifiers.setModifiers();
150159

151160
LOGGER.debug("We fixed the ordering of modifiers");
152-
nodeWithModifiers.setModifiers(sortedModifiers);
161+
NodeList<Modifier> asNodeList = new NodeList<>(sortedModifiers);
162+
nodeWithModifiers.setModifiers(asNodeList);
153163

154164
return true;
155165
}
156166

157167
@SuppressWarnings("PMD.CompareObjectsWithEquals")
158-
private boolean areSameReferences(NodeList<Modifier> modifiers, NodeList<Modifier> mutableModifiers) {
168+
private boolean areSameReferences(List<Modifier> modifiers, List<Modifier> mutableModifiers) {
169+
if (modifiers.size() != mutableModifiers.size()) {
170+
return false;
171+
}
172+
159173
var changed = false;
160174
for (var i = 0; i < modifiers.size(); i++) {
161175
// Check by reference

java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/RemoveExplicitCallToSuper.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 Benoit Lacelle - SOLVEN
2+
* Copyright 2023-2024 Benoit Lacelle - SOLVEN
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.util.Set;
2020

2121
import com.github.javaparser.ast.body.ConstructorDeclaration;
22+
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt;
2223
import com.google.common.collect.ImmutableSet;
2324

2425
import eu.solven.cleanthat.engine.java.IJdkVersionConstants;
@@ -76,21 +77,27 @@ protected boolean processNotRecursively(NodeAndSymbolSolver<?> node) {
7677

7778
if (body.getStatements().isEmpty()) {
7879
return false;
80+
} else if (body.getStatements().size() == 1) {
81+
// We keep `super();` if it is the only statement, as it is generally considered better than having an empty
82+
// constructor.
83+
return false;
7984
}
8085

8186
var firstStatement = body.getStatement(0);
8287

8388
if (!firstStatement.isExplicitConstructorInvocationStmt()) {
89+
// We need either `this(...)` or `super(...)`
8490
return false;
8591
}
8692

87-
if (!firstStatement.asExplicitConstructorInvocationStmt().getArguments().isEmpty()) {
93+
ExplicitConstructorInvocationStmt asCtorInvocationStmt = firstStatement.asExplicitConstructorInvocationStmt();
94+
if (!asCtorInvocationStmt.getArguments().isEmpty()) {
95+
// We would remove only a ref to the default constructor
8896
return false;
8997
}
9098

91-
if (body.getStatements().size() == 1) {
92-
// We keep `super();` if it is the only statement, as it is generally considered better than having an empty
93-
// constructor.
99+
if (asCtorInvocationStmt.isThis()) {
100+
// `this(...)` can not be turned into explicit invocations
94101
return false;
95102
}
96103

java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/UnnecessaryModifier.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 Benoit Lacelle - SOLVEN
2+
* Copyright 2023-2024 Benoit Lacelle - SOLVEN
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020

2121
import com.github.javaparser.ast.Modifier;
2222
import com.github.javaparser.ast.Modifier.Keyword;
23+
import com.github.javaparser.ast.Node;
2324
import com.github.javaparser.ast.body.AnnotationDeclaration;
2425
import com.github.javaparser.ast.body.AnnotationMemberDeclaration;
2526
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
@@ -90,11 +91,12 @@ public String jSparrowUrl() {
9091

9192
@SuppressWarnings({ "PMD.CognitiveComplexity", "PMD.NPathComplexity" })
9293
@Override
93-
protected boolean processNotRecursively(NodeAndSymbolSolver<?> node) {
94-
if (!(node.getNode() instanceof Modifier)) {
94+
protected boolean processNotRecursively(NodeAndSymbolSolver<?> nodeAndSymbolSolver) {
95+
Node node = nodeAndSymbolSolver.getNode();
96+
if (!(node instanceof Modifier)) {
9597
return false;
9698
}
97-
var modifier = (Modifier) node.getNode();
99+
var modifier = (Modifier) node;
98100

99101
if (modifier.getParentNode().isEmpty()) {
100102
return false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me;
2+
3+
import java.util.Arrays;
4+
5+
import eu.solven.cleanthat.engine.java.refactorer.annotations.CompareCompilationUnitsAsStrings;
6+
import eu.solven.cleanthat.engine.java.refactorer.meta.CompositeJavaparserMutator;
7+
import eu.solven.cleanthat.engine.java.refactorer.meta.IJavaparserAstMutator;
8+
import eu.solven.cleanthat.engine.java.refactorer.mutators.ModifierOrder;
9+
import eu.solven.cleanthat.engine.java.refactorer.mutators.UnnecessaryModifier;
10+
import eu.solven.cleanthat.engine.java.refactorer.test.AJavaparserRefactorerCases;
11+
12+
// https://github.com/solven-eu/cleanthat/issues/802
13+
public class TestModifierOrderThenUnnecessaryModifierCases extends AJavaparserRefactorerCases {
14+
@Override
15+
public IJavaparserAstMutator getTransformer() {
16+
return new CompositeJavaparserMutator(Arrays.asList(
17+
new ModifierOrder(),
18+
new UnnecessaryModifier()));
19+
}
20+
21+
// https://github.com/solven-eu/cleanthat/issues/802
22+
@CompareCompilationUnitsAsStrings(pre = "interface TopLevelInterface { public final static int i = 0; }",
23+
post = "interface TopLevelInterface {\n" + "\n" + " int i = 0;\n" + "}\n")
24+
public static class Issue802 {
25+
}
26+
27+
}

java/src/test/java/eu/solven/cleanthat/engine/java/refactorer/cases/do_not_format_me/TestRemoveExplicitCallToSuperCases.java

+58-3
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ public Post() {
9090
}
9191
}
9292

93-
@CompareInnerClasses
93+
// `this();` can be removed for equivalent behavior only because `Pre()` exists but is a noop
94+
@UnmodifiedInnerClass
9495
public static class ThisEmptyInConstructor {
9596
public class Pre {
9697

@@ -103,12 +104,66 @@ public Pre(String s) {
103104
s.toString();
104105
}
105106
}
107+
}
106108

107-
public class Post {
109+
// Dropping `this` would lead to compilation error as `s` in final
110+
@UnmodifiedInnerClass
111+
public static class ThisEmptyInConstructor_notEmptyCtor_finalField {
112+
public class Pre {
113+
final String s;
108114

109-
public Post() {
115+
public Pre() {
116+
s = "";
110117
}
111118

119+
public Pre(String s) {
120+
this();
121+
122+
s.toString();
123+
}
124+
}
125+
}
126+
127+
// Dropping `this` would prevent `s` initialization
128+
@UnmodifiedInnerClass
129+
public static class ThisEmptyInConstructor_notEmptyCtor_notFinalField {
130+
public class Pre {
131+
String s;
132+
133+
public Pre() {
134+
s = "";
135+
}
136+
137+
public Pre(String s) {
138+
this();
139+
140+
s.toString();
141+
}
142+
}
143+
}
144+
145+
@CompareInnerClasses
146+
public static class SuperEmptyInConstructor_notEmptyCtor {
147+
class Parent {
148+
final String s;
149+
150+
public Parent() {
151+
s = "";
152+
}
153+
154+
}
155+
156+
public class Pre extends Parent {
157+
158+
public Pre(String s) {
159+
super();
160+
161+
s.toString();
162+
}
163+
}
164+
165+
public class Post extends Parent {
166+
112167
public Post(String s) {
113168

114169
s.toString();

java/src/test/java/eu/solven/cleanthat/engine/java/refactorer/cases/do_not_format_me/TestUnnecessaryModifierCases.java

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me;
22

3+
import eu.solven.cleanthat.engine.java.refactorer.annotations.CompareCompilationUnitsAsStrings;
34
import eu.solven.cleanthat.engine.java.refactorer.annotations.CompareInnerAnnotations;
45
import eu.solven.cleanthat.engine.java.refactorer.annotations.CompareInnerClasses;
56
import eu.solven.cleanthat.engine.java.refactorer.meta.IJavaparserAstMutator;

parent/pom.xml

+8-2
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,10 @@
378378
<version>${spotless.version}</version>
379379
<configuration>
380380
<!-- optional: limit format enforcement to just the files changed by this feature branch -->
381-
<!--ratchetFrom>origin/master</ratchetFrom-->
381+
<!-- We keep a ratchetFrom to ensure `LicenseHeader.YearMode==UPDATE_TO_TODAY` -->
382+
<!-- https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java -->
383+
<ratchetFrom>origin/master</ratchetFrom>
384+
382385
<formats>
383386
<!-- you can define as many formats as you want, each is independent -->
384387
<format>
@@ -435,11 +438,14 @@
435438
<version>${project.version}</version>
436439
<mutators>
437440
<mutator>SafeAndConsensual</mutator>
441+
-
438442
<mutator>SafeButNotConsensual</mutator>
443+
-
439444
<mutator>SafeButControversial</mutator>
445+
-
440446
<mutator>Guava</mutator>
441447
</mutators>
442-
<includeDraft>true</includeDraft>
448+
<includeDraft>false</includeDraft>
443449
<excludedMutators>
444450
<!-- https://github.com/javaparser/javaparser/issues/3930 -->
445451
<excludedMutator>LambdaReturnsSingleStatement</excludedMutator>

0 commit comments

Comments
 (0)