Skip to content

Commit

Permalink
Issue checkstyle#14424: Added option to skip HideUtilityClassConstruc…
Browse files Browse the repository at this point in the history
…torCheck validation based on list of annotations
  • Loading branch information
kkoutsilis authored and romani committed Oct 28, 2024
1 parent a4b0fc7 commit 8684faa
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 11 deletions.
Original file line number Diff line number Diff line change
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;

/**
* <div>
Expand Down Expand Up @@ -53,6 +57,19 @@
* }
* }
* </pre>
* <ul>
* <li>
* Property {@code ignoreAnnotatedBy} - 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.
* Type is {@code java.lang.String[]}.
* Default value is {@code ""}.
* </li>
* </ul>
*
* <p>
* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
Expand All @@ -78,6 +95,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<String> 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.20.0
*/
public void setIgnoreAnnotatedBy(String... annotationNames) {
ignoreAnnotatedBy = Set.of(annotationNames);
}

@Override
public int[] getDefaultTokens() {
return getRequiredTokens();
Expand All @@ -96,7 +140,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 @@ -146,6 +190,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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@
}
}
&lt;/pre&gt;</description>
<properties>
<property default-value="" name="ignoreAnnotatedBy" type="java.lang.String[]">
<description>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.</description>
</property>
</properties>
<message-keys>
<message-key key="hide.utility.class"/>
</message-keys>
Expand Down
Original file line number Diff line number Diff line change
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
);
}
}
Original file line number Diff line number Diff line change
@@ -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, should not have a public or default constructor
class ToolClass4 {
public static void func() {}
}


@interface Skip {}

@interface SkipWithParam {
String name();
}

@interface SkipWithAnnotationAsParam {
Skip skip();
}

@interface CommonAnnot {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
HideUtilityClassConstructor
ignoreAnnotatedBy = java.lang.Deprecated
*/

package com.puppycrawl.tools.checkstyle.checks.design.hideutilityclassconstructor;

@Deprecated // violation, should not have a public or default constructor
public class InputHideUtilityClassConstructorIgnoreAnnotationByFullyQualifiedName {
public static void func() {}
}

@java.lang.Deprecated
class DeprecatedClass {
public static void func() {}
}

@interface Deprecated {}
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@ protected String getPackageLocation() {
@Test
public void testExample1() throws Exception {
final String[] expected = {
"12:1: " + getCheckMessage(MSG_KEY),
"37:1: " + getCheckMessage(MSG_KEY),
"13:1: " + getCheckMessage(MSG_KEY),
"39:1: " + getCheckMessage(MSG_KEY),
"45:1: " + getCheckMessage(MSG_KEY),
};

verifyWithInlineConfigParser(getPath("Example1.java"), expected);
}

@Test
public void testExample2() throws Exception {
final String[] expected = {
"42:1: " + getCheckMessage(MSG_KEY),
};

verifyWithInlineConfigParser(getPath("Example2.java"), expected);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
package com.puppycrawl.tools.checkstyle.checks.design.hideutilityclassconstructor;

// xdoc section -- start
class Example1 { // violation
// violation below, 'should not have a public or default constructor'
@java.lang.Deprecated
class Example1 {

public Example1() {
}
Expand All @@ -18,24 +20,33 @@ public static void fun() {
}
}

class Foo { // OK
class Foo {

private Foo() {
}

static int n;
}

class Bar { // OK
class Bar {

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

class UtilityClass { // violation
@Deprecated // violation, 'should not have a public or default constructor'
class UtilityClass {

static float f;
}
// violation below, 'should not have a public or default constructor'
@SpringBootApplication
class Application1 {

public static void main(String[] args) {
}
}
// xdoc section -- end
@interface SpringBootApplication {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*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
// ok below, skipped by annotation
@java.lang.Deprecated
class Example2 {

public Example2() {
}

public static void fun() {
}
}

class Foo2 {

private Foo2() {
}

static int n;
}

class Bar2 {

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

@Deprecated // violation, 'should not have a public or default constructor'
class UtilityClass2 {

static float f;
}
// ok below, skipped by annotation
@SpringBootApplication
class Application2 {

public static void main(String[] args) {
}
}
// xdoc section -- end
Loading

0 comments on commit 8684faa

Please sign in to comment.