From b86c508be5f42e7ed0d55cd1abe2c04d2db66676 Mon Sep 17 00:00:00 2001 From: CK Date: Thu, 7 Dec 2023 14:38:12 -0800 Subject: [PATCH] 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 038e4eda6..d1363fed1 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 9ff702d5a..5aa7a1233 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 a0561e2fe..97bb2ffe5 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 000000000..a96ef99e2 --- /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 61a43468a..688b24d8e 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 000000000..25df58096 --- /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 000000000..25df58096 --- /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; + }; + } +}