Skip to content

Commit a26346a

Browse files
gkdncopybara-github
authored andcommitted
Replace JDT usage with javac in GwtIncompatibleStripper for J2CL.
PiperOrigin-RevId: 888178866
1 parent e5bf29f commit a26346a

File tree

7 files changed

+231
-111
lines changed

7 files changed

+231
-111
lines changed

tools/java/com/google/j2cl/tools/gwtincompatible/BUILD

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,19 @@ java_library(
1717
["*.java"],
1818
exclude = ["BazelGwtIncompatibleStripper.java"],
1919
),
20+
add_exports = [
21+
"jdk.compiler/com.sun.tools.javac.api",
22+
"jdk.compiler/com.sun.tools.javac.file",
23+
"jdk.compiler/com.sun.tools.javac.parser",
24+
"jdk.compiler/com.sun.tools.javac.tree",
25+
"jdk.compiler/com.sun.tools.javac.util",
26+
],
2027
visibility = ["//tools/javatests/com/google/j2cl/tools/gwtincompatible:__pkg__"],
2128
deps = [
2229
"//third_party:args4j",
2330
"//third_party:guava",
24-
"//third_party:jdt-core",
2531
"//transpiler/java/com/google/j2cl/common",
26-
"//transpiler/java/com/google/j2cl/transpiler/frontend/jdt",
32+
"//transpiler/java/com/google/j2cl/transpiler/frontend/javac",
2733
],
2834
)
2935

tools/java/com/google/j2cl/tools/gwtincompatible/GwtIncompatibleStripper.java

Lines changed: 98 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.collect.ImmutableList.toImmutableList;
1818
import static java.nio.charset.StandardCharsets.UTF_8;
1919

20+
import com.google.common.collect.ImmutableList;
2021
import com.google.common.collect.ImmutableSet;
2122
import com.google.common.collect.Lists;
2223
import com.google.common.io.MoreFiles;
@@ -26,22 +27,29 @@
2627
import com.google.j2cl.common.Problems.FatalError;
2728
import com.google.j2cl.common.SourceUtils;
2829
import com.google.j2cl.common.SourceUtils.FileInfo;
29-
import com.google.j2cl.transpiler.frontend.jdt.AnnotatedNodeCollector;
30+
import com.google.j2cl.transpiler.frontend.javac.AnnotatedNodeCollector;
31+
import com.sun.source.doctree.DocCommentTree;
32+
import com.sun.source.tree.CompilationUnitTree;
33+
import com.sun.source.tree.ImportTree;
34+
import com.sun.source.tree.Tree;
35+
import com.sun.source.tree.VariableTree;
36+
import com.sun.source.util.DocSourcePositions;
37+
import com.sun.tools.javac.api.JavacTrees;
38+
import com.sun.tools.javac.file.JavacFileManager;
39+
import com.sun.tools.javac.parser.ParserFactory;
40+
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
41+
import com.sun.tools.javac.util.Context;
42+
import com.sun.tools.javac.util.Log;
3043
import java.io.IOException;
44+
import java.io.UncheckedIOException;
45+
import java.net.URI;
3146
import java.nio.file.Path;
3247
import java.nio.file.Paths;
33-
import java.util.Comparator;
34-
import java.util.HashMap;
3548
import java.util.List;
36-
import java.util.Map;
3749
import java.util.stream.Stream;
38-
import org.eclipse.jdt.core.JavaCore;
39-
import org.eclipse.jdt.core.dom.AST;
40-
import org.eclipse.jdt.core.dom.ASTNode;
41-
import org.eclipse.jdt.core.dom.ASTParser;
42-
import org.eclipse.jdt.core.dom.CompilationUnit;
43-
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
44-
import org.eclipse.jdt.core.dom.ImportDeclaration;
50+
import javax.tools.JavaFileObject;
51+
import javax.tools.SimpleJavaFileObject;
52+
import javax.tools.StandardLocation;
4553

