Skip to content

Commit

Permalink
Prepare g-j-f for some internal javac API changes
Browse files Browse the repository at this point in the history
* Refactor comment handling in tokenization to avoid extending javac's internal Comment. This prepares for upcoming JDK changes to the Comment API, which break the current subclass approach by changing the return type of `Comment#getPos`, see also similar changes to Error Prone in unknown commit
* `JCCompilationUnit#getImports` no longer returns `JCImport`, as part of the changes for modular imports. Supporting modular imports is future work, this change just avoids a crash formatting the existing syntax on the latest JDK versions.

PiperOrigin-RevId: 638669955
  • Loading branch information
cushon authored and google-java-format Team committed May 30, 2024
1 parent b9b6730 commit 0b384e3
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@
package com.google.googlejavaformat.java;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.sun.tools.javac.parser.JavaTokenizer;
import com.sun.tools.javac.parser.Scanner;
import com.sun.tools.javac.parser.ScannerFactory;
import com.sun.tools.javac.parser.Tokens.Comment;
import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
import com.sun.tools.javac.parser.Tokens.Token;
import com.sun.tools.javac.parser.Tokens.TokenKind;
import com.sun.tools.javac.parser.UnicodeReader;
import com.sun.tools.javac.util.Context;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/** A wrapper around javac's lexer. */
Expand Down Expand Up @@ -79,16 +80,16 @@ public static ImmutableList<RawTok> getTokens(
}
ScannerFactory fac = ScannerFactory.instance(context);
char[] buffer = (source + EOF_COMMENT).toCharArray();
Scanner scanner =
new AccessibleScanner(fac, new CommentSavingTokenizer(fac, buffer, buffer.length));
CommentSavingTokenizer tokenizer = new CommentSavingTokenizer(fac, buffer, buffer.length);
Scanner scanner = new AccessibleScanner(fac, tokenizer);
ImmutableList.Builder<RawTok> tokens = ImmutableList.builder();
int end = source.length();
int last = 0;
do {
scanner.nextToken();
Token t = scanner.token();
if (t.comments != null) {
for (Comment c : Lists.reverse(t.comments)) {
for (CommentWithTextAndPosition c : getComments(t, tokenizer.comments())) {
if (last < c.getSourcePos(0)) {
tokens.add(new RawTok(null, null, last, c.getSourcePos(0)));
}
Expand Down Expand Up @@ -120,17 +121,36 @@ public static ImmutableList<RawTok> getTokens(
return tokens.build();
}

private static ImmutableList<CommentWithTextAndPosition> getComments(
Token token, Map<Comment, CommentWithTextAndPosition> comments) {
if (token.comments == null) {
return ImmutableList.of();
}
// javac stores the comments in reverse declaration order
return token.comments.stream().map(comments::get).collect(toImmutableList()).reverse();
}

/** A {@link JavaTokenizer} that saves comments. */
static class CommentSavingTokenizer extends JavaTokenizer {

private final Map<Comment, CommentWithTextAndPosition> comments = new HashMap<>();

CommentSavingTokenizer(ScannerFactory fac, char[] buffer, int length) {
super(fac, buffer, length);
}

Map<Comment, CommentWithTextAndPosition> comments() {
return comments;
}

@Override
protected Comment processComment(int pos, int endPos, CommentStyle style) {
char[] buf = getRawCharactersReflectively(pos, endPos);
return new CommentWithTextAndPosition(
pos, endPos, new AccessibleReader(fac, buf, buf.length), style);
Comment comment = super.processComment(pos, endPos, style);
CommentWithTextAndPosition commentWithTextAndPosition =
new CommentWithTextAndPosition(pos, endPos, new String(buf));
comments.put(comment, commentWithTextAndPosition);
return comment;
}

private char[] getRawCharactersReflectively(int beginIndex, int endIndex) {
Expand All @@ -153,21 +173,16 @@ private char[] getRawCharactersReflectively(int beginIndex, int endIndex) {
}

/** A {@link Comment} that saves its text and start position. */
static class CommentWithTextAndPosition implements Comment {
static class CommentWithTextAndPosition {

private final int pos;
private final int endPos;
private final AccessibleReader reader;
private final CommentStyle style;

private String text = null;
private final String text;

public CommentWithTextAndPosition(
int pos, int endPos, AccessibleReader reader, CommentStyle style) {
public CommentWithTextAndPosition(int pos, int endPos, String text) {
this.pos = pos;
this.endPos = endPos;
this.reader = reader;
this.style = style;
this.text = text;
}

/**
Expand All @@ -176,7 +191,6 @@ public CommentWithTextAndPosition(
* <p>The handling of javadoc comments in javac has more logic to skip over leading whitespace
* and '*' characters when indexing into doc comments, but we don't need any of that.
*/
@Override
public int getSourcePos(int index) {
checkArgument(
0 <= index && index < (endPos - pos),
Expand All @@ -186,49 +200,22 @@ public int getSourcePos(int index) {
return pos + index;
}

@Override
public CommentStyle getStyle() {
return style;
}

@Override
public String getText() {
String text = this.text;
if (text == null) {
this.text = text = new String(reader.getRawCharacters());
}
return text;
}

/**
* We don't care about {@code @deprecated} javadoc tags (see the DepAnn check).
*
* @return false
*/
@Override
public boolean isDeprecated() {
return false;
}

@Override
public String toString() {
return String.format("Comment: '%s'", getText());
}
}

// Scanner(ScannerFactory, JavaTokenizer) is package-private
// Scanner(ScannerFactory, JavaTokenizer) is protected
static class AccessibleScanner extends Scanner {
protected AccessibleScanner(ScannerFactory fac, JavaTokenizer tokenizer) {
super(fac, tokenizer);
}
}

// UnicodeReader(ScannerFactory, char[], int) is package-private
static class AccessibleReader extends UnicodeReader {
protected AccessibleReader(ScannerFactory fac, char[] buffer, int length) {
super(fac, buffer, length);
}
}

private JavacTokens() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ private static RangeMap<Integer, String> buildReplacements(
Set<String> usedNames,
Multimap<String, Range<Integer>> usedInJavadoc) {
RangeMap<Integer, String> replacements = TreeRangeMap.create();
for (JCImport importTree : unit.getImports()) {
for (JCTree importTree : unit.getImports()) {
String simpleName = getSimpleName(importTree);
if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) {
continue;
Expand All @@ -291,15 +291,15 @@ private static RangeMap<Integer, String> buildReplacements(
return replacements;
}

private static String getSimpleName(JCImport importTree) {
private static String getSimpleName(JCTree importTree) {
return getQualifiedIdentifier(importTree).getIdentifier().toString();
}

private static boolean isUnused(
JCCompilationUnit unit,
Set<String> usedNames,
Multimap<String, Range<Integer>> usedInJavadoc,
JCImport importTree,
JCTree importTree,
String simpleName) {
JCFieldAccess qualifiedIdentifier = getQualifiedIdentifier(importTree);
String qualifier = qualifiedIdentifier.getExpression().toString();
Expand All @@ -322,7 +322,7 @@ private static boolean isUnused(
return true;
}

private static JCFieldAccess getQualifiedIdentifier(JCImport importTree) {
private static JCFieldAccess getQualifiedIdentifier(JCTree importTree) {
// Use reflection because the return type is JCTree in some versions and JCFieldAccess in others
try {
return (JCFieldAccess) JCImport.class.getMethod("getQualifiedIdentifier").invoke(importTree);
Expand Down

0 comments on commit 0b384e3

Please sign in to comment.