diff --git a/.gitignore b/.gitignore index 74d2ac9d6..4ef0a1d3b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ .java-version .project .settings +.factorypath +.apt_generated_tests/ +bin/ build out/ docs/node_modules/ diff --git a/.policy.yml b/.policy.yml index a436f8b96..95c0afd59 100644 --- a/.policy.yml +++ b/.policy.yml @@ -20,21 +20,21 @@ approval_rules: allow_contributor: false requires: count: 1 - admins: true + permissions: ["admin", "maintain"] - name: two admins have approved options: allow_contributor: true requires: count: 2 - admins: true + permissions: ["admin", "maintain"] - name: changelog only and contributor approval options: allow_contributor: true requires: count: 1 - admins: true + permissions: ["admin", "maintain"] if: only_changed_files: paths: @@ -45,23 +45,25 @@ approval_rules: allow_contributor: true requires: count: 1 - admins: true + permissions: ["admin", "maintain"] if: has_author_in: - users: [ "svc-excavator-bot" ] + users: [ "svc-excavator-bot", "dependabot[bot]" ] - name: excavator only touched baseline, circle, gradle files, godel files, generated code, go dependencies, docker-compose-rule config or versions.props requires: count: 0 if: has_author_in: - users: [ "svc-excavator-bot" ] + users: [ "svc-excavator-bot", "dependabot[bot]" ] only_changed_files: # product-dependencies.lock should never go here, to force review of all product (SLS) dependency changes # this way excavator cannot change the deployability of a service or product via auto-merge paths: - "changelog/@unreleased/.*\\.yml" - "^\\.baseline/.*$" + - "^(.+/)?Cargo.toml$" + - "^Cargo.lock$" - "^\\.circleci/.*$" - "^\\.docker-compose-rule\\.yml$" - "^.*gradle$" @@ -81,8 +83,9 @@ approval_rules: - "^versions.lock$" - "^internal/generated/.*" - "^internal/generated_src/.*" + - "^gradle-baseline-java/src/main/resources/checkstyle.version$" has_valid_signatures_by_keys: - key_ids: ["C9AF124A484882E0"] + key_ids: ["C9AF124A484882E0", "4AEE18F83AFDEB23"] - name: excavator only touched config files requires: diff --git a/README.md b/README.md index 29be6b1f2..73625119b 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,16 @@ tasks.withType(JavaCompile).configureEach(new Action() { }) ``` +To turn all of error-prone's warnings into errors: + +```gradle +allprojects { + tasks.withType(JavaCompile) { + options.compilerArgs += ['-Werror'] + } +} +``` + More information on error-prone severity handling can be found at [errorprone.info/docs/flags](http://errorprone.info/docs/flags). #### Baseline error-prone checks @@ -166,6 +176,7 @@ Safe Logging can be found at [github.com/palantir/safe-logging](https://github.c - `DangerousStringInternUsage`: Disallow String.intern() invocations in favor of more predictable, scalable alternatives. - `OptionalOrElseThrowThrows`: Optional.orElseThrow argument must return an exception, not throw one. - `OptionalOrElseGetValue`: Prefer `Optional.orElse(value)` over `Optional.orElseGet(() -> value)` for trivial expressions. +- `OptionalOrElseMethodInvocation`: Prefer `Optional.orElseGet(() -> methodInvocation())` over `Optional.orElse(methodInvocation())`. - `LambdaMethodReference`: Lambda should use a method reference. - `SafeLoggingExceptionMessageFormat`: SafeLoggable exceptions do not interpolate parameters. - `StrictUnusedVariable`: Functions shouldn't have unused parameters. @@ -204,6 +215,7 @@ Safe Logging can be found at [github.com/palantir/safe-logging](https://github.c - `UnnecessarilyQualified`: Types should not be qualified if they are also imported. - `DeprecatedGuavaObjects`: `com.google.common.base.Objects` has been obviated by `java.util.Objects`. - `JavaTimeSystemDefaultTimeZone`: Avoid using the system default time zone. +- `ZoneIdConstant`: Prefer `ZoneId` constants. - `IncubatingMethod`: Prevents calling Conjure incubating APIs unless you explicitly opt-out of the check on a per-use or per-project basis. - `CompileTimeConstantViolatesLiskovSubstitution`: Requires consistent application of the `@CompileTimeConstant` annotation to resolve inconsistent validation based on the reference type on which the met is invoked. - `ClassInitializationDeadlock`: Detect type structures which can cause deadlocks initializing classes. @@ -215,6 +227,8 @@ Safe Logging can be found at [github.com/palantir/safe-logging](https://github.c - `BugCheckerAutoService`: Concrete BugChecker implementations should be annotated `@AutoService(BugChecker.class)` for auto registration with error-prone. - `DangerousCollapseKeysUsage`: Disallow usage of `EntryStream#collapseKeys()`. - `JooqBatchWithoutBindArgs`: jOOQ batch methods that execute without bind args can cause performance problems. +- `InvocationTargetExceptionGetTargetException`: InvocationTargetException.getTargetException() predates the general-purpose exception chaining facility. The Throwable.getCause() method is now the preferred means of obtaining this information. [(source)](https://docs.oracle.com/en/java/javase/17/docs/api//java.base/java/lang/reflect/InvocationTargetException.html#getTargetException()) +- `PreferInputStreamTransferTo`: Prefer JDK `InputStream.transferTo(OutputStream)` over utility methods such as `com.google.common.io.ByteStreams.copy(InputStream, OutputStream)`, `org.apache.commons.io.IOUtils.copy(InputStream, OutputStream)`, `org.apache.commons.io.IOUtils.copyLong(InputStream, OutputStream)`. ### Programmatic Application diff --git a/baseline-error-prone/baseline-class-uniqueness.lock b/baseline-error-prone/baseline-class-uniqueness.lock index ee641fc71..3fc74511f 100644 --- a/baseline-error-prone/baseline-class-uniqueness.lock +++ b/baseline-error-prone/baseline-class-uniqueness.lock @@ -2,7 +2,7 @@ # Run ./gradlew checkClassUniqueness --write-locks to update this file ## runtimeClasspath -[org.checkerframework:checker-qual, org.checkerframework:dataflow-errorprone] +[io.github.eisop:dataflow-errorprone, org.checkerframework:checker-qual] - org.checkerframework.dataflow.qual.Deterministic - org.checkerframework.dataflow.qual.Pure - org.checkerframework.dataflow.qual.Pure$Kind diff --git a/baseline-error-prone/build.gradle b/baseline-error-prone/build.gradle index b82fb9ec4..6ef5f3848 100644 --- a/baseline-error-prone/build.gradle +++ b/baseline-error-prone/build.gradle @@ -11,7 +11,7 @@ javaVersion { dependencies { implementation 'com.google.errorprone:error_prone_core' // Ensure a new enough version of dataflow-errorprone is available - implementation 'org.checkerframework:dataflow-errorprone' + implementation 'io.github.eisop:dataflow-errorprone' testImplementation gradleApi() testImplementation 'com.palantir.tokens:auth-tokens' @@ -23,6 +23,7 @@ dependencies { testImplementation 'com.palantir.safe-logging:logger' testImplementation 'org.slf4j:slf4j-api' testImplementation 'org.apache.commons:commons-lang3' + testImplementation 'commons-io:commons-io' testImplementation 'commons-lang:commons-lang' testImplementation 'org.assertj:assertj-core' testImplementation 'org.immutables:value::annotations' diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/CardinalityEqualsZero.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/CardinalityEqualsZero.java new file mode 100644 index 000000000..0507c5f5a --- /dev/null +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/CardinalityEqualsZero.java @@ -0,0 +1,143 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.matchers.Matchers; +import com.google.errorprone.matchers.method.MethodMatchers; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree.Kind; +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; +import org.immutables.value.Value.Immutable; + +@AutoService(BugChecker.class) +@BugPattern( + link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", + linkType = BugPattern.LinkType.CUSTOM, + severity = SeverityLevel.SUGGESTION, + summary = "Use the isEmpty method instead of checking collection size") +public final class CardinalityEqualsZero extends BugChecker implements BugChecker.BinaryTreeMatcher { + private static final Matcher COLLECTION_SIZE_METHOD_MATCHER = MethodMatchers.instanceMethod() + .onDescendantOf(Collection.class.getName()) + .named("size") + .withNoParameters(); + + private static final Matcher INT_ZERO = Matchers.intLiteral(0); + + @Override + public Description matchBinary(BinaryTree tree, VisitorState state) { + Optional maybeEqualsZeroExpression = getEqualsZeroExpression(tree, state); + if (maybeEqualsZeroExpression.isEmpty()) { + return Description.NO_MATCH; + } + + EqualsZeroExpression equalsZeroExpression = maybeEqualsZeroExpression.get(); + ExpressionTree operand = equalsZeroExpression.operand(); + if (!Objects.equals(operand.getKind(), Kind.METHOD_INVOCATION)) { + return Description.NO_MATCH; + } + + ExpressionTree collectionInstance = ASTHelpers.getReceiver(operand); + if (collectionInstance == null || isExpressionThis(collectionInstance)) { + return Description.NO_MATCH; + } + + if (COLLECTION_SIZE_METHOD_MATCHER.matches(operand, state)) { + return describeMatch( + tree, + SuggestedFix.replace( + tree, + (equalsZeroExpression.type() == ExpressionType.NEQ ? "!" : "") + + state.getSourceForNode(collectionInstance) + + ".isEmpty()")); + } + + return Description.NO_MATCH; + } + + private static boolean isExpressionThis(ExpressionTree tree) { + switch (tree.getKind()) { + case IDENTIFIER: + return ((IdentifierTree) tree).getName().contentEquals("this"); + case MEMBER_SELECT: + return ((MemberSelectTree) tree).getIdentifier().contentEquals("this"); + default: + return false; + } + } + + private static Optional getEqualsZeroExpression(BinaryTree tree, VisitorState state) { + ExpressionType ret; + switch (tree.getKind()) { + case EQUAL_TO: + ret = ExpressionType.EQ; + break; + case NOT_EQUAL_TO: + ret = ExpressionType.NEQ; + break; + default: + return Optional.empty(); + } + ExpressionTree leftOperand = tree.getLeftOperand(); + ExpressionTree rightOperand = tree.getRightOperand(); + + if (INT_ZERO.matches(leftOperand, state)) { + return Optional.of(EqualsZeroExpression.builder() + .type(ret) + .operand(rightOperand) + .build()); + } else if (INT_ZERO.matches(rightOperand, state)) { + return Optional.of(EqualsZeroExpression.builder() + .type(ret) + .operand(leftOperand) + .build()); + } + + return Optional.empty(); + } + + enum ExpressionType { + EQ, + NEQ + } + + @Immutable + interface EqualsZeroExpression { + ExpressionType type(); + + ExpressionTree operand(); + + class Builder extends ImmutableEqualsZeroExpression.Builder {} + + static Builder builder() { + return new Builder(); + } + } +} diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/CompileTimeConstantViolatesLiskovSubstitution.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/CompileTimeConstantViolatesLiskovSubstitution.java index b3e89258e..ba586be1b 100644 --- a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/CompileTimeConstantViolatesLiskovSubstitution.java +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/CompileTimeConstantViolatesLiskovSubstitution.java @@ -66,7 +66,7 @@ public Description matchMethod(MethodTree tree, VisitorState state) { for (VarSymbol parameter : methodSymbol.getParameters()) { ++parameterIndex; - if (ASTHelpers.hasAnnotation(parameter, CompileTimeConstant.class, state)) { + if (ASTHelpers.hasAnnotation(parameter, CompileTimeConstant.class.getName(), state)) { if (anySuperMethodsMissingParameterAnnotation(superMethods, parameterIndex, state)) { state.reportMatch(buildDescription(tree.getParameters().get(parameterIndex)) .setMessage("@CompileTimeConstant annotations on method parameters " @@ -98,7 +98,7 @@ private boolean anySuperMethodsMissingParameterAnnotation( Set superMethods, int parameterIndex, VisitorState state) { for (MethodSymbol superMethod : superMethods) { VarSymbol parameter = superMethod.getParameters().get(parameterIndex); - if (!ASTHelpers.hasAnnotation(parameter, CompileTimeConstant.class, state)) { + if (!ASTHelpers.hasAnnotation(parameter, CompileTimeConstant.class.getName(), state)) { return true; } } @@ -109,7 +109,7 @@ private boolean anySuperMethodsHaveParameterAnnotation( Set superMethods, int parameterIndex, VisitorState state) { for (MethodSymbol superMethod : superMethods) { VarSymbol parameter = superMethod.getParameters().get(parameterIndex); - if (ASTHelpers.hasAnnotation(parameter, CompileTimeConstant.class, state)) { + if (ASTHelpers.hasAnnotation(parameter, CompileTimeConstant.class.getName(), state)) { return true; } } diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ForbidJavaxParameterType.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ForbidJavaxParameterType.java index 24b9cc14d..8d78a7806 100644 --- a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ForbidJavaxParameterType.java +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ForbidJavaxParameterType.java @@ -120,7 +120,7 @@ private boolean hasJavaxInclusionsOnType(TypeSymbol symbol, VisitorState state) .addAll(classType.getInterfaces()) .build(); for (Type t : thisAndParents) { - for (Symbol sym : t.tsym.getEnclosedElements()) { + for (Symbol sym : ASTHelpers.getEnclosedElements(t.tsym)) { if (HAS_JAXRS_ANNOTATION.test(sym)) { return true; } diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/IncubatingMethod.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/IncubatingMethod.java index 6906e2a50..aaa59abd9 100644 --- a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/IncubatingMethod.java +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/IncubatingMethod.java @@ -36,9 +36,11 @@ public final class IncubatingMethod extends BugChecker implements BugChecker.MethodInvocationTreeMatcher, BugChecker.MemberReferenceTreeMatcher { - /** Matcher for the Incubating annotation, using the full qualified path. */ - private static final Matcher INCUBATING_MATCHER = - Matchers.symbolHasAnnotation("com.palantir.conjure.java.lib.internal.Incubating"); + private static final String INCUBATING = "com.palantir.conjure.java.lib.internal.Incubating"; + + private static final Matcher INCUBATING_MATCHER = Matchers.symbolHasAnnotation(INCUBATING); + private static final Matcher IN_INCUBATING_MATCHER = + Matchers.enclosingMethod(Matchers.symbolHasAnnotation(INCUBATING)); @Override public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { @@ -55,6 +57,10 @@ private Description checkTree(Tree tree, VisitorState state) { return Description.NO_MATCH; } + if (IN_INCUBATING_MATCHER.matches(tree, state)) { + return Description.NO_MATCH; + } + // Allow users to test incubating endpoints in test code without complaining. if (TestCheckUtils.isTestCode(state)) { return Description.NO_MATCH; diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetException.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetException.java new file mode 100644 index 000000000..2e033375d --- /dev/null +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetException.java @@ -0,0 +1,56 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.fixes.SuggestedFixes; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.matchers.Matchers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import java.lang.reflect.InvocationTargetException; + +@AutoService(BugChecker.class) +@BugPattern( + link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", + linkType = BugPattern.LinkType.CUSTOM, + severity = SeverityLevel.SUGGESTION, + summary = + "InvocationTargetException.getTargetException() predates the general-purpose exception chaining" + + " facility. The Throwable.getCause() method is now the preferred means of obtaining this" + + " information. Source: " + + "https://docs.oracle.com/en/java/javase/17/docs/api//java.base/java/lang/reflect/InvocationTargetException.html#getTargetException()") +public final class InvocationTargetExceptionGetTargetException extends BugChecker + implements BugChecker.MethodInvocationTreeMatcher { + + private static final Matcher ITE_GET_TARGET_EXCEPTION_MATCHER = Matchers.instanceMethod() + .onDescendantOf(InvocationTargetException.class.getName()) + .named("getTargetException") + .withNoParameters(); + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + return ITE_GET_TARGET_EXCEPTION_MATCHER.matches(tree, state) + ? describeMatch(tree, SuggestedFixes.renameMethodInvocation(tree, "getCause", state)) + : Description.NO_MATCH; + } +} diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/MoreASTHelpers.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/MoreASTHelpers.java index e9e4feb99..9432e7931 100644 --- a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/MoreASTHelpers.java +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/MoreASTHelpers.java @@ -97,7 +97,7 @@ static ImmutableSet getThrownExceptionsFromTryBody(TryTree tree, VisitorSt /** Returns an optional of the {@link AutoCloseable#close()} method on the provided symbol. */ private static Optional getCloseMethod(Symbol.ClassSymbol symbol, VisitorState state) { Types types = state.getTypes(); - return symbol.getEnclosedElements().stream() + return ASTHelpers.getEnclosedElements(symbol).stream() .filter(sym -> types.isAssignable(symbol.type, state.getTypeFromString(AutoCloseable.class.getName())) && sym.getSimpleName().contentEquals("close") && sym.getTypeParameters().isEmpty()) diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/PreferInputStreamTransferTo.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/PreferInputStreamTransferTo.java new file mode 100644 index 000000000..095eff9d5 --- /dev/null +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/PreferInputStreamTransferTo.java @@ -0,0 +1,123 @@ +/* + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.matchers.Matchers; +import com.google.errorprone.matchers.method.MethodMatchers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.Tree; +import java.util.List; + +@AutoService(BugChecker.class) +@BugPattern( + link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", + linkType = BugPattern.LinkType.CUSTOM, + severity = SeverityLevel.WARNING, + summary = "Prefer JDK `InputStream.transferTo(OutputStream)` over utility methods such as " + + "`com.google.common.io.ByteStreams.copy(InputStream, OutputStream)`, " + + "`org.apache.commons.io.IOUtils.copy(InputStream, OutputStream)`, " + + "`org.apache.commons.io.IOUtils.copyLong(InputStream, OutputStream)` " + + "see: https://github.com/palantir/gradle-baseline/issues/2615 ", + explanation = "Allow for optimization when underlying input stream (such as `ByteArrayInputStream`," + + " `ChannelInputStream`) overrides `long transferTo(OutputStream)` to avoid extra array allocations and" + + " copy larger chunks at a time (e.g. allowing 16KiB chunks via" + + " `ApacheHttpClientBlockingChannel.ModulatingOutputStream` from #1790).\n\n" + + "When running on JDK 21+, this also enables 16KiB byte chunk copies via" + + " `InputStream.transferTo(OutputStream)` per [JDK-8299336](https://bugs.openjdk.org/browse/JDK-8299336)," + + " where as on JDK < 21 and when using Guava `ByteStreams.copy` 8KiB byte chunk copies are used. \n\n" + + "References:\n\n" + + " * https://github.com/palantir/hadoop-crypto/pull/586\n" + + " * https://bugs.openjdk.org/browse/JDK-8299336\n" + + " * https://bugs.openjdk.org/browse/JDK-8067661\n" + + " * https://bugs.openjdk.org/browse/JDK-8265891\n" + + " * https://bugs.openjdk.org/browse/JDK-8273038\n" + + " * https://bugs.openjdk.org/browse/JDK-8279283\n" + + " * https://bugs.openjdk.org/browse/JDK-8296431\n") +public final class PreferInputStreamTransferTo extends BugChecker implements BugChecker.MethodInvocationTreeMatcher { + + private static final long serialVersionUID = 1L; + + private static final String ERROR_MESSAGE = "Prefer InputStream.transferTo(OutputStream)"; + + public static final String INPUT_STREAM = "java.io.InputStream"; + private static final Matcher INPUT_STREAM_MATCHER = MoreMatchers.isSubtypeOf(INPUT_STREAM); + public static final String OUTPUT_STREAM = "java.io.OutputStream"; + private static final Matcher OUTPUT_STREAM_MATCHER = MoreMatchers.isSubtypeOf(OUTPUT_STREAM); + + private static final Matcher GUAVA_BYTE_STREAM_COPY_MATCHER = MethodMatchers.staticMethod() + .onClass("com.google.common.io.ByteStreams") + .namedAnyOf("copy") + .withParameters(INPUT_STREAM, OUTPUT_STREAM); + private static final Matcher APACHE_COMMONS_BYTE_STREAM_COPY_MATCHER = MethodMatchers.staticMethod() + .onClass("org.apache.commons.io.IOUtils") + .namedAnyOf("copy", "copyLarge") + .withParameters(INPUT_STREAM, OUTPUT_STREAM); + private static final Matcher AWS_BYTE_STREAM_COPY_MATCHER = MethodMatchers.staticMethod() + .onClass("com.amazonaws.util.IOUtils") + .namedAnyOf("copy") + .withParameters(INPUT_STREAM, OUTPUT_STREAM); + + private static final Matcher METHOD_MATCHER = Matchers.anyOf( + GUAVA_BYTE_STREAM_COPY_MATCHER, APACHE_COMMONS_BYTE_STREAM_COPY_MATCHER, AWS_BYTE_STREAM_COPY_MATCHER); + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + if (METHOD_MATCHER.matches(tree, state)) { + List args = tree.getArguments(); + if (args.size() != 2) { + return Description.NO_MATCH; + } + + ExpressionTree maybeInputStreamArg = args.get(0); + ExpressionTree maybeOutputStreamArg = args.get(1); + if (INPUT_STREAM_MATCHER.matches(maybeInputStreamArg, state) + && OUTPUT_STREAM_MATCHER.matches(maybeOutputStreamArg, state)) { + String inputStreamArg = state.getSourceForNode(maybeInputStreamArg); + String outputStreamArg = state.getSourceForNode(maybeOutputStreamArg); + if (inputStreamArg == null || outputStreamArg == null) { + return Description.NO_MATCH; + } + + // Avoid possible infinite recursion replacing with `this.transferTo(outputStream)` + if (maybeInputStreamArg instanceof IdentifierTree + && ((IdentifierTree) maybeInputStreamArg).getName().contentEquals("this")) { + inputStreamArg = "super"; + } + + String replacement = inputStreamArg + ".transferTo(" + outputStreamArg + ")"; + SuggestedFix fix = + SuggestedFix.builder().replace(tree, replacement).build(); + return buildDescription(tree) + .setMessage(ERROR_MESSAGE) + .addFix(fix) + .build(); + } + } + + return Description.NO_MATCH; + } +} diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/SafeLoggingPropagation.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/SafeLoggingPropagation.java index 2a41a8dcb..db652149b 100644 --- a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/SafeLoggingPropagation.java +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/SafeLoggingPropagation.java @@ -49,6 +49,7 @@ import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.code.Type; import com.sun.tools.javac.util.Name; import java.util.List; import javax.lang.model.element.Modifier; @@ -76,28 +77,6 @@ public final class SafeLoggingPropagation extends BugChecker Matchers.not(Matchers.isStatic()), Matchers.methodReturns(Matchers.isSameType(String.class))); private static final Matcher METHOD_RETURNS_VOID = Matchers.methodReturns(Matchers.isVoidType()); - private static final Matcher NON_STATIC_NON_CTOR = - Matchers.not(Matchers.anyOf(Matchers.hasModifier(Modifier.STATIC), Matchers.methodIsConstructor())); - - private static final Matcher IS_IMMUTABLES_FIELD = Matchers.anyOf( - Matchers.hasModifier(Modifier.ABSTRACT), - Matchers.symbolHasAnnotation("org.immutables.value.Value.Default"), - Matchers.symbolHasAnnotation("org.immutables.value.Value.Derived"), - Matchers.symbolHasAnnotation("org.immutables.value.Value.Lazy"), - Matchers.allOf(Matchers.hasModifier(Modifier.DEFAULT), Matchers.enclosingClass((Matcher) - (classTree, state) -> { - ClassSymbol classSymbol = ASTHelpers.getSymbol(classTree); - return classSymbol != null && immutablesDefaultAsDefault(classSymbol, state); - })), - (methodTree, state) -> hasJacksonAnnotation(ASTHelpers.getSymbol(methodTree), state)); - private static final Matcher GETTER_METHOD_MATCHER = Matchers.anyOf( - Matchers.allOf( - NON_STATIC_NON_CTOR, - Matchers.not(METHOD_RETURNS_VOID), - Matchers.methodHasNoParameters(), - IS_IMMUTABLES_FIELD), - // Always include toString safety - TO_STRING); private static final com.google.errorprone.suppliers.Supplier TO_STRING_NAME = VisitorState.memoize(state -> state.getName("toString")); @@ -114,7 +93,7 @@ public Description matchClass(ClassTree classTree, VisitorState state) { if (classSymbol == null || classSymbol.isAnonymous()) { return Description.NO_MATCH; } - if (isRecord(classSymbol)) { + if (ASTHelpers.isRecord(classSymbol)) { return matchRecord(classTree, classSymbol, state); } else { return matchClassOrInterface(classTree, classSymbol, state); @@ -165,11 +144,6 @@ private static boolean immutablesDefaultAsDefault(Attribute.Compound styleAnnota .orElse(false); } - private static boolean isRecord(ClassSymbol classSymbol) { - // Can use classSymbol.isRecord() in future versions - return (classSymbol.flags() & 1L << 61) != 0; - } - @SuppressWarnings("unchecked") private static List getRecordComponents(ClassSymbol classSymbol) { // Can use classSymbol.getRecordComponents() in future versions @@ -200,43 +174,86 @@ private Description matchClassOrInterface(ClassTree classTree, ClassSymbol class return matchBasedOnToString(classTree, classSymbol, state); } + private static boolean isImmutablesField( + ClassSymbol enclosingClass, MethodSymbol methodSymbol, VisitorState state) { + return methodSymbol.getModifiers().contains(Modifier.ABSTRACT) + || ASTHelpers.hasAnnotation(methodSymbol, "org.immutables.value.Value.Default", state) + || ASTHelpers.hasAnnotation(methodSymbol, "org.immutables.value.Value.Derived", state) + || ASTHelpers.hasAnnotation(methodSymbol, "org.immutables.value.Value.Lazy", state) + || immutablesDefaultAsDefault(enclosingClass, state) + || hasJacksonAnnotation(methodSymbol, state); + } + + private static boolean isToString(MethodSymbol methodSymbol, VisitorState state) { + return !methodSymbol.isConstructor() + && !methodSymbol.isStaticOrInstanceInit() + && state.getTypes().isSameType(methodSymbol.getReturnType(), state.getSymtab().stringType) + && methodSymbol.name.contentEquals("toString") + && methodSymbol.getParameters().isEmpty(); + } + + private static boolean isGetterMethod(ClassSymbol enclosingClass, MethodSymbol methodSymbol, VisitorState state) { + return !methodSymbol.isConstructor() + && !methodSymbol.isStaticOrInstanceInit() + && !state.getTypes().isSameType(methodSymbol.getReturnType(), state.getSymtab().voidType) + && methodSymbol.getParameters().isEmpty() + && (isImmutablesField(enclosingClass, methodSymbol, state) || isToString(methodSymbol, state)); + } + @SuppressWarnings("checkstyle:CyclomaticComplexity") - private Description matchImmutables(ClassTree classTree, ClassSymbol classSymbol, VisitorState state) { - Safety existingClassSafety = SafetyAnnotations.getSafety(classTree, state); - Safety safety = SafetyAnnotations.getTypeSafetyFromAncestors(classTree, state); - boolean hasKnownGetter = false; - boolean isJson = hasJacksonAnnotation(classSymbol, state); - for (Tree member : classTree.getMembers()) { - if (member instanceof MethodTree) { - MethodTree methodMember = (MethodTree) member; - if (GETTER_METHOD_MATCHER.matches(methodMember, state)) { - MethodSymbol methodSymbol = ASTHelpers.getSymbol(methodMember); + private static Safety scanSymbolMethods(ClassSymbol begin, VisitorState state, boolean usesJackson) { + Safety safety = Safety.UNKNOWN; + for (Symbol enclosed : ASTHelpers.getEnclosedElements(begin)) { + if (enclosed instanceof MethodSymbol) { + MethodSymbol methodSymbol = (MethodSymbol) enclosed; + if (isGetterMethod(begin, methodSymbol, state)) { boolean redacted = ASTHelpers.hasAnnotation(methodSymbol, "org.immutables.value.Value.Redacted", state); - if (redacted && !isJson && !hasJacksonAnnotation(methodSymbol, state)) { + if (redacted && !usesJackson && !hasJacksonAnnotation(methodSymbol, state)) { // Redacted fields can be ignored so long as the object is not json-serializable, in which // case logging may occur using jackson rather than toString. continue; } + Safety getterSafety = + Safety.mergeAssumingUnknownIsSame(safety, SafetyAnnotations.getSafety(methodSymbol, state)); + getterSafety = Safety.mergeAssumingUnknownIsSame( + getterSafety, SafetyAnnotations.getSafety(methodSymbol.getReturnType(), state)); + getterSafety = Safety.mergeAssumingUnknownIsSame( + getterSafety, SafetyAnnotations.getSafety(methodSymbol.getReturnType().tsym, state)); // The redaction check allows us to add @DoNotLog to redacted fields in the same sweep as // adding class-level safety annotations. Otherwise, we would have to run the automatic // fixes twice. - Safety getterSafety = SafetyAnnotations.getSafety(methodMember.getReturnType(), state); if (redacted && (getterSafety == Safety.UNKNOWN || getterSafety == Safety.SAFE)) { // unsafe data may be redacted, however we assume redaction means do-not-log by default getterSafety = Safety.DO_NOT_LOG; } - if (getterSafety != Safety.UNKNOWN) { - hasKnownGetter = true; - } safety = safety.leastUpperBound(getterSafety); } } } - // If no getter-style methods are detected, assume this is not a value type. - if (!hasKnownGetter) { - return Description.NO_MATCH; + Type superClassType = begin.getSuperclass(); + if (superClassType != null && superClassType.tsym instanceof ClassSymbol) { + ClassSymbol superClassSym = (ClassSymbol) superClassType.tsym; + Safety superClassMethodSafety = scanSymbolMethods(superClassSym, state, usesJackson); + safety = Safety.mergeAssumingUnknownIsSame(safety, superClassMethodSafety); + } + for (Type superIface : begin.getInterfaces()) { + if (superIface.tsym instanceof ClassSymbol) { + ClassSymbol superIfaceClassSymbol = (ClassSymbol) superIface.tsym; + Safety superClassMethodSafety = scanSymbolMethods(superIfaceClassSymbol, state, usesJackson); + safety = Safety.mergeAssumingUnknownIsSame(safety, superClassMethodSafety); + } } + return safety; + } + + private Description matchImmutables(ClassTree classTree, ClassSymbol classSymbol, VisitorState state) { + Safety existingClassSafety = SafetyAnnotations.getAnnotatedSafety(classTree, state); + Safety safety = SafetyAnnotations.getTypeSafetyFromAncestors(classTree, state); + boolean isJson = hasJacksonAnnotation(classSymbol, state); + ClassSymbol symbol = ASTHelpers.getSymbol(classTree); + Safety scanned = scanSymbolMethods(symbol, state, isJson); + safety = safety.leastUpperBound(scanned); return handleSafety(classTree, classTree.getModifiers(), state, existingClassSafety, safety); } diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/SortedStreamFirstElement.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/SortedStreamFirstElement.java new file mode 100644 index 000000000..059b6cac7 --- /dev/null +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/SortedStreamFirstElement.java @@ -0,0 +1,115 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.matchers.Matchers; +import com.google.errorprone.matchers.method.MethodMatchers; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.Tree; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import java.util.Comparator; +import java.util.stream.Stream; + +@AutoService(BugChecker.class) +@BugPattern( + link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", + linkType = BugPattern.LinkType.CUSTOM, + severity = SeverityLevel.SUGGESTION, + summary = "Using Stream::min is more efficient than finding the first element of the sorted stream. " + + "Stream::min performs a linear scan through the stream to find the smallest element.") +public final class SortedStreamFirstElement extends BugChecker implements BugChecker.MethodInvocationTreeMatcher { + + private static final Matcher STREAM_FIND_FIRST_MATCHER = MethodMatchers.instanceMethod() + .onDescendantOf(Stream.class.getName()) + .named("findFirst") + .withNoParameters(); + + private static final Matcher RECEIVER_OF_STREAM_SORTED_NO_PARAMS_MATCHER = + Matchers.receiverOfInvocation(MethodMatchers.instanceMethod() + .onDescendantOf(Stream.class.getName()) + .named("sorted") + .withNoParameters()); + + private static final Matcher RECEIVER_OF_STREAM_SORTED_WITH_COMPARATOR_MATCHER = + Matchers.receiverOfInvocation(MethodMatchers.instanceMethod() + .onDescendantOf(Stream.class.getName()) + .named("sorted") + .withParameters(Comparator.class.getName())); + + private static final Matcher MATCHER = Matchers.allOf( + STREAM_FIND_FIRST_MATCHER, + Matchers.anyOf( + RECEIVER_OF_STREAM_SORTED_NO_PARAMS_MATCHER, RECEIVER_OF_STREAM_SORTED_WITH_COMPARATOR_MATCHER)); + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + if (!MATCHER.matches(tree, state)) { + return Description.NO_MATCH; + } + + ExpressionTree sorted = ASTHelpers.getReceiver(tree); + if (sorted == null) { + // Not expected. + return Description.NO_MATCH; + } + MethodInvocationTree sortedTree = (MethodInvocationTree) sorted; + ExpressionTree stream = ASTHelpers.getReceiver(sorted); + if (stream == null) { + // Not expected. + return Description.NO_MATCH; + } + + if (RECEIVER_OF_STREAM_SORTED_NO_PARAMS_MATCHER.matches(tree, state)) { + return describeMatch( + tree, + SuggestedFix.builder() + .replace( + getStartPosition(tree), + state.getEndPosition(tree), + state.getSourceForNode(stream) + ".min(Comparator.naturalOrder())") + .addImport(Comparator.class.getCanonicalName()) + .build()); + } else if (RECEIVER_OF_STREAM_SORTED_WITH_COMPARATOR_MATCHER.matches(tree, state)) { + return describeMatch( + tree, + SuggestedFix.builder() + .replace( + getStartPosition(tree), + state.getEndPosition(tree), + state.getSourceForNode(stream) + ".min(" + + state.getSourceForNode( + sortedTree.getArguments().get(0)) + ")") + .build()); + } + + return Description.NO_MATCH; + } + + private static int getStartPosition(Tree tree) { + return ((JCMethodInvocation) tree).getStartPosition(); + } +} diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/StrictUnusedVariable.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/StrictUnusedVariable.java index 47e094108..91d9eeb44 100644 --- a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/StrictUnusedVariable.java +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/StrictUnusedVariable.java @@ -67,7 +67,10 @@ import com.google.errorprone.bugpatterns.UnusedVariable; import com.google.errorprone.fixes.SuggestedFix; import com.google.errorprone.fixes.SuggestedFixes; +import com.google.errorprone.matchers.ChildMultiMatcher.MatchType; import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.matchers.Matchers; import com.google.errorprone.suppliers.Suppliers; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.AnnotationTree; @@ -92,6 +95,7 @@ import com.sun.source.tree.ReturnTree; import com.sun.source.tree.StatementTree; import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.TryTree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; @@ -124,7 +128,6 @@ */ @AutoService(BugChecker.class) @BugPattern( - altNames = {"unused", "UnusedVariable"}, link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", linkType = BugPattern.LinkType.CUSTOM, summary = "Unused.", @@ -160,6 +163,21 @@ public final class StrictUnusedVariable extends BugChecker implements BugChecker "TAG"); private static final String UNUSED = "unused"; + private static final Matcher ALTERNATIVE_SUPPRESSED = Matchers.allOf( + // Matchers.annotations will throw given a tree that doesn't support annotations, so we must enumerate + // supported types. + Matchers.kindAnyOf(Set.of( + Kind.CLASS, Kind.VARIABLE, Kind.METHOD, Kind.ANNOTATED_TYPE, Kind.PACKAGE, Kind.COMPILATION_UNIT)), + Matchers.annotations( + MatchType.AT_LEAST_ONE, + Matchers.allOf( + Matchers.isType("java.lang.SuppressWarnings"), + Matchers.hasArgumentWithValue( + "value", + Matchers.anyOf( + Matchers.stringLiteral("UnusedVariable"), + Matchers.stringLiteral(UNUSED)))))); + @Override public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) { // We will skip reporting on the whole compilation if there are any native methods found. @@ -277,6 +295,11 @@ private void checkUsedVariables(VisitorState state, VariableFinder variableFinde }); } + @Override + public boolean isSuppressed(Tree tree, VisitorState state) { + return super.isSuppressed(tree, state) || ALTERNATIVE_SUPPRESSED.matches(tree, state); + } + private static SuggestedFix constructUsedVariableSuggestedFix(List usagePaths, VisitorState state) { SuggestedFix.Builder fix = SuggestedFix.builder(); for (TreePath usagePath : usagePaths) { diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/UnsafeGaugeRegistration.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/UnsafeGaugeRegistration.java index 288fe52ef..fb1727db0 100644 --- a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/UnsafeGaugeRegistration.java +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/UnsafeGaugeRegistration.java @@ -26,6 +26,7 @@ import com.google.errorprone.matchers.Matcher; import com.google.errorprone.matchers.method.MethodMatchers; import com.google.errorprone.suppliers.Supplier; +import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.code.Symbol; @@ -79,7 +80,7 @@ private static boolean hasRegisterWithReplacement(VisitorState state) { return false; } Symbol.ClassSymbol classSymbol = (Symbol.ClassSymbol) symbol; - for (Symbol enclosed : classSymbol.getEnclosedElements()) { + for (Symbol enclosed : ASTHelpers.getEnclosedElements(classSymbol)) { if (enclosed instanceof Symbol.MethodSymbol) { Symbol.MethodSymbol enclosedMethod = (Symbol.MethodSymbol) enclosed; if (enclosedMethod.name.contentEquals(REGISTER_WITH_REPLACEMENT)) { diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ZoneIdConstant.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ZoneIdConstant.java new file mode 100644 index 000000000..8d0be6c4b --- /dev/null +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ZoneIdConstant.java @@ -0,0 +1,62 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.fixes.SuggestedFixes; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.matchers.Matchers; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import java.time.ZoneId; +import java.time.ZoneOffset; + +@AutoService(BugChecker.class) +@BugPattern( + link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", + linkType = BugPattern.LinkType.CUSTOM, + severity = BugPattern.SeverityLevel.WARNING, + summary = "Prefer ZoneId constants.") +public final class ZoneIdConstant extends BugChecker implements BugChecker.MethodInvocationTreeMatcher { + + private static final Matcher ZONE_ID_OF = + Matchers.staticMethod().onClass(ZoneId.class.getName()).named("of").withParameters(String.class.getName()); + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + if (ZONE_ID_OF.matches(tree, state)) { + String zone = ASTHelpers.constValue(tree.getArguments().get(0), String.class); + if (zone != null && (zone.equals("Z") || zone.equals("UTC"))) { + SuggestedFix.Builder fix = SuggestedFix.builder(); + return buildDescription(tree) + .addFix(fix.replace( + tree, + SuggestedFixes.qualifyType(state, fix, ZoneOffset.class.getName()) + ".UTC") + .build()) + .build(); + } + } + + return Description.NO_MATCH; + } +} diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/safety/SafetyAnnotations.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/safety/SafetyAnnotations.java index 83dec84f6..e9dc00159 100644 --- a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/safety/SafetyAnnotations.java +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/safety/SafetyAnnotations.java @@ -20,6 +20,7 @@ import com.google.errorprone.VisitorState; import com.google.errorprone.suppliers.Suppliers; import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.AnnotationTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; @@ -69,6 +70,24 @@ public final class SafetyAnnotations { new TypeArgumentHandler(Stream.class), new TypeArgumentHandler(Optional.class)); + public static Safety getAnnotatedSafety(Tree tree, VisitorState state) { + Safety safety = Safety.UNKNOWN; + for (AnnotationTree annotationTree : ASTHelpers.getAnnotations(tree)) { + Tree annotationType = annotationTree.getAnnotationType(); + Type type = ASTHelpers.getType(annotationType); + if (type != null) { + if (type.tsym.getQualifiedName().equals(doNotLogName.get(state))) { + safety = Safety.mergeAssumingUnknownIsSame(safety, Safety.DO_NOT_LOG); + } else if (type.tsym.getQualifiedName().equals(unsafeName.get(state))) { + safety = Safety.mergeAssumingUnknownIsSame(safety, Safety.UNSAFE); + } else if (type.tsym.getQualifiedName().equals(safeName.get(state))) { + safety = Safety.mergeAssumingUnknownIsSame(safety, Safety.SAFE); + } + } + } + return safety; + } + public static Safety getSafety(Tree tree, VisitorState state) { // Check the symbol itself: Symbol treeSymbol = ASTHelpers.getSymbol(tree); diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/safety/SafetyPropagationTransfer.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/safety/SafetyPropagationTransfer.java index cb32e074b..380907fe4 100644 --- a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/safety/SafetyPropagationTransfer.java +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/safety/SafetyPropagationTransfer.java @@ -100,6 +100,7 @@ import org.checkerframework.errorprone.dataflow.cfg.node.DoubleLiteralNode; import org.checkerframework.errorprone.dataflow.cfg.node.EqualToNode; import org.checkerframework.errorprone.dataflow.cfg.node.ExplicitThisNode; +import org.checkerframework.errorprone.dataflow.cfg.node.ExpressionStatementNode; import org.checkerframework.errorprone.dataflow.cfg.node.FieldAccessNode; import org.checkerframework.errorprone.dataflow.cfg.node.FloatLiteralNode; import org.checkerframework.errorprone.dataflow.cfg.node.FloatingDivisionNode; @@ -716,7 +717,12 @@ public TransferResult> visitBitwiseXor( return binary(node, input); } + /** + * Method should no longer be called, leaving in place until deletion for broader compatibility. + * @deprecated StringConcatenateAssignmentNode is generated as separate concatenation and assignment operations + */ @Override + @Deprecated public TransferResult> visitStringConcatenateAssignment( StringConcatenateAssignmentNode node, TransferInput> input) { Safety safety = getValueOfSubNode(input, node.getLeftOperand()) @@ -1296,6 +1302,12 @@ public TransferResult> visitClassDeclaration( return unknown(input); } + @Override + public TransferResult> visitExpressionStatement( + ExpressionStatementNode node, TransferInput> input) { + return unknown(input); + } + /** * Equivalent to {@link TransferInput#getValueOfSubNode(Node)}, * but returning {@link Safety#UNKNOWN} rather than null. diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/CardinalityEqualsZeroTest.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/CardinalityEqualsZeroTest.java new file mode 100644 index 000000000..ee89eef68 --- /dev/null +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/CardinalityEqualsZeroTest.java @@ -0,0 +1,333 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; + +public class CardinalityEqualsZeroTest { + @Test + public void test_size_equals_zero() { + fix().addInputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo) {", + " return foo.size() == 0;", + " }", + "}") + .addOutputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo) {", + " return foo.isEmpty();", + " }", + "}") + .doTest(); + } + + @Test + public void test_size_does_not_equal_zero() { + fix().addInputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo) {", + " return foo.size() != 0;", + " }", + "}") + .addOutputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo) {", + " return !foo.isEmpty();", + " }", + "}") + .doTest(); + } + + @Test + public void test_zero_equals_size() { + fix().addInputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo) {", + " return 0 == foo.size();", + " }", + "}") + .addOutputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo) {", + " return foo.isEmpty();", + " }", + "}") + .doTest(); + } + + @Test + public void test_zero_does_not_equal_size() { + fix().addInputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo) {", + " return 0 != foo.size();", + " }", + "}") + .addOutputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo) {", + " return !foo.isEmpty();", + " }", + "}") + .doTest(); + } + + @Test + public void test_other_collection_types() { + fix().addInputLines( + "Test.java", + "import " + Set.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(Set foo) {", + " return foo.size() != 0;", + " }", + "}") + .addOutputLines( + "Test.java", + "import " + Set.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(Set foo) {", + " return !foo.isEmpty();", + " }", + "}") + .doTest(); + } + + @Test + public void test_conjunction() { + fix().addInputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo, List bar) {", + " return 0 != foo.size() && 0 != bar.size();", + " }", + "}") + .addOutputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo, List bar) {", + " return !foo.isEmpty() && !bar.isEmpty();", + " }", + "}") + .doTest(); + } + + @Test + public void test_not_in_method() { + // Ensure instances of `size() == 0` are matched when they're enclosed in a function call, or a + // variable assignment. + fix().addInputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " private static boolean H = List.of(\"Hello\").size() == 0;", + " static boolean g(boolean x) { return x; }", + " static boolean f(List foo) {", + " return g(foo.size() == 0);", + " }", + "}") + .addOutputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " private static boolean H = List.of(\"Hello\").isEmpty();", + " static boolean g(boolean x) { return x; }", + " static boolean f(List foo) {", + " return g(foo.isEmpty());", + " }", + "}") + .doTest(); + } + + @Test + public void test_no_match() { + fix().addInputLines( + "Test.java", + "import " + List.class.getCanonicalName() + ";", + "class Test {", + " static boolean f(List foo) {", + " return foo.size() == 1 && foo.size() != 1 && 1 == foo.size() && 1 != foo.size();", + " }", + "}") + .expectUnchanged() + .doTest(); + } + + @Test + public void test_implementation_of_isEmpty() { + fix().addInputLines( + "TestCollection.java", + "import " + ArrayList.class.getCanonicalName() + ";", + "class TestCollection extends ArrayList {", + " @Override", + " public boolean isEmpty() {", + " return size() == 0;", + " }", + " public boolean customIsEmpty() {", + " return size() == 0;", + " }", + "}") + .expectUnchanged() + .doTest(); + } + + @Test + public void test_implementation_of_isEmpty_using_this() { + fix().addInputLines( + "TestCollection.java", + "import " + ArrayList.class.getCanonicalName() + ";", + "class TestCollection extends ArrayList {", + " @Override", + " public boolean isEmpty() {", + " return this.size() == 0;", + " }", + "}") + .expectUnchanged() + .doTest(); + } + + @Test + public void test_implementation_of_isEmpty_with_other_collection() { + fix().addInputLines( + "TestCollection.java", + "import " + ArrayList.class.getCanonicalName() + ";", + "import " + List.class.getCanonicalName() + ";", + "class TestCollection extends ArrayList {", + " @Override", + " public boolean isEmpty() {", + " return this.size() == 0 && List.of(\"test\").size() == 0;", + " }", + "}") + .addOutputLines( + "TestCollection.java", + "import " + ArrayList.class.getCanonicalName() + ";", + "import " + List.class.getCanonicalName() + ";", + "class TestCollection extends ArrayList {", + " @Override", + " public boolean isEmpty() {", + " return this.size() == 0 && List.of(\"test\").isEmpty();", + " }", + "}") + .doTest(); + } + + @Test + public void test_size_on_non_collection() { + fix().addInputLines( + "TestNonCollection.java", + "class TestNonCollection {", + " public int size() {", + " return 0;", + " }", + " public boolean myIsEmpty() {", + " return size() == 0;", + " }", + " public boolean anotherIsEmpty() {", + " return this.size() == 0;", + " }", + "}") + .expectUnchanged() + .doTest(); + } + + @Test + public void test_equals_on_non_collection() { + fix().addInputLines( + "TestNonCollection.java", + "class TestNonCollection {", + " public int size() {", + " return 0;", + " }", + " public boolean foo(String key) {", + " String current = \"x\";", + " int comparisonResult = current.compareTo(key);", + " if (comparisonResult == 0) {", + " return true;", + " } else {", + " return false;", + " }", + " }", + "}") + .expectUnchanged() + .doTest(); + } + + @Test + public void test_qualified_this() { + fix().addInputLines( + "TestQualifiedThis.java", + "import " + ArrayList.class.getCanonicalName() + ";", + "import " + List.class.getCanonicalName() + ";", + "class TestQualifiedThis extends ArrayList {", + " boolean myIsEmpty() {", + " return this.size() == 0;", + " }", + " class Inner extends ArrayList {", + " boolean myIsEmpty() {", + " return TestQualifiedThis.this.size() == 0;", + " }", + " boolean anotherIsEmpty() {", + " return List.of().size() == 0 && this.size() == 0;", + " }", + " }", + "}") + .addOutputLines( + "TestQualifiedThis.java", + "import " + ArrayList.class.getCanonicalName() + ";", + "import " + List.class.getCanonicalName() + ";", + "class TestQualifiedThis extends ArrayList {", + " boolean myIsEmpty() {", + " return this.size() == 0;", + " }", + " class Inner extends ArrayList {", + " boolean myIsEmpty() {", + " return TestQualifiedThis.this.size() == 0;", + " }", + " boolean anotherIsEmpty() {", + " return List.of().isEmpty() && this.size() == 0;", + " }", + " }", + "}") + .doTest(); + } + + private RefactoringValidator fix() { + return RefactoringValidator.of(CardinalityEqualsZero.class, getClass()); + } +} diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/IncubatingMethodTest.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/IncubatingMethodTest.java index 56381c6a8..8a75899a0 100644 --- a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/IncubatingMethodTest.java +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/IncubatingMethodTest.java @@ -67,6 +67,26 @@ public void testIncubatingMethodReference() { .doTest(); } + @Test + public void testIncubatingMethodInsideIncubatingMethod() { + CompilationTestHelper.newInstance(IncubatingMethod.class, getClass()) + .addSourceLines("Service.java", SERVICE_DEFINITION) + .addSourceLines( + "Main.java", + "package com.palantir;", + "import java.util.function.Supplier;", + "import com.palantir.conjure.java.lib.internal.Incubating;", + "public final class Main {", + "@Incubating", + "public static void main(String[] args) {", + "Service service = null;", + "int result = service.test();", + "Supplier supp = service::test;", + "}", + "}") + .doTest(); + } + @Test public void testNonIncubatingMethod() { CompilationTestHelper.newInstance(IncubatingMethod.class, getClass()) diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetExceptionTest.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetExceptionTest.java new file mode 100644 index 000000000..4bfb3f202 --- /dev/null +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetExceptionTest.java @@ -0,0 +1,70 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import java.lang.reflect.InvocationTargetException; +import org.junit.jupiter.api.Test; + +public class InvocationTargetExceptionGetTargetExceptionTest { + @Test + public void test_basic() { + fix().addInputLines( + "Test.java", + "import " + InvocationTargetException.class.getName() + ";", + "class Test {", + " static Throwable f(InvocationTargetException foo) {", + " return foo.getTargetException();", + " }", + "}") + .addOutputLines( + "Test.java", + "import " + InvocationTargetException.class.getName() + ";", + "class Test {", + " static Throwable f(InvocationTargetException foo) {", + " return foo.getCause();", + " }", + "}") + .doTest(); + } + + @Test + public void test_subclass() { + fix().addInputLines( + "Test.java", + "import " + InvocationTargetException.class.getName() + ";", + "class Test {", + " class TestException extends InvocationTargetException {}", + " public Throwable getCause(TestException foo) {", + " return foo.getTargetException();", // This should change + " }", + "}") + .addOutputLines( + "Test.java", + "import " + InvocationTargetException.class.getName() + ";", + "class Test {", + " class TestException extends InvocationTargetException {}", + " public Throwable getCause(TestException foo) {", + " return foo.getCause();", + " }", + "}") + .doTest(); + } + + private RefactoringValidator fix() { + return RefactoringValidator.of(InvocationTargetExceptionGetTargetException.class, getClass()); + } +} diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/PreferInputStreamTransferToTests.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/PreferInputStreamTransferToTests.java new file mode 100644 index 000000000..e536d9e16 --- /dev/null +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/PreferInputStreamTransferToTests.java @@ -0,0 +1,294 @@ +/* + * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import com.google.errorprone.BugCheckerRefactoringTestHelper; +import com.google.errorprone.CompilationTestHelper; +import org.junit.jupiter.api.Test; + +public final class PreferInputStreamTransferToTests { + + @Test + public void should_not_use_Guava_ByteStreams_copy() { + CompilationTestHelper.newInstance(PreferInputStreamTransferTo.class, getClass()) + .addSourceLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " // BUG: Diagnostic contains: Prefer InputStream.transferTo(OutputStream)", + " return com.google.common.io.ByteStreams.copy" + + "(InputStream.nullInputStream(), OutputStream.nullOutputStream());", + " }", + "}") + .doTest(); + + CompilationTestHelper.newInstance(PreferInputStreamTransferTo.class, getClass()) + .addSourceLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test extends InputStream {", + " @Override", + " public long transferTo(OutputStream output) throws java.io.IOException {", + " // BUG: Diagnostic contains: Prefer InputStream.transferTo(OutputStream)", + " return com.google.common.io.ByteStreams.copy(this, output);", + " }", + "", + " @Override", + " public int read() throws java.io.IOException {", + " return -1;", + " }", + "}") + .doTest(); + } + + @Test + public void may_use_InputStream_transferTo_OutputStream() { + CompilationTestHelper.newInstance(PreferInputStreamTransferTo.class, getClass()) + .addSourceLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " return InputStream.nullInputStream().transferTo(OutputStream.nullOutputStream());", + " }", + "}") + .doTest(); + } + + @Test + public void auto_fix_Guava_ByteStreams_copy_input_vars() { + RefactoringValidator.of(PreferInputStreamTransferTo.class, getClass()) + .addInputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " InputStream in = InputStream.nullInputStream();", + " OutputStream out = OutputStream.nullOutputStream();", + " return com.google.common.io.ByteStreams.copy(in, out);", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " InputStream in = InputStream.nullInputStream();", + " OutputStream out = OutputStream.nullOutputStream();", + " return in.transferTo(out);", + " }", + "}") + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + } + + @Test + public void auto_fix_Guava_ByteStreams_copy_input_methods() { + RefactoringValidator.of(PreferInputStreamTransferTo.class, getClass()) + .addInputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " return com.google.common.io.ByteStreams.copy" + + "(InputStream.nullInputStream(), OutputStream.nullOutputStream());", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " return InputStream.nullInputStream().transferTo(OutputStream.nullOutputStream());", + " }", + "}") + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + } + + @Test + public void auto_fix_Guava_ByteStreams_copy_stream_implements_transferTo() { + RefactoringValidator.of(PreferInputStreamTransferTo.class, getClass()) + .addInputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test extends InputStream {", + " @Override", + " public long transferTo(OutputStream output) throws java.io.IOException {", + " return com.google.common.io.ByteStreams.copy(this, output);", + " }", + "", + " @Override", + " public int read() throws java.io.IOException {", + " return -1;", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test extends InputStream {", + " @Override", + " public long transferTo(OutputStream output) throws java.io.IOException {", + " return super.transferTo(output);", + " }", + "", + " @Override", + " public int read() throws java.io.IOException {", + " return -1;", + " }", + "}") + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + } + + @Test + public void auto_fix_Guava_ByteStreams_copy_stream_transferTo_delegates() { + RefactoringValidator.of(PreferInputStreamTransferTo.class, getClass()) + .addInputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test extends InputStream {", + " @Override", + " public long transferTo(OutputStream output) throws java.io.IOException {", + " return com.google.common.io.ByteStreams.copy(this.delegate(), output);", + " }", + "", + " @Override", + " public int read() throws java.io.IOException {", + " return -1;", + " }", + "", + " InputStream delegate() {", + " return InputStream.nullInputStream();", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test extends InputStream {", + " @Override", + " public long transferTo(OutputStream output) throws java.io.IOException {", + " return this.delegate().transferTo(output);", + " }", + "", + " @Override", + " public int read() throws java.io.IOException {", + " return -1;", + " }", + "", + " InputStream delegate() {", + " return InputStream.nullInputStream();", + " }", + "}") + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + } + + @Test + public void auto_fix_Apache_copy() { + RefactoringValidator.of(PreferInputStreamTransferTo.class, getClass()) + .addInputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " InputStream in = InputStream.nullInputStream();", + " OutputStream out = OutputStream.nullOutputStream();", + " return org.apache.commons.io.IOUtils.copy(in, out);", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " InputStream in = InputStream.nullInputStream();", + " OutputStream out = OutputStream.nullOutputStream();", + " return in.transferTo(out);", + " }", + "}") + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + + RefactoringValidator.of(PreferInputStreamTransferTo.class, getClass()) + .addInputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " InputStream in = InputStream.nullInputStream();", + " OutputStream out = OutputStream.nullOutputStream();", + " return org.apache.commons.io.IOUtils.copyLarge(in, out);", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test {", + " long f() throws java.io.IOException {", + " InputStream in = InputStream.nullInputStream();", + " OutputStream out = OutputStream.nullOutputStream();", + " return in.transferTo(out);", + " }", + "}") + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + + RefactoringValidator.of(PreferInputStreamTransferTo.class, getClass()) + .addInputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test extends InputStream {", + " @Override", + " public long transferTo(OutputStream output) throws java.io.IOException {", + " return org.apache.commons.io.IOUtils.copyLarge(this, output);", + " }", + "", + " @Override", + " public int read() throws java.io.IOException {", + " return -1;", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.io.InputStream;", + "import java.io.OutputStream;", + "class Test extends InputStream {", + " @Override", + " public long transferTo(OutputStream output) throws java.io.IOException {", + " return super.transferTo(output);", + " }", + "", + " @Override", + " public int read() throws java.io.IOException {", + " return -1;", + " }", + "}") + .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH); + } +} diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/SafeLoggingPropagationTest.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/SafeLoggingPropagationTest.java index 816ee192d..2bf216b85 100644 --- a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/SafeLoggingPropagationTest.java +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/SafeLoggingPropagationTest.java @@ -44,6 +44,70 @@ void testAddsAnnotation_dnlType() { .doTest(); } + @Test + void testAddsAnnotation_extendsDnlInterface() { + fix().addInputLines( + "Test.java", + "import com.palantir.logsafe.*;", + "import org.immutables.value.Value;", + "class Test {", + " @DoNotLog", + " interface DnlIface {", + " Object value();", + " }", + " @Value.Immutable", + " interface ImmutablesIface extends DnlIface {", + " }", + "}") + .addOutputLines( + "Test.java", + "import com.palantir.logsafe.*;", + "import org.immutables.value.Value;", + "class Test {", + " @DoNotLog", + " interface DnlIface {", + " Object value();", + " }", + " @DoNotLog", + " @Value.Immutable", + " interface ImmutablesIface extends DnlIface {", + " }", + "}") + .doTest(); + } + + @Test + void testAddsAnnotation_extendsInterfaceWithDnlType() { + fix().addInputLines( + "Test.java", + "import com.palantir.tokens.auth.*;", + "import com.palantir.logsafe.*;", + "import org.immutables.value.Value;", + "class Test {", + " interface DnlIface {", + " BearerToken value();", + " }", + " @Value.Immutable", + " interface ImmutablesIface extends DnlIface {", + " }", + "}") + .addOutputLines( + "Test.java", + "import com.palantir.tokens.auth.*;", + "import com.palantir.logsafe.*;", + "import org.immutables.value.Value;", + "class Test {", + " interface DnlIface {", + " BearerToken value();", + " }", + " @DoNotLog", + " @Value.Immutable", + " interface ImmutablesIface extends DnlIface {", + " }", + "}") + .doTest(); + } + @Test void testMixedSafety() { fix().addInputLines( diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/SortedStreamFirstElementTest.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/SortedStreamFirstElementTest.java new file mode 100644 index 000000000..1c0755363 --- /dev/null +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/SortedStreamFirstElementTest.java @@ -0,0 +1,133 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import java.util.Comparator; +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + +public class SortedStreamFirstElementTest { + + @Test + public void test_basic() { + fix().addInputLines( + "TestBasic.java", + "import " + Optional.class.getCanonicalName() + ";", + "import " + Stream.class.getCanonicalName() + ";", + "class TestBasic {", + " public Optional basic(Stream s) {", + " return s.sorted().findFirst();", + " }", + "}") + .addOutputLines( + "TestBasic.java", + "import " + Comparator.class.getCanonicalName() + ";", + "import " + Optional.class.getCanonicalName() + ";", + "import " + Stream.class.getCanonicalName() + ";", + "class TestBasic {", + " public Optional basic(Stream s) {", + " return s.min(Comparator.naturalOrder());", + " }", + "}") + .doTest(); + } + + @Test + public void test_comparator_already_imported() { + fix().addInputLines( + "TestComparatorAlreadyImported.java", + "import " + Comparator.class.getCanonicalName() + ";", + "import " + Optional.class.getCanonicalName() + ";", + "import " + Stream.class.getCanonicalName() + ";", + "class TestComparatorAlreadyImported {", + " public Optional f(Stream s) {", + " return s.sorted().findFirst();", + " }", + " public Optional g(Stream s) {", + " return s.min(Comparator.naturalOrder());", + " }", + "}") + .addOutputLines( + "TestComparatorAlreadyImported.java", + "import " + Comparator.class.getCanonicalName() + ";", + "import " + Optional.class.getCanonicalName() + ";", + "import " + Stream.class.getCanonicalName() + ";", + "class TestComparatorAlreadyImported {", + " public Optional f(Stream s) {", + " return s.min(Comparator.naturalOrder());", + " }", + " public Optional g(Stream s) {", + " return s.min(Comparator.naturalOrder());", + " }", + "}") + .doTest(); + } + + @Test + public void test_templated() { + fix().addInputLines( + "TestBasic.java", + "import " + Optional.class.getCanonicalName() + ";", + "import " + Stream.class.getCanonicalName() + ";", + "class TestBasic> {", + " public Optional basic(Stream s) {", + " return s.sorted().findFirst();", + " }", + "}") + .addOutputLines( + "TestBasic.java", + "import " + Comparator.class.getCanonicalName() + ";", + "import " + Optional.class.getCanonicalName() + ";", + "import " + Stream.class.getCanonicalName() + ";", + "class TestBasic> {", + " public Optional basic(Stream s) {", + " return s.min(Comparator.naturalOrder());", + " }", + "}") + .doTest(); + } + + @Test + public void test_comparator() { + fix().addInputLines( + "TestComparator.java", + "import " + Comparator.class.getCanonicalName() + ";", + "import " + Optional.class.getCanonicalName() + ";", + "import " + Stream.class.getCanonicalName() + ";", + "class TestComparator {", + " public Optional f(Stream s, Comparator c) {", + " return s.sorted(c).findFirst();", + " }", + "}") + .addOutputLines( + "TestComparator.java", + "import " + Comparator.class.getCanonicalName() + ";", + "import " + Optional.class.getCanonicalName() + ";", + "import " + Stream.class.getCanonicalName() + ";", + "class TestComparator {", + " public Optional f(Stream s, Comparator c) {", + " return s.min(c);", + " }", + "}") + .doTest(); + } + + private RefactoringValidator fix() { + return RefactoringValidator.of(SortedStreamFirstElement.class, getClass()); + } +} diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/StrictUnusedVariableTest.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/StrictUnusedVariableTest.java index a65ac008c..49393f91f 100644 --- a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/StrictUnusedVariableTest.java +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/StrictUnusedVariableTest.java @@ -325,7 +325,7 @@ public void testRecord() { } @Test - public void testSuppression() { + public void testParameterSuppression() { refactoringTestHelper .addInputLines( "Test.java", @@ -339,6 +339,47 @@ public void testSuppression() { .doTest(TestMode.TEXT_MATCH); } + @Test + public void testMethodSuppression() { + refactoringTestHelper + .addInputLines( + "Test.java", + "class Test {", + " @SuppressWarnings(\"StrictUnusedVariable\")", + " public static void a(int val) {}", + " @SuppressWarnings(\"UnusedVariable\")", + " public static void b(int val) {}", + " @SuppressWarnings(\"unused\")", + " public static void c(int val) {}", + " public static void d(int _val) {}", + "}") + .expectUnchanged() + .doTest(TestMode.TEXT_MATCH); + } + + @Test + public void testClassSuppression() { + refactoringTestHelper + .addInputLines( + "Test.java", + "class Test {", + " @SuppressWarnings(\"StrictUnusedVariable\")", + " class Test1 {", + " public static void a(int val) {}", + " }", + " @SuppressWarnings(\"UnusedVariable\")", + " class Test2 {", + " public static void a(int val) {}", + " }", + " @SuppressWarnings(\"unused\")", + " class Test3 {", + " public static void a(int val) {}", + " }", + "}") + .expectUnchanged() + .doTest(TestMode.TEXT_MATCH); + } + @Test public void allows_unused_loggers() { compilationHelper diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/ZoneIdConstantTest.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/ZoneIdConstantTest.java new file mode 100644 index 000000000..df2718082 --- /dev/null +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/ZoneIdConstantTest.java @@ -0,0 +1,94 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.baseline.errorprone; + +import org.junit.jupiter.api.Test; + +final class ZoneIdConstantTest { + + @Test + void zoneIdZ() { + fix().addInputLines( + "Test.java", + "import java.time.ZoneId;", + "class Test {", + " static void f() {", + " ZoneId zoneId = ZoneId.of(\"Z\");", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.time.ZoneId;", + "import java.time.ZoneOffset;", + "class Test {", + " static void f() {", + " ZoneId zoneId = ZoneOffset.UTC;", + " }", + "}") + .doTest(); + } + + @Test + void zoneIdUtc() { + fix().addInputLines( + "Test.java", + "import java.time.ZoneId;", + "class Test {", + " static void f() {", + " ZoneId zoneId = ZoneId.of(\"UTC\");", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.time.ZoneId;", + "import java.time.ZoneOffset;", + "class Test {", + " static void f() {", + " ZoneId zoneId = ZoneOffset.UTC;", + " }", + "}") + .doTest(); + } + + @Test + void zoneIdConstant() { + fix().addInputLines( + "Test.java", + "import java.time.ZoneId;", + "class Test {", + " static final String Z = \"Z\";", + " static void f() {", + " ZoneId zoneId = ZoneId.of(Z);", + " }", + "}") + .addOutputLines( + "Test.java", + "import java.time.ZoneId;", + "import java.time.ZoneOffset;", + "class Test {", + " static final String Z = \"Z\";", + " static void f() {", + " ZoneId zoneId = ZoneOffset.UTC;", + " }", + "}") + .doTest(); + } + + private RefactoringValidator fix() { + return RefactoringValidator.of(ZoneIdConstant.class, getClass()); + } +} diff --git a/build.gradle b/build.gradle index fc6b277e0..7c1fb1a22 100644 --- a/build.gradle +++ b/build.gradle @@ -8,20 +8,20 @@ buildscript { dependencies { classpath 'com.palantir.jakartapackagealignment:jakarta-package-alignment:0.5.0' - classpath 'com.palantir.gradle.jdks:gradle-jdks:0.31.0' - classpath 'com.palantir.gradle.jdkslatest:gradle-jdks-latest:0.7.0' + classpath 'com.palantir.gradle.jdks:gradle-jdks:0.33.0' + classpath 'com.palantir.gradle.jdkslatest:gradle-jdks-latest:0.11.0' classpath 'com.diffplug.gradle:goomph:3.32.1' classpath 'com.palantir.gradle.externalpublish:gradle-external-publish-plugin:1.12.0' - classpath 'com.palantir.gradle.consistentversions:gradle-consistent-versions:2.11.0' - classpath 'com.gradle.publish:plugin-publish-plugin:1.1.0' - classpath 'com.palantir.baseline:gradle-baseline-java:4.192.0' - classpath 'com.palantir.javaformat:gradle-palantir-java-format:2.28.0' + classpath 'com.palantir.gradle.consistentversions:gradle-consistent-versions:2.16.0' + classpath 'com.gradle.publish:plugin-publish-plugin:1.2.1' + classpath 'com.palantir.baseline:gradle-baseline-java:5.25.0' + classpath 'com.palantir.javaformat:gradle-palantir-java-format:2.33.0' } } plugins { - id 'com.palantir.git-version' version '0.15.0' - id 'org.inferred.processors' version '3.6.0' +id 'com.palantir.git-version' version '3.0.0' +id 'org.inferred.processors' version '3.7.0' } apply plugin: 'com.palantir.external-publish' diff --git a/changelog/5.1.0/pr-2537.v2.yml b/changelog/5.1.0/pr-2537.v2.yml new file mode 100644 index 000000000..d7970b57d --- /dev/null +++ b/changelog/5.1.0/pr-2537.v2.yml @@ -0,0 +1,5 @@ +type: fix +fix: + description: StrictUnusedVariable is not suppressed when UnusedVariable is disabled + links: + - https://github.com/palantir/gradle-baseline/pull/2537 diff --git a/changelog/5.10.0/pr-2582.v2.yml b/changelog/5.10.0/pr-2582.v2.yml new file mode 100644 index 000000000..945388a98 --- /dev/null +++ b/changelog/5.10.0/pr-2582.v2.yml @@ -0,0 +1,6 @@ +type: fix +fix: + description: Revert "Don't ignore all directories named build/ (#2578)" which failed + to ignore some generated files within the build directory + links: + - https://github.com/palantir/gradle-baseline/pull/2582 diff --git a/changelog/5.11.0/pr-2555.v2.yml b/changelog/5.11.0/pr-2555.v2.yml new file mode 100644 index 000000000..73e99a44b --- /dev/null +++ b/changelog/5.11.0/pr-2555.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: Add check replacing `stream.sorted().findFirst()` with `stream.min()` + links: + - https://github.com/palantir/gradle-baseline/pull/2555 diff --git a/changelog/5.12.0/pr-2576.v2.yml b/changelog/5.12.0/pr-2576.v2.yml new file mode 100644 index 000000000..cb0602fa9 --- /dev/null +++ b/changelog/5.12.0/pr-2576.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: Upgrade Error Prone to 2.19.1 (from 2.18.0) + links: + - https://github.com/palantir/gradle-baseline/pull/2576 diff --git a/changelog/5.13.0/pr-2591.v2.yml b/changelog/5.13.0/pr-2591.v2.yml new file mode 100644 index 000000000..42b37433c --- /dev/null +++ b/changelog/5.13.0/pr-2591.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: Opt out of the 'NotJavadoc' errorprone check + links: + - https://github.com/palantir/gradle-baseline/pull/2591 diff --git a/changelog/5.14.0/pr-2599.v2.yml b/changelog/5.14.0/pr-2599.v2.yml new file mode 100644 index 000000000..16c7afc9c --- /dev/null +++ b/changelog/5.14.0/pr-2599.v2.yml @@ -0,0 +1,5 @@ +type: fix +fix: + description: Fix unintentional suppression of StrictUnusedVariable + links: + - https://github.com/palantir/gradle-baseline/pull/2599 diff --git a/changelog/5.16.0/pr-2602.v2.yml b/changelog/5.16.0/pr-2602.v2.yml new file mode 100644 index 000000000..621c0f900 --- /dev/null +++ b/changelog/5.16.0/pr-2602.v2.yml @@ -0,0 +1,5 @@ +type: fix +fix: + description: Fix nullaway checkerframework dependency + links: + - https://github.com/palantir/gradle-baseline/pull/2602 diff --git a/changelog/5.17.0/pr-2596.v2.yml b/changelog/5.17.0/pr-2596.v2.yml new file mode 100644 index 000000000..1acccb333 --- /dev/null +++ b/changelog/5.17.0/pr-2596.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: Add error-prone check to prefer ZoneId constants + links: + - https://github.com/palantir/gradle-baseline/pull/2596 diff --git a/changelog/5.19.0/pr-2616.v2.yml b/changelog/5.19.0/pr-2616.v2.yml new file mode 100644 index 000000000..0ad2bf1ce --- /dev/null +++ b/changelog/5.19.0/pr-2616.v2.yml @@ -0,0 +1,25 @@ +type: improvement +improvement: + description: |- + Prefer `InputStream.transferTo(OutputStream)` + + Add error-prone check to automate migration to prefer `InputStream.transferTo(OutputStream)` instead of utility methods such as Guava's `com.google.common.io.ByteStreams.copy(InputStream, OutputStream)`. + + Allow for optimization when underlying input stream (such as `ByteArrayInputStream`, `ChannelInputStream`) overrides `transferTo(OutputStream)` to avoid extra array allocations and copy larger chunks at a time (e.g. allowing 16KiB chunks via `ApacheHttpClientBlockingChannel.ModulatingOutputStream` from #1790). + + When running on JDK 21+, this also enables 16KiB byte chunk copies via `InputStream.transferTo(OutputStream)` per + [JDK-8299336](https://bugs.openjdk.org/browse/JDK-8299336), where as on JDK < 21 and when using Guava `ByteStreams.copy` 8KiB byte chunk copies are used. + + References: + * https://github.com/palantir/hadoop-crypto/pull/586 + * https://bugs.openjdk.org/browse/JDK-8299336 + * https://bugs.openjdk.org/browse/JDK-8067661 + * https://bugs.openjdk.org/browse/JDK-8265891 + * https://bugs.openjdk.org/browse/JDK-8273038 + * https://bugs.openjdk.org/browse/JDK-8279283 + * https://bugs.openjdk.org/browse/JDK-8296431 + + Closes https://github.com/palantir/gradle-baseline/issues/2615 + links: + - https://github.com/palantir/gradle-baseline/pull/2615 + - https://github.com/palantir/gradle-baseline/pull/2616 diff --git a/changelog/5.2.0/pr-2530.v2.yml b/changelog/5.2.0/pr-2530.v2.yml new file mode 100644 index 000000000..ac9f84ac9 --- /dev/null +++ b/changelog/5.2.0/pr-2530.v2.yml @@ -0,0 +1,6 @@ +type: improvement +improvement: + description: 'Add check: Use Collection.isEmpty() instead of comparing size() with + 0' + links: + - https://github.com/palantir/gradle-baseline/pull/2530 diff --git a/changelog/5.20.0/pr-2629.v2.yml b/changelog/5.20.0/pr-2629.v2.yml new file mode 100644 index 000000000..9d768f188 --- /dev/null +++ b/changelog/5.20.0/pr-2629.v2.yml @@ -0,0 +1,6 @@ +type: improvement +improvement: + description: Improve SafeLoggingPropagation on Immutables, taking into account fields + from superinterfaces + links: + - https://github.com/palantir/gradle-baseline/pull/2629 diff --git a/changelog/5.21.0/pr-2628.v2.yml b/changelog/5.21.0/pr-2628.v2.yml new file mode 100644 index 000000000..9f1826270 --- /dev/null +++ b/changelog/5.21.0/pr-2628.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: Upgrade error-prone to 2.21.1 (from 2.19.1) + links: + - https://github.com/palantir/gradle-baseline/pull/2628 diff --git a/changelog/5.23.0/pr-2605.v2.yml b/changelog/5.23.0/pr-2605.v2.yml new file mode 100644 index 000000000..aa1f0f7b5 --- /dev/null +++ b/changelog/5.23.0/pr-2605.v2.yml @@ -0,0 +1,6 @@ +type: fix +fix: + description: Use a `Proxy` for `JavaInstallationMetadata` so we can work across + Gradle 7 and 8. + links: + - https://github.com/palantir/gradle-baseline/pull/2605 diff --git a/changelog/5.24.0/pr-2639.v2.yml b/changelog/5.24.0/pr-2639.v2.yml new file mode 100644 index 000000000..02fb5d091 --- /dev/null +++ b/changelog/5.24.0/pr-2639.v2.yml @@ -0,0 +1,6 @@ +type: fix +fix: + description: '`baseline-exact-dependencies` is now far more lazy around `Configuration` + creation in order to support Gradle 8.' + links: + - https://github.com/palantir/gradle-baseline/pull/2639 diff --git a/changelog/5.25.0/pr-2642.v2.yml b/changelog/5.25.0/pr-2642.v2.yml new file mode 100644 index 000000000..c3234d032 --- /dev/null +++ b/changelog/5.25.0/pr-2642.v2.yml @@ -0,0 +1,5 @@ +type: fix +fix: + description: Revert "Lazy exact dependencies" + links: + - https://github.com/palantir/gradle-baseline/pull/2642 diff --git a/changelog/5.3.0/pr-2544.v2.yml b/changelog/5.3.0/pr-2544.v2.yml new file mode 100644 index 000000000..1e4a05ea4 --- /dev/null +++ b/changelog/5.3.0/pr-2544.v2.yml @@ -0,0 +1,5 @@ +type: fix +fix: + description: 'Fix #2543 CardinalityEqualsZero equality comparison check' + links: + - https://github.com/palantir/gradle-baseline/pull/2544 diff --git a/changelog/5.4.0/pr-2541.v2.yml b/changelog/5.4.0/pr-2541.v2.yml new file mode 100644 index 000000000..28d640520 --- /dev/null +++ b/changelog/5.4.0/pr-2541.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: 'Add check: Use InvocationTargetException.getCause instead of getTargetException.' + links: + - https://github.com/palantir/gradle-baseline/pull/2541 diff --git a/changelog/5.5.0/pr-2547.v2.yml b/changelog/5.5.0/pr-2547.v2.yml new file mode 100644 index 000000000..7a08e3149 --- /dev/null +++ b/changelog/5.5.0/pr-2547.v2.yml @@ -0,0 +1,8 @@ +type: fix +fix: + description: No longer suggest the "Save Actions" IntelliJ plugin which does not + work in IntelliJ 2023.1 for use with Palantir Java Format. Instead, Palantir Java + Format will support the IntelliJ native "Actions on save" reformat capability + in a future release. + links: + - https://github.com/palantir/gradle-baseline/pull/2547 diff --git a/changelog/5.6.0/pr-2551.v2.yml b/changelog/5.6.0/pr-2551.v2.yml new file mode 100644 index 000000000..77da61ba7 --- /dev/null +++ b/changelog/5.6.0/pr-2551.v2.yml @@ -0,0 +1,6 @@ +type: fix +fix: + description: Ensure all recommendations to install the "Save Actions" plugin are + removed in persistent config in the `.idea` config directory. + links: + - https://github.com/palantir/gradle-baseline/pull/2551 diff --git a/changelog/5.7.0/pr-2563.v2.yml b/changelog/5.7.0/pr-2563.v2.yml new file mode 100644 index 000000000..aac001310 --- /dev/null +++ b/changelog/5.7.0/pr-2563.v2.yml @@ -0,0 +1,6 @@ +type: improvement +improvement: + description: |- + Fix eclipse classpath generation by deleting the existing classpath, which makes the classpath correct when it changes. + links: + - https://github.com/palantir/gradle-baseline/pull/2563 diff --git a/changelog/5.8.0/pr-2577.v2.yml b/changelog/5.8.0/pr-2577.v2.yml new file mode 100644 index 000000000..791385f28 --- /dev/null +++ b/changelog/5.8.0/pr-2577.v2.yml @@ -0,0 +1,5 @@ +type: fix +fix: + description: Only pass posix file attributes if not on Windows + links: + - https://github.com/palantir/gradle-baseline/pull/2577 diff --git a/changelog/5.9.0/pr-2578.v2.yml b/changelog/5.9.0/pr-2578.v2.yml new file mode 100644 index 000000000..c62d72da1 --- /dev/null +++ b/changelog/5.9.0/pr-2578.v2.yml @@ -0,0 +1,5 @@ +type: fix +fix: + description: Don't ignore all directories named build/ + links: + - https://github.com/palantir/gradle-baseline/pull/2578 diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineErrorProneExtension.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineErrorProneExtension.java index 6d14f8dc7..9a97ceee4 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineErrorProneExtension.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/extensions/BaselineErrorProneExtension.java @@ -57,6 +57,7 @@ public class BaselineErrorProneExtension { "PreferBuiltInConcurrentKeySet", "PreferCollectionConstructors", "PreferCollectionTransform", + "PreferInputStreamTransferTo", "PreferListsPartition", "PreferSafeLoggableExceptions", "PreferSafeLogger", @@ -66,6 +67,7 @@ public class BaselineErrorProneExtension { "ReadReturnValueIgnored", "RedundantMethodReference", "RedundantModifier", + "SafeLoggingPropagation", "Slf4jLevelCheck", "Slf4jLogsafeArgs", "Slf4jThrowable", @@ -80,6 +82,7 @@ public class BaselineErrorProneExtension { "UnsafeGaugeRegistration", "VarUsage", "ZeroWarmupRateLimiter", + "ZoneIdConstant", // Built-in checks "ArrayEquals", diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineCircleCi.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineCircleCi.java index 2c496732d..8983ae0f4 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineCircleCi.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineCircleCi.java @@ -27,20 +27,20 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; +import java.util.Locale; import java.util.Set; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.tasks.testing.Test; public final class BaselineCircleCi implements Plugin { - private static final FileAttribute> PERMS_ATTRIBUTE = + private static final FileAttribute> POSIX_PERMS_ATTRIBUTE = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-xr-x")); @Override public void apply(Project project) { configurePluginsForReports(project); configurePluginsForArtifacts(project); - Preconditions.checkState( !project.getName().equals("project"), "Please specify rootProject.name in your settings.gradle, otherwise CircleCI's" @@ -55,7 +55,7 @@ private void configurePluginsForArtifacts(Project project) { } try { - Files.createDirectories(Paths.get(circleArtifactsDir), PERMS_ATTRIBUTE); + createDirectories(Paths.get(circleArtifactsDir)); } catch (IOException e) { throw new RuntimeException("failed to create CIRCLE_ARTIFACTS directory", e); } @@ -74,7 +74,7 @@ private void configurePluginsForReports(Project project) { } try { - Files.createDirectories(Paths.get(circleReportsDir), PERMS_ATTRIBUTE); + createDirectories(Paths.get(circleReportsDir)); } catch (IOException e) { throw new RuntimeException("failed to create CIRCLE_TEST_REPORTS directory", e); } @@ -102,4 +102,14 @@ private static File junitPath(String basePath, String testPath) { } return junitReportsDir.toFile(); } + + private static void createDirectories(Path directoryPath) throws IOException { + boolean isWindows = + System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows"); + if (isWindows) { + Files.createDirectories(directoryPath); + } else { + Files.createDirectories(directoryPath, POSIX_PERMS_ATTRIBUTE); + } + } } diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineEclipse.groovy b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineEclipse.groovy index 8e0522a68..fae827772 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineEclipse.groovy +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineEclipse.groovy @@ -140,6 +140,8 @@ class BaselineEclipse extends AbstractBaselinePlugin { containers.add(eclipseClassPath) } } + // Delete classpath instead of merging with existing classpath + dependsOn "cleanEclipseClasspath" } } }) diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineErrorProne.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineErrorProne.java index 8f7ff541a..be54e4e02 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineErrorProne.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineErrorProne.java @@ -23,6 +23,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.MoreCollectors; import com.palantir.baseline.extensions.BaselineErrorProneExtension; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -131,7 +132,13 @@ private static void configureErrorProneOptions( "CatchSpecificity", "CanIgnoreReturnValueSuggester", "InlineMeSuggester", + // We often use javadoc comments without javadoc parameter information. + "NotJavadoc", "PreferImmutableStreamExCollections", + // StringCaseLocaleUsage duplicates our existing DefaultLocale check which is already + // enforced in some places. + "StringCaseLocaleUsage", + "UnnecessaryTestMethodPrefix", "UnusedVariable", // See VarUsage: The var keyword results in illegible code in most cases and should not be used. "Varifier", @@ -170,13 +177,27 @@ private static void configureErrorProneOptions( // intentionally not using a lambda to reduce gradle warnings @Override public Iterable asArguments() { - // Don't apply checks that have been explicitly disabled - Stream errorProneChecks = getSpecificErrorProneChecks(project) - .orElseGet(() -> getNotDisabledErrorproneChecks( - project, errorProneExtension, javaCompile, maybeSourceSet, errorProneOptions)); - return ImmutableList.of( - "-XepPatchChecks:" + Joiner.on(',').join(errorProneChecks.iterator()), - "-XepPatchLocation:IN_PLACE"); + Optional> specificChecks = getSpecificErrorProneChecks(project); + if (specificChecks.isPresent()) { + List errorProneChecks = specificChecks.get(); + // Work around https://github.com/google/error-prone/issues/3908 by explicitly enabling any + // check we want to use patch checks for (ensuring it is not disabled); if this is fixed, the + // -Xep:*:ERROR arguments could be removed + return Iterables.concat( + errorProneChecks.stream() + .map(checkName -> "-Xep:" + checkName + ":ERROR") + .collect(Collectors.toList()), + ImmutableList.of( + "-XepPatchChecks:" + Joiner.on(',').join(errorProneChecks), + "-XepPatchLocation:IN_PLACE")); + } else { + // Don't apply checks that have been explicitly disabled + Stream errorProneChecks = getNotDisabledErrorproneChecks( + project, errorProneExtension, javaCompile, maybeSourceSet, errorProneOptions); + return ImmutableList.of( + "-XepPatchChecks:" + Joiner.on(',').join(errorProneChecks.iterator()), + "-XepPatchLocation:IN_PLACE"); + } } }); } @@ -189,12 +210,12 @@ static String excludedPathsRegex() { return ".*/(build|generated_.*[sS]rc|src/generated.*)/.*"; } - private static Optional> getSpecificErrorProneChecks(Project project) { + private static Optional> getSpecificErrorProneChecks(Project project) { return Optional.ofNullable(project.findProperty(PROP_ERROR_PRONE_APPLY)) .map(Objects::toString) .flatMap(value -> Optional.ofNullable(Strings.emptyToNull(value))) .map(value -> Splitter.on(',').trimResults().omitEmptyStrings().splitToList(value)) - .flatMap(list -> list.isEmpty() ? Optional.empty() : Optional.of(list.stream())); + .flatMap(list -> list.isEmpty() ? Optional.empty() : Optional.of(list)); } private static Stream getNotDisabledErrorproneChecks( diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineIdea.groovy b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineIdea.groovy index 40821e54e..b662f6618 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineIdea.groovy +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/BaselineIdea.groovy @@ -47,9 +47,6 @@ import org.gradle.plugins.ide.idea.model.ModuleDependency // TODO(dfox): separate the xml manipulation (which really benefits from groovy syntax) from typed things //@CompileStatic class BaselineIdea extends AbstractBaselinePlugin { - - static SAVE_ACTIONS_PLUGIN_MINIMUM_VERSION = '1.9.0' - void apply(Project project) { this.project = project @@ -112,17 +109,7 @@ class BaselineIdea extends AbstractBaselinePlugin { } } - // Suggest and configure the "save actions" plugin if Palantir Java Format is turned on. - // This plugin can only be applied to the root project, and it applied as a side-effect of applying - // 'com.palantir.java-format' to any subproject. - rootProject.getPluginManager().withPlugin("com.palantir.java-format-idea") { - ideaRootModel.project.ipr.withXml {XmlProvider provider -> - Node node = provider.asNode() - configureSaveActions(node) - configureExternalDependencies(node) - } - configureSaveActionsForIntellijImport(rootProject) - } + removeSaveActionsExternalDependency(rootProject) } @CompileStatic @@ -522,18 +509,6 @@ class BaselineIdea extends AbstractBaselinePlugin { '''.stripIndent())) } - private static void configureSaveActionsForIntellijImport(Project project) { - if (!IntellijSupport.isRunningInIntellij()) { - return - } - XmlUtils.createOrUpdateXmlFile( - project.file(".idea/externalDependencies.xml"), - BaselineIdea.&configureExternalDependencies) - XmlUtils.createOrUpdateXmlFile( - project.file(".idea/saveactions_settings.xml"), - BaselineIdea.&configureSaveActions) - } - /** * Configure the default working directory of RunManager configurations to be the module directory. */ @@ -575,34 +550,24 @@ class BaselineIdea extends AbstractBaselinePlugin { } /** - * Configures some defaults on the save-actions plugin, but only if it hasn't been configured before. + * We used to add the 'Save Actions' plugin as an external dependency to support Palantir Java Format, + * however this plugin is broken in IntelliJ 2023.1 and we no longer use it. However, without actually + * removing it from the persistent intellij config people will still get nagged. So this code removing + * needs to remain here a sufficiently long time until every extant repo checkout has had a version of + * baseline run on it that remove the config. */ - private static void configureSaveActions(Node rootNode) { - GroovyXmlUtils.matchOrCreateChild(rootNode, 'component', [name: 'SaveActionSettings'], [:]) { - // Configure defaults if this plugin is configured for the first time only - appendNode('option', [name: 'actions']).appendNode('set').with { - appendNode('option', [value: 'activate']) - appendNode('option', [value: 'noActionIfCompileErrors']) - appendNode('option', [value: 'organizeImports']) - appendNode('option', [value: 'reformat']) - } - appendNode('option', [name: 'configurationPath', value: '']) - appendNode('option', [name: 'inclusions']).appendNode('set').with { - appendNode('option', [value: "src${File.separator}.*\\.java"]) - } + private static void removeSaveActionsExternalDependency(Project rootProject) { + if (!IntellijSupport.isRunningInIntellij()) { + return } - } - private static void configureExternalDependencies(Node rootNode) { - def externalDependencies = - GroovyXmlUtils.matchOrCreateChild(rootNode, 'component', [name: 'ExternalDependencies']) - // I kid you not, this is the id for the save actions plugin: - // https://github.com/dubreuia/intellij-plugin-save-actions/blob/v1.9.0/src/main/resources/META-INF/plugin.xml#L5 - // https://plugins.jetbrains.com/plugin/7642-save-actions/ - GroovyXmlUtils.matchOrCreateChild( - externalDependencies, - 'plugin', - [id: 'com.dubreuia'], - ['min-version': SAVE_ACTIONS_PLUGIN_MINIMUM_VERSION]) + XmlUtils.updateXmlFileIfExists(rootProject.file(".idea/externalDependencies.xml")) { rootNode -> + GroovyXmlUtils.matchChild(rootNode, 'component', [name: 'ExternalDependencies']).ifPresent { externalDeps -> + // No joke the Save Actions plugin's id is 'com.dubreuia'. + GroovyXmlUtils.matchChild(externalDeps, 'plugin', [id: 'com.dubreuia']).ifPresent { saveActionsPlugin -> + externalDeps.remove(saveActionsPlugin) + } + } + } } } diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/GroovyXmlUtils.groovy b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/GroovyXmlUtils.groovy index a498b492e..34f556d10 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/GroovyXmlUtils.groovy +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/GroovyXmlUtils.groovy @@ -23,6 +23,7 @@ final class GroovyXmlUtils { Map attributes = [:], Map defaults = [:], @DelegatesTo(value = Node, strategy = Closure.DELEGATE_FIRST) Closure ifCreated = {}) { + def child = base[name].find { it.attributes().entrySet().containsAll(attributes.entrySet()) } if (child) { return child @@ -33,4 +34,9 @@ final class GroovyXmlUtils { ifCreated(created) return created } + + static Optional matchChild(Node base, String name, Map attributes = [:]) { + Node child = base[name].find { it.attributes().entrySet().containsAll(attributes.entrySet()) } + return Optional.ofNullable(child) + } } diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/XmlUtils.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/XmlUtils.java index 19186b259..9002fb79a 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/XmlUtils.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/XmlUtils.java @@ -34,6 +34,14 @@ final class XmlUtils { private XmlUtils() {} + static void updateXmlFileIfExists(File configurationFile, Consumer configure) { + if (!configurationFile.exists()) { + return; + } + + createOrUpdateXmlFile(configurationFile, configure); + } + static void createOrUpdateXmlFile(File configurationFile, Consumer configure) { createOrUpdateXmlFile( configurationFile, configure, () -> new Node(null, "project", ImmutableMap.of("version", "4"))); diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataWrapper.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataProxy.java similarity index 55% rename from gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataWrapper.java rename to gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataProxy.java index 28c9c38dc..8c907254b 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataWrapper.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaInstallationMetadataProxy.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2022 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,10 @@ package com.palantir.baseline.plugins.javaversions; -import org.gradle.api.file.Directory; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import org.gradle.api.provider.Provider; import org.gradle.jvm.toolchain.JavaInstallationMetadata; import org.gradle.jvm.toolchain.JavaLanguageVersion; @@ -27,38 +30,37 @@ * check if toolchains are enabled. That can cause a dependency cycle, which causes a StackOverflowException. * This class breaks that cycle by immediately providing the JavaLanguageVersion without possibly causing a resolution. */ -final class JavaInstallationMetadataWrapper implements JavaInstallationMetadata { +final class JavaInstallationMetadataProxy implements InvocationHandler { + private final JavaLanguageVersion javaLanguageVersion; private final Provider delegate; - JavaInstallationMetadataWrapper( + private JavaInstallationMetadataProxy( JavaLanguageVersion javaLanguageVersion, Provider delegate) { this.javaLanguageVersion = javaLanguageVersion; this.delegate = delegate; } - @Override - public JavaLanguageVersion getLanguageVersion() { - return javaLanguageVersion; - } - - @Override - public String getJavaRuntimeVersion() { - return delegate.get().getJavaRuntimeVersion(); - } - - @Override - public String getJvmVersion() { - return delegate.get().getJvmVersion(); - } - - @Override - public String getVendor() { - return delegate.get().getVendor(); + static JavaInstallationMetadata proxyForVersion( + JavaLanguageVersion javaLanguageVersion, Provider delegate) { + return (JavaInstallationMetadata) Proxy.newProxyInstance( + JavaInstallationMetadata.class.getClassLoader(), + new Class[] {JavaInstallationMetadata.class}, + new JavaInstallationMetadataProxy(javaLanguageVersion, delegate)); } @Override - public Directory getInstallationPath() { - return delegate.get().getInstallationPath(); + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + try { + if ("getLanguageVersion".equals(method.getName())) { + return javaLanguageVersion; + } else { + return method.invoke(delegate.get(), args); + } + } catch (IllegalAccessException | IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw e.getCause(); + } } } diff --git a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaToolchains.java b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaToolchains.java index 9bec70510..9d449919b 100644 --- a/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaToolchains.java +++ b/gradle-baseline-java/src/main/groovy/com/palantir/baseline/plugins/javaversions/JavaToolchains.java @@ -45,7 +45,7 @@ public Provider forVersion(Provider ja return new ConfiguredJavaToolchain( project.getObjects(), - project.provider(() -> new JavaInstallationMetadataWrapper( + project.provider(() -> JavaInstallationMetadataProxy.proxyForVersion( chosenJavaVersion.javaLanguageVersion(), configuredJdkMetadata))); }); } diff --git a/gradle-baseline-java/src/main/resources/checkstyle.version b/gradle-baseline-java/src/main/resources/checkstyle.version index e102ec566..f2b729ffc 100644 --- a/gradle-baseline-java/src/main/resources/checkstyle.version +++ b/gradle-baseline-java/src/main/resources/checkstyle.version @@ -1 +1 @@ -10.8.1 \ No newline at end of file +10.12.2 \ No newline at end of file diff --git a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineErrorProneIntegrationTest.groovy b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineErrorProneIntegrationTest.groovy index b4b3e0639..974be1c8b 100644 --- a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineErrorProneIntegrationTest.groovy +++ b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineErrorProneIntegrationTest.groovy @@ -96,6 +96,24 @@ class BaselineErrorProneIntegrationTest extends AbstractPluginTest { result.output.contains("[ArrayEquals] Reference equality used to compare arrays") } + def 'compileJava fails when StrictUnusedVariable finds errors'() { + when: + buildFile << standardBuildFile + file('src/main/java/test/Test.java') << ''' + package test; + public class Test { + void test() { + int a = 5; + } + } + '''.stripIndent() + + then: + BuildResult result = with('compileJava').buildAndFail() + result.task(":compileJava").outcome == TaskOutcome.FAILED + result.output.contains("[StrictUnusedVariable]") + } + def 'error-prone can be disabled using property'() { when: buildFile << standardBuildFile diff --git a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineIdeaIntegrationTest.groovy b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineIdeaIntegrationTest.groovy index e35c3a823..53d0a9baa 100644 --- a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineIdeaIntegrationTest.groovy +++ b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineIdeaIntegrationTest.groovy @@ -313,56 +313,6 @@ class BaselineIdeaIntegrationTest extends AbstractPluginTest { !otherSubprojectIml.exists() } - def "idea configures the save-action plugin when PJF is enabled on a subproject"() { - buildFile << standardBuildFile - multiProject.addSubproject('formatted-project', """ - apply plugin: 'com.palantir.java-format' - """.stripIndent()) - - when: - with('idea').build() - - then: - def iprFile = new File(projectDir, "${moduleName}.ipr") - def ipr = new XmlSlurper().parse(iprFile) - ipr.component.find { it.@name == "ExternalDependencies" } - ipr.component.find { it.@name == "SaveActionSettings" } - } - - def "idea does not configure the save-action plugin when PJF is not enabled"() { - buildFile << standardBuildFile - - when: - with('idea').build() - - then: - def iprFile = new File(projectDir, "${moduleName}.ipr") - def ipr = new XmlSlurper().parse(iprFile) - !ipr.component.find { it.@name == "ExternalDependencies" } - !ipr.component.find { it.@name == "SaveActionSettings" } - } - - @RestoreSystemProperties - def "idea configures the save-action plugin for IntelliJ import"() { - buildFile << standardBuildFile - multiProject.addSubproject('formatted-project', """ - apply plugin: 'com.palantir.java-format' - """.stripIndent()) - - when: - System.setProperty("idea.active", "true") - with().build() - - then: - def saveActionsSettingsFile = new File(projectDir, ".idea/saveactions_settings.xml") - def settings = new XmlSlurper().parse(saveActionsSettingsFile) - settings.component.find { it.@name == "SaveActionSettings" } - - def externalDepsSettingsFile = new File(projectDir, ".idea/externalDependencies.xml") - def deps = new XmlSlurper().parse(externalDepsSettingsFile) - deps.component.find { it.@name == "ExternalDependencies" } - } - def 'Idea files use versions derived from the baseline-java-versions plugin'() { when: buildFile << standardBuildFile @@ -385,4 +335,38 @@ class BaselineIdeaIntegrationTest extends AbstractPluginTest { def rootIml = Files.asCharSource(new File(projectDir, projectDir.name + ".iml"), Charsets.UTF_8).read() rootIml.contains('LANGUAGE_LEVEL="JDK_15') } + + @RestoreSystemProperties + def 'Removes the save-actions external dep if it exists'() { + buildFile << standardBuildFile + + file('.idea/externalDependencies.xml') << /* language=xml */ ''' + + + + + + + + '''.stripIndent(true).trim() + + when: + System.setProperty("idea.active", "true") + with().build() + + then: + def newExternalDeps = file('.idea/externalDependencies.xml').text.trim() + + // language=xml + def expected = ''' + + + + + + + '''.stripIndent(true).trim() + + newExternalDeps == expected + } } diff --git a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineJavaVersionIntegrationTest.groovy b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineJavaVersionIntegrationTest.groovy index befc0b909..ba28c4b8b 100644 --- a/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineJavaVersionIntegrationTest.groovy +++ b/gradle-baseline-java/src/test/groovy/com/palantir/baseline/BaselineJavaVersionIntegrationTest.groovy @@ -16,6 +16,9 @@ package com.palantir.baseline +import org.gradle.util.GradleVersion +import spock.lang.Unroll + import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -27,7 +30,10 @@ import org.assertj.core.api.Assumptions * This test exercises both the root-plugin {@code BaselineJavaVersions} AND the subproject * specific plugin, {@code BaselineJavaVersion}. */ +@Unroll class BaselineJavaVersionIntegrationTest extends IntegrationSpec { + private static final List GRADLE_TEST_VERSIONS = ['8.4.0', GradleVersion.current().getVersion()] + private static final int JAVA_8_BYTECODE = 52 private static final int JAVA_11_BYTECODE = 55 private static final int JAVA_17_BYTECODE = 61 @@ -101,8 +107,9 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { buildFile << standardBuildFile } - def 'java 11 compilation fails targeting java 8'() { + def '#gradleVersionNumber: java 11 compilation fails targeting java 8'() { when: + gradleVersion = gradleVersionNumber buildFile << ''' javaVersions { libraryTarget = 8 @@ -113,9 +120,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: runTasksWithFailure('compileJava') + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'distribution target is used when no artifacts are published'() { + def '#gradleVersionNumber: distribution target is used when no artifacts are published'() { when: buildFile << ''' javaVersions { @@ -129,9 +139,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: runTasksSuccessfully('compileJava') assertBytecodeVersion(compiledClass, JAVA_17_BYTECODE, NOT_ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'java 17 preview compilation works'() { + def '#gradleVersionNumber: java 17 preview compilation works'() { when: buildFile << ''' javaVersions { @@ -145,9 +158,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: runTasksSuccessfully('compileJava', '-i') assertBytecodeVersion(compiledClass, JAVA_17_BYTECODE, ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'setting library target to preview version fails'() { + def '#gradleVersionNumber: setting library target to preview version fails'() { when: buildFile << ''' javaVersions { @@ -159,9 +175,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: ExecutionResult result = runTasksWithFailure('compileJava', '-i') result.standardError.contains 'cannot be run on newer JVMs' + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'java 17 preview on single project works'() { + def '#gradleVersionNumber: java 17 preview on single project works'() { when: buildFile << ''' javaVersion { @@ -175,9 +194,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: runTasksSuccessfully('compileJava', '-i') assertBytecodeVersion(compiledClass, JAVA_17_BYTECODE, ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'java 17 preview javadoc works'() { + def '#gradleVersionNumber: java 17 preview javadoc works'() { when: buildFile << ''' javaVersions { @@ -189,9 +211,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: runTasksSuccessfully('javadoc', '-i') + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'library target is used when no artifacts are published but project is overridden as a library'() { + def '#gradleVersionNumber: library target is used when no artifacts are published but project is overridden as a library'() { when: buildFile << ''' javaVersions { @@ -208,9 +233,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: runTasksSuccessfully('compileJava') assertBytecodeVersion(compiledClass, JAVA_11_BYTECODE, NOT_ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'library target is used when nebula maven publishing plugin is applied'() { + def '#gradleVersionNumber: library target is used when nebula maven publishing plugin is applied'() { when: buildFile << ''' apply plugin: 'nebula.maven-publish' @@ -225,9 +253,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: runTasksSuccessfully('compileJava') assertBytecodeVersion(compiledClass, JAVA_11_BYTECODE, NOT_ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'library target is used when the palantir shadowjar plugin is applied'() { + def '#gradleVersionNumber: library target is used when the palantir shadowjar plugin is applied'() { when: buildFile << ''' apply plugin: 'com.palantir.consistent-versions' // required by shadow-jar @@ -244,9 +275,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { runTasksSuccessfully('--write-locks') runTasksSuccessfully('compileJava') assertBytecodeVersion(compiledClass, JAVA_11_BYTECODE, NOT_ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'java 11 compilation succeeds targeting java 11'() { + def '#gradleVersionNumber: java 11 compilation succeeds targeting java 11'() { when: buildFile << ''' javaVersions { @@ -259,9 +293,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: runTasksSuccessfully('compileJava') assertBytecodeVersion(compiledClass, JAVA_11_BYTECODE, NOT_ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'java 11 execution succeeds on java 11'() { + def '#gradleVersionNumber: java 11 execution succeeds on java 11'() { when: buildFile << ''' javaVersions { @@ -275,9 +312,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { ExecutionResult result = runTasksSuccessfully('run') result.standardOutput.contains 'jdk11 features on runtime 11' assertBytecodeVersion(compiledClass, JAVA_11_BYTECODE, NOT_ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'java 11 execution succeeds on java 17'() { + def '#gradleVersionNumber: java 11 execution succeeds on java 17'() { when: buildFile << ''' javaVersions { @@ -292,9 +332,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { ExecutionResult result = runTasksSuccessfully('run') result.standardOutput.contains 'jdk11 features on runtime 17' assertBytecodeVersion(compiledClass, JAVA_11_BYTECODE, NOT_ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'java 8 execution succeeds on java 8'() { + def '#gradleVersionNumber: java 8 execution succeeds on java 8'() { Assumptions.assumeThat(System.getProperty("os.arch")).describedAs( "On an M1 mac, this test will fail to download https://api.adoptopenjdk.net/v3/binary/latest/8/ga/mac/aarch64/jdk/hotspot/normal/adoptopenjdk") .isNotEqualTo("aarch64"); @@ -309,9 +352,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: ExecutionResult result = runTasksSuccessfully('run') result.standardOutput.contains 'jdk8 features on runtime 1.8' + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'java 8 execution succeeds on java 11'() { + def '#gradleVersionNumber: java 8 execution succeeds on java 11'() { Assumptions.assumeThat(System.getProperty("os.arch")).describedAs( "On an M1 mac, this test will fail to download https://api.adoptopenjdk.net/v3/binary/latest/8/ga/mac/aarch64/jdk/hotspot/normal/adoptopenjdk") .isNotEqualTo("aarch64"); @@ -330,9 +376,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { ExecutionResult result = runTasksSuccessfully('run') result.standardOutput.contains 'jdk8 features on runtime 11' assertBytecodeVersion(compiledClass, JAVA_8_BYTECODE, NOT_ENABLE_PREVIEW_BYTECODE) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'JavaPluginConvention.getTargetCompatibility() produces the runtime java version'() { + def '#gradleVersionNumber: JavaPluginConvention.getTargetCompatibility() produces the runtime java version'() { when: buildFile << ''' javaVersions { @@ -351,9 +400,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: ExecutionResult result = runTasksSuccessfully('printTargetCompatibility') result.standardOutput.contains '[[[17]]]' + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'verification should fail when target exceeds the runtime version'() { + def '#gradleVersionNumber: verification should fail when target exceeds the runtime version'() { when: buildFile << ''' javaVersions { @@ -365,9 +417,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: ExecutionResult result = runTasksWithFailure('checkJavaVersions') result.standardError.contains 'The requested compilation target' + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'verification should fail when --enable-preview is on, but versions differ'() { + def '#gradleVersionNumber: verification should fail when --enable-preview is on, but versions differ'() { when: buildFile << ''' javaVersions { @@ -379,9 +434,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: ExecutionResult result = runTasksWithFailure('checkJavaVersions') result.standardError.contains 'Runtime Java version (15_PREVIEW) must be exactly the same as the compilation target (11_PREVIEW)' + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'verification should fail when runtime does not use --enable-preview but compilation does'() { + def '#gradleVersionNumber: verification should fail when runtime does not use --enable-preview but compilation does'() { when: buildFile << ''' javaVersions { @@ -393,9 +451,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: ExecutionResult result = runTasksWithFailure('checkJavaVersions') result.standardError.contains 'Runtime Java version (17) must be exactly the same as the compilation target (17_PREVIEW)' + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'verification should succeed when target and runtime versions match'() { + def '#gradleVersionNumber: verification should succeed when target and runtime versions match'() { when: buildFile << ''' javaVersions { @@ -406,9 +467,12 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: runTasksSuccessfully('checkJavaVersions') + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } - def 'can configure a jdk path to be used'() { + def '#gradleVersionNumber: can configure a jdk path to be used'() { Assumptions.assumeThat(System.getenv("CI")).describedAs( "This test deletes a directory locally, you don't want to run it on your mac").isNotNull(); @@ -453,6 +517,9 @@ class BaselineJavaVersionIntegrationTest extends IntegrationSpec { then: stdout.contains(newJavaHome.toString()) + + where: + gradleVersionNumber << GRADLE_TEST_VERSIONS } private static final int BYTECODE_IDENTIFIER = (int) 0xCAFEBABE diff --git a/gradle-junit-reports/build.gradle b/gradle-junit-reports/build.gradle index 0af1e9190..9d04c6b3d 100644 --- a/gradle-junit-reports/build.gradle +++ b/gradle-junit-reports/build.gradle @@ -11,6 +11,8 @@ dependencies { compileOnly 'org.immutables:value::annotations' compileOnly 'org.inferred:freebuilder' + implementation 'com.palantir.safe-logging:safe-logging' + testImplementation 'com.google.guava:guava' testImplementation 'com.netflix.nebula:nebula-test' testImplementation 'org.assertj:assertj-core' diff --git a/gradle-junit-reports/src/main/java/com/palantir/gradle/junit/BuildFailureListener.java b/gradle-junit-reports/src/main/java/com/palantir/gradle/junit/BuildFailureListener.java index 166370a46..83c69d2ec 100644 --- a/gradle-junit-reports/src/main/java/com/palantir/gradle/junit/BuildFailureListener.java +++ b/gradle-junit-reports/src/main/java/com/palantir/gradle/junit/BuildFailureListener.java @@ -65,6 +65,7 @@ public List getTestCases() { return testCases; } + @SuppressWarnings("SafeLoggingPropagation") private static String getMessage(Throwable throwable) { if (throwable.getMessage() == null) { return throwable.getClass().getSimpleName(); diff --git a/gradle-junit-reports/src/main/java/com/palantir/gradle/junit/CheckstyleReportHandler.java b/gradle-junit-reports/src/main/java/com/palantir/gradle/junit/CheckstyleReportHandler.java index 7978aa7c9..00ff473a6 100644 --- a/gradle-junit-reports/src/main/java/com/palantir/gradle/junit/CheckstyleReportHandler.java +++ b/gradle-junit-reports/src/main/java/com/palantir/gradle/junit/CheckstyleReportHandler.java @@ -18,6 +18,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.gradle.api.plugins.quality.Checkstyle; import org.xml.sax.Attributes; @@ -43,7 +44,7 @@ public void startElement(String uri, String localName, String qName, Attributes case "error": failures.add(Failure.builder() .source(attributes.getValue("source")) - .severity(attributes.getValue("severity").toUpperCase()) + .severity(attributes.getValue("severity").toUpperCase(Locale.ROOT)) .file(file) .line(Integer.parseInt(attributes.getValue("line"))) .message(attributes.getValue("message")) diff --git a/versions.lock b/versions.lock index d4b00fca6..7ad3001e0 100644 --- a/versions.lock +++ b/versions.lock @@ -6,57 +6,59 @@ com.diffplug.durian:durian-io:1.2.0 (1 constraints: 1313c62d) com.diffplug.spotless:spotless-lib:2.25.3 (2 constraints: f424dde6) com.diffplug.spotless:spotless-lib-extra:2.25.3 (1 constraints: 4c132341) com.diffplug.spotless:spotless-plugin-gradle:6.6.0 (1 constraints: 0e051b36) -com.github.ben-manes.caffeine:caffeine:3.0.5 (1 constraints: e312a21b) +com.github.ben-manes.caffeine:caffeine:3.1.6 (2 constraints: ee17f057) com.github.kevinstern:software-and-algorithms:1.0 (1 constraints: 7e12fcf5) -com.google.auto:auto-common:1.2.1 (3 constraints: 14321a1e) -com.google.auto.service:auto-service:1.0.1 (1 constraints: 0405f135) -com.google.auto.service:auto-service-annotations:1.0.1 (2 constraints: 8920021c) +com.google.auto:auto-common:1.2.1 (3 constraints: 73322261) +com.google.auto.service:auto-service:1.1.1 (1 constraints: 0505f435) +com.google.auto.service:auto-service-annotations:1.1.1 (2 constraints: 8a20341c) com.google.auto.value:auto-value-annotations:1.9 (3 constraints: 802d5ac8) com.google.code.findbugs:jsr305:3.0.2 (6 constraints: 66626968) -com.google.errorprone:error_prone_annotation:2.18.0 (3 constraints: cd3893ef) -com.google.errorprone:error_prone_annotations:2.18.0 (12 constraints: 92bdac9f) -com.google.errorprone:error_prone_check_api:2.18.0 (2 constraints: b825d0ff) -com.google.errorprone:error_prone_core:2.18.0 (1 constraints: 3d05473b) -com.google.errorprone:error_prone_type_annotations:2.18.0 (1 constraints: 28115ac9) -com.google.guava:failureaccess:1.0.1 (1 constraints: 140ae1b4) -com.google.guava:guava:31.1-jre (14 constraints: d6e9749b) +com.google.errorprone:error_prone_annotation:2.21.1 (3 constraints: be3843ec) +com.google.errorprone:error_prone_annotations:2.21.1 (12 constraints: aebde2ad) +com.google.errorprone:error_prone_check_api:2.21.1 (2 constraints: ae2598fe) +com.google.errorprone:error_prone_core:2.21.1 (1 constraints: 3805373b) +com.google.errorprone:error_prone_type_annotations:2.21.1 (1 constraints: 23114ac9) +com.google.guava:failureaccess:1.0.1 (2 constraints: f1150513) +com.google.guava:guava:32.1.2-jre (15 constraints: 18f6510c) com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava (1 constraints: bd17c918) -com.google.inject:guice:4.2.2 (1 constraints: e40b61f3) -com.google.j2objc:j2objc-annotations:1.3 (1 constraints: b809eda0) -com.google.protobuf:protobuf-java:3.19.2 (1 constraints: 2c1165c9) +com.google.inject:guice:5.1.0 (2 constraints: c21eb8b6) +com.google.j2objc:j2objc-annotations:2.8 (1 constraints: be09f5a0) +com.google.protobuf:protobuf-java:3.19.6 (1 constraints: 301169c9) com.googlecode.concurrent-trees:concurrent-trees:2.6.1 (1 constraints: 761166da) com.googlecode.java-diff-utils:diffutils:1.3.0 (1 constraints: 0605f935) com.googlecode.javaewah:JavaEWAH:1.1.12 (1 constraints: 750eee5e) com.palantir.gradle.utils:lazily-configured-mapping:0.1.0 (1 constraints: 0305ee35) com.palantir.javaformat:gradle-palantir-java-format:1.1.0 (1 constraints: 0405f335) com.palantir.javaformat:palantir-java-format-spi:1.1.0 (1 constraints: 711560be) -com.uber.nullaway:nullaway:0.10.10 (1 constraints: 64058840) -commons-io:commons-io:2.11.0 (1 constraints: 57119bcb) +com.palantir.safe-logging:safe-logging:3.6.0 (8 constraints: 7977f2e6) +com.uber.nullaway:nullaway:0.10.12 (1 constraints: 66058a40) +commons-io:commons-io:2.13.0 (2 constraints: f519f007) commons-lang:commons-lang:2.6 (1 constraints: ac04232c) +io.github.eisop:dataflow-errorprone:3.34.0-eisop1 (3 constraints: 444080e0) io.github.java-diff-utils:java-diff-utils:4.0 (1 constraints: 811205f6) javax.annotation:javax.annotation-api:1.3.2 (2 constraints: cd204f35) javax.inject:javax.inject:1 (9 constraints: e5867a2e) -net.ltgt.gradle:gradle-errorprone-plugin:3.0.1 (1 constraints: 0605fb35) -org.apache.commons:commons-lang3:3.12.0 (4 constraints: da318b7f) -org.apache.maven:maven-artifact:3.8.5 (4 constraints: 1e3ed043) -org.apache.maven:maven-builder-support:3.8.5 (3 constraints: 1a2c0e66) -org.apache.maven:maven-core:3.8.5 (2 constraints: a51938e0) -org.apache.maven:maven-model:3.8.5 (4 constraints: d73a0919) -org.apache.maven:maven-model-builder:3.8.5 (2 constraints: 381d020b) -org.apache.maven:maven-plugin-api:3.8.5 (1 constraints: ec0b71f3) -org.apache.maven:maven-repository-metadata:3.8.5 (2 constraints: 381d020b) -org.apache.maven:maven-resolver-provider:3.8.5 (1 constraints: ec0b71f3) -org.apache.maven:maven-settings:3.8.5 (2 constraints: b31c1fea) -org.apache.maven:maven-settings-builder:3.8.5 (1 constraints: ec0b71f3) -org.apache.maven.resolver:maven-resolver-api:1.6.3 (5 constraints: 595676e7) -org.apache.maven.resolver:maven-resolver-impl:1.6.3 (2 constraints: 2c1db809) -org.apache.maven.resolver:maven-resolver-spi:1.6.3 (3 constraints: 5930fd65) -org.apache.maven.resolver:maven-resolver-util:1.6.3 (3 constraints: 5930fd65) -org.apache.maven.shared:maven-dependency-analyzer:1.13.0 (1 constraints: 3705323b) +net.ltgt.gradle:gradle-errorprone-plugin:3.1.0 (1 constraints: 0605fd35) +org.apache.commons:commons-lang3:3.13.0 (4 constraints: 053205b2) +org.apache.maven:maven-artifact:3.9.1 (4 constraints: 153e4040) +org.apache.maven:maven-builder-support:3.9.1 (3 constraints: 112c6464) +org.apache.maven:maven-core:3.9.1 (2 constraints: a21989df) +org.apache.maven:maven-model:3.9.1 (5 constraints: 5e4f88ec) +org.apache.maven:maven-model-builder:3.9.1 (2 constraints: 321d6d0a) +org.apache.maven:maven-plugin-api:3.9.1 (1 constraints: e90b70f3) +org.apache.maven:maven-repository-metadata:3.9.1 (2 constraints: 321d6d0a) +org.apache.maven:maven-resolver-provider:3.9.1 (1 constraints: e90b70f3) +org.apache.maven:maven-settings:3.9.1 (2 constraints: ad1c8de9) +org.apache.maven:maven-settings-builder:3.9.1 (1 constraints: e90b70f3) +org.apache.maven.resolver:maven-resolver-api:1.9.7 (5 constraints: 7c5643f6) +org.apache.maven.resolver:maven-resolver-impl:1.9.7 (2 constraints: 3a1d290b) +org.apache.maven.resolver:maven-resolver-named-locks:1.9.7 (1 constraints: 3513e634) +org.apache.maven.resolver:maven-resolver-spi:1.9.7 (3 constraints: 6e306f6a) +org.apache.maven.resolver:maven-resolver-util:1.9.7 (3 constraints: 6e306f6a) +org.apache.maven.shared:maven-dependency-analyzer:1.13.2 (1 constraints: 3905343b) org.apache.maven.shared:maven-shared-utils:3.3.4 (1 constraints: e60b61f3) -org.checkerframework:checker-qual:3.32.0 (3 constraints: 08256088) -org.checkerframework:dataflow-errorprone:3.32.0 (4 constraints: 093ebc5f) -org.checkerframework:dataflow-nullaway:3.32.0 (2 constraints: 691171f1) +org.checkerframework:checker-qual:3.33.0 (3 constraints: 0725c187) +org.checkerframework:dataflow-nullaway:3.33.0 (2 constraints: 6a1198f1) org.codehaus.groovy:groovy:3.0.10 (3 constraints: e32879d6) org.codehaus.groovy:groovy-xml:3.0.10 (1 constraints: 791161da) org.codehaus.plexus:plexus-cipher:2.0 (1 constraints: 641174c7) @@ -64,71 +66,70 @@ org.codehaus.plexus:plexus-classworlds:2.6.0 (3 constraints: 572b5104) org.codehaus.plexus:plexus-component-annotations:2.1.0 (2 constraints: 241d860a) org.codehaus.plexus:plexus-interpolation:1.26 (3 constraints: 7b2bf0f5) org.codehaus.plexus:plexus-sec-dispatcher:2.0 (1 constraints: 5c100b99) -org.codehaus.plexus:plexus-utils:3.4.2 (12 constraints: b6bc07f5) +org.codehaus.plexus:plexus-utils:3.5.1 (11 constraints: 3fa89966) org.eclipse.jgit:org.eclipse.jgit:5.13.0.202109080827-r (3 constraints: a43f6e15) org.eclipse.sisu:org.eclipse.sisu.inject:0.3.5 (3 constraints: 852caa93) org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.5 (2 constraints: 141abc34) org.immutables:value:2.9.3 (1 constraints: 10051336) org.inferred:freebuilder:1.14.6 (1 constraints: 3e053b3b) -org.ow2.asm:asm:9.4 (3 constraints: eb2289eb) +org.ow2.asm:asm:9.5 (3 constraints: ee22e1eb) org.pcollections:pcollections:3.1.4 (1 constraints: f51029b8) -org.slf4j:slf4j-api:1.7.36 (8 constraints: a3719ea2) +org.slf4j:slf4j-api:1.7.36 (10 constraints: 769c0f4c) [Test dependencies] cglib:cglib-nodep:3.2.2 (1 constraints: 490ded24) -com.fasterxml.jackson.core:jackson-annotations:2.14.2 (4 constraints: 5c35b0b9) -com.fasterxml.jackson.core:jackson-core:2.14.2 (2 constraints: 0a2a8e13) -com.fasterxml.jackson.core:jackson-databind:2.14.2 (5 constraints: ac4ad65f) -com.fasterxml.jackson.module:jackson-module-afterburner:2.14.2 (1 constraints: 3b053d3b) +com.fasterxml.jackson.core:jackson-annotations:2.15.2 (4 constraints: 5f3598ba) +com.fasterxml.jackson.core:jackson-core:2.15.2 (2 constraints: 0c2ad513) +com.fasterxml.jackson.core:jackson-databind:2.15.2 (5 constraints: b04a5461) +com.fasterxml.jackson.module:jackson-module-afterburner:2.15.2 (1 constraints: 3c05403b) com.github.stefanbirkner:system-rules:1.19.0 (1 constraints: 3d05443b) com.google.auto.value:auto-value:1.7.4 (1 constraints: 1f1221fb) -com.google.errorprone:error_prone_test_helpers:2.18.0 (1 constraints: 3d05473b) +com.google.errorprone:error_prone_test_helpers:2.21.1 (1 constraints: 3805373b) com.google.googlejavaformat:google-java-format:1.13.0 (1 constraints: 8b149d75) com.google.jimfs:jimfs:1.2 (1 constraints: fb138b38) com.google.testing.compile:compile-testing:0.19 (1 constraints: 3214b94c) com.google.truth:truth:1.1.3 (2 constraints: 1126a31d) com.netflix.nebula:nebula-test:10.2.0 (1 constraints: 35052d3b) com.palantir.conjure.java:conjure-lib:6.77.0 (1 constraints: 4605743b) -com.palantir.conjure.java.api:errors:2.34.0 (2 constraints: 6d21c6b0) -com.palantir.conjure.java.api:test-utils:2.34.0 (1 constraints: 3b05433b) -com.palantir.conjure.java.runtime:conjure-java-annotations:7.40.0 (1 constraints: 3d05593b) +com.palantir.conjure.java.api:errors:2.35.0 (2 constraints: 6e21c9b0) +com.palantir.conjure.java.api:test-utils:2.35.0 (1 constraints: 3c05463b) +com.palantir.conjure.java.runtime:conjure-java-annotations:7.60.0 (1 constraints: 3f05613b) com.palantir.ri:resource-identifier:2.5.0 (1 constraints: ef0f7999) -com.palantir.safe-logging:logger:3.2.0 (2 constraints: da127d36) -com.palantir.safe-logging:logger-slf4j:3.2.0 (1 constraints: 000e5942) -com.palantir.safe-logging:logger-spi:3.2.0 (2 constraints: 0f1e997a) -com.palantir.safe-logging:preconditions:3.2.0 (6 constraints: 1457bcb5) -com.palantir.safe-logging:preconditions-assertj:3.2.0 (1 constraints: 07050036) -com.palantir.safe-logging:safe-logging:3.2.0 (8 constraints: 5f77a6d7) -com.palantir.tokens:auth-tokens:3.17.0 (2 constraints: 5f1502db) -com.palantir.tritium:tritium-registry:0.62.0 (1 constraints: 3a053d3b) -io.dropwizard.metrics:metrics-core:4.1.1 (1 constraints: 901088a5) +com.palantir.safe-logging:logger:3.6.0 (2 constraints: e0122f37) +com.palantir.safe-logging:logger-slf4j:3.6.0 (1 constraints: 040e6542) +com.palantir.safe-logging:logger-spi:3.6.0 (2 constraints: 171e6d7b) +com.palantir.safe-logging:preconditions:3.6.0 (6 constraints: 245760bc) +com.palantir.safe-logging:preconditions-assertj:3.6.0 (1 constraints: 0b050c36) +com.palantir.tokens:auth-tokens:3.18.0 (2 constraints: 601534db) +com.palantir.tritium:tritium-registry:0.73.0 (1 constraints: 3c05443b) +io.dropwizard.metrics:metrics-core:4.2.19 (1 constraints: ca1055b6) io.r2dbc:r2dbc-spi:1.0.0.RELEASE (1 constraints: ea08e898) javax.ws.rs:javax.ws.rs-api:2.1.1 (1 constraints: ec0f6e99) junit:junit:4.13.2 (8 constraints: 1d727319) junit:junit-dep:4.11 (1 constraints: ba1063b3) -net.bytebuddy:byte-buddy:1.14.1 (2 constraints: bd16a34f) -net.bytebuddy:byte-buddy-agent:1.14.1 (1 constraints: 410b3fde) +net.bytebuddy:byte-buddy:1.14.5 (2 constraints: c116a74f) +net.bytebuddy:byte-buddy-agent:1.14.5 (1 constraints: 450b43de) net.lingala.zip4j:zip4j:1.3.2 (1 constraints: 0805fb35) one.util:streamex:0.8.1 (1 constraints: 0b050436) org.apiguardian:apiguardian-api:1.1.2 (7 constraints: 9d791b5f) -org.assertj:assertj-core:3.24.2 (3 constraints: e62ace9f) +org.assertj:assertj-core:3.24.2 (3 constraints: ea2a48a0) org.hamcrest:hamcrest:2.2 (2 constraints: 43187376) org.hamcrest:hamcrest-core:2.2 (4 constraints: 2b2b359e) org.hamcrest:hamcrest-library:2.2 (1 constraints: fc138e38) -org.jetbrains:annotations:23.0.0 (2 constraints: f8204e6d) -org.jooq:jooq:3.18.0 (1 constraints: 3e054d3b) -org.junit.jupiter:junit-jupiter:5.9.2 (1 constraints: 12052136) -org.junit.jupiter:junit-jupiter-api:5.9.2 (5 constraints: 6353b5bf) -org.junit.jupiter:junit-jupiter-engine:5.9.2 (1 constraints: 0d0ee23b) -org.junit.jupiter:junit-jupiter-migrationsupport:5.9.2 (1 constraints: 12052136) -org.junit.jupiter:junit-jupiter-params:5.9.2 (1 constraints: 0d0ee23b) -org.junit.platform:junit-platform-commons:1.9.2 (2 constraints: df20424b) -org.junit.platform:junit-platform-engine:1.9.2 (3 constraints: 622ef47f) -org.junit.vintage:junit-vintage-engine:5.9.2 (1 constraints: 12052136) -org.mockito:mockito-core:5.2.0 (3 constraints: 12254117) -org.mockito:mockito-junit-jupiter:5.2.0 (1 constraints: 09050a36) +org.jetbrains:annotations:24.0.1 (2 constraints: fc20bc6d) +org.jooq:jooq:3.18.6 (1 constraints: 4405533b) +org.junit.jupiter:junit-jupiter:5.10.0 (1 constraints: 3805413b) +org.junit.jupiter:junit-jupiter-api:5.10.0 (5 constraints: fc53e3ab) +org.junit.jupiter:junit-jupiter-engine:5.10.0 (1 constraints: 330efd49) +org.junit.jupiter:junit-jupiter-migrationsupport:5.10.0 (1 constraints: 3805413b) +org.junit.jupiter:junit-jupiter-params:5.10.0 (1 constraints: 330efd49) +org.junit.platform:junit-platform-commons:1.10.0 (2 constraints: 2b211983) +org.junit.platform:junit-platform-engine:1.10.0 (3 constraints: ae2e51c4) +org.junit.vintage:junit-vintage-engine:5.10.0 (1 constraints: 3805413b) +org.mockito:mockito-core:5.4.0 (3 constraints: e9244afa) +org.mockito:mockito-junit-jupiter:5.4.0 (1 constraints: 0b051036) org.objenesis:objenesis:3.3 (2 constraints: 9b17f557) -org.opentest4j:opentest4j:1.2.0 (2 constraints: cd205b49) +org.opentest4j:opentest4j:1.3.0 (2 constraints: cf209249) org.reactivestreams:reactive-streams:1.0.3 (1 constraints: ef07e77b) org.spockframework:spock-core:2.1-M2-groovy-3.0 (2 constraints: e622905a) org.spockframework:spock-junit4:2.1-M2-groovy-3.0 (1 constraints: 241154df) diff --git a/versions.props b/versions.props index 52192f6e3..975ac098f 100644 --- a/versions.props +++ b/versions.props @@ -1,43 +1,43 @@ -com.fasterxml.jackson.core:jackson-databind = 2.14.2 -com.google.auto.service:auto-service = 1.0.1 -com.google.guava:guava = 31.1-jre -com.palantir.safe-logging:* = 3.2.0 +com.fasterxml.jackson.core:jackson-databind = 2.15.2 +com.github.ben-manes.caffeine:caffeine = 3.1.6 +com.google.auto.service:auto-service = 1.1.1 +com.google.guava:guava = 32.1.2-jre +com.palantir.safe-logging:* = 3.6.0 commons-lang:commons-lang = 2.6 -org.apache.maven.shared:maven-dependency-analyzer = 1.13.0 -org.apache.maven:maven-core = 3.8.5 +org.apache.maven.shared:maven-dependency-analyzer = 1.13.2 +org.apache.maven:maven-core = 3.9.1 org.inferred:freebuilder = 1.14.6 -org.jooq:jooq = 3.18.0 +org.jooq:jooq = 3.18.6 org.slf4j:* = 1.7.36 org.immutables:* = 2.9.3 -org.ow2.asm:asm = 9.4 -com.google.errorprone:error_prone_* = 2.18.0 +org.ow2.asm:asm = 9.5 +com.google.errorprone:error_prone_* = 2.21.1 com.googlecode.java-diff-utils:diffutils = 1.3.0 -com.puppycrawl.tools:checkstyle = 10.8.1 +com.puppycrawl.tools:checkstyle = 10.12.2 com.palantir.gradle.utils:* = 0.1.0 -com.uber.nullaway:nullaway = 0.10.10 +com.uber.nullaway:nullaway = 0.10.12 # test deps -com.fasterxml.jackson.*:* = 2.14.2 +com.fasterxml.jackson.*:* = 2.15.2 com.github.stefanbirkner:system-rules = 1.19.0 com.netflix.nebula:nebula-test = 10.2.0 com.palantir.conjure.java:* = 6.77.0 -com.palantir.conjure.java.api:* = 2.34.0 -com.palantir.conjure.java.runtime:* = 7.40.0 -com.palantir.tokens:auth-tokens = 3.17.0 -com.palantir.tritium:* = 0.62.0 -commons-io:* = 2.11.0 +com.palantir.conjure.java.api:* = 2.35.0 +com.palantir.conjure.java.runtime:* = 7.60.0 +com.palantir.tokens:auth-tokens = 3.18.0 +com.palantir.tritium:* = 0.73.0 +commons-io:* = 2.13.0 junit:junit = 4.13.2 net.lingala.zip4j:zip4j = 1.3.2 -net.ltgt.gradle:gradle-errorprone-plugin = 3.0.1 +net.ltgt.gradle:gradle-errorprone-plugin = 3.1.0 one.util:streamex = 0.8.1 -org.apache.commons:commons-lang3 = 3.12.0 +org.apache.commons:commons-lang3 = 3.13.0 org.assertj:assertj-core = 3.24.2 -org.checkerframework:* = 3.32.0 org.hamcrest:hamcrest-core = 2.2 -org.junit.jupiter:* = 5.9.2 -org.junit.vintage:* = 5.9.2 -org.junit.platform:* = 1.9.2 -org.mockito:* = 5.2.0 +org.junit.jupiter:* = 5.10.0 +org.junit.vintage:* = 5.10.0 +org.junit.platform:* = 1.10.0 +org.mockito:* = 5.4.0 # dependency-upgrader:OFF # Don't upgrade, we will remove this in a future release. @@ -47,4 +47,7 @@ com.palantir.javaformat:gradle-palantir-java-format = 1.1.0 com.diffplug.spotless:spotless-plugin-gradle = 6.6.0 # Groovy versions must be compatible with gradle org.codehaus.groovy:* = 3.0.10 +# checkerframework dependencies should be limited to errorpropne and nullaway +# requirements, upgrading prior to those often causes compilation failures. +org.checkerframework:* = 3.33.0 # dependency-upgrader:ON