4654
/**
4755
* A helper to comment out source code elements annotated with "incompatible" annotations
@@ -89,69 +97,86 @@ public static String strip(String fileContent, List<String> annotationNames) {
8997
return fileContent;
9098
}
9199

92-
Map<String, String> compilerOptions = new HashMap<>();
93-
compilerOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_14);
94-
compilerOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_14);
95-
compilerOptions.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_14);
100+
Context context = new Context();
101+
JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8);
102+
try {
103+
// Set an empty bootclasspath to save time on javac initialization.
104+
// By default it will read classes from the host JDK's bootclasspath.
105+
fileManager.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of());
106+
} catch (IOException e) {
107+
throw new UncheckedIOException(e);
108+
}
96109

97-
// Parse the file.
98-
ASTParser parser = ASTParser.newParser(AST.JLS14);
99-
parser.setCompilerOptions(compilerOptions);
100-
parser.setResolveBindings(false);
101-
parser.setSource(fileContent.toCharArray());
102-
CompilationUnit compilationUnit = (CompilationUnit) parser.createAST(null);
110+
SimpleJavaFileObject fileObject =
111+
new SimpleJavaFileObject(URI.create("string:///temp.java"), JavaFileObject.Kind.SOURCE) {
112+
@Override
113+
public String getCharContent(boolean ignoreEncodingErrors) {
114+
return fileContent;
115+
}
116+
};
117+
118+
Log.instance(context).useSource(fileObject);
119+
120+
JCCompilationUnit compilationUnit =
121+
ParserFactory.instance(context)
122+
.newParser(
123+
fileContent,
124+
/* keepDocComments= */ true,
125+
/* keepEndPos= */ true,
126+
/* keepLineMap= */ true)
127+
.parseCompilationUnit();
103128

104129
// Find all the declarations with the annotation name
105130
AnnotatedNodeCollector gwtIncompatibleVisitor =
106131
new AnnotatedNodeCollector(
107-
ImmutableSet.copyOf(annotationNames),
132+
ImmutableList.copyOf(annotationNames),
108133
// Stop traversing on the first matching scope. Since we're deleting that entire scope
109134
// there's no need to traverse within it.
110135
/* stopTraversalOnMatch= */ true);
111-
compilationUnit.accept(gwtIncompatibleVisitor);
112-
ImmutableSet<ASTNode> nodesToRemove = gwtIncompatibleVisitor.getNodes();
113-
114-
// Delete the gwtIncompatible nodes.
115-
for (ASTNode gwtIncompatibleNode : nodesToRemove) {
116-
gwtIncompatibleNode.delete();
117-
}
136+
gwtIncompatibleVisitor.scan(compilationUnit, null);
137+
ImmutableSet<Tree> incompatibleNodes = gwtIncompatibleVisitor.getNodes();
118138

119139
// Gets all the imports that are no longer needed.
120-
UnusedImportsNodeCollector unusedImportsNodeCollector = new UnusedImportsNodeCollector();
121-
compilationUnit.accept(unusedImportsNodeCollector);
122-
List<ImportDeclaration> unusedImportsNodes = unusedImportsNodeCollector.getUnusedImports();
140+
UnusedImportsNodeCollector unusedImportsNodeCollector =
141+
new UnusedImportsNodeCollector(incompatibleNodes);
142+
unusedImportsNodeCollector.scan(compilationUnit, null);
143+
List<ImportTree> unusedImportsNodes = unusedImportsNodeCollector.getUnusedImports();
123144

