From 295e7a43f8a84d31c9e39fd853c8c4e52f586240 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Fri, 27 Oct 2023 11:00:13 -0700 Subject: [PATCH 01/10] Don't try to reflow text blocks Fixes https://github.com/google/google-java-format/issues/976 PiperOrigin-RevId: 577249036 --- .../googlejavaformat/java/StringWrapper.java | 5 +++++ .../java/StringWrapperTest.java | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) 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 d801c9b8..f241ae47 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; @@ -122,6 +123,10 @@ public Void visitLiteral(LiteralTree literalTree, Void aVoid) { 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)) { diff --git a/core/src/test/java/com/google/googlejavaformat/java/StringWrapperTest.java b/core/src/test/java/com/google/googlejavaformat/java/StringWrapperTest.java index 99e1b2fc..f7be369f 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/StringWrapperTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/StringWrapperTest.java @@ -15,6 +15,7 @@ package com.google.googlejavaformat.java; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; import com.google.common.base.Joiner; import org.junit.Test; @@ -52,6 +53,26 @@ public void testAwkwardLineEndWrapping() throws Exception { assertThat(StringWrapper.wrap(100, input, new Formatter())).isEqualTo(output); } + @Test + public void textBlock() throws Exception { + assumeTrue(Runtime.version().feature() >= 15); + String input = + lines( + "package com.mypackage;", + "public class ReproBug {", + " private String myString;", + " private ReproBug() {", + " String str =", + " \"\"\"", + " " + + " {\"sourceEndpoint\":\"ri.something.1-1.object-internal.1\",\"targetEndpoint\":\"ri.some" + + "thing.1-1.object-internal.2\",\"typeId\":\"typeId\"}\"\"\";", + " myString = str;", + " }", + "}"); + assertThat(StringWrapper.wrap(100, input, new Formatter())).isEqualTo(input); + } + private static String lines(String... line) { return Joiner.on('\n').join(line) + '\n'; } From 53390d99b56edae23cfab5adcafd7df28d9984c8 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Mon, 30 Oct 2023 08:22:01 -0700 Subject: [PATCH 02/10] Handle type annotations on method reference qualifiers e.g. ``` Supplier> a = @A(0x45) ArrayList::new; Supplier> b = @A(0x46) ImmutableList::of; ``` The necessary logic is already there, but the start position information for the method reference doesn't include the leading type annotations, which was resulting in an assertion to fail. PiperOrigin-RevId: 577850593 --- .../java/JavaInputAstVisitor.java | 1 - .../java/testdata/B308157568.input | 23 +++++++++++++++++++ .../java/testdata/B308157568.output | 23 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/B308157568.input create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/B308157568.output 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 89c944c5..5a75afa0 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java @@ -930,7 +930,6 @@ public boolean visitEnumDeclaration(ClassTree node) { @Override public Void visitMemberReference(MemberReferenceTree node, Void unused) { - sync(node); builder.open(plusFour); scan(node.getQualifierExpression(), null); builder.breakOp(); diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B308157568.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B308157568.input new file mode 100644 index 00000000..089a3f1e --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B308157568.input @@ -0,0 +1,23 @@ +class C { + @A(0x14) + int f(Object o) { + @A(0x40) + int local; + try (@A(0x41) + JarFile jarFile = new JarFile("hello.jar")) { + } catch ( + @A(0x42) + IOException e) { + } + if (o instanceof @A(0x43) String) {} + new @A(0x44) ArrayList<>(); + Supplier> a = @A(0x45) ArrayList::new; + Supplier> b = @A(0x46) ImmutableList::of; + String s = (@A(0x47) String) o; + List xs = new ArrayList<@A(0x48) String>(); + xs = ImmutableList.<@A(0x49) String>of(); + Supplier> c = ArrayList<@A(0x4A) String>::new; + Supplier> d = ImmutableList::<@A(0x4B) String>of; + return 0; + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/B308157568.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B308157568.output new file mode 100644 index 00000000..089a3f1e --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/B308157568.output @@ -0,0 +1,23 @@ +class C { + @A(0x14) + int f(Object o) { + @A(0x40) + int local; + try (@A(0x41) + JarFile jarFile = new JarFile("hello.jar")) { + } catch ( + @A(0x42) + IOException e) { + } + if (o instanceof @A(0x43) String) {} + new @A(0x44) ArrayList<>(); + Supplier> a = @A(0x45) ArrayList::new; + Supplier> b = @A(0x46) ImmutableList::of; + String s = (@A(0x47) String) o; + List xs = new ArrayList<@A(0x48) String>(); + xs = ImmutableList.<@A(0x49) String>of(); + Supplier> c = ArrayList<@A(0x4A) String>::new; + Supplier> d = ImmutableList::<@A(0x4B) String>of; + return 0; + } +} From 205b85f2315c48b2d6280a18c6ea9dbe814d3709 Mon Sep 17 00:00:00 2001 From: google-java-format Team Date: Thu, 30 Nov 2023 13:11:50 -0800 Subject: [PATCH 03/10] Add javax.tools.Tool implementation for google-java-format. PiperOrigin-RevId: 586765411 --- .../java/GoogleJavaFormatTool.java | 53 +++++++++++++++ .../java/GoogleJavaFormatToolProvider.java | 5 +- .../google/googlejavaformat/java/Main.java | 23 +++++-- .../GoogleJavaFormatToolProviderTest.java | 2 +- .../java/GoogleJavaFormatToolTest.java | 65 +++++++++++++++++++ 5 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatTool.java create mode 100644 core/src/test/java/com/google/googlejavaformat/java/GoogleJavaFormatToolTest.java 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 00000000..3c315aaf --- /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 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 7bcad4cc..438eac59 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 String name() { @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/Main.java b/core/src/main/java/com/google/googlejavaformat/java/Main.java index 62423f22..0845e0ec 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/Main.java +++ b/core/src/main/java/com/google/googlejavaformat/java/Main.java @@ -26,6 +26,7 @@ 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; @@ -66,20 +67,28 @@ public Main(PrintWriter outWriter, PrintWriter errWriter, InputStream inStream) * * @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()); diff --git a/core/src/test/java/com/google/googlejavaformat/java/GoogleJavaFormatToolProviderTest.java b/core/src/test/java/com/google/googlejavaformat/java/GoogleJavaFormatToolProviderTest.java index d060fef1..3d41a073 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/GoogleJavaFormatToolProviderTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/GoogleJavaFormatToolProviderTest.java @@ -46,7 +46,7 @@ public void testUsageOutputAfterLoadingViaToolName() { int result = format.run(new PrintWriter(out, true), new PrintWriter(err, true), "--help"); - assertThat(result).isEqualTo(2); + assertThat(result).isNotEqualTo(0); String usage = err.toString(); diff --git a/core/src/test/java/com/google/googlejavaformat/java/GoogleJavaFormatToolTest.java b/core/src/test/java/com/google/googlejavaformat/java/GoogleJavaFormatToolTest.java new file mode 100644 index 00000000..691bb223 --- /dev/null +++ b/core/src/test/java/com/google/googlejavaformat/java/GoogleJavaFormatToolTest.java @@ -0,0 +1,65 @@ +/* + * 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.common.truth.Truth8.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.ServiceLoader; +import javax.tools.Tool; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link GoogleJavaFormatToolProvider}. */ +@RunWith(JUnit4.class) +public class GoogleJavaFormatToolTest { + + @Test + public void testUsageOutputAfterLoadingViaToolName() { + String name = "google-java-format"; + + assertThat( + ServiceLoader.load(Tool.class).stream() + .map(ServiceLoader.Provider::get) + .map(Tool::name)) + .contains(name); + + Tool format = + ServiceLoader.load(Tool.class).stream() + .filter(provider -> name.equals(provider.get().name())) + .findFirst() + .get() + .get(); + + InputStream in = new ByteArrayInputStream(new byte[0]); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + + int result = format.run(in, out, err, "--help"); + + assertThat(result).isNotEqualTo(0); + + String usage = new String(err.toByteArray(), UTF_8); + + // Check that doc links are included. + assertThat(usage).containsMatch("http.*/google-java-format"); + assertThat(usage).contains("Usage: google-java-format"); + } +} From ad771541474eebceb2c6666cb61d4836bf6d8560 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 7 Dec 2023 09:50:25 -0800 Subject: [PATCH 04/10] Bump Guava to 32.1.3 There is an issue about resolving transitive dependencies in Guava breaks GJF 1.18.1 integration in [Spotless](https://github.com/diffplug/spotless), which has been fixed in Guava 32.1.3, it would be nice to follow this update. See: - https://github.com/google/guava/issues/6657. - https://github.com/google/guava/releases/tag/v32.1.3. Fixes #996 COPYBARA_INTEGRATE_REVIEW=https://github.com/google/google-java-format/pull/996 from Goooler:bump-guava 1e5ed85f3daa885a90a4d375918ed382e176f5c9 PiperOrigin-RevId: 588824173 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1fe2803e..8b3ca51c 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ UTF-8 1.8 - 32.1.1-jre + 32.1.3-jre 1.1.3 3.21.2 2.16 From b86c508be5f42e7ed0d55cd1abe2c04d2db66676 Mon Sep 17 00:00:00 2001 From: CK Date: Thu, 7 Dec 2023 14:38:12 -0800 Subject: [PATCH 05/10] Add support for guard clauses in Java 21 switch expressions This PR adds support for `switch` statements where a `case` has a guard clause. See Issue #983 Fixes #988 COPYBARA_INTEGRATE_REVIEW=https://github.com/google/google-java-format/pull/988 from TheCK:master 4771486db7d8aab84eb4ecf8e68af2612d0c2b5c PiperOrigin-RevId: 588913297 --- core/pom.xml | 29 ++++++++++++++- .../googlejavaformat/java/Formatter.java | 30 ++++++++++------ .../java/java17/Java17InputAstVisitor.java | 14 ++++++++ .../java/java21/Java21InputAstVisitor.java | 36 +++++++++++++++++++ .../java/FormatterIntegrationTest.java | 1 + .../java/testdata/SwitchGuardClause.input | 9 +++++ .../java/testdata/SwitchGuardClause.output | 9 +++++ 7 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchGuardClause.input create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchGuardClause.output diff --git a/core/pom.xml b/core/pom.xml index 038e4eda..d1363fed 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -226,7 +226,7 @@ jdk11 - (,17) + [11,17) @@ -236,6 +236,7 @@ **/Java17InputAstVisitor.java + **/Java21InputAstVisitor.java @@ -243,6 +244,32 @@ maven-javadoc-plugin com.google.googlejavaformat.java.java17 + com.google.googlejavaformat.java.java21 + + + + + + + jdk17 + + [17,21) + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + **/Java21InputAstVisitor.java + + + + + maven-javadoc-plugin + + com.google.googlejavaformat.java.java21 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 9ff702d5..5aa7a123 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/Formatter.java +++ b/core/src/main/java/com/google/googlejavaformat/java/Formatter.java @@ -151,16 +151,14 @@ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOExcept OpsBuilder builder = new OpsBuilder(javaInput, javaOutput); // Output the compilation unit. JavaInputAstVisitor visitor; - if (Runtime.version().feature() >= 17) { - try { - visitor = - Class.forName("com.google.googlejavaformat.java.java17.Java17InputAstVisitor") - .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 CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOExcept 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; diff --git a/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java index a0561e2f..97bb2ffe 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java @@ -29,6 +29,7 @@ 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; @@ -238,6 +239,15 @@ public Void visitCase(CaseTree node, Void unused) { } 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(":"); @@ -267,4 +277,8 @@ public Void visitCase(CaseTree node, Void unused) { } return null; } + + 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 00000000..a96ef99e --- /dev/null +++ b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java @@ -0,0 +1,36 @@ +/* + * 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.ExpressionTree; + +/** + * 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(); + } +} 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 61a43468..688b24d8 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java @@ -52,6 +52,7 @@ public class FormatterIntegrationTest { .putAll(15, "I603") .putAll(16, "I588") .putAll(17, "I683", "I684", "I696") + .putAll(21, "SwitchGuardClause") .build(); @Parameters(name = "{index}: {0}") diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchGuardClause.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchGuardClause.input new file mode 100644 index 00000000..25df5809 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchGuardClause.input @@ -0,0 +1,9 @@ +class SwitchGuardClause { + boolean test(Object x) { + return switch (x) { + case String s when s.length() < 5 -> true; + case Integer i -> false; + default -> true; + }; + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchGuardClause.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchGuardClause.output new file mode 100644 index 00000000..25df5809 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchGuardClause.output @@ -0,0 +1,9 @@ +class SwitchGuardClause { + boolean test(Object x) { + return switch (x) { + case String s when s.length() < 5 -> true; + case Integer i -> false; + default -> true; + }; + } +} From 430ba3b0e237b746157a3392663da4b4bb7c733d Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Fri, 8 Dec 2023 08:33:22 -0800 Subject: [PATCH 06/10] Initial support for pattern matching in switches Fixes https://github.com/google/google-java-format/issues/937, https://github.com/google/google-java-format/issues/880 PiperOrigin-RevId: 589140113 --- .../java/java17/Java17InputAstVisitor.java | 17 ++++++- .../java/java21/Java21InputAstVisitor.java | 43 +++++++++++++++++ .../java/FormatterIntegrationTest.java | 5 +- .../googlejavaformat/java/testdata/I880.input | 19 ++++++++ .../java/testdata/I880.output | 21 +++++++++ .../java/testdata/SwitchDouble.input | 7 +++ .../java/testdata/SwitchDouble.output | 7 +++ .../java/testdata/SwitchRecord.input | 21 +++++++++ .../java/testdata/SwitchRecord.output | 46 +++++++++++++++++++ .../java/testdata/SwitchUnderscore.input | 8 ++++ .../java/testdata/SwitchUnderscore.output | 8 ++++ 11 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/I880.input create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/I880.output create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchDouble.input create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchDouble.output create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchRecord.input create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchRecord.output create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchUnderscore.input create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchUnderscore.output diff --git a/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java index 97bb2ffe..6818f4a0 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/java17/Java17InputAstVisitor.java @@ -83,6 +83,7 @@ public Void visitBindingPattern(BindingPatternTree node, Void unused) { } private void visitBindingPattern(ModifiersTree modifiers, Tree type, Name name) { + builder.open(plusFour); if (modifiers != null) { List annotations = visitModifiers(modifiers, Direction.HORIZONTAL, Optional.empty()); @@ -90,7 +91,12 @@ private void visitBindingPattern(ModifiersTree modifiers, Tree type, Name name) } scan(type, null); builder.breakOp(" "); - visit(name); + if (name.isEmpty()) { + token("_"); + } else { + visit(name); + } + builder.close(); } @Override @@ -222,6 +228,11 @@ public Void visitCase(CaseTree node, Void unused) { List 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 { @@ -259,8 +270,8 @@ public Void visitCase(CaseTree node, Void unused) { 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(), @@ -268,6 +279,7 @@ public Void visitCase(CaseTree node, Void unused) { AllowLeadingBlankLine.NO, AllowTrailingBlankLine.NO); } else { + builder.breakOp(" "); scan(node.getBody(), null); } builder.guessToken(";"); @@ -275,6 +287,7 @@ public Void visitCase(CaseTree node, Void unused) { default: throw new AssertionError(node.getCaseKind()); } + builder.close(); 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 index a96ef99e..ad936cae 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java @@ -17,7 +17,12 @@ 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; /** * Extends {@link Java17InputAstVisitor} with support for AST nodes that were added or modified in @@ -33,4 +38,42 @@ public Java21InputAstVisitor(OpsBuilder builder, int indentMultiplier) { 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) { + 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; + } } 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 688b24d8..67f0eec9 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java @@ -52,7 +52,8 @@ public class FormatterIntegrationTest { .putAll(15, "I603") .putAll(16, "I588") .putAll(17, "I683", "I684", "I696") - .putAll(21, "SwitchGuardClause") + .putAll( + 21, "SwitchGuardClause", "SwitchRecord", "SwitchDouble", "SwitchUnderscore", "I880") .build(); @Parameters(name = "{index}: {0}") @@ -94,7 +95,7 @@ public static Iterable data() throws IOException { String expectedOutput = outputs.get(fileName); Optional version = VERSIONED_TESTS.inverse().get(fileName).stream().collect(toOptional()); - if (version.isPresent() && Runtime.version().feature() < version.get()) { + if (Runtime.version().feature() < version.orElse(Integer.MAX_VALUE)) { continue; } testInputs.add(new Object[] {fileName, input, expectedOutput}); diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I880.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I880.input new file mode 100644 index 00000000..dfc8a4cf --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I880.input @@ -0,0 +1,19 @@ +class I880 { + public String f(int i) { + return switch (i) { + case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 -> "looooooooooooooooooooooooooooooooooooooooong expression"; + default -> "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong expression"; + }; + } + + public boolean test(int i) { + return switch (i) { + case 0 -> // zero + false; + case 1 -> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".length() + == 0; + default -> // otherwise + true; + }; + } +} \ No newline at end of file diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I880.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I880.output new file mode 100644 index 00000000..f918665f --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I880.output @@ -0,0 +1,21 @@ +class I880 { + public String f(int i) { + return switch (i) { + case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 -> + "looooooooooooooooooooooooooooooooooooooooong expression"; + default -> + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong expression"; + }; + } + + public boolean test(int i) { + return switch (i) { + case 0 -> // zero + false; + case 1 -> + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".length() == 0; + default -> // otherwise + true; + }; + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchDouble.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchDouble.input new file mode 100644 index 00000000..54e22a76 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchDouble.input @@ -0,0 +1,7 @@ +class SwitchDouble { + void x(Object o) { + switch (o) { + case null, default: + } + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchDouble.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchDouble.output new file mode 100644 index 00000000..54e22a76 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchDouble.output @@ -0,0 +1,7 @@ +class SwitchDouble { + void x(Object o) { + switch (o) { + case null, default: + } + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchRecord.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchRecord.input new file mode 100644 index 00000000..2f4fb35d --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchRecord.input @@ -0,0 +1,21 @@ +record SwitchRecord(int i) { + int x(Object o) { + return switch (o) { + case SwitchRecord(int i) -> i; + default -> 0; + }; + } + int f(Object o) { + return switch (o) { + case SwitchRecord(int one, int two, int three, int four, int five, int six, int seven, int eight, int nine) -> nine; + default -> 0; + }; + } + int g(Object o) { + return switch (o) { + case SwitchRecord(int one, int two, int three, int four, int five, int six, int seven, int eight, int nine) -> + System.err.println("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."); + default -> 0; + }; + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchRecord.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchRecord.output new file mode 100644 index 00000000..89d212f8 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchRecord.output @@ -0,0 +1,46 @@ +record SwitchRecord(int i) { + int x(Object o) { + return switch (o) { + case SwitchRecord(int i) -> i; + default -> 0; + }; + } + + int f(Object o) { + return switch (o) { + case SwitchRecord( + int one, + int two, + int three, + int four, + int five, + int six, + int seven, + int eight, + int nine) -> + nine; + default -> 0; + }; + } + + int g(Object o) { + return switch (o) { + case SwitchRecord( + int one, + int two, + int three, + int four, + int five, + int six, + int seven, + int eight, + int nine) -> + System.err.println( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" + + " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis" + + " nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo" + + " consequat."); + default -> 0; + }; + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchUnderscore.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchUnderscore.input new file mode 100644 index 00000000..8d611d24 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchUnderscore.input @@ -0,0 +1,8 @@ +public class SwitchUnderscore { + void x(Object o) { + switch (o) { + case String _: + default: + } + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchUnderscore.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchUnderscore.output new file mode 100644 index 00000000..8d611d24 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/SwitchUnderscore.output @@ -0,0 +1,8 @@ +public class SwitchUnderscore { + void x(Object o) { + switch (o) { + case String _: + default: + } + } +} From b92435acb86e549300bf74bb75ff674db70682e7 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Fri, 8 Dec 2023 10:44:11 -0800 Subject: [PATCH 07/10] Support unnamed variables Fixes https://github.com/google/google-java-format/issues/978 PiperOrigin-RevId: 589179786 --- .../java/JavaInputAstVisitor.java | 8 +++- .../java/java21/Java21InputAstVisitor.java | 10 ++++ .../java/FormatterIntegrationTest.java | 8 +++- .../java/testdata/Unnamed.input | 44 +++++++++++++++++ .../java/testdata/Unnamed.output | 48 +++++++++++++++++++ 5 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.input create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.output 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 5a75afa0..ea967b3d 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java @@ -3563,7 +3563,7 @@ int declareOne( if (receiverExpression.isPresent()) { scan(receiverExpression.get(), null); } else { - visit(name); + variableName(name); } builder.op(op); } @@ -3606,6 +3606,10 @@ int declareOne( return baseDims; } + protected void variableName(Name name) { + visit(name); + } + private void maybeAddDims(Deque> annotations) { maybeAddDims(new ArrayDeque<>(), annotations); } @@ -3696,7 +3700,7 @@ private void declareMany(List fragments, Direction annotationDirec 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/java21/Java21InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java index ad936cae..6abb93bb 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java @@ -23,6 +23,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.PatternCaseLabelTree; import com.sun.source.tree.PatternTree; +import javax.lang.model.element.Name; /** * Extends {@link Java17InputAstVisitor} with support for AST nodes that were added or modified in @@ -76,4 +77,13 @@ public Void visitDeconstructionPattern(DeconstructionPatternTree node, Void unus token(")"); return null; } + + @Override + protected void variableName(Name name) { + if (name.isEmpty()) { + token("_"); + } else { + visit(name); + } + } } 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 67f0eec9..ffcfd218 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java @@ -53,7 +53,13 @@ public class FormatterIntegrationTest { .putAll(16, "I588") .putAll(17, "I683", "I684", "I696") .putAll( - 21, "SwitchGuardClause", "SwitchRecord", "SwitchDouble", "SwitchUnderscore", "I880") + 21, + "SwitchGuardClause", + "SwitchRecord", + "SwitchDouble", + "SwitchUnderscore", + "I880", + "Unnamed") .build(); @Parameters(name = "{index}: {0}") diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.input new file mode 100644 index 00000000..6c5efd12 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.input @@ -0,0 +1,44 @@ +class Unnamed { + { + int acc = 0; + for (Order _ : orders) { + if (acc < LIMIT) { + acc++; + } + } + + for (int i = 0, _ = sideEffect(); i < 10; i++) { } + + Queue q = null; + while (q.size() >= 3) { + var x = q.remove(); + var y = q.remove(); + var _ = q.remove(); + new Point(x, y); + } + + while (q.size() >= 3) { + var x = q.remove(); + var _ = q.remove(); + var _ = q.remove(); + new Point(x, 0) ; + } + + String s = null; + try { + int i = Integer.parseInt(s); + } catch (NumberFormatException _) { + System.out.println("Bad number: " + s); + } + + try { doSomething(); } + catch (Exception _) { doSomething(); } + catch (Throwable _) { doSomething(); } + + try (var _ = ScopedContext.acquire()) { + doSomething(); + } + + stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA")); + } +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.output new file mode 100644 index 00000000..84d8f872 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/Unnamed.output @@ -0,0 +1,48 @@ +class Unnamed { + { + int acc = 0; + for (Order _ : orders) { + if (acc < LIMIT) { + acc++; + } + } + + for (int i = 0, _ = sideEffect(); i < 10; i++) {} + + Queue q = null; + while (q.size() >= 3) { + var x = q.remove(); + var y = q.remove(); + var _ = q.remove(); + new Point(x, y); + } + + while (q.size() >= 3) { + var x = q.remove(); + var _ = q.remove(); + var _ = q.remove(); + new Point(x, 0); + } + + String s = null; + try { + int i = Integer.parseInt(s); + } catch (NumberFormatException _) { + System.out.println("Bad number: " + s); + } + + try { + doSomething(); + } catch (Exception _) { + doSomething(); + } catch (Throwable _) { + doSomething(); + } + + try (var _ = ScopedContext.acquire()) { + doSomething(); + } + + stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA")); + } +} From dc8b461b8599df1f043d6a1efaadfc44dfa6b823 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Fri, 8 Dec 2023 12:27:55 -0800 Subject: [PATCH 08/10] Make g-j-f native image more compatible I was reminded of this by https://github.com/bazelbuild/bazel/pull/20306 PiperOrigin-RevId: 589210771 --- .../META-INF/native-image/reflect-config.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/src/main/resources/META-INF/native-image/reflect-config.json b/core/src/main/resources/META-INF/native-image/reflect-config.json index 2c658034..4d30840f 100644 --- a/core/src/main/resources/META-INF/native-image/reflect-config.json +++ b/core/src/main/resources/META-INF/native-image/reflect-config.json @@ -2,5 +2,23 @@ { "name": "com.sun.tools.javac.parser.UnicodeReader", "allDeclaredMethods": true + }, + { + "name": "com.google.googlejavaformat.java.java17.Java17InputAstVisitor", + "methods": [ + { + "name": "", + "parameterTypes": ["com.google.googlejavaformat.OpsBuilder", "int"] + } + ] + }, + { + "name": "com.google.googlejavaformat.java.java21.Java21InputAstVisitor", + "methods": [ + { + "name": "", + "parameterTypes": ["com.google.googlejavaformat.OpsBuilder", "int"] + } + ] } ] From b5feefe4ab4dbdbba393d769eb1815f26896104b Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Mon, 18 Dec 2023 12:40:05 -0800 Subject: [PATCH 09/10] Initial support for string templates Fixes https://github.com/google/google-java-format/issues/981 PiperOrigin-RevId: 591982309 --- .../googlejavaformat/java/JavaInput.java | 13 +++-- .../googlejavaformat/java/JavacTokens.java | 52 ++++++++++++++++--- .../java/java21/Java21InputAstVisitor.java | 12 +++++ .../java/FormatterIntegrationTest.java | 3 +- .../googlejavaformat/java/testdata/I981.input | 12 +++++ .../java/testdata/I981.output | 14 +++++ 6 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/I981.input create mode 100644 core/src/test/resources/com/google/googlejavaformat/java/testdata/I981.output 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 eee210e2..7b5eb841 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java +++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInput.java @@ -387,7 +387,14 @@ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOExcept final boolean isNumbered; // Is this tok numbered? (tokens and comments) String extraNewline = null; // Extra newline at end? List 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 it = Newlines.lineIterator(originalTokText); @@ -404,10 +411,6 @@ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOExcept 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/JavacTokens.java b/core/src/main/java/com/google/googlejavaformat/java/JavacTokens.java index ba7e3b77..dd8760b2 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.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 @@ public String stringVal() { } } + 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 getTokens( String source, Context context, Set stopTokens) { @@ -106,13 +118,39 @@ public static ImmutableList getTokens( 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/java21/Java21InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java index 6abb93bb..897d6ffc 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java +++ b/core/src/main/java/com/google/googlejavaformat/java/java21/Java21InputAstVisitor.java @@ -23,6 +23,7 @@ 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; /** @@ -60,6 +61,7 @@ public Void visitConstantCaseLabel(ConstantCaseLabelTree node, Void aVoid) { @Override public Void visitDeconstructionPattern(DeconstructionPatternTree node, Void unused) { + sync(node); scan(node.getDeconstructor(), null); builder.open(plusFour); token("("); @@ -78,6 +80,16 @@ public Void visitDeconstructionPattern(DeconstructionPatternTree node, Void unus 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()) { 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 ffcfd218..cf15ecbc 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java @@ -59,7 +59,8 @@ public class FormatterIntegrationTest { "SwitchDouble", "SwitchUnderscore", "I880", - "Unnamed") + "Unnamed", + "I981") .build(); @Parameters(name = "{index}: {0}") diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I981.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I981.input new file mode 100644 index 00000000..bba0b726 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I981.input @@ -0,0 +1,12 @@ +class Foo { + private static final int X = 42; + private static final String A = STR."\{X} = \{X}"; + private static final String B = STR.""; + private static final String C = STR."\{X}"; + private static final String D = STR."\{X}\{X}"; + private static final String E = STR."\{X}\{X}\{X}"; + private static final String F = STR." \{X}"; + private static final String G = STR."\{X} "; + private static final String H = STR."\{X} one long incredibly unbroken sentence moving from "+"topic to topic so that no-one had a chance to interrupt"; + private static final String I = STR."\{X} \uD83D\uDCA9 "; +} diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/I981.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I981.output new file mode 100644 index 00000000..ff173fb6 --- /dev/null +++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/I981.output @@ -0,0 +1,14 @@ +class Foo { + private static final int X = 42; + private static final String A = STR."\{X} = \{X}"; + private static final String B = STR.""; + private static final String C = STR."\{X}"; + private static final String D = STR."\{X}\{X}"; + private static final String E = STR."\{X}\{X}\{X}"; + private static final String F = STR." \{X}"; + private static final String G = STR."\{X} "; + private static final String H = + STR."\{X} one long incredibly unbroken sentence moving from " + + "topic to topic so that no-one had a chance to interrupt"; + private static final String I = STR."\{X} \uD83D\uDCA9 "; +} From bfb281efb0c6790354d4f4f1b9fc8cbb211bd7c8 Mon Sep 17 00:00:00 2001 From: cushon Date: Mon, 18 Dec 2023 21:04:20 +0000 Subject: [PATCH 10/10] Release google-java-format 1.19.0 --- core/pom.xml | 2 +- eclipse_plugin/META-INF/MANIFEST.MF | 2 +- eclipse_plugin/pom.xml | 2 +- pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index d1363fed..9c09d165 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -22,7 +22,7 @@ com.google.googlejavaformat google-java-format-parent - HEAD-SNAPSHOT + 1.19.0 google-java-format diff --git a/eclipse_plugin/META-INF/MANIFEST.MF b/eclipse_plugin/META-INF/MANIFEST.MF index 91324539..2713d865 100644 --- a/eclipse_plugin/META-INF/MANIFEST.MF +++ b/eclipse_plugin/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: google-java-format Bundle-SymbolicName: google-java-format-eclipse-plugin;singleton:=true Bundle-Vendor: Google -Bundle-Version: 1.13.0 +Bundle-Version: 1.19.0 Bundle-RequiredExecutionEnvironment: JavaSE-11 Require-Bundle: org.eclipse.jdt.core;bundle-version="3.10.0", org.eclipse.jface, diff --git a/eclipse_plugin/pom.xml b/eclipse_plugin/pom.xml index 9e6acdac..7bcb44db 100644 --- a/eclipse_plugin/pom.xml +++ b/eclipse_plugin/pom.xml @@ -22,7 +22,7 @@ com.google.googlejavaformat google-java-format-eclipse-plugin eclipse-plugin - 1.13.0 + 1.19.0 Google Java Format Plugin for Eclipse 4.5+ diff --git a/pom.xml b/pom.xml index 8b3ca51c..5fe7e880 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ com.google.googlejavaformat google-java-format-parent pom - HEAD-SNAPSHOT + 1.19.0 core