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 26, 2024
1 parent 194a6f7 commit dfc82c6
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 6 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,14 @@
* }
* }
* </pre>
* <ul>
* <li>
* Property {@code ignoreAnnotatedBy} - Ignore classes annotated
* with the specified annotation(s).
* 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 +86,11 @@ public class HideUtilityClassConstructorCheck extends AbstractCheck {
*/
public static final String MSG_KEY = "hide.utility.class";

/**
* Ignore classes annotated with the specified annotation(s).
*/
private Set<String> ignoreAnnotatedBy = Collections.emptySet();

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

/**
* Setter to ignore classes annotated with the specified annotation(s).
*
* @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,12 @@
}
}
&lt;/pre&gt;</description>
<properties>
<property default-value="" name="ignoreAnnotatedBy" type="java.lang.String[]">
<description>Ignore classes annotated
with the specified annotation(s).</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 @@ -34,7 +35,8 @@ protected Bar() {
}
}

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

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

private Foo1() {
}

static int n;
}

class Bar1 { // OK

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

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

static float f;
}
// xdoc section -- end
@interface SpringBootApplication {}
79 changes: 77 additions & 2 deletions src/xdocs/checks/design/hideutilityclassconstructor.xml
Expand Up @@ -42,6 +42,27 @@ public class StringUtils // not final to allow subclassing
</source>
</subsection>

<subsection name="Properties" id="Properties">
<div class="wrapper">
<table>
<tr>
<th>name</th>
<th>description</th>
<th>type</th>
<th>default value</th>
<th>since</th>
</tr>
<tr>
<td>ignoreAnnotatedBy</td>
<td>Ignore classes annotated with the specified annotation(s).</td>
<td><a href="../../property_types.html#String.5B.5D">String[]</a></td>
<td><code>{}</code></td>
<td>10.15.0</td>
</tr>
</table>
</div>
</subsection>

<subsection name="Examples" id="Examples">
<p id="Example1-config">
To configure the check:
Expand All @@ -55,7 +76,8 @@ public class StringUtils // not final to allow subclassing
</source>
<p id="Example1-code">Example:</p>
<source>
class Example1 { // violation
@java.lang.Deprecated // violation
class Example1 {

public Example1() {
}
Expand All @@ -80,11 +102,64 @@ class Bar { // OK
}
}

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

static float f;
}
</source>

<p id="Example2-config">
To configure the check:
</p>
<source>
&lt;module name=&quot;Checker&quot;&gt;
&lt;module name=&quot;TreeWalker&quot;&gt;
&lt;module name=&quot;HideUtilityClassConstructor&quot;&gt;
&lt;property name=&quot;ignoreAnnotatedBy&quot;
value=&quot;SpringBootApplication, java.lang.Deprecated&quot; /&gt;
&lt;/module&gt;
&lt;/module&gt;
&lt;/module&gt;
</source>
<p id="Example2-code">Example:</p>
<source>
@java.lang.Deprecated
class Example2 { // OK, skipped by annotation

public Example2() {
}

public static void fun() {
}
}

class Foo1 { // OK

private Foo1() {
}

static int n;
}

class Bar1 { // OK

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

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

static float f;
}
</source>
<p>
Note: Annotation names specified in the <code>ignoreAnnotatedBy</code> property
must be an exact match for the annotation name on the class.
</p>
</subsection>

<subsection name="Example of Usage" id="Example_of_Usage">
Expand Down

0 comments on commit dfc82c6

Please sign in to comment.