From 70bd1b75eab538fbe7507b5b4805ac43206d66b2 Mon Sep 17 00:00:00 2001 From: Minghao Li Date: Thu, 21 Mar 2024 13:19:31 -0700 Subject: [PATCH] Issue #14424: Add option to skip validation based on list of annotations --- .../HideUtilityClassConstructorCheck.java | 56 +++++++- .../HideUtilityClassConstructorCheck.xml | 11 ++ .../HideUtilityClassConstructorCheckTest.java | 22 +++ ...ityClassConstructorIgnoreAnnotationBy.java | 46 ++++++ ...rIgnoreAnnotationByFullyQualifiedName.java | 19 +++ ...lityClassConstructorCheckExamplesTest.java | 18 ++- .../hideutilityclassconstructor/Example1.java | 14 +- .../hideutilityclassconstructor/Example2.java | 47 ++++++ .../hideutilityclassconstructor/Example3.java | 46 ++++++ .../design/hideutilityclassconstructor.xml | 134 +++++++++++++++++- .../hideutilityclassconstructor.xml.template | 42 ++++++ 11 files changed, 441 insertions(+), 14 deletions(-) create mode 100644 src/test/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/InputHideUtilityClassConstructorIgnoreAnnotationBy.java create mode 100644 src/test/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/InputHideUtilityClassConstructorIgnoreAnnotationByFullyQualifiedName.java create mode 100644 src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example2.java create mode 100644 src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example3.java diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheck.java index 795881b1ec9..47ab70a11e1 100644 --- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheck.java +++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheck.java @@ -19,10 +19,14 @@ package com.puppycrawl.tools.checkstyle.checks.design; +import java.util.Collections; +import java.util.Set; + import com.puppycrawl.tools.checkstyle.StatelessCheck; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; +import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; /** *

@@ -51,6 +55,19 @@ * } * } * + *

*

* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} *

