diff --git a/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java b/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java index ec2fdf03c7..8d6dce79e5 100644 --- a/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java +++ b/archunit/src/main/java/com/tngtech/archunit/library/GeneralCodingRules.java @@ -22,6 +22,7 @@ import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; +import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaAccess.Functions.Get; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaField; @@ -39,6 +40,7 @@ import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; +import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner; import static com.tngtech.archunit.core.domain.properties.HasParameterTypes.Predicates.rawParameterTypes; @@ -46,6 +48,7 @@ import static com.tngtech.archunit.lang.ConditionEvent.createMessage; import static com.tngtech.archunit.lang.SimpleConditionEvent.violated; import static com.tngtech.archunit.lang.conditions.ArchConditions.accessField; +import static com.tngtech.archunit.lang.conditions.ArchConditions.accessTargetWhere; import static com.tngtech.archunit.lang.conditions.ArchConditions.beAnnotatedWith; import static com.tngtech.archunit.lang.conditions.ArchConditions.callCodeUnitWhere; import static com.tngtech.archunit.lang.conditions.ArchConditions.callMethodWhere; @@ -498,4 +501,15 @@ public void check(JavaClass implementationClass, ConditionEvents events) { public static final ArchRule ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE = noClasses().should().callConstructor(AssertionError.class /* without detailMessage */) .because("assertions should have a detail message"); + + /** + * A rule checking that no class accesses {@link Deprecated} members (i.e. calls methods or constructors, or accesses fields) + * or in other ways depends on {@link Deprecated} classes. + */ + @PublicAPI(usage = ACCESS) + public static final ArchRule DEPRECATED_API_SHOULD_NOT_BE_USED = + noClasses() + .should(accessTargetWhere(JavaAccess.Predicates.target(annotatedWith(Deprecated.class))).as("access @Deprecated members")) + .orShould(dependOnClassesThat(annotatedWith(Deprecated.class)).as("depend on @Deprecated classes")) + .because("there should be a better alternative"); } diff --git a/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java b/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java index d41308d898..07a578e2d4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/library/GeneralCodingRulesTest.java @@ -12,10 +12,11 @@ import com.tngtech.archunit.library.testclasses.packages.incorrect.wrongsubdir.defaultsuffix.subdir.ImplementationClassWithWrongTestClassPackageTest; import org.junit.Test; +import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME; import static com.tngtech.archunit.library.GeneralCodingRules.ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE; +import static com.tngtech.archunit.library.GeneralCodingRules.DEPRECATED_API_SHOULD_NOT_BE_USED; import static com.tngtech.archunit.library.GeneralCodingRules.testClassesShouldResideInTheSamePackageAsImplementation; import static com.tngtech.archunit.testutil.Assertions.assertThatRule; -import static java.util.regex.Pattern.quote; public class GeneralCodingRulesTest { @@ -119,4 +120,64 @@ void f() { .checking(new ClassFileImporter().importClasses(ValidAssertions.class)) .hasNoViolation(); } + + @Test + public void DEPRECATED_API_SHOULD_NOT_BE_USED_should_fail_on_call_to_deprecated_method() { + @SuppressWarnings("DeprecatedIsStillUsed") + class ClassWithDeprecatedMembers { + @Deprecated + int target; + + @Deprecated + ClassWithDeprecatedMembers() { + } + + @Deprecated + void target() { + } + } + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + class DeprecatedClass { + int target; + + void target() { + } + } + @SuppressWarnings("unused") + class Origin { + @DeprecatedAnnotation + void origin() { + ClassWithDeprecatedMembers instanceOfClassWithDeprecatedMembers = new ClassWithDeprecatedMembers(); + instanceOfClassWithDeprecatedMembers.target++; + instanceOfClassWithDeprecatedMembers.target(); + DeprecatedClass instanceOfDeprecatedClass = new DeprecatedClass(); + instanceOfDeprecatedClass.target++; + instanceOfDeprecatedClass.target(); + Class deprecatedClass = DeprecatedClass.class; + } + } + + String innerClassConstructor = CONSTRUCTOR_NAME + "(" + GeneralCodingRulesTest.class.getName() + ")"; + String violatingMethod = "Method <" + Origin.class.getName() + ".origin()>"; + assertThatRule(DEPRECATED_API_SHOULD_NOT_BE_USED) + .hasDescriptionContaining("no classes should access @Deprecated members or should depend on @Deprecated classes, because there should be a better alternative") + .checking(new ClassFileImporter().importClasses(Origin.class, ClassWithDeprecatedMembers.class, DeprecatedClass.class)) + .hasViolations(10) + .hasViolationContaining("%s calls constructor <%s.%s>", violatingMethod, ClassWithDeprecatedMembers.class.getName(), innerClassConstructor) + .hasViolationContaining("%s gets field <%s.target>", violatingMethod, ClassWithDeprecatedMembers.class.getName()) + .hasViolationContaining("%s sets field <%s.target>", violatingMethod, ClassWithDeprecatedMembers.class.getName()) + .hasViolationContaining("%s calls method <%s.target()>", violatingMethod, ClassWithDeprecatedMembers.class.getName()) + .hasViolationContaining("%s calls constructor <%s.%s>", violatingMethod, DeprecatedClass.class.getName(), innerClassConstructor) + .hasViolationContaining("%s gets field <%s.target>", violatingMethod, DeprecatedClass.class.getName()) + .hasViolationContaining("%s sets field <%s.target>", violatingMethod, DeprecatedClass.class.getName()) + .hasViolationContaining("%s calls method <%s.target()>", violatingMethod, DeprecatedClass.class.getName()) + .hasViolationContaining("%s is annotated with <%s>", violatingMethod, DeprecatedAnnotation.class.getName()) + .hasViolationContaining("%s references class object <%s>", violatingMethod, DeprecatedClass.class.getName()); + } + + @Deprecated + @SuppressWarnings("DeprecatedIsStillUsed") + private @interface DeprecatedAnnotation { + } }