124-
// Wrap all the not needed nodes inside comments in the original source
145+
// Replace all the not needed nodes with whitespace in the original source
125146
// (so we can preserve line numbers and have accurate source maps).
126-
List<ASTNode> nodesToWrap = Lists.newArrayList(unusedImportsNodes);
127-
// Add the nodes to remove in start position order.
128-
nodesToRemove.stream()
129-
.sorted(Comparator.comparingInt(ASTNode::getStartPosition))
130-
.forEach(nodesToWrap::add);
131-
if (nodesToWrap.isEmpty()) {
147+
List<Tree> nodesToRemove = Lists.newArrayList(unusedImportsNodes);
148+
nodesToRemove.addAll(incompatibleNodes);
149+
if (nodesToRemove.isEmpty()) {
132150
// Nothing was changed.
133151
return fileContent;
134152
}
135153

136-
// Precondition: Node ranges must not overlap and they must be sorted by position.
154+
JavacTrees docTrees = JavacTrees.instance(context);
155+
DocSourcePositions sourcePositions = docTrees.getSourcePositions();
156+
157+
// The nodes to remove are sorted by position. Overlapping node ranges (multivariable) are
158+
// handled by virtue of skipping ranges that have already been processed.
137159
StringBuilder newFileContent = new StringBuilder();
138160
int currentPosition = 0;
139-
for (ASTNode nodeToWrap : nodesToWrap) {
140-
int startPosition = nodeToWrap.getStartPosition();
141-
int endPosition = startPosition + nodeToWrap.getLength();
142-
checkState(
143-
currentPosition <= startPosition,
144-
"Unexpected node position: %s, must be >= %s",
145-
startPosition,
146-
currentPosition);
161+
for (Tree node : nodesToRemove) {
162+
int startPosition =
163+
getStartPosition(node, docTrees, sourcePositions, compilationUnit, fileContent);
164+
int endPosition = getEndPosition(node, sourcePositions, compilationUnit);
165+
166+
// If a node is overlapping with a previously stripped node, its startPosition will be
167+
// adjusted to the currentPosition.
168+
// Multivariable declarations are only current example of this where both share start
169+
// position and first one end before the second one.
170+
startPosition = Math.max(startPosition, currentPosition);
171+
checkState(startPosition < endPosition);
147172

148173
newFileContent.append(fileContent, currentPosition, startPosition);
149174

150175
StringBuilder strippedCodeBuilder = new StringBuilder();
151176
for (char c : fileContent.substring(startPosition, endPosition).toCharArray()) {
152177
strippedCodeBuilder.append(Character.isWhitespace(c) ? c : ' ');
153178
}
154-
if (nodeToWrap instanceof EnumConstantDeclaration) {
179+
if (node instanceof VariableTree) {
155180
// HACK: We assume that if there is a comma, it directly follows the enum constant and we
156181
// remove it. In practice this should work for most cases since if there is a comma
157182
// following the constant, the formatter will have it adjacent to the constant. If this is
@@ -170,5 +195,29 @@ public static String strip(String fileContent, List<String> annotationNames) {
170195
return newFileContent.toString();
171196
}
172197

198+
private static int getStartPosition(
199+
Tree node,
200+
JavacTrees trees,
201+
DocSourcePositions sourcePositions,
202+
CompilationUnitTree compilationUnit,
203+
String fileContent) {
204+
int start = (int) sourcePositions.getStartPosition(compilationUnit, node);
205+
DocCommentTree javaDoc = trees.getDocCommentTree(trees.getPath(compilationUnit, node));
206+
if (javaDoc != null) {
207+
int javadocStart = (int) sourcePositions.getStartPosition(compilationUnit, javaDoc, javaDoc);
208+
checkState(javadocStart < start);
209+
// DocTrees.getSourcePositions() returns the position slightly after the initial "/**".
210+
// To ensure we strip the entire Javadoc block, we reverse search for the exact "/**"
211+
// prefix starting from the returned docStart position.
212+
start = fileContent.lastIndexOf("/**", javadocStart);
213+
}
214+
return start;
215+
}
216+
217+
private static int getEndPosition(
218+
Tree node, DocSourcePositions sourcePositions, CompilationUnitTree compilationUnit) {
219+
return (int) sourcePositions.getEndPosition(compilationUnit, node);
220+
}
221+
173222
private GwtIncompatibleStripper() {}
174223
}

tools/java/com/google/j2cl/tools/gwtincompatible/UnusedImportsNodeCollector.java

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,67 +15,70 @@
1515
*/
1616
package com.google.j2cl.tools.gwtincompatible;
1717

18+
import com.sun.source.tree.CompilationUnitTree;
19+
import com.sun.source.tree.IdentifierTree;
20+
import com.sun.source.tree.ImportTree;
21+
import com.sun.source.tree.Tree;
22+
import com.sun.source.util.TreeScanner;
1823
import java.util.ArrayList;
1924
import java.util.HashSet;
2025
import java.util.List;
2126
import java.util.Set;
22-
import org.eclipse.jdt.core.dom.ASTVisitor;
23-
import org.eclipse.jdt.core.dom.CompilationUnit;
24-
import org.eclipse.jdt.core.dom.ImportDeclaration;
25-
import org.eclipse.jdt.core.dom.Name;
26-
import org.eclipse.jdt.core.dom.QualifiedName;
27-
import org.eclipse.jdt.core.dom.SimpleName;
2827

29-
/** Collects unused imports, so they can be commented out, {@see GwtIncompatibleStripper}. */
30-
class UnusedImportsNodeCollector extends ASTVisitor {
31-
private List<ImportDeclaration> unusedImports = new ArrayList<>();
32-
private Set<String> referencedNames = new HashSet<>();
28+
/** Collects unused imports, so they can be commented out. */
29+
class UnusedImportsNodeCollector extends TreeScanner<Void, Void> {
30+
private final List<ImportTree> unusedImports = new ArrayList<>();
31+
private final Set<String> referencedNames = new HashSet<>();
32+
private final Set<Tree> nodesToRemove;
3333

34-
@Override
35-
public boolean visit(ImportDeclaration importDeclaration) {
36-
// Prevent visiting the names inside the import themselves.
37-
return false;
34+
public UnusedImportsNodeCollector(Set<Tree> nodesToRemove) {
35+
this.nodesToRemove = nodesToRemove;
3836
}
3937

4038
@Override
41-
public boolean visit(QualifiedName qualifiedName) {
42-
// We need the first component of the qualified name for example for Foo.Bar.baz
43-
// we are looking for the import of Foo.
44-
Name qualifier = qualifiedName.getQualifier();
45-
while (!qualifier.isSimpleName()) {
46-
qualifier = ((QualifiedName) qualifier).getQualifier();
39+
public Void scan(Tree tree, Void unused) {
40+
if (tree != null && nodesToRemove.contains(tree)) {
41+
return null; // Do not scan children of stripped nodes.
4742
}
48-
referencedNames.add(qualifier.getFullyQualifiedName());
49-
return false;
43+
return super.scan(tree, null);
5044
}
5145

5246
@Override
53-
public boolean visit(SimpleName simpleName) {
54-
referencedNames.add(simpleName.getIdentifier());
55-
return false;
47+
public Void visitImport(ImportTree importTree, Void unused) {
48+
// Prevent visiting the names inside the import themselves.
49+
return null;
5650
}
5751

58-
@SuppressWarnings("unchecked")
5952
@Override
60-
public void endVisit(CompilationUnit compilationUnit) {
61-
List<ImportDeclaration> imports = compilationUnit.imports();
62-
for (ImportDeclaration importDeclaration : imports) {
63-
if (importDeclaration.isOnDemand()) {
53+
public Void visitIdentifier(IdentifierTree node, Void unused) {
54+
referencedNames.add(node.getName().toString());
55+
return null;
56+
}
57+
58+
@Override
59+
public Void visitCompilationUnit(CompilationUnitTree compilationUnitTree, Void unused) {
60+
if (compilationUnitTree.getPackage() != null) {
61+
scan(compilationUnitTree.getPackage().getAnnotations(), null);
62+
}
63+
super.visitCompilationUnit(compilationUnitTree, null);
64+
65+
for (ImportTree importTree : compilationUnitTree.getImports()) {
66+
String importString = importTree.getQualifiedIdentifier().toString();
67+
if (importString.endsWith(".*")) {
6468
// Assume .* imports are always needed.
6569
continue;
6670
}
67-
SimpleName importedClass =
68-
importDeclaration.getName().isSimpleName()
69-
? (SimpleName) importDeclaration.getName()
70-
: ((QualifiedName) importDeclaration.getName()).getName();
7171

72-
if (!referencedNames.contains(importedClass.getIdentifier())) {
73-
unusedImports.add(importDeclaration);
72+
String importedClass = importString.substring(importString.lastIndexOf('.') + 1);
73+
74+
if (!referencedNames.contains(importedClass)) {
75+
unusedImports.add(importTree);
7476
}
7577
}
78+
return null;
7679
}
7780

78-
public List<ImportDeclaration> getUnusedImports() {
81+
public List<ImportTree> getUnusedImports() {
7982
return unusedImports;
8083
}
8184
}

tools/javatests/com/google/j2cl/tools/gwtincompatible/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ java_test(
2020
srcs = ["UnusedImportsNodeCollectorTest.java"],
2121
deps = [
2222
"//third_party:guava",
23-
"//third_party:jdt-core",
2423
"//third_party:junit",
2524
"//tools/java/com/google/j2cl/tools/gwtincompatible:gwtincompatible_lib",
2625
],

0 commit comments

Comments
 (0)