diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 0bbae82ab..74e1bcb6d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -41,10 +41,10 @@ Please replace this sentence with log output, if applicable. -- Operating system (e.g. MacOS Monterey). -- Java version (i.e. `java --version`, e.g. `17.0.16`). -- Error Prone version (e.g. `2.41.0`). -- Error Prone Support version (e.g. `0.24.0`). +- Operating system (e.g. MacOS Tahoe). +- Java version (i.e. `java --version`, e.g. `21.0.8`). +- Error Prone version (e.g. `2.43.0`). +- Error Prone Support version (e.g. `0.26.0`). ### Additional context diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 78a7d4eea..f25464126 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,12 +19,8 @@ jobs: strategy: matrix: os: [ ubuntu-24.04, macos-15, windows-2025 ] - build-jdk: [ 21.0.8 ] - target-jdk: [ 17.0.16 ] - include: - - os: ubuntu-24.04 - build-jdk: 25 - target-jdk: 17.0.16 + build-jdk: [ 25 ] + target-jdk: [ 21.0.8 ] runs-on: ${{ matrix.os }} steps: - name: Install Harden-Runner @@ -84,7 +80,7 @@ jobs: - name: Check out code and set up JDK and Maven uses: s4u/setup-maven-action@6c4e9964d4ecb8f1026310cd8618791fd51a8016 # v1.19.0 with: - java-version: 21.0.8 + java-version: 25 java-distribution: temurin maven-version: 3.9.11 - name: Build and deploy diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5b8a3ef1b..58ede7c3e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,7 +40,7 @@ jobs: - name: Check out code and set up JDK and Maven uses: s4u/setup-maven-action@6c4e9964d4ecb8f1026310cd8618791fd51a8016 # v1.19.0 with: - java-version: 21.0.8 + java-version: 25 java-distribution: temurin maven-version: 3.9.11 - name: Initialize CodeQL diff --git a/.github/workflows/error-prone-compat.yml b/.github/workflows/error-prone-compat.yml index 81fe429a8..3594b0578 100644 --- a/.github/workflows/error-prone-compat.yml +++ b/.github/workflows/error-prone-compat.yml @@ -38,7 +38,7 @@ jobs: - name: Check out code and set up JDK and Maven uses: s4u/setup-maven-action@6c4e9964d4ecb8f1026310cd8618791fd51a8016 # v1.19.0 with: - java-version: 21.0.8 + java-version: 25 java-distribution: temurin maven-version: 3.9.11 - name: Quickly build and install project diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b4d255ca2..967de3c8c 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -63,7 +63,7 @@ jobs: uses: s4u/setup-maven-action@6c4e9964d4ecb8f1026310cd8618791fd51a8016 # v1.19.0 with: checkout-ref: ${{ github.event.issue.pull_request && format('refs/pull/{0}/head', github.event.issue.number) || github.ref }} - java-version: 21.0.8 + java-version: 25 java-distribution: temurin maven-version: 3.9.11 - name: Install project to local Maven repository diff --git a/.github/workflows/pitest-analyze-pr.yml b/.github/workflows/pitest-analyze-pr.yml index a3103d7e1..9577fe052 100644 --- a/.github/workflows/pitest-analyze-pr.yml +++ b/.github/workflows/pitest-analyze-pr.yml @@ -29,7 +29,7 @@ jobs: uses: s4u/setup-maven-action@6c4e9964d4ecb8f1026310cd8618791fd51a8016 # v1.19.0 with: checkout-fetch-depth: 2 - java-version: 21.0.8 + java-version: 25 java-distribution: temurin maven-version: 3.9.11 - name: Run Pitest diff --git a/.github/workflows/pitest-update-pr.yml b/.github/workflows/pitest-update-pr.yml index d0671fb22..b89f4bcf5 100644 --- a/.github/workflows/pitest-update-pr.yml +++ b/.github/workflows/pitest-update-pr.yml @@ -36,7 +36,7 @@ jobs: - name: Check out code and set up JDK and Maven uses: s4u/setup-maven-action@6c4e9964d4ecb8f1026310cd8618791fd51a8016 # v1.19.0 with: - java-version: 21.0.8 + java-version: 25 java-distribution: temurin maven-version: 3.9.11 - name: Download Pitest analysis artifact diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index c9fd7ce6f..2e09af0da 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -31,7 +31,7 @@ jobs: - name: Check out code and set up JDK and Maven uses: s4u/setup-maven-action@6c4e9964d4ecb8f1026310cd8618791fd51a8016 # v1.19.0 with: - java-version: 21.0.8 + java-version: 25 java-distribution: temurin maven-version: 3.9.11 - name: Set up Reviewdog diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 5c64e7a2e..39524d31b 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -39,7 +39,7 @@ jobs: uses: s4u/setup-maven-action@6c4e9964d4ecb8f1026310cd8618791fd51a8016 # v1.19.0 with: checkout-fetch-depth: 0 - java-version: 21.0.8 + java-version: 25 java-distribution: temurin maven-version: 3.9.11 - name: Create missing `test` directory diff --git a/.mvn/jvm.config b/.mvn/jvm.config index b79f9a238..35f7f8c98 100644 --- a/.mvn/jvm.config +++ b/.mvn/jvm.config @@ -1,5 +1,3 @@ --XX:ReservedCodeCacheSize=512m --XX:SoftRefLRUPolicyMSPerMB=10 -XX:+UseParallelGC --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 000000000..fbceb95a9 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,3 @@ +java=25-tem +maven=3.9.11 +mvnd=1.0.3 diff --git a/README.md b/README.md index 0a8324253..2c3beed68 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ high-quality and consistent Java code_][picnic-blog-ep-post]. This library is built on top of [Error Prone][error-prone-orig-repo]. To use it, read the installation guide for Maven or Gradle below. The library requires -that your build is executed using JDK 17 or above, but supports builds that +that your build is executed using JDK 21 or above, but supports builds that [target][baeldung-java-source-target-options] older versions of Java. #### Maven @@ -188,7 +188,8 @@ rules][refaster-rules]. This is a [Maven][maven] project, so running `mvn clean install` performs a full clean build and installs the library to your local Maven repository. While -the project targets JDK 17, building it requires JDK 21. +the project targets JDK 21, building it requires JDK 25. If you use +[SDKMAN!][sdmkan], simply run `sdk env` to configure the current shell. Once you've made changes, the build may fail due to a warning or error emitted by static code analysis. The flags and commands listed below allow you to @@ -322,6 +323,7 @@ OSS license generously provided by [Arcmutate][arcmutate]. Check them out! [script-run-branch-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-branch-mutation-tests.sh [script-run-full-build]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-full-build.sh [script-run-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-mutation-tests.sh +[sdkman]: https://sdkman.io [security]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/SECURITY.md [sonarcloud-coverage-badge]: https://sonarcloud.io/api/project_badges/measure?project=PicnicSupermarket_error-prone-support&metric=coverage [sonarcloud-coverage-master]: https://sonarcloud.io/component_measures?id=PicnicSupermarket_error-prone-support&metric=coverage diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java index 0ace6d09e..96a76e3f7 100644 --- a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java @@ -128,9 +128,9 @@ private ImmutableList getCollectedTests() { private static Optional getClassUnderTest( MethodInvocationTree tree, VisitorState state) { if (TEST_HELPER_NEW_INSTANCE.matches(tree, state)) { - return Optional.ofNullable(ASTHelpers.getSymbol(tree.getArguments().get(0))) + return Optional.ofNullable(ASTHelpers.getSymbol(tree.getArguments().getFirst())) .filter(s -> !s.type.allparams().isEmpty()) - .map(s -> s.type.allparams().get(0).tsym.getQualifiedName().toString()); + .map(s -> s.type.allparams().getFirst().tsym.getQualifiedName().toString()); } ExpressionTree receiver = ASTHelpers.getReceiver(tree); @@ -142,7 +142,7 @@ private static Optional getClassUnderTest( private static void extractIdentificationBugPatternTestCases( MethodInvocationTree tree, List sink, VisitorState state) { if (IDENTIFICATION_SOURCE_LINES.matches(tree, state)) { - String path = ASTHelpers.constValue(tree.getArguments().get(0), String.class); + String path = ASTHelpers.constValue(tree.getArguments().getFirst(), String.class); Optional sourceCode = getSourceCode(tree).filter(s -> s.contains("// BUG: Diagnostic")); if (path != null && sourceCode.isPresent()) { @@ -169,7 +169,7 @@ private static void extractReplacementBugPatternTestCases( requireNonNull( ASTHelpers.getReceiver(tree), "Instance method invocation must have receiver"); - String path = ASTHelpers.constValue(inputTree.getArguments().get(0), String.class); + String path = ASTHelpers.constValue(inputTree.getArguments().getFirst(), String.class); Optional inputCode = getSourceCode(inputTree); if (path != null && inputCode.isPresent()) { Optional outputCode = diff --git a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/Compilation.java b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/Compilation.java index b15104a70..c4458c8a1 100644 --- a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/Compilation.java +++ b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/Compilation.java @@ -31,9 +31,8 @@ public static void compileWithDocumentationGenerator( String outputDirectory, String path, String... lines) { /* * The compiler options specified here largely match those used by Error Prone's - * `CompilationTestHelper`. A key difference is the stricter linting configuration. When - * compiling using JDK 21+, these lint options also require that certain JDK modules are - * explicitly exported. + * `CompilationTestHelper`. A key difference is the stricter linting configuration, which in + * turn also requires that certain JDK modules are explicitly exported. */ compile( ImmutableList.of( diff --git a/error-prone-contrib/pom.xml b/error-prone-contrib/pom.xml index dcdc0c474..97c3e301a 100644 --- a/error-prone-contrib/pom.xml +++ b/error-prone-contrib/pom.xml @@ -218,11 +218,6 @@ rewrite-java provided - - org.openrewrite - rewrite-java-17 - test - org.openrewrite rewrite-java-21 diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/CanonicalAnnotationSyntax.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/CanonicalAnnotationSyntax.java index f9939c44f..8966fd322 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/CanonicalAnnotationSyntax.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/CanonicalAnnotationSyntax.java @@ -87,7 +87,7 @@ private static Optional dropRedundantValueAttribute( return Optional.empty(); } - ExpressionTree arg = args.get(0); + ExpressionTree arg = args.getFirst(); if (state.getSourceForNode(arg) == null) { /* * The annotation argument doesn't have a source representation, e.g. because `value` isn't @@ -139,7 +139,7 @@ private static Optional simplifyAttributeValue(ExpressionTree expr, Visi private static Optional simplifySingletonArray(NewArrayTree array, VisitorState state) { return Optional.of(array.getInitializers()) .filter(initializers -> initializers.size() == 1) - .map(initializers -> SourceCode.treeToString(initializers.get(0), state)); + .map(initializers -> SourceCode.treeToString(initializers.getFirst(), state)); } private static Optional dropTrailingComma(NewArrayTree array, VisitorState state) { diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/DirectReturn.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/DirectReturn.java index 7b7a09318..21cb6acac 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/DirectReturn.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/DirectReturn.java @@ -75,7 +75,7 @@ public Description matchBlock(BlockTree tree, VisitorState state) { return Description.NO_MATCH; } - StatementTree finalStatement = statements.get(statements.size() - 1); + StatementTree finalStatement = statements.getLast(); if (!VARIABLE_RETURN.matches(finalStatement, state)) { return Description.NO_MATCH; } @@ -105,24 +105,20 @@ public Description matchBlock(BlockTree tree, VisitorState state) { } private static Optional tryMatchAssignment(Symbol targetSymbol, Tree tree) { - if (tree instanceof ExpressionStatementTree expressionStatement) { - return tryMatchAssignment(targetSymbol, expressionStatement.getExpression()); - } - - if (tree instanceof AssignmentTree assignment) { - return targetSymbol.equals(ASTHelpers.getSymbol(assignment.getVariable())) - ? Optional.of(assignment.getExpression()) - : Optional.empty(); - } - - if (tree instanceof VariableTree declaration) { - return declaration.getModifiers().getAnnotations().isEmpty() - && targetSymbol.equals(ASTHelpers.getSymbol(declaration)) - ? Optional.ofNullable(declaration.getInitializer()) - : Optional.empty(); - } - - return Optional.empty(); + return switch (tree) { + case ExpressionStatementTree expressionStatement -> + tryMatchAssignment(targetSymbol, expressionStatement.getExpression()); + case AssignmentTree assignment -> + targetSymbol.equals(ASTHelpers.getSymbol(assignment.getVariable())) + ? Optional.of(assignment.getExpression()) + : Optional.empty(); + case VariableTree declaration -> + declaration.getModifiers().getAnnotations().isEmpty() + && targetSymbol.equals(ASTHelpers.getSymbol(declaration)) + ? Optional.ofNullable(declaration.getInitializer()) + : Optional.empty(); + default -> Optional.empty(); + }; } /** diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/EagerStringFormatting.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/EagerStringFormatting.java index e3987b595..c9cce789b 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/EagerStringFormatting.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/EagerStringFormatting.java @@ -134,7 +134,7 @@ private Description analyzeFormatStringContext( private Description analyzeRequireNonNullStringFormatContext( StringFormatExpression stringFormat, MethodInvocationTree context) { List arguments = context.getArguments(); - if (arguments.size() != 2 || arguments.get(0).equals(stringFormat.expression())) { + if (arguments.size() != 2 || arguments.getFirst().equals(stringFormat.expression())) { /* Vacuous validation that string formatting doesn't yield `null`. */ return buildDescription(context).setMessage(MESSAGE_NEVER_NULL_ARGUMENT).build(); } @@ -157,7 +157,7 @@ private Description analyzeRequireNonNullStringFormatContext( private Description analyzeGuavaGuardStringFormatContext( StringFormatExpression stringFormat, MethodInvocationTree context, VisitorState state) { List arguments = context.getArguments(); - if (arguments.get(0).equals(stringFormat.expression())) { + if (arguments.getFirst().equals(stringFormat.expression())) { /* * Vacuous `checkNotNull` or `verifyNotNull` validation that string formatting doesn't yield * `null`. @@ -185,8 +185,8 @@ private Description analyzeSlf4jLoggerStringFormatContext( } List arguments = context.getArguments(); - int leftOffset = SLF4J_MARKER.matches(arguments.get(0), state) ? 1 : 0; - int rightOffset = THROWABLE.matches(arguments.get(arguments.size() - 1), state) ? 1 : 0; + int leftOffset = SLF4J_MARKER.matches(arguments.getFirst(), state) ? 1 : 0; + int rightOffset = THROWABLE.matches(arguments.getLast(), state) ? 1 : 0; if (arguments.size() != leftOffset + 1 + rightOffset) { /* * The format string produces a format string itself, or its result is the input to another @@ -258,7 +258,7 @@ private static Optional tryCreate( if (STATIC_FORMAT_STRING.matches(tree, state)) { List arguments = tree.getArguments(); - int argOffset = LOCALE.matches(arguments.get(0), state) ? 1 : 0; + int argOffset = LOCALE.matches(arguments.getFirst(), state) ? 1 : 0; return Optional.of( create( tree, diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ExplicitArgumentEnumeration.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ExplicitArgumentEnumeration.java index 7ccdc2140..cd6123f3c 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ExplicitArgumentEnumeration.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ExplicitArgumentEnumeration.java @@ -93,7 +93,7 @@ public final class ExplicitArgumentEnumeration extends BugChecker .named("copyOf"), symbolMatcher( (symbol, state) -> - state.getTypes().isArray(((MethodSymbol) symbol).params().get(0).type))), + state.getTypes().isArray(((MethodSymbol) symbol).params().getFirst().type))), staticMethod().onClass(Arrays.class.getCanonicalName()).named("asList")); private static final Matcher FLUX = staticMethod().onDescendantOf("reactor.core.publisher.Flux"); @@ -141,7 +141,7 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState return Description.NO_MATCH; } - ExpressionTree argument = tree.getArguments().get(0); + ExpressionTree argument = tree.getArguments().getFirst(); if (!EXPLICIT_ITERABLE_CREATOR.matches(argument, state)) { return Description.NO_MATCH; } @@ -156,7 +156,7 @@ private static boolean isUnaryIterableAcceptingMethod(MethodSymbol method, Visit List params = method.params(); return !method.isVarArgs() && params.size() == 1 - && ASTHelpers.isSubtype(params.get(0).type, state.getSymtab().iterableType, state); + && ASTHelpers.isSubtype(params.getFirst().type, state.getSymtab().iterableType, state); } private static boolean isLocalOverload(MethodSymbol calledMethod, VisitorState state) { diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java index b77fdf588..5e2b73473 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java @@ -105,7 +105,7 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState return Description.NO_MATCH; } - ExpressionTree sourceTree = arguments.get(0); + ExpressionTree sourceTree = arguments.getFirst(); Type sourceType = ASTHelpers.getType(sourceTree); Type resultType = ASTHelpers.getType(tree); TargetType targetType = TargetType.targetType(state); diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationListing.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationListing.java index 973eb3ba7..5976de277 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationListing.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationListing.java @@ -90,7 +90,7 @@ public Description matchModifiers(ModifiersTree tree, VisitorState state) { } return describeMatch( - originalOrdering.get(0), fixOrdering(originalOrdering, sortedAnnotations, state)); + originalOrdering.getFirst(), fixOrdering(originalOrdering, sortedAnnotations, state)); } private static ImmutableList sort( diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MockitoMockClassReference.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MockitoMockClassReference.java index 32d3c823b..c3c64c90c 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MockitoMockClassReference.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MockitoMockClassReference.java @@ -20,8 +20,10 @@ import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.Matcher; import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.ReturnTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; import java.util.List; @@ -64,18 +66,18 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState } List arguments = tree.getArguments(); - return describeMatch(tree, SuggestedFixes.removeElement(arguments.get(0), arguments, state)); + return describeMatch( + tree, SuggestedFixes.removeElement(arguments.getFirst(), arguments, state)); } - // XXX: Use switch pattern matching once the targeted JDK supports this. private static boolean isTypeDerivableFromContext(MethodInvocationTree tree, VisitorState state) { Tree parent = state.getPath().getParentPath().getLeaf(); - return switch (parent.getKind()) { - case VARIABLE -> - !ASTHelpers.hasImplicitType((VariableTree) parent, state) - && MoreASTHelpers.areSameType(tree, parent, state); - case ASSIGNMENT -> MoreASTHelpers.areSameType(tree, parent, state); - case RETURN -> + return switch (parent) { + case VariableTree variable -> + !ASTHelpers.hasImplicitType(variable, state) + && MoreASTHelpers.areSameType(tree, variable, state); + case AssignmentTree assignment -> MoreASTHelpers.areSameType(tree, assignment, state); + case ReturnTree returnTree -> MoreASTHelpers.findMethodExitedOnReturn(state) .filter(m -> MoreASTHelpers.areSameType(tree, m.getReturnType(), state)) .isPresent(); diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/PrimitiveComparison.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/PrimitiveComparison.java index 4369fcdac..b2425149c 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/PrimitiveComparison.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/PrimitiveComparison.java @@ -84,7 +84,7 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState return Description.NO_MATCH; } - return getPotentiallyBoxedReturnType(tree.getArguments().get(0)) + return getPotentiallyBoxedReturnType(tree.getArguments().getFirst()) .flatMap(cmpType -> attemptMethodInvocationReplacement(tree, cmpType, isStatic, state)) .map(fix -> describeMatch(tree, fix)) .orElse(Description.NO_MATCH); @@ -122,7 +122,7 @@ private static String prefixTypeArgumentsIfRelevant( String typeArguments = Stream.concat( - Stream.of(SourceCode.treeToString(tree.getTypeArguments().get(0), state)), + Stream.of(SourceCode.treeToString(tree.getTypeArguments().getFirst(), state)), Stream.of(cmpType.tsym.getSimpleName()) .filter(u -> "comparing".equals(preferredMethodName))) .collect(joining(", ", "<", ">")); @@ -149,44 +149,46 @@ private static String getPreferredMethod(Type cmpType, boolean isStatic, Visitor return isStatic ? "comparing" : "thenComparing"; } - // XXX: Use switch pattern matching once the targeted JDK supports this. private static Optional getPotentiallyBoxedReturnType(ExpressionTree tree) { - if (tree instanceof LambdaExpressionTree lambdaExpression) { - /* Return the lambda expression's actual return type. */ - return Optional.ofNullable(ASTHelpers.getType(lambdaExpression.getBody())); - } - - // XXX: The match against a concrete type and reference to one of its fields is fragile. Do - // better. - if (tree instanceof JCMemberReference memberReference) { - /* Return the method's declared return type. */ - Type subType = memberReference.referentType; - return Optional.of(subType.getReturnType()); - } - - /* This appears to be a genuine `{,ToInt,ToLong,ToDouble}Function`. */ - return Optional.empty(); + return switch (tree) { + case LambdaExpressionTree lambdaExpression -> + /* Return the lambda expression's actual return type. */ + Optional.ofNullable(ASTHelpers.getType(lambdaExpression.getBody())); + case JCMemberReference memberReference -> + // XXX: The match against a concrete type and reference to one of its fields is fragile. + // Do better. + /* Return the method's declared return type. */ + Optional.of(memberReference.referentType.getReturnType()); + default -> + /* This appears to be a genuine `{,ToInt,ToLong,ToDouble}Function`. */ + Optional.empty(); + }; } - // XXX: Use switch pattern matching once the targeted JDK supports this. private static Fix suggestFix( MethodInvocationTree tree, String preferredMethodName, VisitorState state) { ExpressionTree expr = tree.getMethodSelect(); + return switch (expr) { + case IdentifierTree identifier -> replaceIdentifier(preferredMethodName, state, expr); + case MemberSelectTree memberSelect -> + replaceMemberSelect(preferredMethodName, state, memberSelect); + default -> throw new VerifyException("Unexpected type of expression: " + expr.getKind()); + }; + } - if (expr instanceof IdentifierTree) { - SuggestedFix.Builder fix = SuggestedFix.builder(); - String replacement = - SuggestedFixes.qualifyStaticImport( - Comparator.class.getCanonicalName() + '.' + preferredMethodName, fix, state); - return fix.replace(expr, replacement).build(); - } - - if (expr instanceof MemberSelectTree memberSelect) { - return SuggestedFix.replace( - memberSelect, - SourceCode.treeToString(memberSelect.getExpression(), state) + '.' + preferredMethodName); - } + private static SuggestedFix replaceMemberSelect( + String preferredMethodName, VisitorState state, MemberSelectTree memberSelect) { + return SuggestedFix.replace( + memberSelect, + SourceCode.treeToString(memberSelect.getExpression(), state) + '.' + preferredMethodName); + } - throw new VerifyException("Unexpected type of expression: " + expr.getKind()); + private static SuggestedFix replaceIdentifier( + String preferredMethodName, VisitorState state, ExpressionTree expr) { + SuggestedFix.Builder fix = SuggestedFix.builder(); + String replacement = + SuggestedFixes.qualifyStaticImport( + Comparator.class.getCanonicalName() + '.' + preferredMethodName, fix, state); + return fix.replace(expr, replacement).build(); } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RedundantStringConversion.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RedundantStringConversion.java index 31b0c6a7a..848211dcd 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RedundantStringConversion.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RedundantStringConversion.java @@ -284,7 +284,7 @@ private Optional tryFixSlf4jLogger( // XXX: Not so nice: we effectively try to simplify the final argument twice. boolean omitLast = !arguments.isEmpty() - && trySimplify(arguments.get(arguments.size() - 1), state) + && trySimplify(arguments.getLast(), state) .filter(replacement -> THROWABLE.matches(replacement, state)) .isPresent(); return tryFixFormatterArguments( @@ -301,7 +301,7 @@ private Optional tryFixFormatterArguments( return Optional.empty(); } - int patternIndex = firstArgFilter.matches(arguments.get(0), state) ? 1 : 0; + int patternIndex = firstArgFilter.matches(arguments.getFirst(), state) ? 1 : 0; if (arguments.size() <= patternIndex) { /* This format method accepts only an ignored parameter. Some odd overload? */ return Optional.empty(); diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/Slf4jLogStatement.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/Slf4jLogStatement.java index 382d55923..7523088db 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/Slf4jLogStatement.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/Slf4jLogStatement.java @@ -69,7 +69,7 @@ private static List getTrimmedArguments( * SLF4J log statements may accept a "marker" as a first argument, before the format string. * We ignore such markers. */ - int lTrim = SLF4J_MARKER.matches(args.get(0), state) ? 1 : 0; + int lTrim = SLF4J_MARKER.matches(args.getFirst(), state) ? 1 : 0; /* * SLF4J treats the final argument to a log statement specially if it is a `Throwabe`: it * will always choose to render the associated stacktrace, even if the argument has a @@ -77,13 +77,13 @@ private static List getTrimmedArguments( * the purpose of matching arguments against format string placeholders a trailing * `Throwable` effectively doesn't exist. */ - int rTrim = THROWABLE.matches(args.get(args.size() - 1), state) ? 1 : 0; + int rTrim = THROWABLE.matches(args.getLast(), state) ? 1 : 0; return args.subList(lTrim, args.size() - rTrim); } private static Optional getFormatString(List args) { verify(!args.isEmpty(), "Failed to identify SLF4J log method format string"); - return Optional.ofNullable(ASTHelpers.constValue(args.get(0), String.class)); + return Optional.ofNullable(ASTHelpers.constValue(args.getFirst(), String.class)); } private Description validateFormatString( @@ -92,7 +92,7 @@ private Description validateFormatString( List args, VisitorState state) { Description.Builder description = buildDescription(tree); - return isFormatString(formatString, args.get(0), state, description) + return isFormatString(formatString, args.getFirst(), state, description) && hasValidArguments(formatString, args.subList(1, args.size()), description) ? Description.NO_MATCH : description.build(); diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/SpringMvcAnnotation.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/SpringMvcAnnotation.java index df6a19177..2d1d88e88 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/SpringMvcAnnotation.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/SpringMvcAnnotation.java @@ -22,6 +22,7 @@ import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.NewArrayTree; import java.util.Optional; @@ -86,15 +87,14 @@ private static Optional extractUniqueMethod(ExpressionTree arg, VisitorS return expr instanceof NewArrayTree newArray ? Optional.of(newArray.getInitializers()) .filter(args -> args.size() == 1) - .map(args -> extractMethod(args.get(0), state)) + .map(args -> extractMethod(args.getFirst(), state)) : Optional.of(extractMethod(expr, state)); } - // XXX: Use switch pattern matching once the targeted JDK supports this. private static String extractMethod(ExpressionTree expr, VisitorState state) { - return switch (expr.getKind()) { - case IDENTIFIER -> SourceCode.treeToString(expr, state); - case MEMBER_SELECT -> ((MemberSelectTree) expr).getIdentifier().toString(); + return switch (expr) { + case IdentifierTree identifier -> SourceCode.treeToString(identifier, state); + case MemberSelectTree memberSelect -> memberSelect.getIdentifier().toString(); default -> throw new VerifyException("Unexpected type of expression: " + expr.getKind()); }; } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/StringJoining.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/StringJoining.java index 4bd757411..9abdf2c7e 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/StringJoining.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/StringJoining.java @@ -68,7 +68,7 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState // this argument is not vacuous, and that as a result the expression cannot be simplified using // `#valueOf` or `#join`. Implement a separate check that identifies and drops redundant // `Locale` arguments. See also a related comment in `FormatStringConcatenation`. - String formatString = ASTHelpers.constValue(tree.getArguments().get(0), String.class); + String formatString = ASTHelpers.constValue(tree.getArguments().getFirst(), String.class); if (formatString == null) { return Description.NO_MATCH; } @@ -85,7 +85,7 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState } int lastIndex = separators.size() - 1; - if (!separators.get(0).isEmpty() || !separators.get(lastIndex).isEmpty()) { + if (!separators.getFirst().isEmpty() || !separators.get(lastIndex).isEmpty()) { /* The format string contains leading or trailing characters. */ return Description.NO_MATCH; } diff --git a/error-prone-experimental/src/main/java/tech/picnic/errorprone/experimental/bugpatterns/MethodReferenceUsage.java b/error-prone-experimental/src/main/java/tech/picnic/errorprone/experimental/bugpatterns/MethodReferenceUsage.java index b108c78ca..11da14da2 100644 --- a/error-prone-experimental/src/main/java/tech/picnic/errorprone/experimental/bugpatterns/MethodReferenceUsage.java +++ b/error-prone-experimental/src/main/java/tech/picnic/errorprone/experimental/bugpatterns/MethodReferenceUsage.java @@ -84,17 +84,17 @@ public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState .orElse(Description.NO_MATCH); } - // XXX: Use switch pattern matching once the targeted JDK supports this. private static Optional constructMethodRef( LambdaExpressionTree lambdaExpr, Tree subTree) { - return switch (subTree.getKind()) { - case BLOCK -> constructMethodRef(lambdaExpr, (BlockTree) subTree); - case EXPRESSION_STATEMENT -> - constructMethodRef(lambdaExpr, ((ExpressionStatementTree) subTree).getExpression()); - case METHOD_INVOCATION -> constructMethodRef(lambdaExpr, (MethodInvocationTree) subTree); - case PARENTHESIZED -> - constructMethodRef(lambdaExpr, ((ParenthesizedTree) subTree).getExpression()); - case RETURN -> constructMethodRef(lambdaExpr, ((ReturnTree) subTree).getExpression()); + return switch (subTree) { + case BlockTree block -> constructMethodRef(lambdaExpr, block); + case ExpressionStatementTree expressionStatement -> + constructMethodRef(lambdaExpr, expressionStatement.getExpression()); + case MethodInvocationTree methodInvocation -> + constructMethodRef(lambdaExpr, methodInvocation); + case ParenthesizedTree parenthesized -> + constructMethodRef(lambdaExpr, parenthesized.getExpression()); + case ReturnTree returnTree -> constructMethodRef(lambdaExpr, returnTree.getExpression()); default -> Optional.empty(); }; } @@ -103,7 +103,7 @@ private static Optional constructMethodRef( LambdaExpressionTree lambdaExpr, BlockTree subTree) { return Optional.of(subTree.getStatements()) .filter(statements -> statements.size() == 1) - .flatMap(statements -> constructMethodRef(lambdaExpr, statements.get(0))); + .flatMap(statements -> constructMethodRef(lambdaExpr, statements.getFirst())); } // XXX: Replace nested `Optional` usage. @@ -114,30 +114,28 @@ private static Optional constructMethodRef( .flatMap(expectedInstance -> constructMethodRef(lambdaExpr, subTree, expectedInstance)); } - // XXX: Review whether to use switch pattern matching once the targeted JDK supports this. private static Optional constructMethodRef( LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree, Optional expectedInstance) { ExpressionTree methodSelect = subTree.getMethodSelect(); - - if (methodSelect instanceof IdentifierTree) { - if (expectedInstance.isPresent()) { - /* Direct method call; there is no matching "implicit parameter". */ - return Optional.empty(); + return switch (methodSelect) { + case IdentifierTree identifier -> { + if (expectedInstance.isPresent()) { + /* Direct method call; there is no matching "implicit parameter". */ + yield Optional.empty(); + } + + Symbol sym = ASTHelpers.getSymbol(subTree); + yield ASTHelpers.isStatic(sym) + ? constructFix(lambdaExpr, sym.owner, methodSelect) + : constructFix(lambdaExpr, "this", methodSelect); } - - Symbol sym = ASTHelpers.getSymbol(subTree); - return ASTHelpers.isStatic(sym) - ? constructFix(lambdaExpr, sym.owner, methodSelect) - : constructFix(lambdaExpr, "this", methodSelect); - } - - if (methodSelect instanceof MemberSelectTree memberSelect) { - return constructMethodRef(lambdaExpr, memberSelect, expectedInstance); - } - - throw new VerifyException("Unexpected type of expression: " + methodSelect.getKind()); + case MemberSelectTree memberSelect -> + constructMethodRef(lambdaExpr, memberSelect, expectedInstance); + default -> + throw new VerifyException("Unexpected type of expression: " + methodSelect.getKind()); + }; } private static Optional constructMethodRef( @@ -187,7 +185,7 @@ private static Optional> matchArguments( } } - return Optional.of(diff == 0 ? Optional.empty() : Optional.of(expectedArguments.get(0))); + return Optional.of(diff == 0 ? Optional.empty() : Optional.of(expectedArguments.getFirst())); } private static ImmutableList getVariables(LambdaExpressionTree tree) { diff --git a/error-prone-guidelines/src/main/java/tech/picnic/errorprone/guidelines/bugpatterns/ErrorProneTestHelperSourceFormat.java b/error-prone-guidelines/src/main/java/tech/picnic/errorprone/guidelines/bugpatterns/ErrorProneTestHelperSourceFormat.java index 858deca10..eb738f46e 100644 --- a/error-prone-guidelines/src/main/java/tech/picnic/errorprone/guidelines/bugpatterns/ErrorProneTestHelperSourceFormat.java +++ b/error-prone-guidelines/src/main/java/tech/picnic/errorprone/guidelines/bugpatterns/ErrorProneTestHelperSourceFormat.java @@ -97,8 +97,8 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState return buildDescription(tree).setMessage("No source code provided").build(); } - int startPos = ASTHelpers.getStartPosition(sourceLines.get(0)); - int endPos = state.getEndPosition(sourceLines.get(sourceLines.size() - 1)); + int startPos = ASTHelpers.getStartPosition(sourceLines.getFirst()); + int endPos = state.getEndPosition(sourceLines.getLast()); /* Attempt to format the source code only if it fully consists of constant expressions. */ return getConstantSourceCode(sourceLines) diff --git a/error-prone-guidelines/src/main/java/tech/picnic/errorprone/guidelines/bugpatterns/RefasterAnyOfUsage.java b/error-prone-guidelines/src/main/java/tech/picnic/errorprone/guidelines/bugpatterns/RefasterAnyOfUsage.java index e2f85603e..8709ed414 100644 --- a/error-prone-guidelines/src/main/java/tech/picnic/errorprone/guidelines/bugpatterns/RefasterAnyOfUsage.java +++ b/error-prone-guidelines/src/main/java/tech/picnic/errorprone/guidelines/bugpatterns/RefasterAnyOfUsage.java @@ -54,6 +54,6 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState return describeMatch( tree, - SuggestedFix.replace(tree, SourceCode.treeToString(tree.getArguments().get(0), state))); + SuggestedFix.replace(tree, SourceCode.treeToString(tree.getArguments().getFirst(), state))); } } diff --git a/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/SourceCode.java b/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/SourceCode.java index 5266385a2..04d2557f7 100644 --- a/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/SourceCode.java +++ b/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/SourceCode.java @@ -34,7 +34,7 @@ private SourceCode() {} * * @param str The string of interest. * @return {@code true} if the given string is a valid identifier in the Java language. - * @see JDK 17 JLS + * @see JDK 21 JLS * section 3.8: Identifiers */ public static boolean isValidIdentifier(String str) { diff --git a/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/ThirdPartyLibrary.java b/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/ThirdPartyLibrary.java index 91978b47a..96d320ec6 100644 --- a/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/ThirdPartyLibrary.java +++ b/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/ThirdPartyLibrary.java @@ -6,13 +6,11 @@ import com.google.errorprone.suppliers.Supplier; import com.sun.tools.javac.code.ClassFinder; import com.sun.tools.javac.code.Source; -import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.CompletionFailure; import com.sun.tools.javac.code.Symbol.ModuleSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.util.Name; -import javax.lang.model.element.Modifier; /** * Utility class that helps decide whether it is appropriate to introduce references to (well-known) @@ -93,7 +91,7 @@ private static boolean isKnownClass(String typeName, VisitorState state) { private static boolean isPublicClassInSymbolTable(String typeName, VisitorState state) { Type type = state.getTypeFromString(typeName); - return type != null && isPublic(type.tsym); + return type != null && type.tsym.isPublic(); } private static boolean canLoadPublicClass(String typeName, VisitorState state) { @@ -106,7 +104,7 @@ private static boolean canLoadPublicClass(String typeName, VisitorState state) { : symtab.unnamedModule; Name binaryName = state.binaryNameFromClassname(typeName); try { - return isPublic(classFinder.loadClass(module, binaryName)); + return classFinder.loadClass(module, binaryName).isPublic(); } catch ( @SuppressWarnings("java:S1166" /* Not exceptional. */) CompletionFailure e) { @@ -114,11 +112,6 @@ private static boolean canLoadPublicClass(String typeName, VisitorState state) { } } - // XXX: Once we target JDK 14+, drop this method in favour of `Symbol#isPublic()`. - private static boolean isPublic(Symbol symbol) { - return symbol.getModifiers().contains(Modifier.PUBLIC); - } - private static boolean shouldIgnoreClasspath(VisitorState state) { return state .errorProneOptions() diff --git a/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/MoreASTHelpersTest.java b/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/MoreASTHelpersTest.java index 905023bba..4ec21122c 100644 --- a/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/MoreASTHelpersTest.java +++ b/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/MoreASTHelpersTest.java @@ -241,7 +241,7 @@ public Description matchMethod(MethodTree tree, VisitorState state) { List parameters = tree.getParameters(); return parameters.stream() .skip(1) - .allMatch(p -> MoreASTHelpers.areSameType(p, parameters.get(0), state)) + .allMatch(p -> MoreASTHelpers.areSameType(p, parameters.getFirst(), state)) ? describeMatch(tree) : Description.NO_MATCH; } diff --git a/pom.xml b/pom.xml index 915a1e299..a95b35bde 100644 --- a/pom.xml +++ b/pom.xml @@ -203,7 +203,7 @@ ${version.error-prone-orig} ${version.error-prone-orig}-picnic-1 2.42.0 - 17 + 21 3.9.11 2.2.5 @@ -1160,7 +1160,7 @@ src/main/resources/**/*.properties,src/test/resources/**/*.properties - 21 + 25 true @@ -1236,7 +1236,7 @@ jar - @@ -1940,10 +1940,6 @@ ${error-prone.patch-args} ${error-prone.self-check-args} - - -XDaddTypeAnnotationsToSymbol=true