Skip to content

Commit 46c07c2

Browse files
committed
Check that @API and @Deprecated annotations are consistent
* both must be declared * `API.status()` must be `DEPRECATED` * `since()` attributes must match
1 parent 3df36b8 commit 46c07c2

File tree

2 files changed

+51
-2
lines changed
  • junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition
  • platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests

2 files changed

+51
-2
lines changed

junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public enum JRE {
133133
* @deprecated in favor of {@link #currentJre()}
134134
*/
135135
@API(status = DEPRECATED, since = "5.12")
136-
@Deprecated
136+
@Deprecated(since = "5.12")
137137
public static JRE currentVersion() {
138138
return currentJre();
139139
}

platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@
1010

1111
package platform.tooling.support.tests;
1212

13+
import static com.tngtech.archunit.base.DescribedPredicate.and;
1314
import static com.tngtech.archunit.base.DescribedPredicate.describe;
1415
import static com.tngtech.archunit.base.DescribedPredicate.not;
1516
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES;
1617
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.TOP_LEVEL_CLASSES;
1718
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
1819
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName;
1920
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameEndingWith;
21+
import static com.tngtech.archunit.core.domain.JavaMember.Predicates.declaredIn;
2022
import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC;
23+
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith;
2124
import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier;
2225
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
2326
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining;
@@ -26,7 +29,9 @@
2629
import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
2730
import static com.tngtech.archunit.lang.conditions.ArchPredicates.have;
2831
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
32+
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.members;
2933
import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
34+
import static org.apiguardian.api.API.Status.DEPRECATED;
3035
import static org.assertj.core.api.Assertions.assertThat;
3136
import static org.junit.jupiter.api.Assertions.assertTrue;
3237

@@ -35,6 +40,7 @@
3540
import java.lang.annotation.Retention;
3641
import java.lang.annotation.Target;
3742
import java.util.Arrays;
43+
import java.util.Objects;
3844
import java.util.function.BiPredicate;
3945
import java.util.stream.Stream;
4046

@@ -43,6 +49,8 @@
4349
import com.tngtech.archunit.core.domain.JavaClasses;
4450
import com.tngtech.archunit.core.domain.JavaPackage;
4551
import com.tngtech.archunit.core.domain.PackageMatcher;
52+
import com.tngtech.archunit.core.domain.properties.HasAnnotations;
53+
import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation;
4654
import com.tngtech.archunit.junit.AnalyzeClasses;
4755
import com.tngtech.archunit.junit.ArchTest;
4856
import com.tngtech.archunit.lang.ArchCondition;
@@ -81,7 +89,7 @@ class ArchUnitTests {
8189
.and(not(describe("are Kotlin SAM type implementations", simpleName("")))) //
8290
.and(not(describe("are Kotlin-generated classes that contain only top-level functions",
8391
simpleNameEndingWith("Kt")))) //
84-
.and(not(describe("are shadowed", resideInAPackage("..shadow..")))) //
92+
.and(notShadowed()) //
8593
.should().beAnnotatedWith(API.class);
8694

8795
@SuppressWarnings("unused")
@@ -101,6 +109,22 @@ class ArchUnitTests {
101109
private final ArchRule jupiterAssertionsShouldBeSelfContained = classes().that(jupiterAssertions) //
102110
.should(onlyBeAccessedByClassesThat(jupiterAssertions));
103111

112+
@SuppressWarnings("unused")
113+
@ArchTest
114+
private final ArchRule deprecatedAnnotationOnMembersShouldBeDeclaredConsistently = members() //
115+
.that(annotatedWith(Deprecated.class)) //
116+
.or(haveApiAnnotationWithDeprecatedStatus()) //
117+
.and(declaredIn(notShadowed())) //
118+
.should(haveBothAnnotationsWithMatchingSinceAttributes());
119+
120+
@SuppressWarnings("unused")
121+
@ArchTest
122+
private final ArchRule deprecatedAnnotationOnClassesShouldBeDeclaredConsistently = classes() //
123+
.that(annotatedWith(Deprecated.class)) //
124+
.or(haveApiAnnotationWithDeprecatedStatus()) //
125+
.and(notShadowed()) //
126+
.should(haveBothAnnotationsWithMatchingSinceAttributes());
127+
104128
@ArchTest
105129
void packagesShouldBeNullMarked(JavaClasses classes) {
106130
var exclusions = Stream.of( //
@@ -203,6 +227,31 @@ private static ArchCondition<? super JavaClass> haveContainerAnnotationWithSameT
203227
(expectedTarget, actualTarget) -> Arrays.equals(expectedTarget.value(), actualTarget.value())));
204228
}
205229

230+
private static <T extends HasAnnotations<?> & HasSourceCodeLocation> ArchCondition<T> haveBothAnnotationsWithMatchingSinceAttributes() {
231+
return ArchCondition.from( //
232+
DescribedPredicate.and( //
233+
annotatedWith(Deprecated.class), haveApiAnnotationWithDeprecatedStatus(), //
234+
haveSinceAttributeMatchingApiAnnotation()));
235+
}
236+
237+
private static <T extends HasAnnotations<?> & HasSourceCodeLocation> DescribedPredicate<T> haveApiAnnotationWithDeprecatedStatus() {
238+
return and(annotatedWith(API.class), describe("status() is DEPRECATED",
239+
element -> element.getAnnotationOfType(API.class).status() == DEPRECATED));
240+
}
241+
242+
private static <T extends HasAnnotations<?> & HasSourceCodeLocation> DescribedPredicate<T> haveSinceAttributeMatchingApiAnnotation() {
243+
return describe("@API(since) equals @Deprecated(since)", element -> {
244+
var deprecatedAnnotation = element.getAnnotationOfType(Deprecated.class);
245+
var apiAnnotation = element.getAnnotationOfType(API.class);
246+
return deprecatedAnnotation.since() != null //
247+
&& Objects.equals(deprecatedAnnotation.since(), apiAnnotation.since());
248+
});
249+
}
250+
251+
private static DescribedPredicate<JavaClass> notShadowed() {
252+
return not(describe("are shadowed", resideInAPackage("..shadow..")));
253+
}
254+
206255
private static class RepeatableAnnotationPredicate<T extends Annotation> extends DescribedPredicate<JavaClass> {
207256

208257
private final Class<T> annotationType;

0 commit comments

Comments
 (0)