diff --git a/.travis.yml b/.travis.yml index 048e3ed19..b8a7c33af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ notifications: jdk: - openjdk11 + - openjdk14 - openjdk-ea matrix: diff --git a/core/pom.xml b/core/pom.xml index 6ec553fb0..c6f1e664b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -256,4 +256,32 @@ + + + + jdk11 + + (,14) + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + **/Java14InputAstVisitor.java + + + + + maven-javadoc-plugin + + com.google.googlejavaformat.java.java14 + + + + + + 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 4348506b8..3e973958d 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/Formatter.java +++ b/core/src/main/java/com/google/googlejavaformat/java/Formatter.java @@ -14,6 +14,8 @@ package com.google.googlejavaformat.java; +import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; @@ -40,6 +42,7 @@ import com.sun.tools.javac.util.Options; import java.io.IOError; import java.io.IOException; +import java.lang.reflect.Method; import java.net.URI; import java.util.ArrayList; import java.util.Collection; @@ -115,6 +118,7 @@ static void format(final JavaInput javaInput, JavaOutput javaOutput, JavaFormatt DiagnosticCollector diagnostics = new DiagnosticCollector<>(); context.put(DiagnosticListener.class, diagnostics); Options.instance(context).put("allowStringFolding", "false"); + Options.instance(context).put("--enable-preview", "true"); JCCompilationUnit unit; JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); try { @@ -149,7 +153,21 @@ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOExcept } OpsBuilder builder = new OpsBuilder(javaInput, javaOutput); // Output the compilation unit. - new JavaInputAstVisitor(builder, options.indentationMultiplier()).scan(unit, null); + JavaInputAstVisitor visitor; + if (getMajor() >= 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); + } + } else { + visitor = new JavaInputAstVisitor(builder, options.indentationMultiplier()); + } + visitor.scan(unit, null); builder.sync(javaInput.getText().length()); builder.drain(); Doc doc = new DocBuilder().withOps(builder.build()).build(); @@ -158,6 +176,23 @@ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOExcept javaOutput.flush(); } + // Runtime.Version was added in JDK 9, so use reflection to access it to preserve source + // compatibility with Java 8. + private static int getMajor() { + try { + Method versionMethod = Runtime.class.getMethod("version"); + Object version = versionMethod.invoke(null); + return (int) version.getClass().getMethod("major").invoke(version); + } catch (Exception e) { + // continue below + } + int version = (int) Double.parseDouble(JAVA_CLASS_VERSION.value()); + if (49 <= version && version <= 52) { + return version - (49 - 5); + } + throw new IllegalStateException("Unknown Java version: " + JAVA_SPECIFICATION_VERSION.value()); + } + static boolean errorDiagnostic(Diagnostic input) { if (input.getKind() != Diagnostic.Kind.ERROR) { return false; 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 e5e4f4580..17ae0fac0 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java @@ -39,6 +39,7 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Log.DeferredDiagnosticHandler; +import com.sun.tools.javac.util.Options; import java.io.IOException; import java.net.URI; import java.util.ArrayList; @@ -347,6 +348,7 @@ static ImmutableList buildToks(String text, ImmutableSet stopTok throws FormatterException { stopTokens = ImmutableSet.builder().addAll(stopTokens).add(TokenKind.EOF).build(); Context context = new Context(); + Options.instance(context).put("--enable-preview", "true"); new JavacFileManager(context, true, UTF_8); DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); context.put(DiagnosticListener.class, diagnosticCollector); 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 6ece007c7..11b9e3a0c 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java @@ -134,6 +134,7 @@ import com.sun.source.util.TreePathScanner; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.TreeScanner; import java.util.ArrayDeque; import java.util.ArrayList; @@ -154,10 +155,10 @@ * An AST visitor that builds a stream of {@link Op}s to format from the given {@link * CompilationUnitTree}. */ -public final class JavaInputAstVisitor extends TreePathScanner { +public class JavaInputAstVisitor extends TreePathScanner { /** Direction for Annotations (usually VERTICAL). */ - enum Direction { + protected enum Direction { VERTICAL, HORIZONTAL; @@ -211,7 +212,7 @@ static AllowTrailingBlankLine valueOf(boolean b) { } /** Whether to include braces. */ - enum BracesOrNot { + protected enum BracesOrNot { YES, NO; @@ -259,7 +260,7 @@ boolean isYes() { } /** Whether these declarations are the first in the block. */ - enum FirstDeclarationsOrNot { + protected enum FirstDeclarationsOrNot { YES, NO; @@ -268,14 +269,14 @@ boolean isYes() { } } - private final OpsBuilder builder; + protected final OpsBuilder builder; - private static final Indent.Const ZERO = Indent.Const.ZERO; - private final int indentMultiplier; - private final Indent.Const minusTwo; - private final Indent.Const minusFour; - private final Indent.Const plusTwo; - private final Indent.Const plusFour; + protected static final Indent.Const ZERO = Indent.Const.ZERO; + protected final int indentMultiplier; + protected final Indent.Const minusTwo; + protected final Indent.Const minusFour; + protected final Indent.Const plusTwo; + protected final Indent.Const plusFour; private static final ImmutableList breakList(Optional breakTag) { return ImmutableList.of(Doc.Break.make(Doc.FillMode.UNIFIED, " ", ZERO, breakTag)); @@ -382,7 +383,7 @@ public Void visitCompilationUnit(CompilationUnitTree node, Void unused) { } /** Skips over extra semi-colons at the top-level, or in a class member declaration lists. */ - private void dropEmptyDeclarations() { + protected void dropEmptyDeclarations() { if (builder.peekToken().equals(Optional.of(";"))) { while (builder.peekToken().equals(Optional.of(";"))) { builder.forcedBreak(); @@ -1331,12 +1332,19 @@ public Void visitAnnotatedType(AnnotatedTypeTree node, Void unused) { return null; } + // TODO(cushon): Use Flags.COMPACT_RECORD_CONSTRUCTOR once if/when we drop support for Java 11 + protected static final long COMPACT_RECORD_CONSTRUCTOR = 1L << 51; + @Override public Void visitMethod(MethodTree node, Void unused) { sync(node); List annotations = node.getModifiers().getAnnotations(); List returnTypeAnnotations = ImmutableList.of(); + boolean isRecordConstructor = + (((JCMethodDecl) node).mods.flags & COMPACT_RECORD_CONSTRUCTOR) + == COMPACT_RECORD_CONSTRUCTOR; + if (!node.getTypeParameters().isEmpty() && !annotations.isEmpty()) { int typeParameterStart = getStartPosition(node.getTypeParameters().get(0)); for (int i = 0; i < annotations.size(); i++) { @@ -1406,7 +1414,9 @@ public Void visitMethod(MethodTree node, Void unused) { name = builder.peekToken().get(); } token(name); - token("("); + if (!isRecordConstructor) { + token("("); + } // end of name and type scope builder.close(); } @@ -1416,12 +1426,14 @@ public Void visitMethod(MethodTree node, Void unused) { builder.open(Indent.If.make(breakBeforeType, plusFour, ZERO)); builder.open(ZERO); { - if (!node.getParameters().isEmpty() || node.getReceiverParameter() != null) { - // Break before args. - builder.breakToFill(""); - visitFormals(Optional.ofNullable(node.getReceiverParameter()), node.getParameters()); + if (!isRecordConstructor) { + if (!node.getParameters().isEmpty() || node.getReceiverParameter() != null) { + // Break before args. + builder.breakToFill(""); + visitFormals(Optional.ofNullable(node.getReceiverParameter()), node.getParameters()); + } + token(")"); } - token(")"); if (dims != null) { maybeAddDims(dims); } @@ -1795,17 +1807,22 @@ public Void visitCase(CaseTree node, Void unused) { @Override public Void visitSwitch(SwitchTree node, Void unused) { sync(node); + visitSwitch(node.getExpression(), node.getCases()); + return null; + } + + protected void visitSwitch(ExpressionTree expression, List cases) { token("switch"); builder.space(); token("("); - scan(skipParen(node.getExpression()), null); + scan(skipParen(expression), null); token(")"); builder.space(); tokenBreakTrailingComment("{", plusTwo); builder.blankLineWanted(BlankLineWanted.NO); builder.open(plusTwo); boolean first = true; - for (CaseTree caseTree : node.getCases()) { + for (CaseTree caseTree : cases) { if (!first) { builder.blankLineWanted(BlankLineWanted.PRESERVE); } @@ -1816,7 +1833,6 @@ public Void visitSwitch(SwitchTree node, Void unused) { builder.forcedBreak(); builder.blankLineWanted(BlankLineWanted.NO); token("}", plusFour); - return null; } @Override @@ -2126,7 +2142,7 @@ private void visitStatement( } } - private void visitStatements(List statements) { + protected void visitStatements(List statements) { boolean first = true; PeekingIterator it = Iterators.peekingIterator(statements.iterator()); dropEmptyDeclarations(); @@ -2164,7 +2180,7 @@ public Void visitModifiers(ModifiersTree node, Void unused) { } /** Output combined modifiers and annotations and returns the trailing break. */ - private List visitModifiers( + protected List visitModifiers( ModifiersTree modifiersTree, Direction annotationsDirection, Optional declarationAnnotationBreak) { @@ -2172,7 +2188,7 @@ private List visitModifiers( modifiersTree.getAnnotations(), annotationsDirection, declarationAnnotationBreak); } - private List visitModifiers( + protected List visitModifiers( List annotationTrees, Direction annotationsDirection, Optional declarationAnnotationBreak) { @@ -2339,7 +2355,7 @@ private static void walkInfix( } } - private void visitFormals( + protected void visitFormals( Optional receiver, List parameters) { if (!receiver.isPresent() && parameters.isEmpty()) { return; @@ -2559,7 +2575,7 @@ private void visitToDeclare( } /** Does not omit the leading '<', which should be associated with the type name. */ - private void typeParametersRest( + protected void typeParametersRest( List typeParameters, Indent plusIndent) { builder.open(plusIndent); builder.breakOp(); @@ -3446,7 +3462,7 @@ private void declareMany(List fragments, Direction annotationDirec } /** Add a list of declarations. */ - void addBodyDeclarations( + protected void addBodyDeclarations( List bodyDeclarations, BracesOrNot braces, FirstDeclarationsOrNot first0) { if (bodyDeclarations.isEmpty()) { if (braces.isYes()) { @@ -3592,7 +3608,7 @@ private Direction fieldAnnotationDirection(ModifiersTree modifiers) { * * @param token the {@link String} to wrap in a {@link Doc.Token} */ - final void token(String token) { + protected final void token(String token) { builder.token( token, Doc.Token.RealOrImaginary.REAL, @@ -3606,7 +3622,7 @@ final void token(String token) { * @param token the {@link String} to wrap in a {@link Doc.Token} * @param plusIndentCommentsBefore extra indent for comments before this token */ - final void token(String token, Indent plusIndentCommentsBefore) { + protected final void token(String token, Indent plusIndentCommentsBefore) { builder.token( token, Doc.Token.RealOrImaginary.REAL, @@ -3620,7 +3636,7 @@ final void tokenBreakTrailingComment(String token, Indent breakAndIndentTrailing token, Doc.Token.RealOrImaginary.REAL, ZERO, Optional.of(breakAndIndentTrailingComment)); } - private void markForPartialFormat() { + protected void markForPartialFormat() { if (!inExpression()) { builder.markForPartialFormat(); } @@ -3632,7 +3648,7 @@ private void markForPartialFormat() { * * @param node the ASTNode holding the input position */ - final void sync(Tree node) { + protected final void sync(Tree node) { builder.sync(((JCTree) node).getStartPosition()); } 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 9d0bb27d2..d93948082 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java +++ b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java @@ -200,6 +200,7 @@ private static JCCompilationUnit parse(Context context, String javaInput) throws FormatterException { DiagnosticCollector diagnostics = new DiagnosticCollector<>(); context.put(DiagnosticListener.class, diagnostics); + Options.instance(context).put("--enable-preview", "true"); Options.instance(context).put("allowStringFolding", "false"); JCCompilationUnit unit; JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); 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 6bb63db3a..e41bb6663 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/StringWrapper.java +++ b/core/src/main/java/com/google/googlejavaformat/java/StringWrapper.java @@ -372,6 +372,7 @@ private static JCTree.JCCompilationUnit parse(String source, boolean allowString DiagnosticCollector diagnostics = new DiagnosticCollector<>(); Context context = new Context(); context.put(DiagnosticListener.class, diagnostics); + Options.instance(context).put("--enable-preview", "true"); Options.instance(context).put("allowStringFolding", Boolean.toString(allowStringFolding)); JCTree.JCCompilationUnit unit; JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); diff --git a/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java new file mode 100644 index 000000000..f3dee6b67 --- /dev/null +++ b/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java @@ -0,0 +1,219 @@ +/* + * Copyright 2020 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.java14; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.MoreCollectors.onlyElement; + +import com.google.common.base.Verify; +import com.google.googlejavaformat.Op; +import com.google.googlejavaformat.OpsBuilder; +import com.google.googlejavaformat.java.JavaInputAstVisitor; +import com.sun.source.tree.BindingPatternTree; +import com.sun.source.tree.CaseTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.InstanceOfTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.SwitchExpressionTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.YieldTree; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.TreeInfo; +import java.util.List; +import java.util.Optional; + +/** + * Extends {@link JavaInputAstVisitor} with support for AST nodes that were added or modified for + * Java 14. + */ +public class Java14InputAstVisitor extends JavaInputAstVisitor { + + public Java14InputAstVisitor(OpsBuilder builder, int indentMultiplier) { + super(builder, indentMultiplier); + } + + @Override + public Void visitBindingPattern(BindingPatternTree node, Void unused) { + sync(node); + scan(node.getType(), null); + builder.breakOp(" "); + visit(node.getBinding()); + return null; + } + + @Override + public Void visitYield(YieldTree node, Void aVoid) { + sync(node); + return super.visitYield(node, aVoid); + } + + @Override + public Void visitSwitchExpression(SwitchExpressionTree node, Void aVoid) { + sync(node); + visitSwitch(node.getExpression(), node.getCases()); + return null; + } + + @Override + public Void visitClass(ClassTree tree, Void unused) { + switch (tree.getKind()) { + case ANNOTATION_TYPE: + visitAnnotationType(tree); + break; + case CLASS: + case INTERFACE: + visitClassDeclaration(tree); + break; + case ENUM: + visitEnumDeclaration(tree); + break; + case RECORD: + visitRecordDeclaration(tree); + break; + default: + throw new AssertionError(tree.getKind()); + } + return null; + } + + public void visitRecordDeclaration(ClassTree node) { + sync(node); + List breaks = + visitModifiers( + node.getModifiers(), + Direction.VERTICAL, + /* declarationAnnotationBreak= */ Optional.empty()); + Verify.verify(node.getExtendsClause() == null); + boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty(); + builder.addAll(breaks); + token("record"); + builder.space(); + visit(node.getSimpleName()); + if (!node.getTypeParameters().isEmpty()) { + token("<"); + } + builder.open(plusFour); + { + if (!node.getTypeParameters().isEmpty()) { + typeParametersRest(node.getTypeParameters(), hasSuperInterfaceTypes ? plusFour : ZERO); + } + MethodTree constructor = + node.getMembers().stream() + .filter(JCMethodDecl.class::isInstance) + .map(JCMethodDecl.class::cast) + .filter( + m -> (m.mods.flags & COMPACT_RECORD_CONSTRUCTOR) == COMPACT_RECORD_CONSTRUCTOR) + .collect(onlyElement()); + token("("); + if (!constructor.getParameters().isEmpty() || constructor.getReceiverParameter() != null) { + // Break before args. + builder.breakToFill(""); + } + visitFormals( + Optional.ofNullable(constructor.getReceiverParameter()), constructor.getParameters()); + token(")"); + if (hasSuperInterfaceTypes) { + builder.breakToFill(" "); + builder.open(node.getImplementsClause().size() > 1 ? plusFour : ZERO); + token("implements"); + builder.space(); + boolean first = true; + for (Tree superInterfaceType : node.getImplementsClause()) { + if (!first) { + token(","); + builder.breakOp(" "); + } + scan(superInterfaceType, null); + first = false; + } + builder.close(); + } + } + builder.close(); + if (node.getMembers() == null) { + token(";"); + } else { + List members = + node.getMembers().stream() + .filter(t -> (TreeInfo.flags((JCTree) t) & Flags.GENERATED_MEMBER) == 0) + .collect(toImmutableList()); + addBodyDeclarations(members, BracesOrNot.YES, FirstDeclarationsOrNot.YES); + } + dropEmptyDeclarations(); + } + + @Override + public Void visitInstanceOf(InstanceOfTree node, Void unused) { + sync(node); + builder.open(plusFour); + scan(node.getExpression(), null); + builder.breakOp(" "); + builder.open(ZERO); + token("instanceof"); + builder.breakOp(" "); + if (node.getPattern() != null) { + scan(node.getPattern(), null); + } else { + scan(node.getType(), null); + } + builder.close(); + builder.close(); + return null; + } + + @Override + public Void visitCase(CaseTree node, Void unused) { + sync(node); + markForPartialFormat(); + builder.forcedBreak(); + if (node.getExpressions().isEmpty()) { + token("default", plusTwo); + } else { + token("case", plusTwo); + builder.space(); + boolean first = true; + for (ExpressionTree expression : node.getExpressions()) { + if (!first) { + token(","); + builder.space(); + } + scan(expression, null); + first = false; + } + } + switch (node.getCaseKind()) { + case STATEMENT: + token(":"); + builder.open(plusTwo); + visitStatements(node.getStatements()); + builder.close(); + break; + case RULE: + builder.space(); + token("-"); + token(">"); + builder.space(); + scan(node.getBody(), null); + token(";"); + break; + default: + throw new AssertionError(node.getCaseKind()); + } + return null; + } +} diff --git a/core/src/test/java/com/google/googlejavaformat/java/DiagnosticTest.java b/core/src/test/java/com/google/googlejavaformat/java/DiagnosticTest.java index 58ea959fd..0b81ba6b4 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/DiagnosticTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/DiagnosticTest.java @@ -143,8 +143,7 @@ public void oneFileParseErrorReplace() throws Exception { int result = main.format("-i", pathOne.toString(), pathTwo.toString()); assertThat(stdout.toString()).isEmpty(); - assertThat(stderr.toString()) - .contains("One.java:1:14: error: class, interface, or enum expected"); + assertThat(stderr.toString()).contains("One.java:1:14: error: class, interface"); assertThat(result).isEqualTo(1); // don't edit files with parse errors assertThat(Files.readAllLines(pathOne, UTF_8)).containsExactly("class One {}}"); diff --git a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java index bb722f2ea..289ea1baf 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java @@ -14,6 +14,8 @@ package com.google.googlejavaformat.java; +import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; import static com.google.common.io.Files.getFileExtension; import static com.google.common.io.Files.getNameWithoutExtension; import static java.nio.charset.StandardCharsets.UTF_8; @@ -21,6 +23,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.common.collect.ImmutableSet; import com.google.common.io.CharStreams; import com.google.common.reflect.ClassPath; import com.google.common.reflect.ClassPath.ResourceInfo; @@ -28,6 +31,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.lang.reflect.Method; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -43,6 +47,8 @@ @RunWith(Parameterized.class) public class FormatterIntegrationTest { + private static final ImmutableSet JAVA14_TESTS = ImmutableSet.of("java14"); + @Parameters(name = "{index}: {0}") public static Iterable data() throws IOException { Path testDataPath = Paths.get("com/google/googlejavaformat/java/testdata"); @@ -80,11 +86,29 @@ public static Iterable data() throws IOException { String input = inputs.get(fileName); assertTrue("unmatched input", outputs.containsKey(fileName)); String expectedOutput = outputs.get(fileName); + if (JAVA14_TESTS.contains(fileName) && getMajor() < 14) { + continue; + } testInputs.add(new Object[] {fileName, input, expectedOutput}); } return testInputs; } + private static int getMajor() { + try { + Method versionMethod = Runtime.class.getMethod("version"); + Object version = versionMethod.invoke(null); + return (int) version.getClass().getMethod("major").invoke(version); + } catch (Exception e) { + // continue below + } + int version = (int) Double.parseDouble(JAVA_CLASS_VERSION.value()); + if (49 <= version && version <= 52) { + return version - (49 - 5); + } + throw new IllegalStateException("Unknown Java version: " + JAVA_SPECIFICATION_VERSION.value()); + } + private final String name; private final String input; private final String expected; diff --git a/core/src/test/java/com/google/googlejavaformat/java/MainTest.java b/core/src/test/java/com/google/googlejavaformat/java/MainTest.java index 0d559440d..613d3912c 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/MainTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/MainTest.java @@ -294,7 +294,7 @@ public void importRemoveErrorParseError() throws Exception { new PrintWriter(err, true), new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8))); assertThat(main.format("-")).isEqualTo(1); - assertThat(err.toString()).contains(":4:3: error: class, interface, or enum expected"); + assertThat(err.toString()).contains(":4:3: error: class, interface"); } finally { Locale.setDefault(backupLocale); @@ -491,7 +491,7 @@ public void assumeFilename_error() throws Exception { new PrintWriter(err, true), new ByteArrayInputStream(joiner.join(input).getBytes(UTF_8))); assertThat(main.format("--assume-filename=Foo.java", "-")).isEqualTo(1); - assertThat(err.toString()).contains("Foo.java:1:15: error: class, interface, or enum expected"); + assertThat(err.toString()).contains("Foo.java:1:15: error: class, interface"); } @Test diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.input new file mode 100644 index 000000000..ce9c1257c --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.input @@ -0,0 +1,38 @@ +class Java14 { + void f() { + if (obj instanceof String s) { + } else { + } + } + + record Range(T lo, T hi) implements Comparable> { + + public Range {} + + Range(T lo) { + this(lo, lo); + } + + @Override + public int compareTo(Range other) { + throw new UnsupportedOperationException(); + } + } + + void g() { + var block = """ + hello + text + blocks + """.indent(6); + } + + void h() { + int numLetters = switch (day) { + case MONDAY, FRIDAY, SUNDAY -> 6; + case TUESDAY -> 7; + case THURSDAY, SATURDAY -> 8; + case WEDNESDAY -> 9; + }; + } +} \ No newline at end of file diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.output new file mode 100644 index 000000000..bddf08da7 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/java14.output @@ -0,0 +1,39 @@ +class Java14 { + void f() { + if (obj instanceof String s) { + } else { + } + } + + record Range(T lo, T hi) implements Comparable> { + + public Range {} + + Range(T lo) { + this(lo, lo); + } + + @Override + public int compareTo(Range other) { + throw new UnsupportedOperationException(); + } + } + + void g() { + var block = """ + hello + text + blocks + """.indent(6); + } + + void h() { + int numLetters = + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> 6; + case TUESDAY -> 7; + case THURSDAY, SATURDAY -> 8; + case WEDNESDAY -> 9; + }; + } +}