diff --git a/README.md b/README.md index 202e69d1..3b79cd0b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ and run it with: ``` -java -jar /path/to/google-java-format-1.11.0-all-deps.jar [files...] +java -jar /path/to/google-java-format-1.12.0-all-deps.jar [files...] ``` The formatter can act on whole files, on limited lines (`--lines`), on specific @@ -39,7 +39,7 @@ java \ --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ - -jar google-java-format-1.11.0-all-deps.jar [files...] + -jar google-java-format-1.12.0-all-deps.jar [files...] ``` ### IntelliJ, Android Studio, and other JetBrains IDEs @@ -71,7 +71,7 @@ and import it into File→Settings→Editor→Code Style. ### Eclipse Version 1.11 of the -[google-java-format Eclipse plugin](https://github.com/google/google-java-format/releases/download/v1.11.0/google-java-format-eclipse-plugin-1.11.0.jar) +[google-java-format Eclipse plugin](https://github.com/google/google-java-format/releases/download/v1.12.0/google-java-format-eclipse-plugin-1.12.0.jar) can be downloaded from the releases page. Drop it into the Eclipse [drop-ins folder](http://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fmisc%2Fp2_dropins_format.html) to activate the plugin. @@ -113,7 +113,7 @@ configuration. com.google.googlejavaformat google-java-format - 1.11.0 + 1.12.0 ``` @@ -121,7 +121,7 @@ configuration. ```groovy dependencies { - implementation 'com.google.googlejavaformat:google-java-format:1.11.0' + implementation 'com.google.googlejavaformat:google-java-format:1.12.0' } ``` diff --git a/core/pom.xml b/core/pom.xml index 5ebdb591..45c2f487 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -22,7 +22,7 @@ com.google.googlejavaformat google-java-format-parent - HEAD-SNAPSHOT + 1.13.0 google-java-format 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 b2b484ad..20e55e94 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java +++ b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java @@ -32,6 +32,7 @@ import com.google.googlejavaformat.Newlines; import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.ReferenceTree; +import com.sun.source.tree.CaseTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.ImportTree; import com.sun.source.tree.Tree; @@ -55,8 +56,10 @@ 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.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import javax.tools.Diagnostic; @@ -115,6 +118,31 @@ public Void visitIdentifier(IdentifierTree tree, Void unused) { return null; } + // TODO(cushon): remove this override when pattern matching in switch is no longer a preview + // feature, and TreePathScanner visits CaseTree#getLabels instead of CaseTree#getExpressions + @SuppressWarnings("unchecked") // reflection + @Override + public Void visitCase(CaseTree tree, Void unused) { + if (CASE_TREE_GET_LABELS != null) { + try { + scan((List) CASE_TREE_GET_LABELS.invoke(tree), null); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + return super.visitCase(tree, null); + } + + private static final Method CASE_TREE_GET_LABELS = caseTreeGetLabels(); + + private static Method caseTreeGetLabels() { + try { + return CaseTree.class.getMethod("getLabels"); + } catch (NoSuchMethodException e) { + return null; + } + } + @Override public Void scan(Tree tree, Void unused) { if (tree == null) { @@ -146,7 +174,9 @@ public Void visitIdentifier(com.sun.source.doctree.IdentifierTree node, Void aVo public Void visitReference(ReferenceTree referenceTree, Void unused) { DCReference reference = (DCReference) referenceTree; long basePos = - reference.getSourcePosition((DCTree.DCDocComment) getCurrentPath().getDocComment()); + reference + .pos((DCTree.DCDocComment) getCurrentPath().getDocComment()) + .getStartPosition(); // the position of trees inside the reference node aren't stored, but the qualifier's // start position is the beginning of the reference node if (reference.qualifierExpression != null) { 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 index 3facbd71..890687fe 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java @@ -15,6 +15,7 @@ package com.google.googlejavaformat.java.java14; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Iterables.getOnlyElement; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; @@ -27,7 +28,6 @@ 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; @@ -59,6 +59,7 @@ public class Java14InputAstVisitor extends JavaInputAstVisitor { 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 Java14InputAstVisitor(OpsBuilder builder, int indentMultiplier) { super(builder, indentMultiplier); @@ -247,14 +248,25 @@ public Void visitCase(CaseTree node, Void unused) { sync(node); markForPartialFormat(); builder.forcedBreak(); - if (node.getExpressions().isEmpty()) { + List labels; + boolean isDefault; + if (CASE_TREE_GET_LABELS != null) { + labels = (List) 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(); + } + if (isDefault) { token("default", plusTwo); } else { token("case", plusTwo); - builder.open(node.getExpressions().size() > 1 ? plusFour : ZERO); + builder.open(labels.size() > 1 ? plusFour : ZERO); builder.space(); boolean first = true; - for (ExpressionTree expression : node.getExpressions()) { + for (Tree expression : labels) { if (!first) { token(","); builder.breakOp(" "); 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 edbb12b6..22202e98 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java @@ -14,8 +14,7 @@ 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.collect.MoreCollectors.toOptional; import static com.google.common.io.Files.getFileExtension; import static com.google.common.io.Files.getNameWithoutExtension; import static java.nio.charset.StandardCharsets.UTF_8; @@ -23,7 +22,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableMultimap; import com.google.common.io.CharStreams; import com.google.common.reflect.ClassPath; import com.google.common.reflect.ClassPath.ResourceInfo; @@ -31,12 +30,12 @@ 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; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.TreeMap; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,12 +46,13 @@ @RunWith(Parameterized.class) public class FormatterIntegrationTest { - private static final ImmutableSet JAVA14_TESTS = - ImmutableSet.of("I477", "Records", "RSLs", "Var", "ExpressionSwitch", "I574", "I594"); - - private static final ImmutableSet JAVA15_TESTS = ImmutableSet.of("I603"); - - private static final ImmutableSet JAVA16_TESTS = ImmutableSet.of("I588"); + private static final ImmutableMultimap VERSIONED_TESTS = + ImmutableMultimap.builder() + .putAll(14, "I477", "Records", "RSLs", "Var", "ExpressionSwitch", "I574", "I594") + .putAll(15, "I603") + .putAll(16, "I588") + .putAll(17, "I683", "I684") + .build(); @Parameters(name = "{index}: {0}") public static Iterable data() throws IOException { @@ -91,13 +91,9 @@ 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; - } - if (JAVA15_TESTS.contains(fileName) && getMajor() < 15) { - continue; - } - if (JAVA16_TESTS.contains(fileName) && getMajor() < 16) { + Optional version = + VERSIONED_TESTS.inverse().get(fileName).stream().collect(toOptional()); + if (version.isPresent() && Runtime.version().feature() < version.get()) { continue; } testInputs.add(new Object[] {fileName, input, expectedOutput}); @@ -105,21 +101,6 @@ public static Iterable data() throws IOException { 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 575153ef..ac3eb396 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/MainTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/MainTest.java @@ -322,7 +322,7 @@ public void packageInfo() throws Exception { "@ParametersAreNonnullByDefault", "package com.google.common.labs.base;", "", - "import javax.annotation.CheckReturnValue;", + "import com.google.errorprone.annotations.CheckReturnValue;", "import javax.annotation.ParametersAreNonnullByDefault;", "", }; diff --git a/core/src/test/java/com/google/googlejavaformat/java/RemoveUnusedImportsCaseLabelsTest.java b/core/src/test/java/com/google/googlejavaformat/java/RemoveUnusedImportsCaseLabelsTest.java new file mode 100644 index 00000000..c0babb0d --- /dev/null +++ b/core/src/test/java/com/google/googlejavaformat/java/RemoveUnusedImportsCaseLabelsTest.java @@ -0,0 +1,49 @@ +/* + * 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.truth.Truth.assertThat; +import static com.google.googlejavaformat.java.RemoveUnusedImports.removeUnusedImports; +import static org.junit.Assume.assumeTrue; + +import com.google.common.base.Joiner; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests that unused import removal doesn't remove types used in case labels. */ +@RunWith(JUnit4.class) +public class RemoveUnusedImportsCaseLabelsTest { + @Test + public void preserveTypesInCaseLabels() throws FormatterException { + assumeTrue(Runtime.version().feature() >= 17); + String input = + Joiner.on('\n') + .join( + "package example;", + "import example.model.SealedInterface;", + "import example.model.TypeA;", + "import example.model.TypeB;", + "public class Main {", + " public void apply(SealedInterface sealedInterface) {", + " switch(sealedInterface) {", + " case TypeA a -> System.out.println(\"A!\");", + " case TypeB b -> System.out.println(\"B!\");", + " }", + " }", + "}"); + assertThat(removeUnusedImports(input)).isEqualTo(input); + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I683.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I683.input new file mode 100644 index 00000000..9104f190 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I683.input @@ -0,0 +1,14 @@ +interface Test { + + static class Test1 implements Test{} + static class Test2 implements Test{} + + public static void main(String[] args) { + Test test = new Test1(); + switch (test) { + case Test1 test1 -> {} + case Test2 test2 -> {} + default -> throw new IllegalStateException("Unexpected value: " + test); + } + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I683.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I683.output new file mode 100644 index 00000000..5b9c4665 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I683.output @@ -0,0 +1,15 @@ +interface Test { + + static class Test1 implements Test {} + + static class Test2 implements Test {} + + public static void main(String[] args) { + Test test = new Test1(); + switch (test) { + case Test1 test1 -> {} + case Test2 test2 -> {} + default -> throw new IllegalStateException("Unexpected value: " + test); + } + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I684.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I684.input new file mode 100644 index 00000000..cbce0ddd --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I684.input @@ -0,0 +1,14 @@ +package example; + +import example.model.SealedInterface; +import example.model.TypeA; +import example.model.TypeB; + +public class Main { + public void apply(SealedInterface sealedInterface) { + switch(sealedInterface) { + case TypeA a -> System.out.println("A!"); + case TypeB b -> System.out.println("B!"); + } + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I684.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I684.output new file mode 100644 index 00000000..4e5e9b4b --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I684.output @@ -0,0 +1,14 @@ +package example; + +import example.model.SealedInterface; +import example.model.TypeA; +import example.model.TypeB; + +public class Main { + public void apply(SealedInterface sealedInterface) { + switch (sealedInterface) { + case TypeA a -> System.out.println("A!"); + case TypeB b -> System.out.println("B!"); + } + } +} diff --git a/pom.xml b/pom.xml index 75a18293..df860793 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ com.google.googlejavaformat google-java-format-parent pom - HEAD-SNAPSHOT + 1.13.0 core diff --git a/scripts/google-java-format-diff.py b/scripts/google-java-format-diff.py index cf2ded9e..151ae33d 100755 --- a/scripts/google-java-format-diff.py +++ b/scripts/google-java-format-diff.py @@ -33,7 +33,7 @@ import subprocess import io import sys -from distutils.spawn import find_executable +from shutil import which def main(): parser = argparse.ArgumentParser(description= @@ -105,7 +105,7 @@ def main(): elif args.google_java_format_jar: base_command = ['java', '-jar', args.google_java_format_jar] else: - binary = find_executable('google-java-format') or '/usr/bin/google-java-format' + binary = which('google-java-format') or '/usr/bin/google-java-format' base_command = [binary] # Reformat files containing changes in place. @@ -134,11 +134,11 @@ def main(): if not args.i: with open(filename) as f: code = f.readlines() - formatted_code = io.StringIO(stdout).readlines() + formatted_code = io.StringIO(stdout.decode('utf-8')).readlines() diff = difflib.unified_diff(code, formatted_code, filename, filename, '(before formatting)', '(after formatting)') - diff_string = string.join(diff, '') + diff_string = ''.join(diff) if len(diff_string) > 0: sys.stdout.write(diff_string)