diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ImmutablesInterfaceDefaultValue.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ImmutablesInterfaceDefaultValue.java new file mode 100644 index 000000000..0bf1175d8 --- /dev/null +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/ImmutablesInterfaceDefaultValue.java @@ -0,0 +1,73 @@ +/* + * (c) Copyright 2020 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.bugpatterns.BugChecker.MethodTreeMatcher; +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.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import javax.lang.model.element.Modifier; + +/** + * Checks that interface default methods in an immutables.org @Value.Immutable are + * annotated with + * @Value.Default , + * @Value.Derived , or + * @Value.Lazy . + *
+ * This check only applies to interfaces annotated with @ImmutablesConfigStyle. + */ +@AutoService(BugChecker.class) +@BugPattern( + linkType = BugPattern.LinkType.CUSTOM, + link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", + severity = BugPattern.SeverityLevel.ERROR, + summary = "@Value.Immutable interface default methods should be annotated with" + + " @Value.Default, @Value.Derived, @Value.Lazy, or @JsonIgnore") +public final class ImmutablesInterfaceDefaultValue extends BugChecker implements MethodTreeMatcher { + + private static final Matcher MISSING_ANNOTATION_MATCHER = Matchers.allOf( + Matchers.enclosingClass(Matchers.allOf( + Matchers.hasAnnotation("org.immutables.value.Value.Immutable"), + Matchers.hasAnnotation("com.palantir.immutables.style.ImmutablesConfigStyle"))), + Matchers.hasModifier(Modifier.DEFAULT), + Matchers.not(Matchers.anyOf( + Matchers.hasAnnotation("org.immutables.value.Value.Default"), + Matchers.hasAnnotation("org.immutables.value.Value.Derived"), + Matchers.hasAnnotation("org.immutables.value.Value.Lazy"), + Matchers.hasAnnotation("com.fasterxml.jackson.annotation.JsonIgnore")))); + + @Override + public Description matchMethod(MethodTree tree, VisitorState state) { + if (MISSING_ANNOTATION_MATCHER.matches(tree, state)) { + SuggestedFix.Builder builder = SuggestedFix.builder(); + String annotation = SuggestedFixes.qualifyType(state, builder, "org.immutables.value.Value.Default"); + return buildDescription(tree) + .addFix(builder.prefixWith(tree, "@" + annotation + " ").build()) + .build(); + } + return Description.NO_MATCH; + } +} diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/ImmutablesInterfaceDefaultValueTest.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/ImmutablesInterfaceDefaultValueTest.java new file mode 100644 index 000000000..7ee57dafe --- /dev/null +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/ImmutablesInterfaceDefaultValueTest.java @@ -0,0 +1,208 @@ +/* + * (c) Copyright 2020 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.CompilationTestHelper; +import org.junit.jupiter.api.Test; + +public class ImmutablesInterfaceDefaultValueTest { + + @Test + public void testFailsWhenDefaultMethodNotAnnotated() { + helper().addSourceLines( + "Test.java", + "import org.immutables.value.*;", + "import com.palantir.immutables.style.ImmutablesConfigStyle;", + "public class Test {", + " @Value.Immutable", + " @ImmutablesConfigStyle", + " public interface InterfaceWithValueDefault {", + " String value();", + "", + " // BUG: Diagnostic contains: @Value.Immutable interface" + + " default methods should be annotated with" + + " @Value.Default, @Value.Derived, @Value.Lazy, or @JsonIgnore", + " default String defaultValue() {", + " return \"default\";", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void testPassesWhenInterfaceIsNotConfig() { + helper().addSourceLines( + "Test.java", + "import org.immutables.value.*;", + "public class Test {", + " @Value.Immutable", + " public interface InterfaceWithValueDefault {", + " String value();", + "", + " default String defaultValue() {", + " return \"default\";", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void testPassesWhenDefaultMethodAnnotatedValueDefault() { + helper().addSourceLines( + "Test.java", + "import org.immutables.value.*;", + "public class Test {", + " @Value.Immutable", + " public interface InterfaceWithValueDefault {", + " String value();", + "", + " @Value.Default", + " default String defaultValue() {", + " return \"default\";", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void testPassesWhenDefaultMethodAnnotatedValueDerived() { + helper().addSourceLines( + "Test.java", + "import org.immutables.value.*;", + "public class Test {", + " @Value.Immutable", + " public interface InterfaceWithValueDefault {", + " String value();", + "", + " @Value.Derived", + " default String derivedValue() {", + " return value();", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void testPassesWhenDefaultMethodAnnotatedValueLazy() { + helper().addSourceLines( + "Test.java", + "import org.immutables.value.*;", + "public class Test {", + " @Value.Immutable", + " public interface InterfaceWithValueDefault {", + " String value();", + "", + " @Value.Lazy", + " default String lazyValue() {", + " return value();", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void testPassesWhenDefaultMethodAnnotatedJsonIgnore() { + helper().addSourceLines( + "Test.java", + "import com.fasterxml.jackson.annotation.JsonIgnore;", + "import org.immutables.value.*;", + "public class Test {", + " @Value.Immutable", + " public interface InterfaceWithValueDefault {", + " String value();", + "", + " @JsonIgnore", + " default String lazyValue() {", + " return value();", + " }", + " }", + "}") + .doTest(); + } + + @Test + public void refactorsMissingDefaultValueAnnotation() { + refactoring() + .addInputLines( + "Test.java", + "import org.immutables.value.*;", + "import com.palantir.immutables.style.ImmutablesConfigStyle;", + "public class Test {", + " @Value.Immutable", + " @ImmutablesConfigStyle", + " public interface InterfaceWithValueDefault {", + " String value();", + "", + " // BUG: Diagnostic contains: " + + "@Value.Immutable interface default methods should be annotated with " + + "@Value.Default, @Value.Default, @Value.Derived, @Value.Lazy, or @JsonIgnore", + " default String defaultValue() {", + " return \"default\";", + " }", + "", + " @Value.Derived", + " default String derivedValue() {", + " return value();", + " }", + "", + " @Value.Lazy", + " default String lazyValue() {", + " return value();", + " }", + " }", + "}") + .addOutputLines( + "Test.java", + "import org.immutables.value.*;", + "import com.palantir.immutables.style.ImmutablesConfigStyle;", + "public class Test {", + " @Value.Immutable", + " @ImmutablesConfigStyle", + " public interface InterfaceWithValueDefault {", + " String value();", + "", + " @Value.Default", + " default String defaultValue() {", + " return \"default\";", + " }", + "", + " @Value.Derived", + " default String derivedValue() {", + " return value();", + " }", + "", + " @Value.Lazy", + " default String lazyValue() {", + " return value();", + " }", + " }", + "}") + .doTest(); + } + + private CompilationTestHelper helper() { + return CompilationTestHelper.newInstance(ImmutablesInterfaceDefaultValue.class, getClass()); + } + + private RefactoringValidator refactoring() { + return RefactoringValidator.of(ImmutablesInterfaceDefaultValue.class, getClass()); + } +} diff --git a/baseline-error-prone/src/test/java/com/palantir/immutables/style/ImmutablesConfigStyle.java b/baseline-error-prone/src/test/java/com/palantir/immutables/style/ImmutablesConfigStyle.java new file mode 100644 index 000000000..7444000d2 --- /dev/null +++ b/baseline-error-prone/src/test/java/com/palantir/immutables/style/ImmutablesConfigStyle.java @@ -0,0 +1,22 @@ +/* + * (c) Copyright 2024 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.immutables.style; + +/** + * Placeholder definition for use in tests. + */ +public @interface ImmutablesConfigStyle {} diff --git a/changelog/@unreleased/pr-1761.v2.yml b/changelog/@unreleased/pr-1761.v2.yml new file mode 100644 index 000000000..add184da3 --- /dev/null +++ b/changelog/@unreleased/pr-1761.v2.yml @@ -0,0 +1,8 @@ +type: improvement +improvement: + description: |- + Add ImmutablesInterfaceDefaultValue + + @Value.Immutable interface default methods should be annotated @Value.Default + links: + - https://github.com/palantir/gradle-baseline/pull/1761