From 5c26d0df6ee6e98027c2d41375cd6961b6ec38f9 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Thu, 18 Jul 2024 07:41:26 -0700 Subject: [PATCH 1/4] Fix `ASTHelpers#isRuleKind` on JDK versions without `CaseTree#getCaseKind` Fixes https://github.com/google/error-prone/issues/4479#issuecomment-2235327984 PiperOrigin-RevId: 653615823 --- .../src/main/java/com/google/errorprone/util/ASTHelpers.java | 3 +++ .../bugpatterns/TraditionalSwitchExpressionTest.java | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java index 1707c6a4b50..79bf0896a8f 100644 --- a/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java +++ b/check_api/src/main/java/com/google/errorprone/util/ASTHelpers.java @@ -2813,6 +2813,9 @@ private static Method getCaseTreeGetExpressionsMethod() { * }. */ public static boolean isRuleKind(CaseTree caseTree) { + if (GET_CASE_KIND_METHOD == null) { + return false; + } Enum kind; try { kind = (Enum) GET_CASE_KIND_METHOD.invoke(caseTree); diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/TraditionalSwitchExpressionTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/TraditionalSwitchExpressionTest.java index 931626e04b4..00a3dbc18c4 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/TraditionalSwitchExpressionTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/TraditionalSwitchExpressionTest.java @@ -50,7 +50,6 @@ public void positive() { @Test public void negativeStatement() { - assumeTrue(RuntimeVersion.isAtLeast14()); testHelper .addSourceLines( "Test.java", From a19bcbc0a839f403b32ac01bd85f8cbe49a1403e Mon Sep 17 00:00:00 2001 From: markbrady Date: Thu, 18 Jul 2024 12:43:29 -0700 Subject: [PATCH 2/4] StatementSwitchToExpressionSwitch: Enhance code comment handling for direct conversion, including retaining comments after the final statement in a case PiperOrigin-RevId: 653720761 --- .../StatementSwitchToExpressionSwitch.java | 95 +++++++-- ...StatementSwitchToExpressionSwitchTest.java | 187 ++++++++++++++++-- 2 files changed, 256 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java index 82011eb8dc7..11ec6a498be 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitch.java @@ -33,6 +33,7 @@ import static java.util.stream.Collectors.joining; import com.google.auto.value.AutoValue; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -45,6 +46,7 @@ import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.Matchers; import com.google.errorprone.util.ASTHelpers; +import com.google.errorprone.util.ErrorProneComment; import com.google.errorprone.util.Reachability; import com.google.errorprone.util.SourceVersion; import com.sun.source.tree.AssignmentTree; @@ -513,6 +515,11 @@ private static SuggestedFix convertDirectlyToExpressionSwitch( SwitchTree switchTree, VisitorState state, AnalysisResult analysisResult) { List cases = switchTree.getCases(); + ImmutableList allSwitchComments = + state.getTokensForNode(switchTree).stream() + .flatMap(errorProneToken -> errorProneToken.comments().stream()) + .collect(toImmutableList()); + StringBuilder replacementCodeBuilder = new StringBuilder(); replacementCodeBuilder .append("switch ") @@ -540,19 +547,49 @@ private static SuggestedFix convertDirectlyToExpressionSwitch( replacementCodeBuilder.append( isDefaultCase ? "default" : printCaseExpressions(caseTree, state)); + Optional commentsAfterCaseOptional = + extractCommentsAfterCase(switchTree, allSwitchComments, state, caseIndex); if (analysisResult.groupedWithNextCase().get(caseIndex)) { firstCaseInGroup = false; replacementCodeBuilder.append(", "); // Capture comments from this case so they can be added to the group's transformed case if (!transformedBlockSource.trim().isEmpty()) { - groupedCaseCommentsAccumulator.append(removeFallThruLines(transformedBlockSource)); + String commentsToAppend = removeFallThruLines(transformedBlockSource); + if (groupedCaseCommentsAccumulator.length() > 0) { + groupedCaseCommentsAccumulator.append("\n"); + } + groupedCaseCommentsAccumulator.append(commentsToAppend); + } + + if (commentsAfterCaseOptional.isPresent()) { + if (groupedCaseCommentsAccumulator.length() > 0) { + groupedCaseCommentsAccumulator.append("\n"); + } + groupedCaseCommentsAccumulator.append(commentsAfterCaseOptional.get()); } + // Add additional cases to the list on the lhs of the arrow continue; } else { - // This case is the last case in its group, so insert the collected comments from the lhs of - // the colon here - transformedBlockSource = groupedCaseCommentsAccumulator + transformedBlockSource; + // Extract comments (if any) preceding break that was removed as redundant + Optional commentsBeforeRemovedBreak = + filteredStatements.isEmpty() + ? Optional.empty() + : extractCommentsBeforeRemovedBreak(caseTree, state, filteredStatements); + + // Join together all comments and code, separating with newlines + transformedBlockSource = + Joiner.on("\n") + .skipNulls() + .join( + // This case is the last case in its group, so insert any comments from prior + // grouped cases first + groupedCaseCommentsAccumulator.length() == 0 + ? null + : groupedCaseCommentsAccumulator.toString(), + transformedBlockSource.isEmpty() ? null : transformedBlockSource, + commentsBeforeRemovedBreak.orElse(null), + commentsAfterCaseOptional.orElse(null)); } replacementCodeBuilder.append(" -> "); @@ -570,17 +607,10 @@ private static SuggestedFix convertDirectlyToExpressionSwitch( } } else { // Transformed block has code - // Extract comments (if any) for break that was removed as redundant - Optional commentsBeforeRemovedBreak = - extractCommentsBeforeRemovedBreak(caseTree, state, filteredStatements); - if (commentsBeforeRemovedBreak.isPresent()) { - transformedBlockSource = transformedBlockSource + "\n" + commentsBeforeRemovedBreak.get(); - } - // To improve readability, don't use braces on the rhs if not needed if (shouldTransformCaseWithoutBraces(filteredStatements)) { - // Single statement with no comments - no braces needed - replacementCodeBuilder.append(transformedBlockSource); + // No braces needed + replacementCodeBuilder.append("\n").append(transformedBlockSource); } else { // Use braces on the rhs replacementCodeBuilder.append("{\n").append(transformedBlockSource).append("\n}"); @@ -599,7 +629,7 @@ private static SuggestedFix convertDirectlyToExpressionSwitch( /** * Transforms the supplied statement switch into a {@code return switch ...} style of expression * switch. In this conversion, each nontrivial statement block is mapped one-to-one to a new - * expression on the right-hand side of the arrow. Comments are presevered where possible. + * expression on the right-hand side of the arrow. Comments are preserved where possible. * Precondition: the {@code AnalysisResult} for the {@code SwitchTree} must have deduced that this * conversion is possible. */ @@ -903,6 +933,43 @@ private static int extractLhsComments( return lhsEnd; } + /** + * Extracts any comments appearing after the specified {@code caseIndex} but before the subsequent + * case or end of the {@code switchTree}. Comments are merged into a single string separated by + * newlines. + */ + private static Optional extractCommentsAfterCase( + SwitchTree switchTree, + ImmutableList allSwitchComments, + VisitorState state, + int caseIndex) { + + // Indexing relative to the start position of the switch statement + int switchStart = getStartPosition(switchTree); + // Invariant: caseEndIndex >= 0 + int caseEndIndex = state.getEndPosition(switchTree.getCases().get(caseIndex)) - switchStart; + // Invariant: nextCaseStartIndex >= caseEndIndex + int nextCaseStartIndex = + caseIndex == switchTree.getCases().size() - 1 + ? state.getEndPosition(switchTree) - switchStart + : getStartPosition(switchTree.getCases().get(caseIndex + 1)) - switchStart; + + String filteredComments = + allSwitchComments.stream() + // Comments after the end of the current case and before the start of the next case + .filter( + comment -> + comment.getPos() >= caseEndIndex && comment.getPos() < nextCaseStartIndex) + .map(ErrorProneComment::getText) + // Remove "fall thru" comments + .map(commentText -> removeFallThruLines(commentText)) + // Remove empty comments + .filter(commentText -> !commentText.isEmpty()) + .collect(joining("\n")); + + return filteredComments.isEmpty() ? Optional.empty() : Optional.of(filteredComments); + } + /** * Finds the position in source corresponding to the end of the code block of the supplied {@code * caseIndex} within all {@code cases}. diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java index d1e2d6c144e..08f4a892626 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/StatementSwitchToExpressionSwitchTest.java @@ -110,6 +110,7 @@ public void switchByEnum_removesRedundantBreak_error() { " // Middle comment", " System.out.println(\"obverse\");", " // Break comment", + " // End comment", " }", " case REVERSE -> System.out.println(\"reverse\");", " }", @@ -268,7 +269,8 @@ public void switchByEnumCard_combinesCaseComments_error() { " public void foo(Side side) { ", " switch(side) {", " case HEART -> System.out.println(\"heart2\");", - " case DIAMOND, SPADE, CLUB -> { /* sparkly */", + " case DIAMOND, SPADE, CLUB -> {", + " /* sparkly */", " // Empty block comment 1", " // Empty block comment 2", " // Start of block comment 1", @@ -354,7 +356,7 @@ public void switchByEnumCard2_removesRedundantBreaks_error() { " case HEART -> ", " System.out.println(\"heart\");", " // Pre break comment", - " ", + " // Post break comment", " case DIAMOND -> {", " // Diamond break comment", " break;", @@ -444,7 +446,8 @@ public void switchByEnumCard_onlyExpressionsAndThrowAreBraceless_error() { " case SPADE -> {", " return;", " }", - " case CLUB -> throw new AssertionError();", + " case CLUB ->", + " throw new AssertionError();", " }", " }", " }", @@ -591,10 +594,11 @@ public void switchWithDefaultInMiddle_error() { " System.out.println(\"diamond\");", " return;", " }", - " default -> /* comment: */", + " default -> ", + " /* comment: */", " System.out.println(\"club\");", - " ", - " case SPADE -> System.out.println(\"spade\");", + " case SPADE -> ", + " System.out.println(\"spade\");", " }", " }", "}") @@ -675,8 +679,11 @@ public void switchWithLabelledBreak_error() { " System.out.println(\"will return\");", " return;", " }", - " case DIAMOND -> {break outer;}", - " case SPADE, CLUB -> System.out.println(\"everything else\");", + " case DIAMOND -> {", + " break outer;", + " }", + " case SPADE, CLUB -> ", + " System.out.println(\"everything else\");", " }", " }", " }", @@ -886,9 +893,12 @@ public void switchByEnumCardWithReturnNested1_error() { " ", " public void foo(Side side) { ", " switch(side) {", - " case HEART-> System.out.println(\"heart\");", - " case DIAMOND -> System.out.println(\"nested1\");", - " case SPADE, CLUB -> System.out.println(\"everything else\");", + " case HEART-> ", + " System.out.println(\"heart\");", + " case DIAMOND -> ", + " System.out.println(\"nested1\");", + " case SPADE, CLUB -> ", + " System.out.println(\"everything else\");", " }", " }", "}") @@ -1217,7 +1227,6 @@ public void switchByEnum_caseHasOnlyComments_error() { " // more comments.", " // Diamond comment", " System.out.println(\"Heart or diamond\");", - " ", " case SPADES, CLUBS -> {", " bar();", " System.out.println(\"Black suit\");", @@ -1230,6 +1239,86 @@ public void switchByEnum_caseHasOnlyComments_error() { .doTest(); } + @Test + public void switchByEnum_accumulatedComments_error() { + // Comments should be aggregated across multiple cases + assumeTrue(RuntimeVersion.isAtLeast14()); + helper + .addSourceLines( + "Test.java", + "class Test {", + " enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};", + " public Test() {}", + " private void foo(Suit suit) {", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " switch(suit) {", + " case /* red */ HEARTS:", + " // A comment here", + " // more comments.", + " case /* red */ DIAMONDS:", + " // Diamonds comment", + " case /* black */SPADES:", + " // Spades comment", + " case /* black */CLUBS:", + " bar();", + " System.out.println(\"Any suit\");", + " }", + " }", + " private void bar() {}", + "}") + .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion") + .doTest(); + + refactoringHelper + .addInputLines( + "Test.java", + "class Test {", + " enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};", + " public Test() {}", + " private void foo(Suit suit) {", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " switch(suit) {", + " case /* red */ HEARTS:", + " // A comment here", + " // more comments.", + " case /* red */ DIAMONDS:", + " // Diamonds comment", + " case /* black */SPADES:", + " // Spades comment", + " case /* black */CLUBS:", + " bar();", + " System.out.println(\"Any suit\");", + " }", + " }", + " private void bar() {}", + "}") + .addOutputLines( + "Test.java", + "class Test {", + " enum Suit {HEARTS, CLUBS, SPADES, DIAMONDS};", + " public Test() {}", + " private void foo(Suit suit) {", + " switch(suit) {", + " case HEARTS, DIAMONDS, SPADES, CLUBS -> {", + " /* red */", + " // A comment here", + " /* red */", + " // more comments.", + " // Diamonds comment", + " /* black */", + " // Spades comment", + " /* black */", + " bar();", + " System.out.println(\"Any suit\");", + " }", + " }", + " }", + " private void bar() {}", + "}") + .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion") + .doTest(); + } + @Test public void switchByEnum_surroundingBracesCannotRemove_error() { // Can't remove braces around OBVERSE because break statements are not a member of @@ -1380,6 +1469,80 @@ public void switchByEnum_surroundingBracesEmpty_error() { .doTest(); } + @Test + public void switchByEnum_afterReturnComments_error() { + assumeTrue(RuntimeVersion.isAtLeast14()); + helper + .addSourceLines( + "Test.java", + "class Test {", + " enum Suit {HEART, SPADE, DIAMOND, CLUB};", + " public Test(int foo) {", + " }", + " ", + " public int foo(Suit suit) { ", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " switch(suit) {", + " case HEART:", + " // before return comment", + " return 123;", + " // after return comment", + " /* more comments */", + " default:", + " }", + " return 0;", + " }", + "}") + .setArgs( + ImmutableList.of("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion")) + .doTest(); + + refactoringHelper + .addInputLines( + "Test.java", + "class Test {", + " enum Suit {HEART, SPADE, DIAMOND, CLUB};", + " public Test(int foo) {", + " }", + " ", + " public int foo(Suit suit) { ", + " // BUG: Diagnostic contains: [StatementSwitchToExpressionSwitch]", + " switch(suit) {", + " case HEART:", + " // before return comment", + " return 123;", + " // after return comment", + " /* more comments */", + " default:", + " //default comment", + " }", + " return 0;", + " }", + "}") + .addOutputLines( + "Test.java", + "class Test {", + " enum Suit {HEART, SPADE, DIAMOND, CLUB};", + " public Test(int foo) {}", + " public int foo(Suit suit) {", + " switch(suit) {", + " case HEART -> {", + " // before return comment", + " return 123;", + " // after return comment", + " /* more comments */", + " }", + " default -> {", + " //default comment", + " }", + " }", + " return 0;", + " }", + "}") + .setArgs("-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion") + .doTest(); + } + /********************************** * * Return switch test cases From 526aa72ce8c09d10129c4d51bda0decddd99d56a Mon Sep 17 00:00:00 2001 From: cpovirk Date: Thu, 18 Jul 2024 13:25:12 -0700 Subject: [PATCH 3/4] Restore `module-info`. PiperOrigin-RevId: 653734294 --- annotations/src/main/java/module-info.java | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 annotations/src/main/java/module-info.java diff --git a/annotations/src/main/java/module-info.java b/annotations/src/main/java/module-info.java new file mode 100644 index 00000000000..3dccd229104 --- /dev/null +++ b/annotations/src/main/java/module-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015 The Error Prone 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. + */ + +open module com.google.errorprone.annotations { + requires java.compiler; + + exports com.google.errorprone.annotations; + exports com.google.errorprone.annotations.concurrent; +} From 436f891d38b23e405214d7ada9d77f6ba6099ad2 Mon Sep 17 00:00:00 2001 From: cushon Date: Thu, 18 Jul 2024 20:58:59 +0000 Subject: [PATCH 4/4] Release Error Prone 2.29.2 --- annotation/pom.xml | 2 +- annotations/pom.xml | 2 +- check_api/pom.xml | 2 +- core/pom.xml | 2 +- docgen/pom.xml | 2 +- docgen_processor/pom.xml | 2 +- pom.xml | 2 +- refaster/pom.xml | 2 +- test_helpers/pom.xml | 2 +- type_annotations/pom.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/annotation/pom.xml b/annotation/pom.xml index fbbba7c5524..9411bcb2b60 100644 --- a/annotation/pom.xml +++ b/annotation/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.29.2 @BugPattern annotation diff --git a/annotations/pom.xml b/annotations/pom.xml index 62ebfe88a40..5d66e1f6860 100644 --- a/annotations/pom.xml +++ b/annotations/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.29.2 error-prone annotations diff --git a/check_api/pom.xml b/check_api/pom.xml index 1a96ffd6473..a3c011f48a9 100644 --- a/check_api/pom.xml +++ b/check_api/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.29.2 error-prone check api diff --git a/core/pom.xml b/core/pom.xml index db42176e114..1653f7ace57 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.29.2 error-prone library diff --git a/docgen/pom.xml b/docgen/pom.xml index 8ee00587502..fcfda431b00 100644 --- a/docgen/pom.xml +++ b/docgen/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.29.2 Documentation tool for generating Error Prone bugpattern documentation diff --git a/docgen_processor/pom.xml b/docgen_processor/pom.xml index 7d97a4cb4ca..80b0dc557f4 100644 --- a/docgen_processor/pom.xml +++ b/docgen_processor/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.29.2 JSR-269 annotation processor for @BugPattern annotation diff --git a/pom.xml b/pom.xml index 8b5587c1b00..471bb4ef4f6 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ Error Prone parent POM com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.29.2 pom Error Prone is a static analysis tool for Java that catches common programming mistakes at compile-time. diff --git a/refaster/pom.xml b/refaster/pom.xml index 968b0c2bae4..dabb53219b1 100644 --- a/refaster/pom.xml +++ b/refaster/pom.xml @@ -19,7 +19,7 @@ error_prone_parent com.google.errorprone - 1.0-HEAD-SNAPSHOT + 2.29.2 4.0.0 diff --git a/test_helpers/pom.xml b/test_helpers/pom.xml index 76fbfaf30d9..4e7ec27fd1f 100644 --- a/test_helpers/pom.xml +++ b/test_helpers/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.29.2 error-prone test helpers diff --git a/type_annotations/pom.xml b/type_annotations/pom.xml index 365123af188..75ea6d5928e 100644 --- a/type_annotations/pom.xml +++ b/type_annotations/pom.xml @@ -21,7 +21,7 @@ com.google.errorprone error_prone_parent - 1.0-HEAD-SNAPSHOT + 2.29.2 error-prone type annotations