Skip to content

Commit

Permalink
Issue #14424: Add option to skip validation based on list of annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
Lmh-java committed Mar 28, 2024
1 parent f4b7158 commit 80b09ce
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 14 deletions.
Expand Up @@ -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;

/**
* <p>
Expand Down Expand Up @@ -51,6 +55,18 @@
* }
* }
* </pre>
* <ul>
* <li>
* Property {@code ignoreAnnotatedBy} - Ignore classes annotated
* with the specified annotation(s). Name in this property must
* be an exact match for the annotation name on the class. If target
* class has annotation in fully qualified name (with package), this
* property should too. If target class has annotation in simple name,
* then this property needs to have the same simple name.
* Type is {@code java.lang.String[]}.
* Default value is {@code ""}.
* </li>
* </ul>
* <p>
* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
* </p>
Expand All @@ -74,6 +90,15 @@ public class HideUtilityClassConstructorCheck extends AbstractCheck {
*/
public static final String MSG_KEY = "hide.utility.class";

/**
* Ignore classes annotated with the specified annotation(s). Name in
* this property must be an exact match for the annotation name on the class.
* If target class has annotation in fully qualified name (with package), this
* property should too. If target class has annotation in simple name,
* then this property needs to have the same simple name.
*/
private Set<String> ignoreAnnotatedBy = Collections.emptySet();

@Override
public int[] getDefaultTokens() {
return getRequiredTokens();
Expand All @@ -92,7 +117,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);
Expand Down Expand Up @@ -142,6 +167,30 @@ private static boolean isStatic(DetailAST ast) {
.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
}

/**
* Setter to ignore classes annotated with the specified annotation(s).
* Name in this property must be an exact match for the annotation name on the class.
* If target class has annotation in fully qualified name (with package),
* this property should too. If target class has annotation in simple name,
* then this property needs to have the same simple name.
*
* @param annotationNames specified annotation(s)
* @since 10.15.0
*/
public void setIgnoreAnnotatedBy(String... annotationNames) {
ignoreAnnotatedBy = Set.of(annotationNames);
}

/**
* 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.
*/
Expand Down
Expand Up @@ -30,6 +30,16 @@
}
}
&lt;/pre&gt;</description>
<properties>
<property default-value="" name="ignoreAnnotatedBy" type="java.lang.String[]">
<description>Ignore classes annotated
with the specified annotation(s). Name in this property must
be an exact match for the annotation name on the class. If target
class has annotation in fully qualified name (with package), this
property should too. If target class has annotation in simple name,
then this property needs to have the same simple name.</description>
</property>
</properties>
<message-keys>
<message-key key="hide.utility.class"/>
</message-keys>
Expand Down
Expand Up @@ -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
);
}
}
@@ -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 {}
@@ -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 {}
Expand Up @@ -36,9 +36,16 @@ 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);
}
}
Expand Up @@ -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() {
}
Expand All @@ -18,23 +19,24 @@ public static void fun() {
}
}

class Foo { // OK
class Foo1 { // OK

private Foo() {
private Foo1() {
}

static int n;
}

class Bar { // OK
class Bar1 { // OK

protected Bar() {
protected Bar1() {
// prevents calls from subclass
throw new UnsupportedOperationException();
}
}

class UtilityClass { // violation
@SpringBootApplication // violation
class ApplicationClass1 {

static float f;
}
Expand Down
@@ -0,0 +1,47 @@
/*xml
<module name="Checker">
<module name="TreeWalker">
<module name="HideUtilityClassConstructor">
<property name="ignoreAnnotatedBy"
value="SpringBootApplication, java.lang.Deprecated" />
</module>
</module>
</module>
*/

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 { // OK

private Foo2() {
}

static int n;
}

class Bar2 { // OK

protected Bar2() {
// prevents calls from subclass
throw new UnsupportedOperationException();
}
}

@SpringBootApplication
class ApplicationClass2 { // OK, skipped by annotation

static float f;
}
// xdoc section -- end
@interface SpringBootApplication {}

0 comments on commit 80b09ce

Please sign in to comment.