Skip to content

Commit a621455

Browse files
author
Vincent Potucek
committed
fix RemoveUnusedImportsStep leftovers
1 parent e5e4414 commit a621455

File tree

2 files changed

+528
-0
lines changed

2 files changed

+528
-0
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
package com.palantir.javaformat.java;
2+
3+
import com.google.common.collect.ImmutableList;
4+
import com.google.common.collect.Range;
5+
import com.google.common.collect.RangeMap;
6+
import com.google.common.collect.TreeRangeMap;
7+
import com.sun.source.tree.AnnotationTree;
8+
import com.sun.source.tree.ClassTree;
9+
import com.sun.source.tree.CompilationUnitTree;
10+
import com.sun.source.tree.MethodTree;
11+
import com.sun.source.tree.ModifiersTree;
12+
import com.sun.source.tree.Tree;
13+
import com.sun.source.tree.VariableTree;
14+
import com.sun.source.util.JavacTask;
15+
import com.sun.source.util.SourcePositions;
16+
import com.sun.source.util.TreePath;
17+
import com.sun.source.util.TreePathScanner;
18+
import com.sun.source.util.Trees;
19+
import com.sun.tools.javac.api.JavacTool;
20+
import com.sun.tools.javac.file.JavacFileManager;
21+
import com.sun.tools.javac.util.Context;
22+
import java.io.IOException;
23+
import java.net.URI;
24+
import java.util.Comparator;
25+
import java.util.LinkedHashSet;
26+
import java.util.Map;
27+
import java.util.Set;
28+
import java.util.stream.Collectors;
29+
import javax.lang.model.element.Modifier;
30+
import javax.tools.Diagnostic;
31+
import javax.tools.DiagnosticCollector;
32+
import javax.tools.JavaFileObject;
33+
import javax.tools.SimpleJavaFileObject;
34+
35+
/**
36+
* Removes unused declarations from Java source code, including:
37+
* - Redundant modifiers in interfaces (public, static, final, abstract)
38+
* - Redundant modifiers in classes, enums, and annotations
39+
* - Redundant final modifiers on method parameters (preserved now)
40+
*/
41+
public class RemoveUnusedDeclarations {
42+
public static String removeUnusedDeclarations(String source) throws FormatterException {
43+
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
44+
var task = JavacTool.create()
45+
.getTask(
46+
null,
47+
new JavacFileManager(new Context(), true, null),
48+
diagnostics,
49+
ImmutableList.of("-Xlint:-processing"),
50+
null,
51+
ImmutableList.of((JavaFileObject)
52+
new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) {
53+
@Override
54+
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
55+
return source;
56+
}
57+
}));
58+
59+
try {
60+
Iterable<? extends CompilationUnitTree> units = task.parse();
61+
if (!units.iterator().hasNext()) {
62+
throw new FormatterException("No compilation units found");
63+
}
64+
65+
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
66+
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
67+
throw new FormatterException("Syntax error in source: " + diagnostic.getMessage(null));
68+
}
69+
}
70+
71+
var scanner = new UnusedDeclarationScanner(task);
72+
scanner.scan(units.iterator().next(), null);
73+
74+
return applyReplacements(source, scanner.getReplacements());
75+
} catch (IOException e) {
76+
throw new FormatterException("Error processing source file: " + e.getMessage());
77+
}
78+
}
79+
80+
private static class UnusedDeclarationScanner extends TreePathScanner<Void, Void> {
81+
private final RangeMap<Integer, String> replacements = TreeRangeMap.create();
82+
private final SourcePositions sourcePositions;
83+
private final Trees trees;
84+
85+
private static final ImmutableList<Modifier> CANONICAL_MODIFIER_ORDER = ImmutableList.of(
86+
Modifier.PUBLIC,
87+
Modifier.PROTECTED,
88+
Modifier.PRIVATE,
89+
Modifier.ABSTRACT,
90+
Modifier.STATIC,
91+
Modifier.FINAL,
92+
Modifier.TRANSIENT,
93+
Modifier.VOLATILE,
94+
Modifier.SYNCHRONIZED,
95+
Modifier.NATIVE,
96+
Modifier.STRICTFP);
97+
98+
private UnusedDeclarationScanner(JavacTask task) {
99+
this.sourcePositions = Trees.instance(task).getSourcePositions();
100+
this.trees = Trees.instance(task);
101+
}
102+
103+
public RangeMap<Integer, String> getReplacements() {
104+
return replacements;
105+
}
106+
107+
@Override
108+
public Void visitClass(ClassTree node, Void unused) {
109+
var parentPath = getCurrentPath().getParentPath();
110+
if (node.getKind() == Tree.Kind.INTERFACE) {
111+
checkForRedundantModifiers(node, Set.of(Modifier.PUBLIC, Modifier.ABSTRACT));
112+
} else if ((parentPath != null ? parentPath.getLeaf().getKind() : null) == Tree.Kind.INTERFACE) {
113+
checkForRedundantModifiers(node, Set.of(Modifier.PUBLIC, Modifier.STATIC));
114+
} else if (node.getKind() == Tree.Kind.ANNOTATION_TYPE) {
115+
checkForRedundantModifiers(node, Set.of(Modifier.ABSTRACT));
116+
} else {
117+
checkForRedundantModifiers(node, Set.of()); // Always sort
118+
}
119+
120+
return super.visitClass(node, unused);
121+
}
122+
123+
@Override
124+
public Void visitMethod(MethodTree node, Void unused) {
125+
var parentPath = getCurrentPath().getParentPath();
126+
var parentKind = parentPath != null ? parentPath.getLeaf().getKind() : null;
127+
128+
if (parentKind == Tree.Kind.INTERFACE) {
129+
if (!node.getModifiers().getFlags().contains(Modifier.DEFAULT)
130+
&& !node.getModifiers().getFlags().contains(Modifier.STATIC)) {
131+
checkForRedundantModifiers(node, Set.of(Modifier.PUBLIC, Modifier.ABSTRACT));
132+
} else {
133+
checkForRedundantModifiers(node, Set.of());
134+
}
135+
} else if (parentKind == Tree.Kind.ANNOTATION_TYPE) {
136+
checkForRedundantModifiers(node, Set.of(Modifier.ABSTRACT));
137+
} else {
138+
checkForRedundantModifiers(node, Set.of()); // Always sort
139+
}
140+
141+
return super.visitMethod(node, unused);
142+
}
143+
144+
@Override
145+
public Void visitVariable(VariableTree node, Void unused) {
146+
var parentPath = getCurrentPath().getParentPath();
147+
var parentKind = parentPath != null ? parentPath.getLeaf().getKind() : null;
148+
149+
if (node.getKind() == Tree.Kind.ENUM) {
150+
return super.visitVariable(node, unused);
151+
}
152+
153+
if (parentKind == Tree.Kind.INTERFACE || parentKind == Tree.Kind.ANNOTATION_TYPE) {
154+
checkForRedundantModifiers(node, Set.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL));
155+
} else {
156+
checkForRedundantModifiers(node, Set.of()); // Always sort
157+
}
158+
159+
return super.visitVariable(node, unused);
160+
}
161+
162+
private void checkForRedundantModifiers(Tree node, Set<Modifier> redundantModifiers) {
163+
var modifiers = getModifiers(node);
164+
if (modifiers == null) return;
165+
try {
166+
addReplacementForModifiers(
167+
node,
168+
new LinkedHashSet<>(modifiers.getFlags())
169+
.stream().filter(redundantModifiers::contains).collect(Collectors.toSet()));
170+
} catch (IOException e) {
171+
throw new RuntimeException(e);
172+
}
173+
}
174+
175+
private ModifiersTree getModifiers(Tree node) {
176+
if (node instanceof ClassTree) return ((ClassTree) node).getModifiers();
177+
if (node instanceof MethodTree) return ((MethodTree) node).getModifiers();
178+
if (node instanceof VariableTree) return ((VariableTree) node).getModifiers();
179+
return null;
180+
}
181+
182+
private void addReplacementForModifiers(Tree node, Set<Modifier> toRemove) throws IOException {
183+
TreePath path = trees.getPath(getCurrentPath().getCompilationUnit(), node);
184+
if (path == null) return;
185+
186+
CompilationUnitTree unit = path.getCompilationUnit();
187+
String source = unit.getSourceFile().getCharContent(true).toString();
188+
189+
ModifiersTree modifiers = getModifiers(node);
190+
if (modifiers == null) return;
191+
192+
long modifiersStart = sourcePositions.getStartPosition(unit, modifiers);
193+
long modifiersEnd = sourcePositions.getEndPosition(unit, modifiers);
194+
if (modifiersStart == -1 || modifiersEnd == -1) return;
195+
196+
String newModifiersText = modifiers.getFlags().stream()
197+
.filter(m -> !toRemove.contains(m))
198+
.collect(Collectors.toCollection(LinkedHashSet::new))
199+
.stream()
200+
.sorted(Comparator.comparingInt(mod -> {
201+
int idx = CANONICAL_MODIFIER_ORDER.indexOf(mod);
202+
return idx == -1 ? Integer.MAX_VALUE : idx;
203+
}))
204+
.map(Modifier::toString)
205+
.collect(Collectors.joining(" "));
206+
207+
long annotationsEnd = modifiersStart;
208+
for (AnnotationTree annotation : modifiers.getAnnotations()) {
209+
long end = sourcePositions.getEndPosition(unit, annotation);
210+
if (end > annotationsEnd) annotationsEnd = end;
211+
}
212+
213+
int effectiveStart = (int) annotationsEnd;
214+
while (effectiveStart < modifiersEnd && Character.isWhitespace(source.charAt(effectiveStart))) {
215+
effectiveStart++;
216+
}
217+
218+
String current = source.substring(effectiveStart, (int) modifiersEnd);
219+
if (!newModifiersText.trim().equals(current.trim())) {
220+
int globalEnd = (int) modifiersEnd;
221+
if (newModifiersText.isEmpty()) {
222+
while (globalEnd < source.length() && Character.isWhitespace(source.charAt(globalEnd))) {
223+
globalEnd++;
224+
}
225+
}
226+
replacements.put(Range.closedOpen(effectiveStart, globalEnd), newModifiersText);
227+
}
228+
}
229+
}
230+
231+
private static String applyReplacements(String source, RangeMap<Integer, String> replacements) {
232+
StringBuilder sb = new StringBuilder(source);
233+
for (Map.Entry<Range<Integer>, String> entry :
234+
replacements.asDescendingMapOfRanges().entrySet()) {
235+
Range<Integer> range = entry.getKey();
236+
sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue());
237+
}
238+
return sb.toString();
239+
}
240+
}

0 commit comments

Comments
 (0)