diff options
Diffstat (limited to 'core/src/main/java/com/google/googlejavaformat')
18 files changed, 484 insertions, 227 deletions
diff --git a/core/src/main/java/com/google/googlejavaformat/CommentsHelper.java b/core/src/main/java/com/google/googlejavaformat/CommentsHelper.java index 45e507b..1e33003 100644 --- a/core/src/main/java/com/google/googlejavaformat/CommentsHelper.java +++ b/core/src/main/java/com/google/googlejavaformat/CommentsHelper.java @@ -14,6 +14,10 @@ package com.google.googlejavaformat; +import com.google.googlejavaformat.Input.Tok; +import java.util.Optional; +import java.util.regex.Pattern; + /** * Rewrite comments. This interface is implemented by {@link * com.google.googlejavaformat.java.JavaCommentsHelper JavaCommentsHelper}. @@ -28,4 +32,19 @@ public interface CommentsHelper { * @return the rewritten comment */ String rewrite(Input.Tok tok, int maxWidth, int column0); + + static Optional<String> reformatParameterComment(Tok tok) { + if (!tok.isSlashStarComment()) { + return Optional.empty(); + } + var match = PARAMETER_COMMENT.matcher(tok.getOriginalText()); + if (!match.matches()) { + return Optional.empty(); + } + return Optional.of(String.format("/* %s= */", match.group(1))); + } + + Pattern PARAMETER_COMMENT = + Pattern.compile( + "/\\*\\s*(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\Q...\\E)?)\\s*=\\s*\\*/"); } diff --git a/core/src/main/java/com/google/googlejavaformat/Doc.java b/core/src/main/java/com/google/googlejavaformat/Doc.java index 35acca3..cab6885 100644 --- a/core/src/main/java/com/google/googlejavaformat/Doc.java +++ b/core/src/main/java/com/google/googlejavaformat/Doc.java @@ -15,9 +15,12 @@ package com.google.googlejavaformat; import static com.google.common.collect.Iterables.getLast; +import static com.google.googlejavaformat.CommentsHelper.reformatParameterComment; import static java.lang.Math.max; import com.google.common.base.MoreObjects; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import com.google.common.collect.DiscreteDomain; import com.google.common.collect.Iterators; import com.google.common.collect.Range; @@ -101,16 +104,13 @@ public abstract class Doc { private static final DiscreteDomain<Integer> INTEGERS = DiscreteDomain.integers(); // Memoized width; Float.POSITIVE_INFINITY if contains forced breaks. - private boolean widthComputed = false; - private float width = 0.0F; + private final Supplier<Float> width = Suppliers.memoize(this::computeWidth); // Memoized flat; not defined (and never computed) if contains forced breaks. - private boolean flatComputed = false; - private String flat = ""; + private final Supplier<String> flat = Suppliers.memoize(this::computeFlat); // Memoized Range. - private boolean rangeComputed = false; - private Range<Integer> range = EMPTY_RANGE; + private final Supplier<Range<Integer>> range = Suppliers.memoize(this::computeRange); /** * Return the width of a {@code Doc}, or {@code Float.POSITIVE_INFINITY} if it must be broken. @@ -118,11 +118,7 @@ public abstract class Doc { * @return the width */ final float getWidth() { - if (!widthComputed) { - width = computeWidth(); - widthComputed = true; - } - return width; + return width.get(); } /** @@ -132,11 +128,7 @@ public abstract class Doc { * @return the flat-string value */ final String getFlat() { - if (!flatComputed) { - flat = computeFlat(); - flatComputed = true; - } - return flat; + return flat.get(); } /** @@ -145,11 +137,7 @@ public abstract class Doc { * @return the {@code Doc}'s {@link Range} */ final Range<Integer> range() { - if (!rangeComputed) { - range = computeRange(); - rangeComputed = true; - } - return range; + return range.get(); } /** @@ -727,7 +715,7 @@ public abstract class Doc { // Account for line comments with missing spaces, see computeFlat. return tok.length() + 1; } else { - return tok.length(); + return reformatParameterComment(tok).map(String::length).orElse(tok.length()); } } return idx != -1 ? Float.POSITIVE_INFINITY : (float) tok.length(); @@ -741,7 +729,7 @@ public abstract class Doc { if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) { return "// " + tok.getOriginalText().substring("//".length()); } - return tok.getOriginalText(); + return reformatParameterComment(tok).orElse(tok.getOriginalText()); } @Override diff --git a/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java b/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java index db431c0..a45e83b 100644 --- a/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java +++ b/core/src/main/java/com/google/googlejavaformat/OpsBuilder.java @@ -159,7 +159,7 @@ public final class OpsBuilder { int depth = 0; /** Add an {@link Op}, and record open/close ops for later validation of unclosed levels. */ - private void add(Op op) { + public final void add(Op op) { if (op instanceof OpenOp) { depth++; } else if (op instanceof CloseOp) { diff --git a/core/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java b/core/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java index 9d8ae41..3d68a23 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java +++ b/core/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java @@ -14,40 +14,73 @@ package com.google.googlejavaformat.java; +import com.google.auto.value.AutoValue; import com.google.common.collect.Range; import com.google.common.collect.RangeSet; import com.google.common.collect.TreeRangeSet; +import java.nio.file.Path; import java.util.concurrent.Callable; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Encapsulates information about a file to be formatted, including which parts of the file to * format. */ -class FormatFileCallable implements Callable<String> { +class FormatFileCallable implements Callable<FormatFileCallable.Result> { + + @AutoValue + abstract static class Result { + abstract @Nullable Path path(); + + abstract String input(); + + abstract @Nullable String output(); + + boolean changed() { + return !input().equals(output()); + } + + abstract @Nullable FormatterException exception(); + + static Result create( + @Nullable Path path, + String input, + @Nullable String output, + @Nullable FormatterException exception) { + return new AutoValue_FormatFileCallable_Result(path, input, output, exception); + } + } + + private final Path path; private final String input; private final CommandLineOptions parameters; private final JavaFormatterOptions options; public FormatFileCallable( - CommandLineOptions parameters, String input, JavaFormatterOptions options) { + CommandLineOptions parameters, Path path, String input, JavaFormatterOptions options) { + this.path = path; this.input = input; this.parameters = parameters; this.options = options; } @Override - public String call() throws FormatterException { - if (parameters.fixImportsOnly()) { - return fixImports(input); - } + public Result call() { + try { + if (parameters.fixImportsOnly()) { + return Result.create(path, input, fixImports(input), /* exception= */ null); + } - Formatter formatter = new Formatter(options); - String formatted = formatter.formatSource(input, characterRanges(input).asRanges()); - formatted = fixImports(formatted); - if (parameters.reflowLongStrings()) { - formatted = StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, formatted, formatter); + Formatter formatter = new Formatter(options); + String formatted = formatter.formatSource(input, characterRanges(input).asRanges()); + formatted = fixImports(formatted); + if (parameters.reflowLongStrings()) { + formatted = StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, formatted, formatter); + } + return Result.create(path, input, formatted, /* exception= */ null); + } catch (FormatterException e) { + return Result.create(path, input, /* output= */ null, e); } - return formatted; } private String fixImports(String input) throws FormatterException { diff --git a/core/src/main/java/com/google/googlejavaformat/java/Formatter.java b/core/src/main/java/com/google/googlejavaformat/java/Formatter.java index aac829d..5aa7a12 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/Formatter.java +++ b/core/src/main/java/com/google/googlejavaformat/java/Formatter.java @@ -136,9 +136,9 @@ public final class Formatter { JavacParser parser = parserFactory.newParser( javaInput.getText(), - /*keepDocComments=*/ true, - /*keepEndPos=*/ true, - /*keepLineMap=*/ true); + /* keepDocComments= */ true, + /* keepEndPos= */ true, + /* keepLineMap= */ true); unit = parser.parseCompilationUnit(); unit.sourcefile = source; @@ -151,16 +151,14 @@ public final class Formatter { OpsBuilder builder = new OpsBuilder(javaInput, javaOutput); // Output the compilation unit. JavaInputAstVisitor visitor; - if (Runtime.version().feature() >= 14) { - try { - visitor = - Class.forName("com.google.googlejavaformat.java.java14.Java14InputAstVisitor") - .asSubclass(JavaInputAstVisitor.class) - .getConstructor(OpsBuilder.class, int.class) - .newInstance(builder, options.indentationMultiplier()); - } catch (ReflectiveOperationException e) { - throw new LinkageError(e.getMessage(), e); - } + if (Runtime.version().feature() >= 21) { + visitor = + createVisitor( + "com.google.googlejavaformat.java.java21.Java21InputAstVisitor", builder, options); + } else if (Runtime.version().feature() >= 17) { + visitor = + createVisitor( + "com.google.googlejavaformat.java.java17.Java17InputAstVisitor", builder, options); } else { visitor = new JavaInputAstVisitor(builder, options.indentationMultiplier()); } @@ -173,6 +171,18 @@ public final class Formatter { javaOutput.flush(); } + private static JavaInputAstVisitor createVisitor( + final String className, final OpsBuilder builder, final JavaFormatterOptions options) { + try { + return Class.forName(className) + .asSubclass(JavaInputAstVisitor.class) + .getConstructor(OpsBuilder.class, int.class) + .newInstance(builder, options.indentationMultiplier()); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + static boolean errorDiagnostic(Diagnostic<?> input) { if (input.getKind() != Diagnostic.Kind.ERROR) { return false; @@ -262,7 +272,9 @@ public final class Formatter { // TODO(cushon): this is only safe because the modifier ordering doesn't affect whitespace, // and doesn't change the replacements that are output. This is not true in general for // 'de-linting' changes (e.g. import ordering). - javaInput = ModifierOrderer.reorderModifiers(javaInput, characterRanges); + if (options.reorderModifiers()) { + javaInput = ModifierOrderer.reorderModifiers(javaInput, characterRanges); + } String lineSeparator = Newlines.guessLineSeparator(input); JavaOutput javaOutput = diff --git a/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatTool.java b/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatTool.java new file mode 100644 index 0000000..3c315aa --- /dev/null +++ b/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatTool.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.googlejavaformat.java; + +import static com.google.common.collect.Sets.toImmutableEnumSet; + +import com.google.auto.service.AutoService; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Set; +import javax.lang.model.SourceVersion; +import javax.tools.Tool; + +/** Provide a way to be invoked without necessarily starting a new VM. */ +@AutoService(Tool.class) +public class GoogleJavaFormatTool implements Tool { + @Override + public String name() { + return "google-java-format"; + } + + @Override + public Set<SourceVersion> getSourceVersions() { + return Arrays.stream(SourceVersion.values()).collect(toImmutableEnumSet()); + } + + @Override + public int run(InputStream in, OutputStream out, OutputStream err, String... args) { + PrintStream outStream = new PrintStream(out); + PrintStream errStream = new PrintStream(err); + try { + return Main.main(in, outStream, errStream, args); + } catch (RuntimeException e) { + errStream.print(e.getMessage()); + errStream.flush(); + return 1; // pass non-zero value back indicating an error has happened + } + } +} diff --git a/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatToolProvider.java b/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatToolProvider.java index 7bcad4c..438eac5 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatToolProvider.java +++ b/core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatToolProvider.java @@ -29,10 +29,11 @@ public class GoogleJavaFormatToolProvider implements ToolProvider { @Override public int run(PrintWriter out, PrintWriter err, String... args) { try { - return Main.main(out, err, args); + return Main.main(System.in, out, err, args); } catch (RuntimeException e) { err.print(e.getMessage()); - return -1; // pass non-zero value back indicating an error has happened + err.flush(); + return 1; // pass non-zero value back indicating an error has happened } } } diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java b/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java index 346324a..d34ecc4 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java @@ -53,11 +53,13 @@ public final class JavaCommentsHelper implements CommentsHelper { } if (tok.isSlashSlashComment()) { return indentLineComments(lines, column0); - } else if (javadocShaped(lines)) { - return indentJavadoc(lines, column0); - } else { - return preserveIndentation(lines, column0); } + return CommentsHelper.reformatParameterComment(tok) + .orElseGet( + () -> + javadocShaped(lines) + ? indentJavadoc(lines, column0) + : preserveIndentation(lines, column0)); } // For non-javadoc-shaped block comments, shift the entire block to the correct diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java b/core/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java index fbb6fe7..67c13d0 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java @@ -14,6 +14,7 @@ package com.google.googlejavaformat.java; +import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.Immutable; /** @@ -27,7 +28,8 @@ import com.google.errorprone.annotations.Immutable; * preferences, and in fact it would work directly against our primary goals. */ @Immutable -public class JavaFormatterOptions { +@AutoValue +public abstract class JavaFormatterOptions { public enum Style { /** The default Google Java Style configuration. */ @@ -47,27 +49,17 @@ public class JavaFormatterOptions { } } - private final Style style; - private final boolean formatJavadoc; - - private JavaFormatterOptions(Style style, boolean formatJavadoc) { - this.style = style; - this.formatJavadoc = formatJavadoc; - } - /** Returns the multiplier for the unit of indent. */ public int indentationMultiplier() { - return style.indentationMultiplier(); + return style().indentationMultiplier(); } - public boolean formatJavadoc() { - return formatJavadoc; - } + public abstract boolean formatJavadoc(); + + public abstract boolean reorderModifiers(); /** Returns the code style. */ - public Style style() { - return style; - } + public abstract Style style(); /** Returns the default formatting options. */ public static JavaFormatterOptions defaultOptions() { @@ -76,28 +68,22 @@ public class JavaFormatterOptions { /** Returns a builder for {@link JavaFormatterOptions}. */ public static Builder builder() { - return new Builder(); + return new AutoValue_JavaFormatterOptions.Builder() + .style(Style.GOOGLE) + .formatJavadoc(true) + .reorderModifiers(true); } /** A builder for {@link JavaFormatterOptions}. */ - public static class Builder { - private Style style = Style.GOOGLE; - private boolean formatJavadoc = true; + @AutoValue.Builder + public abstract static class Builder { - private Builder() {} + public abstract Builder style(Style style); - public Builder style(Style style) { - this.style = style; - return this; - } + public abstract Builder formatJavadoc(boolean formatJavadoc); - public Builder formatJavadoc(boolean formatJavadoc) { - this.formatJavadoc = formatJavadoc; - return this; - } + public abstract Builder reorderModifiers(boolean reorderModifiers); - public JavaFormatterOptions build() { - return new JavaFormatterOptions(style, formatJavadoc); - } + public abstract JavaFormatterOptions build(); } } diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java b/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java index 165bdeb..7b5eb84 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java @@ -49,6 +49,7 @@ import java.util.List; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.DiagnosticListener; +import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import javax.tools.SimpleJavaFileObject; @@ -349,7 +350,8 @@ public final class JavaInput extends Input { stopTokens = ImmutableSet.<TokenKind>builder().addAll(stopTokens).add(TokenKind.EOF).build(); Context context = new Context(); Options.instance(context).put("--enable-preview", "true"); - new JavacFileManager(context, true, UTF_8); + JavaFileManager fileManager = new JavacFileManager(context, false, UTF_8); + context.put(JavaFileManager.class, fileManager); DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>(); context.put(DiagnosticListener.class, diagnosticCollector); Log log = Log.instance(context); @@ -385,7 +387,14 @@ public final class JavaInput extends Input { final boolean isNumbered; // Is this tok numbered? (tokens and comments) String extraNewline = null; // Extra newline at end? List<String> strings = new ArrayList<>(); - if (Character.isWhitespace(tokText0)) { + if (tokText.startsWith("'") + || tokText.startsWith("\"") + || JavacTokens.isStringFragment(t.kind())) { + // Perform this check first, STRINGFRAGMENT tokens can start with arbitrary characters. + isToken = true; + isNumbered = true; + strings.add(originalTokText); + } else if (Character.isWhitespace(tokText0)) { isToken = false; isNumbered = false; Iterator<String> it = Newlines.lineIterator(originalTokText); @@ -402,10 +411,6 @@ public final class JavaInput extends Input { strings.add(line); } } - } else if (tokText.startsWith("'") || tokText.startsWith("\"")) { - isToken = true; - isNumbered = true; - strings.add(originalTokText); } else if (tokText.startsWith("//") || tokText.startsWith("/*")) { // For compatibility with an earlier lexer, the newline after a // comment is its own tok. if (tokText.startsWith("//") diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java index daed250..ea967b3 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java @@ -285,7 +285,10 @@ public class JavaInputAstVisitor extends TreePathScanner<Void, Void> { ImmutableSetMultimap.Builder<String, String> result = ImmutableSetMultimap.builder(); for (String annotation : ImmutableList.of( + "org.jspecify.annotations.NonNull", + "org.jspecify.annotations.Nullable", "org.jspecify.nullness.Nullable", + "org.checkerframework.checker.nullness.qual.NonNull", "org.checkerframework.checker.nullness.qual.Nullable")) { String simpleName = annotation.substring(annotation.lastIndexOf('.') + 1); result.put(simpleName, annotation); @@ -927,7 +930,6 @@ public class JavaInputAstVisitor extends TreePathScanner<Void, Void> { @Override public Void visitMemberReference(MemberReferenceTree node, Void unused) { - sync(node); builder.open(plusFour); scan(node.getQualifierExpression(), null); builder.breakOp(); @@ -1247,7 +1249,10 @@ public class JavaInputAstVisitor extends TreePathScanner<Void, Void> { token(","); builder.breakOp(" "); } - scan(parameter, null); + visitVariables( + ImmutableList.of(parameter), + DeclarationKind.NONE, + fieldAnnotationDirection(parameter.getModifiers())); first = false; } if (parens) { @@ -2639,7 +2644,7 @@ public class JavaInputAstVisitor extends TreePathScanner<Void, Void> { for (ExpressionTree thrownExceptionType : thrownExceptionTypes) { if (!first) { token(","); - builder.breakToFill(" "); + builder.breakOp(" "); } scan(thrownExceptionType, null); first = false; @@ -3558,7 +3563,7 @@ public class JavaInputAstVisitor extends TreePathScanner<Void, Void> { if (receiverExpression.isPresent()) { scan(receiverExpression.get(), null); } else { - visit(name); + variableName(name); } builder.op(op); } @@ -3601,6 +3606,10 @@ public class JavaInputAstVisitor extends TreePathScanner<Void, Void> { return baseDims; } + protected void variableName(Name name) { + visit(name); + } + private void maybeAddDims(Deque<List<? extends AnnotationTree>> annotations) { maybeAddDims(new ArrayDeque<>(), annotations); } @@ -3691,7 +3700,7 @@ public class JavaInputAstVisitor extends TreePathScanner<Void, Void> { builder.breakOp(" "); builder.open(ZERO); maybeAddDims(dims); - visit(fragment.getName()); + variableName(fragment.getName()); maybeAddDims(dims); ExpressionTree initializer = fragment.getInitializer(); if (initializer != null) { diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java b/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java index c43a91a..656b65c 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaOutput.java @@ -111,7 +111,7 @@ public final class JavaOutput extends Output { * there's a blank line here and it's a comment. */ BlankLineWanted wanted = blankLines.getOrDefault(lastK, BlankLineWanted.NO); - if (isComment(text) ? sawNewlines : wanted.wanted().orElse(sawNewlines)) { + if ((sawNewlines && isComment(text)) || wanted.wanted().orElse(sawNewlines)) { ++newlinesPending; } } diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavacTokens.java b/core/src/main/java/com/google/googlejavaformat/java/JavacTokens.java index ba7e3b7..dd8760b 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavacTokens.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavacTokens.java @@ -15,6 +15,7 @@ package com.google.googlejavaformat.java; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Arrays.stream; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -27,6 +28,7 @@ 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.Objects; import java.util.Set; /** A wrapper around javac's lexer. */ @@ -71,6 +73,16 @@ class JavacTokens { } } + private static final TokenKind STRINGFRAGMENT = + stream(TokenKind.values()) + .filter(t -> t.name().contentEquals("STRINGFRAGMENT")) + .findFirst() + .orElse(null); + + static boolean isStringFragment(TokenKind kind) { + return STRINGFRAGMENT != null && Objects.equals(kind, STRINGFRAGMENT); + } + /** Lex the input and return a list of {@link RawTok}s. */ public static ImmutableList<RawTok> getTokens( String source, Context context, Set<TokenKind> stopTokens) { @@ -106,13 +118,39 @@ class JavacTokens { if (last < t.pos) { tokens.add(new RawTok(null, null, last, t.pos)); } - tokens.add( - new RawTok( - t.kind == TokenKind.STRINGLITERAL ? "\"" + t.stringVal() + "\"" : null, - t.kind, - t.pos, - t.endPos)); - last = t.endPos; + int pos = t.pos; + int endPos = t.endPos; + if (isStringFragment(t.kind)) { + // A string template is tokenized as a series of STRINGFRAGMENT tokens containing the string + // literal values, followed by the tokens for the template arguments. For the formatter, we + // want the stream of tokens to appear in order by their start position, and also to have + // all the content from the original source text (including leading and trailing ", and the + // \ escapes from template arguments). This logic processes the token stream from javac to + // meet those requirements. + while (isStringFragment(t.kind)) { + endPos = t.endPos; + scanner.nextToken(); + t = scanner.token(); + } + // Read tokens for the string template arguments, until we read the end of the string + // template. The last token in a string template is always a trailing string fragment. Use + // lookahead to defer reading the token after the template until the next iteration of the + // outer loop. + while (scanner.token(/* lookahead= */ 1).endPos < endPos) { + scanner.nextToken(); + t = scanner.token(); + } + tokens.add(new RawTok(source.substring(pos, endPos), t.kind, pos, endPos)); + last = endPos; + } else { + tokens.add( + new RawTok( + t.kind == TokenKind.STRINGLITERAL ? "\"" + t.stringVal() + "\"" : null, + t.kind, + t.pos, + t.endPos)); + last = t.endPos; + } } while (scanner.token().kind != TokenKind.EOF); if (last < end) { tokens.add(new RawTok(null, null, last, end)); diff --git a/core/src/main/java/com/google/googlejavaformat/java/Main.java b/core/src/main/java/com/google/googlejavaformat/java/Main.java index 953ca58..0845e0e 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/Main.java +++ b/core/src/main/java/com/google/googlejavaformat/java/Main.java @@ -16,25 +16,30 @@ package com.google.googlejavaformat.java; import static java.lang.Math.min; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Comparator.comparing; import com.google.common.io.ByteStreams; +import com.google.common.util.concurrent.MoreExecutors; import com.google.googlejavaformat.FormatterDiagnostic; import com.google.googlejavaformat.java.JavaFormatterOptions.Style; import java.io.IOError; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; +import java.io.PrintStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.Collections; +import java.util.List; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; /** The main class for the Java formatter CLI. */ public final class Main { @@ -62,24 +67,33 @@ public final class Main { * * @param args the command-line arguments */ - public static void main(String[] args) { - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out, UTF_8)); - PrintWriter err = new PrintWriter(new OutputStreamWriter(System.err, UTF_8)); - int result = main(out, err, args); + public static void main(String... args) { + int result = main(System.in, System.out, System.err, args); System.exit(result); } /** - * Package-private main entry point used this CLI program and the java.util.spi.ToolProvider + * Package-private main entry point used by the {@link javax.tools.Tool Tool} implementation in + * the same package as this Main class. + */ + static int main(InputStream in, PrintStream out, PrintStream err, String... args) { + PrintWriter outWriter = new PrintWriter(new OutputStreamWriter(out, UTF_8)); + PrintWriter errWriter = new PrintWriter(new OutputStreamWriter(err, UTF_8)); + return main(in, outWriter, errWriter, args); + } + + /** + * Package-private main entry point used by the {@link java.util.spi.ToolProvider ToolProvider} * implementation in the same package as this Main class. */ - static int main(PrintWriter out, PrintWriter err, String... args) { + static int main(InputStream in, PrintWriter out, PrintWriter err, String... args) { try { - Main formatter = new Main(out, err, System.in); + Main formatter = new Main(out, err, in); return formatter.format(args); } catch (UsageException e) { err.print(e.getMessage()); - return 0; + // We return exit code 2 to differentiate usage issues from code formatting issues. + return 2; } finally { err.flush(); out.flush(); @@ -120,50 +134,55 @@ public final class Main { int numThreads = min(MAX_THREADS, parameters.files().size()); ExecutorService executorService = Executors.newFixedThreadPool(numThreads); - Map<Path, String> inputs = new LinkedHashMap<>(); - Map<Path, Future<String>> results = new LinkedHashMap<>(); + ExecutorCompletionService<FormatFileCallable.Result> cs = + new ExecutorCompletionService<>(executorService); boolean allOk = true; + int files = 0; for (String fileName : parameters.files()) { if (!fileName.endsWith(".java")) { errWriter.println("Skipping non-Java file: " + fileName); continue; } Path path = Paths.get(fileName); - String input; try { - input = new String(Files.readAllBytes(path), UTF_8); - inputs.put(path, input); - results.put( - path, executorService.submit(new FormatFileCallable(parameters, input, options))); + String input = new String(Files.readAllBytes(path), UTF_8); + cs.submit(new FormatFileCallable(parameters, path, input, options)); + files++; } catch (IOException e) { errWriter.println(fileName + ": could not read file: " + e.getMessage()); allOk = false; } } - for (Map.Entry<Path, Future<String>> result : results.entrySet()) { - Path path = result.getKey(); - String formatted; + List<FormatFileCallable.Result> results = new ArrayList<>(); + while (files > 0) { try { - formatted = result.getValue().get(); + files--; + results.add(cs.take().get()); } catch (InterruptedException e) { errWriter.println(e.getMessage()); allOk = false; continue; } catch (ExecutionException e) { - if (e.getCause() instanceof FormatterException) { - for (FormatterDiagnostic diagnostic : ((FormatterException) e.getCause()).diagnostics()) { - errWriter.println(path + ":" + diagnostic); - } - } else { - errWriter.println(path + ": error: " + e.getCause().getMessage()); - e.getCause().printStackTrace(errWriter); + errWriter.println("error: " + e.getCause().getMessage()); + e.getCause().printStackTrace(errWriter); + allOk = false; + continue; + } + } + Collections.sort(results, comparing(FormatFileCallable.Result::path)); + for (FormatFileCallable.Result result : results) { + Path path = result.path(); + if (result.exception() != null) { + for (FormatterDiagnostic diagnostic : result.exception().diagnostics()) { + errWriter.println(path + ":" + diagnostic); } allOk = false; continue; } - boolean changed = !formatted.equals(inputs.get(path)); + String formatted = result.output(); + boolean changed = result.changed(); if (changed && parameters.setExitIfChanged()) { allOk = false; } @@ -186,6 +205,10 @@ public final class Main { outWriter.write(formatted); } } + if (!MoreExecutors.shutdownAndAwaitTermination(executorService, Duration.ofSeconds(5))) { + errWriter.println("Failed to shut down ExecutorService"); + allOk = false; + } return allOk ? 0 : 1; } @@ -198,9 +221,16 @@ public final class Main { } String stdinFilename = parameters.assumeFilename().orElse(STDIN_FILENAME); boolean ok = true; - try { - String output = new FormatFileCallable(parameters, input, options).call(); - boolean changed = !input.equals(output); + FormatFileCallable.Result result = + new FormatFileCallable(parameters, null, input, options).call(); + if (result.exception() != null) { + for (FormatterDiagnostic diagnostic : result.exception().diagnostics()) { + errWriter.println(stdinFilename + ":" + diagnostic); + } + ok = false; + } else { + String output = result.output(); + boolean changed = result.changed(); if (changed && parameters.setExitIfChanged()) { ok = false; } @@ -211,12 +241,6 @@ public final class Main { } else { outWriter.write(output); } - } catch (FormatterException e) { - for (FormatterDiagnostic diagnostic : e.diagnostics()) { - errWriter.println(stdinFilename + ":" + diagnostic); - } - ok = false; - // TODO(cpovirk): Catch other types of exception (as we do in the formatFiles case). } return ok ? 0 : 1; } diff --git a/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java index 20e55e9..a0fc2f5 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java +++ b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java @@ -49,7 +49,6 @@ import com.sun.tools.javac.tree.DCTree.DCReference; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; -import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; @@ -252,7 +251,10 @@ public class RemoveUnusedImports { ParserFactory parserFactory = ParserFactory.instance(context); JavacParser parser = parserFactory.newParser( - javaInput, /*keepDocComments=*/ true, /*keepEndPos=*/ true, /*keepLineMap=*/ true); + javaInput, + /* keepDocComments= */ true, + /* keepEndPos= */ true, + /* keepLineMap= */ true); unit = parser.parseCompilationUnit(); unit.sourcefile = source; Iterable<Diagnostic<? extends JavaFileObject>> errorDiagnostics = @@ -290,9 +292,7 @@ public class RemoveUnusedImports { } private static String getSimpleName(JCImport importTree) { - return importTree.getQualifiedIdentifier() instanceof JCIdent - ? ((JCIdent) importTree.getQualifiedIdentifier()).getName().toString() - : ((JCFieldAccess) importTree.getQualifiedIdentifier()).getIdentifier().toString(); + return getQualifiedIdentifier(importTree).getIdentifier().toString(); } private static boolean isUnused( @@ -301,18 +301,15 @@ public class RemoveUnusedImports { Multimap<String, Range<Integer>> usedInJavadoc, JCImport importTree, String simpleName) { - String qualifier = - ((JCFieldAccess) importTree.getQualifiedIdentifier()).getExpression().toString(); + JCFieldAccess qualifiedIdentifier = getQualifiedIdentifier(importTree); + String qualifier = qualifiedIdentifier.getExpression().toString(); if (qualifier.equals("java.lang")) { return true; } if (unit.getPackageName() != null && unit.getPackageName().toString().equals(qualifier)) { return true; } - if (importTree.getQualifiedIdentifier() instanceof JCFieldAccess - && ((JCFieldAccess) importTree.getQualifiedIdentifier()) - .getIdentifier() - .contentEquals("*")) { + if (qualifiedIdentifier.getIdentifier().contentEquals("*")) { return false; } @@ -325,6 +322,15 @@ public class RemoveUnusedImports { return true; } + private static JCFieldAccess getQualifiedIdentifier(JCImport 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); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + /** Applies the replacements to the given source, and re-format any edited javadoc. */ private static String applyReplacements(String source, RangeMap<Integer, String> replacements) { // save non-empty fixed ranges for reformatting after fixes are applied diff --git a/core/src/main/java/com/google/googlejavaformat/java/StringWrapper.java b/core/src/main/java/com/google/googlejavaformat/java/StringWrapper.java index c0f16e9..f241ae4 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/StringWrapper.java +++ b/core/src/main/java/com/google/googlejavaformat/java/StringWrapper.java @@ -15,6 +15,7 @@ package com.google.googlejavaformat.java; import static com.google.common.collect.Iterables.getLast; +import static java.lang.Math.min; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.joining; @@ -96,7 +97,9 @@ public final class StringWrapper { if (!expected.equals(actual)) { throw new FormatterException( String.format( - "Something has gone terribly wrong. Please file a bug: " + "Something has gone terribly wrong. We planned to make the below formatting change," + + " but have aborted because it would unexpectedly change the AST.\n" + + "Please file a bug: " + "https://github.com/google/google-java-format/issues/new" + "\n\n=== Actual: ===\n%s\n=== Expected: ===\n%s\n", actual, expected)); @@ -120,6 +123,10 @@ public final class StringWrapper { if (literalTree.getKind() != Kind.STRING_LITERAL) { return null; } + int pos = getStartPosition(literalTree); + if (input.substring(pos, min(input.length(), pos + 3)).equals("\"\"\"")) { + return null; + } Tree parent = getCurrentPath().getParentPath().getLeaf(); if (parent instanceof MemberSelectTree && ((MemberSelectTree) parent).getExpression().equals(literalTree)) { @@ -396,7 +403,7 @@ public final class StringWrapper { ParserFactory parserFactory = ParserFactory.instance(context); JavacParser parser = parserFactory.newParser( - source, /*keepDocComments=*/ true, /*keepEndPos=*/ true, /*keepLineMap=*/ true); + source, /* keepDocComments= */ true, /* keepEndPos= */ true, /* keepLineMap= */ true); unit = parser.parseCompilationUnit(); unit.sourcefile = sjfo; Iterable<Diagnostic<? extends JavaFileObject>> errorDiagnostics = diff --git a/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java index 890687f..6818f4a 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java @@ -12,7 +12,7 @@ * the License. */ -package com.google.googlejavaformat.java.java14; +package com.google.googlejavaformat.java.java17; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.getOnlyElement; @@ -25,9 +25,11 @@ import com.google.googlejavaformat.java.JavaInputAstVisitor; import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.BindingPatternTree; import com.sun.source.tree.BlockTree; +import com.sun.source.tree.CaseLabelTree; import com.sun.source.tree.CaseTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.InstanceOfTree; import com.sun.source.tree.ModifiersTree; import com.sun.source.tree.ModuleTree; @@ -39,39 +41,23 @@ import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.TreeInfo; -import java.lang.reflect.Method; import java.util.List; import java.util.Optional; import javax.lang.model.element.Name; /** - * Extends {@link JavaInputAstVisitor} with support for AST nodes that were added or modified for - * Java 14. + * Extends {@link JavaInputAstVisitor} with support for AST nodes that were added or modified in + * Java 17. */ -public class Java14InputAstVisitor extends JavaInputAstVisitor { - private static final Method COMPILATION_UNIT_TREE_GET_MODULE = - maybeGetMethod(CompilationUnitTree.class, "getModule"); - private static final Method CLASS_TREE_GET_PERMITS_CLAUSE = - maybeGetMethod(ClassTree.class, "getPermitsClause"); - private static final Method BINDING_PATTERN_TREE_GET_VARIABLE = - maybeGetMethod(BindingPatternTree.class, "getVariable"); - private static final Method BINDING_PATTERN_TREE_GET_TYPE = - maybeGetMethod(BindingPatternTree.class, "getType"); - private static final Method BINDING_PATTERN_TREE_GET_BINDING = - maybeGetMethod(BindingPatternTree.class, "getBinding"); - private static final Method CASE_TREE_GET_LABELS = maybeGetMethod(CaseTree.class, "getLabels"); +public class Java17InputAstVisitor extends JavaInputAstVisitor { - public Java14InputAstVisitor(OpsBuilder builder, int indentMultiplier) { + public Java17InputAstVisitor(OpsBuilder builder, int indentMultiplier) { super(builder, indentMultiplier); } @Override protected void handleModule(boolean first, CompilationUnitTree node) { - if (COMPILATION_UNIT_TREE_GET_MODULE == null) { - // Java < 17, see https://bugs.openjdk.java.net/browse/JDK-8255464 - return; - } - ModuleTree module = (ModuleTree) invoke(COMPILATION_UNIT_TREE_GET_MODULE, node); + ModuleTree module = node.getModule(); if (module != null) { if (!first) { builder.blankLineWanted(BlankLineWanted.YES); @@ -84,34 +70,20 @@ public class Java14InputAstVisitor extends JavaInputAstVisitor { @Override protected List<? extends Tree> getPermitsClause(ClassTree node) { - if (CLASS_TREE_GET_PERMITS_CLAUSE != null) { - return (List<? extends Tree>) invoke(CLASS_TREE_GET_PERMITS_CLAUSE, node); - } else { - // Java < 15 - return super.getPermitsClause(node); - } + return node.getPermitsClause(); } @Override public Void visitBindingPattern(BindingPatternTree node, Void unused) { sync(node); - if (BINDING_PATTERN_TREE_GET_VARIABLE != null) { - VariableTree variableTree = (VariableTree) invoke(BINDING_PATTERN_TREE_GET_VARIABLE, node); - visitBindingPattern( - variableTree.getModifiers(), variableTree.getType(), variableTree.getName()); - } else if (BINDING_PATTERN_TREE_GET_TYPE != null && BINDING_PATTERN_TREE_GET_BINDING != null) { - Tree type = (Tree) invoke(BINDING_PATTERN_TREE_GET_TYPE, node); - Name name = (Name) invoke(BINDING_PATTERN_TREE_GET_BINDING, node); - visitBindingPattern(/* modifiers= */ null, type, name); - } else { - throw new LinkageError( - "BindingPatternTree must have either getVariable() or both getType() and getBinding()," - + " but does not"); - } + VariableTree variableTree = node.getVariable(); + visitBindingPattern( + variableTree.getModifiers(), variableTree.getType(), variableTree.getName()); return null; } private void visitBindingPattern(ModifiersTree modifiers, Tree type, Name name) { + builder.open(plusFour); if (modifiers != null) { List<AnnotationTree> annotations = visitModifiers(modifiers, Direction.HORIZONTAL, Optional.empty()); @@ -119,7 +91,12 @@ public class Java14InputAstVisitor extends JavaInputAstVisitor { } scan(type, null); builder.breakOp(" "); - visit(name); + if (name.isEmpty()) { + token("_"); + } else { + visit(name); + } + builder.close(); } @Override @@ -248,17 +225,14 @@ public class Java14InputAstVisitor extends JavaInputAstVisitor { sync(node); markForPartialFormat(); builder.forcedBreak(); - List<? extends Tree> labels; - boolean isDefault; - if (CASE_TREE_GET_LABELS != null) { - labels = (List<? extends Tree>) invoke(CASE_TREE_GET_LABELS, node); - isDefault = - labels.size() == 1 - && getOnlyElement(labels).getKind().name().equals("DEFAULT_CASE_LABEL"); - } else { - labels = node.getExpressions(); - isDefault = labels.isEmpty(); - } + List<? extends CaseLabelTree> labels = node.getLabels(); + boolean isDefault = + labels.size() == 1 && getOnlyElement(labels).getKind().name().equals("DEFAULT_CASE_LABEL"); + builder.open( + node.getCaseKind().equals(CaseTree.CaseKind.RULE) + && !node.getBody().getKind().equals(Tree.Kind.BLOCK) + ? plusFour + : ZERO); if (isDefault) { token("default", plusTwo); } else { @@ -276,6 +250,15 @@ public class Java14InputAstVisitor extends JavaInputAstVisitor { } builder.close(); } + + final ExpressionTree guard = getGuard(node); + if (guard != null) { + builder.space(); + token("when"); + builder.space(); + scan(guard, null); + } + switch (node.getCaseKind()) { case STATEMENT: token(":"); @@ -287,8 +270,8 @@ public class Java14InputAstVisitor extends JavaInputAstVisitor { builder.space(); token("-"); token(">"); - builder.space(); if (node.getBody().getKind() == Tree.Kind.BLOCK) { + builder.space(); // Explicit call with {@link CollapseEmptyOrNot.YES} to handle empty case blocks. visitBlock( (BlockTree) node.getBody(), @@ -296,6 +279,7 @@ public class Java14InputAstVisitor extends JavaInputAstVisitor { AllowLeadingBlankLine.NO, AllowTrailingBlankLine.NO); } else { + builder.breakOp(" "); scan(node.getBody(), null); } builder.guessToken(";"); @@ -303,22 +287,11 @@ public class Java14InputAstVisitor extends JavaInputAstVisitor { default: throw new AssertionError(node.getCaseKind()); } + builder.close(); return null; } - private static Method maybeGetMethod(Class<?> c, String name) { - try { - return c.getMethod(name); - } catch (ReflectiveOperationException e) { - return null; - } - } - - private static Object invoke(Method m, Object target) { - try { - return m.invoke(target); - } catch (ReflectiveOperationException e) { - throw new LinkageError(e.getMessage(), e); - } + protected ExpressionTree getGuard(final CaseTree node) { + return null; } } diff --git a/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java new file mode 100644 index 0000000..897d6ff --- /dev/null +++ b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 The google-java-format Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.googlejavaformat.java.java21; + +import com.google.googlejavaformat.OpsBuilder; +import com.google.googlejavaformat.java.java17.Java17InputAstVisitor; +import com.sun.source.tree.CaseTree; +import com.sun.source.tree.ConstantCaseLabelTree; +import com.sun.source.tree.DeconstructionPatternTree; +import com.sun.source.tree.DefaultCaseLabelTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.PatternCaseLabelTree; +import com.sun.source.tree.PatternTree; +import com.sun.source.tree.StringTemplateTree; +import javax.lang.model.element.Name; + +/** + * Extends {@link Java17InputAstVisitor} with support for AST nodes that were added or modified in + * Java 21. + */ +public class Java21InputAstVisitor extends Java17InputAstVisitor { + + public Java21InputAstVisitor(OpsBuilder builder, int indentMultiplier) { + super(builder, indentMultiplier); + } + + @Override + protected ExpressionTree getGuard(final CaseTree node) { + return node.getGuard(); + } + + @Override + public Void visitDefaultCaseLabel(DefaultCaseLabelTree node, Void unused) { + token("default"); + return null; + } + + @Override + public Void visitPatternCaseLabel(PatternCaseLabelTree node, Void unused) { + scan(node.getPattern(), null); + return null; + } + + @Override + public Void visitConstantCaseLabel(ConstantCaseLabelTree node, Void aVoid) { + scan(node.getConstantExpression(), null); + return null; + } + + @Override + public Void visitDeconstructionPattern(DeconstructionPatternTree node, Void unused) { + sync(node); + scan(node.getDeconstructor(), null); + builder.open(plusFour); + token("("); + builder.breakOp(); + boolean first = true; + for (PatternTree pattern : node.getNestedPatterns()) { + if (!first) { + token(","); + builder.breakOp(" "); + } + first = false; + scan(pattern, null); + } + builder.close(); + token(")"); + return null; + } + + @SuppressWarnings("preview") + @Override + public Void visitStringTemplate(StringTemplateTree node, Void aVoid) { + sync(node); + scan(node.getProcessor(), null); + token("."); + token(builder.peekToken().get()); + return null; + } + + @Override + protected void variableName(Name name) { + if (name.isEmpty()) { + token("_"); + } else { + visit(name); + } + } +} |