1010
1111package platform .tooling .support .tests ;
1212
13+ import static com .tngtech .archunit .base .DescribedPredicate .and ;
1314import static com .tngtech .archunit .base .DescribedPredicate .describe ;
1415import static com .tngtech .archunit .base .DescribedPredicate .not ;
1516import static com .tngtech .archunit .core .domain .JavaClass .Predicates .ANONYMOUS_CLASSES ;
1617import static com .tngtech .archunit .core .domain .JavaClass .Predicates .TOP_LEVEL_CLASSES ;
1718import static com .tngtech .archunit .core .domain .JavaClass .Predicates .resideInAPackage ;
1819import static com .tngtech .archunit .core .domain .JavaClass .Predicates .simpleName ;
1920import static com .tngtech .archunit .core .domain .JavaClass .Predicates .simpleNameEndingWith ;
21+ import static com .tngtech .archunit .core .domain .JavaMember .Predicates .declaredIn ;
2022import static com .tngtech .archunit .core .domain .JavaModifier .PUBLIC ;
23+ import static com .tngtech .archunit .core .domain .properties .CanBeAnnotated .Predicates .annotatedWith ;
2124import static com .tngtech .archunit .core .domain .properties .HasModifiers .Predicates .modifier ;
2225import static com .tngtech .archunit .core .domain .properties .HasName .Predicates .name ;
2326import static com .tngtech .archunit .core .domain .properties .HasName .Predicates .nameContaining ;
2629import static com .tngtech .archunit .lang .conditions .ArchPredicates .are ;
2730import static com .tngtech .archunit .lang .conditions .ArchPredicates .have ;
2831import static com .tngtech .archunit .lang .syntax .ArchRuleDefinition .classes ;
32+ import static com .tngtech .archunit .lang .syntax .ArchRuleDefinition .members ;
2933import static com .tngtech .archunit .library .dependencies .SlicesRuleDefinition .slices ;
34+ import static org .apiguardian .api .API .Status .DEPRECATED ;
3035import static org .assertj .core .api .Assertions .assertThat ;
3136import static org .junit .jupiter .api .Assertions .assertTrue ;
3237
3540import java .lang .annotation .Retention ;
3641import java .lang .annotation .Target ;
3742import java .util .Arrays ;
43+ import java .util .Objects ;
3844import java .util .function .BiPredicate ;
3945import java .util .stream .Stream ;
4046
4349import com .tngtech .archunit .core .domain .JavaClasses ;
4450import com .tngtech .archunit .core .domain .JavaPackage ;
4551import com .tngtech .archunit .core .domain .PackageMatcher ;
52+ import com .tngtech .archunit .core .domain .properties .HasAnnotations ;
53+ import com .tngtech .archunit .core .domain .properties .HasSourceCodeLocation ;
4654import com .tngtech .archunit .junit .AnalyzeClasses ;
4755import com .tngtech .archunit .junit .ArchTest ;
4856import 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