@@ -74,6 +91,33 @@ public class HideUtilityClassConstructorCheck extends AbstractCheck { */ public static final String MSG_KEY = "hide.utility.class"; + /** + * Ignore classes annotated with the specified annotation(s). Annotation names + * provided in this property must exactly match the annotation names on the classes. + * If the target class has annotations specified with their fully qualified names + * (including package), the annotations in this property should also be specified with + * their fully qualified names. Similarly, if the target class has annotations specified + * with their simple names, this property should contain the annotations with the same + * simple names. + */ + private Set ignoreAnnotatedBy = Collections.emptySet(); + + /** + * Setter to ignore classes annotated with the specified annotation(s). Annotation names + * provided in this property must exactly match the annotation names on the classes. + * If the target class has annotations specified with their fully qualified names + * (including package), the annotations in this property should also be specified with + * their fully qualified names. Similarly, if the target class has annotations specified + * with their simple names, this property should contain the annotations with the same + * simple names. + * + * @param annotationNames specified annotation(s) + * @since 10.17.0 + */ + public void setIgnoreAnnotatedBy(String... annotationNames) { + ignoreAnnotatedBy = Set.of(annotationNames); + } + @Override public int[] getDefaultTokens() { return getRequiredTokens(); @@ -92,7 +136,7 @@ public int[] getRequiredTokens() { @Override public void visitToken(DetailAST ast) { // abstract class could not have private constructor - if (!isAbstract(ast)) { + if (!isAbstract(ast) && !shouldIgnoreClass(ast)) { final boolean hasStaticModifier = isStatic(ast); final Details details = new Details(ast); @@ -142,6 +186,16 @@ private static boolean isStatic(DetailAST ast) { .findFirstToken(TokenTypes.LITERAL_STATIC) != null; } + /** + * Checks if class is annotated by specific annotation(s) to skip. + * + * @param ast class to check + * @return true if annotated by ignored annotations + */ + private boolean shouldIgnoreClass(DetailAST ast) { + return AnnotationUtil.containsAnnotation(ast, ignoreAnnotatedBy); + } + /** * Details of class that are required for validation. */ diff --git a/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/design/HideUtilityClassConstructorCheck.xml b/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/design/HideUtilityClassConstructorCheck.xml index 897b5c51760..73093631a47 100644 --- a/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/design/HideUtilityClassConstructorCheck.xml +++ b/src/main/resources/com/puppycrawl/tools/checkstyle/meta/checks/design/HideUtilityClassConstructorCheck.xml @@ -30,6 +30,17 @@ } } </pre> + + + Ignore classes annotated + with the specified annotation(s). Annotation names provided in this property + must exactly match the annotation names on the classes. If the target class has annotations + specified with their fully qualified names (including package), the annotations in this + property should also be specified with their fully qualified names. Similarly, if the target + class has annotations specified with their simple names, this property should contain the + annotations with the same simple names. + + diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheckTest.java index 347c5ba6a3a..7e6d4c32102 100644 --- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheckTest.java +++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheckTest.java @@ -146,4 +146,26 @@ public void testGetAcceptableTokens() { .isEqualTo(expected); } + @Test + public void testIgnoreAnnotatedBy() throws Exception { + final String[] expected = { + "30:1: " + getCheckMessage(MSG_KEY), + }; + verifyWithInlineConfigParser( + getPath("InputHideUtilityClassConstructorIgnoreAnnotationBy.java"), + expected + ); + } + + @Test + public void testIgnoreAnnotatedByFullQualifier() throws Exception { + final String[] expected = { + "9:1: " + getCheckMessage(MSG_KEY), + }; + verifyWithInlineConfigParser( + getPath("InputHideUtilityClassConstructor" + + "IgnoreAnnotationByFullyQualifiedName.java"), + expected + ); + } } diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/InputHideUtilityClassConstructorIgnoreAnnotationBy.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/InputHideUtilityClassConstructorIgnoreAnnotationBy.java new file mode 100644 index 00000000000..3af48f65fc0 --- /dev/null +++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/InputHideUtilityClassConstructorIgnoreAnnotationBy.java @@ -0,0 +1,46 @@ +/* +HideUtilityClassConstructor +ignoreAnnotatedBy = Skip, SkipWithParam, SkipWithAnnotationAsParam + +*/ + +package com.puppycrawl.tools.checkstyle.checks.design.hideutilityclassconstructor; + +@Skip +public class InputHideUtilityClassConstructorIgnoreAnnotationBy { + public static void func() {} +} + +@SkipWithParam(name = "tool1") +class ToolClass1 { + public static void func() {} +} + +@SkipWithAnnotationAsParam(skip = @Skip) +class ToolClass2 { + public static void func() {} +} + +@CommonAnnot +@Skip +class ToolClass3 { + public static void func() {} +} + +@CommonAnnot // violation +class ToolClass4 { + public static void func() {} +} + + +@interface Skip {} + +@interface SkipWithParam { + String name(); +} + +@interface SkipWithAnnotationAsParam { + Skip skip(); +} + +@interface CommonAnnot {} diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/InputHideUtilityClassConstructorIgnoreAnnotationByFullyQualifiedName.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/InputHideUtilityClassConstructorIgnoreAnnotationByFullyQualifiedName.java new file mode 100644 index 00000000000..b01b3cdfff2 --- /dev/null +++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/InputHideUtilityClassConstructorIgnoreAnnotationByFullyQualifiedName.java @@ -0,0 +1,19 @@ +/* +HideUtilityClassConstructor +ignoreAnnotatedBy = java.lang.Deprecated + +*/ + +package com.puppycrawl.tools.checkstyle.checks.design.hideutilityclassconstructor; + +@Deprecated // violation +public class InputHideUtilityClassConstructorIgnoreAnnotationByFullyQualifiedName { + public static void func() {} +} + +@java.lang.Deprecated +class DeprecatedClass { + public static void func() {} +} + +@interface Deprecated {} diff --git a/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheckExamplesTest.java b/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheckExamplesTest.java index 2537e6182b6..70100f6a255 100644 --- a/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheckExamplesTest.java +++ b/src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks/design/HideUtilityClassConstructorCheckExamplesTest.java @@ -36,9 +36,25 @@ protected String getPackageLocation() { public void testExample1() throws Exception { final String[] expected = { "12:1: " + getCheckMessage(MSG_KEY), - "37:1: " + getCheckMessage(MSG_KEY), + "38:1: " + getCheckMessage(MSG_KEY), }; verifyWithInlineConfigParser(getPath("Example1.java"), expected); } + + @Test + public void testExample2() throws Exception { + final String[] expected = {}; + + verifyWithInlineConfigParser(getPath("Example2.java"), expected); + } + + @Test + public void testExample3() throws Exception { + final String[] expected = { + "15:1: " + getCheckMessage(MSG_KEY), + }; + + verifyWithInlineConfigParser(getPath("Example3.java"), expected); + } } diff --git a/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example1.java b/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example1.java index 0af95eb74bb..102c2e0ffd3 100644 --- a/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example1.java +++ b/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example1.java @@ -9,7 +9,8 @@ package com.puppycrawl.tools.checkstyle.checks.design.hideutilityclassconstructor; // xdoc section -- start -class Example1 { // violation +@java.lang.Deprecated // violation +class Example1 { public Example1() { } @@ -18,23 +19,24 @@ public static void fun() { } } -class Foo { // OK +class Foo1 { - private Foo() { + private Foo1() { } static int n; } -class Bar { // OK +class Bar1 { - protected Bar() { + protected Bar1() { // prevents calls from subclass throw new UnsupportedOperationException(); } } -class UtilityClass { // violation +@SpringBootApplication // violation +class ApplicationClass1 { static float f; } diff --git a/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example2.java b/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example2.java new file mode 100644 index 00000000000..cea07636b07 --- /dev/null +++ b/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example2.java @@ -0,0 +1,47 @@ +/*xml + + + + + + + +*/ + +package com.puppycrawl.tools.checkstyle.checks.design.hideutilityclassconstructor; + +// xdoc section -- start +@java.lang.Deprecated +class Example2 { // OK, skipped by annotation + + public Example2() { + } + + public static void fun() { + } +} + +class Foo2 { + + private Foo2() { + } + + static int n; +} + +class Bar2 { + + protected Bar2() { + // prevents calls from subclass + throw new UnsupportedOperationException(); + } +} + +@SpringBootApplication +class ApplicationClass2 { // OK, skipped by annotation + + static float f; +} +// xdoc section -- end +@interface SpringBootApplication {} diff --git a/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example3.java b/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example3.java new file mode 100644 index 00000000000..e000c958a30 --- /dev/null +++ b/src/xdocs-examples/resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example3.java @@ -0,0 +1,46 @@ +/*xml + + + + + + + +*/ + +package com.puppycrawl.tools.checkstyle.checks.design.hideutilityclassconstructor; + +// xdoc section -- start +@java.lang.Deprecated // violation +class Example3 { + + public Example3() { + } + + public static void fun() { + } +} + +class Foo3 { + + private Foo3() { + } + + static int n; +} + +class Bar3 { + + protected Bar3() { + // prevents calls from subclass + throw new UnsupportedOperationException(); + } +} + +@SpringBootApplication +class ApplicationClass3 { // OK, skipped by annotation + + static float f; +} +// xdoc section -- end diff --git a/src/xdocs/checks/design/hideutilityclassconstructor.xml b/src/xdocs/checks/design/hideutilityclassconstructor.xml index cfe453eee81..8d7bb57a551 100644 --- a/src/xdocs/checks/design/hideutilityclassconstructor.xml +++ b/src/xdocs/checks/design/hideutilityclassconstructor.xml @@ -42,6 +42,27 @@ public class StringUtils // not final to allow subclassing + +
+ + + + + + + + + + + + + + + +
namedescriptiontypedefault valuesince
ignoreAnnotatedByIgnore classes annotated with the specified annotation(s). Annotation names provided in this property must exactly match the annotation names on the classes. If the target class has annotations specified with their fully qualified names (including package), the annotations in this property should also be specified with their fully qualified names. Similarly, if the target class has annotations specified with their simple names, this property should contain the annotations with the same simple names.String[]{}10.17.0
+
+
+

To configure the check: @@ -55,7 +76,8 @@ public class StringUtils // not final to allow subclassing

Example:

-class Example1 { // violation +@java.lang.Deprecated // violation +class Example1 { public Example1() { } @@ -64,23 +86,123 @@ class Example1 { // violation } } -class Foo { // OK +class Foo1 { + + private Foo1() { + } + + static int n; +} + +class Bar1 { + + protected Bar1() { + // prevents calls from subclass + throw new UnsupportedOperationException(); + } +} + +@SpringBootApplication // violation +class ApplicationClass1 { + + static float f; +} + + +

+ To configure the check to ignore classes annotated with SpringBootApplication + or java.lang.Deprecated. +

+ +<module name="Checker"> + <module name="TreeWalker"> + <module name="HideUtilityClassConstructor"> + <property name="ignoreAnnotatedBy" + value="SpringBootApplication, java.lang.Deprecated" /> + </module> + </module> +</module> + +

Example:

+ +@java.lang.Deprecated +class Example2 { // OK, skipped by annotation + + public Example2() { + } + + public static void fun() { + } +} + +class Foo2 { + + private Foo2() { + } + + static int n; +} + +class Bar2 { + + protected Bar2() { + // prevents calls from subclass + throw new UnsupportedOperationException(); + } +} + +@SpringBootApplication +class ApplicationClass2 { // OK, skipped by annotation + + static float f; +} + + +

+ To configure the check to ignore classes annotated with SpringBootApplication + or Deprecated. In this case, java.lang.Deprecated will not + be ignored. +

+ +<module name="Checker"> + <module name="TreeWalker"> + <module name="HideUtilityClassConstructor"> + <property name="ignoreAnnotatedBy" + value="SpringBootApplication, Deprecated" /> + </module> + </module> +</module> + +

Example:

+ +@java.lang.Deprecated // violation +class Example3 { + + public Example3() { + } + + public static void fun() { + } +} + +class Foo3 { - private Foo() { + private Foo3() { } static int n; } -class Bar { // OK +class Bar3 { - protected Bar() { + protected Bar3() { // prevents calls from subclass throw new UnsupportedOperationException(); } } -class UtilityClass { // violation +@SpringBootApplication +class ApplicationClass3 { // OK, skipped by annotation static float f; } diff --git a/src/xdocs/checks/design/hideutilityclassconstructor.xml.template b/src/xdocs/checks/design/hideutilityclassconstructor.xml.template index ee91c5b509e..ee7ffff9272 100644 --- a/src/xdocs/checks/design/hideutilityclassconstructor.xml.template +++ b/src/xdocs/checks/design/hideutilityclassconstructor.xml.template @@ -42,6 +42,15 @@ public class StringUtils // not final to allow subclassing
+ +
+ + + +
+
+

To configure the check: @@ -57,6 +66,39 @@ public class StringUtils // not final to allow subclassing value="resources/com/puppycrawl/tools/checkstyle/checks/design/hideutilityclassconstructor/Example1.java"/> + +

+ To configure the check to ignore classes annotated with SpringBootApplication + or java.lang.Deprecated. +

+ + + + +

Example:

+ + + + + +

+ To configure the check to ignore classes annotated with SpringBootApplication + or Deprecated. In this case, java.lang.Deprecated will not + be ignored. +

+ + + + +

Example:

+ + + +