1717import static com .google .common .collect .ImmutableList .toImmutableList ;
1818import static java .nio .charset .StandardCharsets .UTF_8 ;
1919
20+ import com .google .common .collect .ImmutableList ;
2021import com .google .common .collect .ImmutableSet ;
2122import com .google .common .collect .Lists ;
2223import com .google .common .io .MoreFiles ;
2627import com .google .j2cl .common .Problems .FatalError ;
2728import com .google .j2cl .common .SourceUtils ;
2829import 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 ;
3043import java .io .IOException ;
44+ import java .io .UncheckedIOException ;
45+ import java .net .URI ;
3146import java .nio .file .Path ;
3247import java .nio .file .Paths ;
33- import java .util .Comparator ;
34- import java .util .HashMap ;
3548import java .util .List ;
36- import java .util .Map ;
3749import 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}
0 commit comments