Skip to content

Commit e66589d

Browse files
committed
Issue #461: Field single declaration check implemented
1 parent bd5ca6b commit e66589d

File tree

12 files changed

+327
-1
lines changed

12 files changed

+327
-1
lines changed

eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ EmptyPublicCtorInClassCheck.desc = <p>This Check looks for useless empty public
4343
EmptyPublicCtorInClassCheck.classAnnotationNames = Regex which matches names of class annotations which require class to have public no-argument ctor. Default value is "javax\\.persistence\\.Entity".
4444
EmptyPublicCtorInClassCheck.ctorAnnotationNames = Regex which matches names of ctor annotations which make empty public ctor essential. Default value is "com\\.google\\.inject\\.Inject".
4545
46+
FieldSingleDeclarationCheck.name = Field Single Declaration Check
47+
FieldSingleDeclarationCheck.desc = <p>This checks ensures that classes have at most 1 field of the given className. This can be useful for example to ensure that only one Logger is used.</p>
48+
FieldSingleDeclarationCheck.fullyQualifiedClassName = Fully qualified name of class of which there should only be max. 1 field per class. Example: "org.slf4j.Logger".
49+
4650
FinalizeImplementationCheck.name = Finalize Implementation
4751
FinalizeImplementationCheck.desc = <p>This Check detects 3 most common cases of incorrect finalize() method implementation:</p><ul><li>negates effect of superclass finalize<br/><code>protected void finalize() { } <br/> protected void finalize() { doSomething(); }</code></li><li>useless (or worse) finalize<br/><code>protected void finalize() { super.finalize(); }</code></li><li>public finalize<br/><code>public void finalize() { try { doSomething(); } finally { super.finalize() } }</code></li></ul>
4852

eclipsecs-sevntu-plugin/src/com/github/sevntu/checkstyle/checks/coding/checkstyle-metadata.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@
162162
<message-key key="empty.public.ctor"/>
163163
</rule-metadata>
164164

165+
<rule-metadata name="%FieldSingleDeclarationCheck.name" internal-name="FieldSingleDeclarationCheck" parent="TreeWalker">
166+
<alternative-name internal-name="com.github.sevntu.checkstyle.checks.coding.FieldSingleDeclarationCheck"/>
167+
<description>%FieldSingleDeclarationCheck.desc</description>
168+
169+
<property-metadata name="fullyQualifiedClassName" datatype="String" default-value="java.util.logging.Logger">
170+
<description>%FieldSingleDeclarationCheck.fullyQualifiedClassName</description>
171+
</property-metadata>
172+
173+
<message-key key="field.count"/>
174+
</rule-metadata>
175+
165176
<rule-metadata name="%ForbidCertainImportsCheck.name" internal-name="ForbidCertainImportsCheck" parent="TreeWalker">
166177
<alternative-name internal-name="com.github.sevntu.checkstyle.checks.coding.ForbidCertainImportsCheck"/>
167178
<description>%ForbidCertainImportsCheck.desc</description>

sevntu-checks/sevntu-checks.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@
152152
<property name="ignoreFieldNamePattern" value="serialVersionUID"/>
153153
</module>
154154
<module name="com.github.sevntu.checkstyle.checks.coding.EmptyPublicCtorInClassCheck"/>
155+
<module name="com.github.sevntu.checkstyle.checks.coding.FieldSingleDeclarationCheck">
156+
<property name="fullyQualifiedClassName" value="java.util.logging.Logger"/>
157+
</module>
155158
<module name="com.github.sevntu.checkstyle.checks.coding.DiamondOperatorForVariableDefinitionCheck"/>
156159
<module name="com.github.sevntu.checkstyle.checks.coding.NameConventionForJunit4TestClassesCheck"/>
157160
<module name="com.github.sevntu.checkstyle.checks.coding.UselessSuperCtorCallCheck"/>

sevntu-checks/src/main/java/com/github/sevntu/checkstyle/SevntuUtil.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121

2222
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
2323
import com.puppycrawl.tools.checkstyle.api.DetailAST;
24+
import com.puppycrawl.tools.checkstyle.api.FullIdent;
25+
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26+
import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
2427
import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
2528

2629
/**
@@ -76,4 +79,40 @@ public static DetailAST getNextSubTreeNode(DetailAST node, DetailAST subTreeRoot
7679
return toVisitAst;
7780
}
7881

82+
/**
83+
* Obtains full type name of first token in node.
84+
* @param ast an AST node
85+
* @return fully qualified name (FQN) of type, or null if none was found
86+
*/
87+
public static String getTypeNameOfFirstToken(DetailAST ast) {
88+
final DetailAST findFirstToken = ast.findFirstToken(TokenTypes.TYPE);
89+
final FullIdent ident = CheckUtil.createFullType(findFirstToken);
90+
91+
return ident.getText();
92+
}
93+
94+
/**
95+
* Checks node for matching class, taken both FQN and short name into account.
96+
*
97+
* @param ast
98+
* an AST node
99+
* @param fqClassName
100+
* fully qualified class name
101+
* @return true if type name of first token in node is fqnClassName, or its short name; false
102+
* otherwise
103+
*/
104+
public static boolean matchesFullyQualifiedName(DetailAST ast, String fqClassName) {
105+
final String typeName = getTypeNameOfFirstToken(ast);
106+
final int lastDotPosition = fqClassName.lastIndexOf('.');
107+
boolean isMatched = false;
108+
if (lastDotPosition == -1) {
109+
isMatched = typeName.equals(fqClassName);
110+
}
111+
else {
112+
final String shortClassName = fqClassName.substring(lastDotPosition + 1);
113+
isMatched = typeName.equals(fqClassName) || typeName.equals(shortClassName);
114+
}
115+
return isMatched;
116+
}
117+
79118
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
////////////////////////////////////////////////////////////////////////////////
2+
// checkstyle: Checks Java source code for adherence to a set of rules.
3+
// Copyright (C) 2001-2020 the original author or authors.
4+
//
5+
// This library is free software; you can redistribute it and/or
6+
// modify it under the terms of the GNU Lesser General Public
7+
// License as published by the Free Software Foundation; either
8+
// version 2.1 of the License, or (at your option) any later version.
9+
//
10+
// This library is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
// Lesser General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Lesser General Public
16+
// License along with this library; if not, write to the Free Software
17+
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18+
////////////////////////////////////////////////////////////////////////////////
19+
20+
package com.github.sevntu.checkstyle.checks.coding;
21+
22+
import com.github.sevntu.checkstyle.SevntuUtil;
23+
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24+
import com.puppycrawl.tools.checkstyle.api.DetailAST;
25+
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26+
27+
/**
28+
* <p>This checks ensures that classes have at most 1 field of the given className.</p>
29+
*
30+
* <p>This can be useful for example to ensure that only one Logger is used:</p>
31+
*
32+
* <pre>
33+
* &lt;module name="TreeWalker"&gt;
34+
* &lt;module name="com.github.sevntu.checkstyle.checks.coding.FieldSingleDeclarationCheck"&gt;
35+
* &lt;property name="fullyQualifiedClassName" value="org.slf4j.Logger" /&gt;
36+
* &lt;/module&gt;
37+
* &lt;/module&gt;
38+
*
39+
* class Example {
40+
* private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Example.class); // OK
41+
* private static org.slf4j.Logger logger2; // NOK!
42+
* }
43+
* </pre>
44+
*
45+
* @author Milos Fabian, Pantheon Technologies - original author (in OpenDaylight.org)
46+
* @author Michael Vorburger.ch - refactored and made more generalized for contribution to Sevntu
47+
* @author <a href="mailto:[email protected]">Yasser Aziza</a> - completed sevntu integration
48+
*/
49+
public class FieldSingleDeclarationCheck extends AbstractCheck {
50+
51+
/**
52+
* Violation message key.
53+
*/
54+
public static final String MSG_KEY = "field.count";
55+
56+
/**
57+
* Configuration property with class name check for.
58+
*/
59+
private String fullyQualifiedClassName = "java.util.logging.Logger";
60+
61+
/**
62+
* Field to remember if class was previously seen in current File.
63+
*/
64+
private boolean hasPreviouslySeenClass;
65+
66+
/**
67+
* Set Class of which there should only be max. 1 field per class.
68+
* @param className the fully qualified name (FQN) of the class
69+
*/
70+
public void setFullyQualifiedClassName(String className) {
71+
this.fullyQualifiedClassName = className;
72+
}
73+
74+
@Override
75+
public int[] getDefaultTokens() {
76+
return new int[] {TokenTypes.VARIABLE_DEF };
77+
}
78+
79+
@Override
80+
public int[] getRequiredTokens() {
81+
return getDefaultTokens();
82+
}
83+
84+
@Override
85+
public int[] getAcceptableTokens() {
86+
return getDefaultTokens();
87+
}
88+
89+
@Override
90+
public void beginTree(DetailAST rootAST) {
91+
this.hasPreviouslySeenClass = false;
92+
}
93+
94+
@Override
95+
public void visitToken(DetailAST ast) {
96+
if (SevntuUtil.matchesFullyQualifiedName(ast, fullyQualifiedClassName)) {
97+
if (hasPreviouslySeenClass) {
98+
log(ast.getLineNo(), MSG_KEY, fullyQualifiedClassName);
99+
}
100+
this.hasPreviouslySeenClass = true;
101+
}
102+
}
103+
104+
}

sevntu-checks/src/main/resources/com/github/sevntu/checkstyle/checks/coding/messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ custom.declaration.order.method=Method definition in wrong order. Expected ''{0}
1414
diamond.operator.for.variable.definition=Diamond operator expected.
1515
either.log.or.throw=Either log or throw exception.
1616
empty.public.ctor=This empty public constructor is useless.
17+
field.count={0} should be declared only once.
1718
finalize.implementation.missed.super.finalize=You have to call super.finalize() right after finally opening brace.
1819
finalize.implementation.missed.try.finally=finalize() method should contain try-finally block with super.finalize() call inside finally block.
1920
finalize.implementation.public=finalize() method should have a "protected" visibility.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
////////////////////////////////////////////////////////////////////////////////
2+
// checkstyle: Checks Java source code for adherence to a set of rules.
3+
// Copyright (C) 2001-2020 the original author or authors.
4+
//
5+
// This library is free software; you can redistribute it and/or
6+
// modify it under the terms of the GNU Lesser General Public
7+
// License as published by the Free Software Foundation; either
8+
// version 2.1 of the License, or (at your option) any later version.
9+
//
10+
// This library is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
// Lesser General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Lesser General Public
16+
// License along with this library; if not, write to the Free Software
17+
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18+
////////////////////////////////////////////////////////////////////////////////
19+
20+
package com.github.sevntu.checkstyle.checks.coding;
21+
22+
import org.junit.Test;
23+
24+
import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
25+
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
26+
27+
/**
28+
* LoggerDeclarationsCountCheck test.
29+
*
30+
* @author <a href="mailto:[email protected]">Michael Vorburger</a> - completed sevntu integration
31+
*/
32+
public class FieldSingleDeclarationCheckTest extends AbstractModuleTestSupport {
33+
34+
@Override
35+
protected String getPackageLocation() {
36+
return "com/github/sevntu/checkstyle/checks/coding";
37+
}
38+
39+
@Test
40+
public void testFieldSingleDeclarationOk() throws Exception {
41+
final DefaultConfiguration checkConfig =
42+
createModuleConfig(FieldSingleDeclarationCheck.class);
43+
44+
checkConfig.addAttribute("fullyQualifiedClassName", "org.slf4j.Logger");
45+
46+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckOk.java"), new String[] {});
47+
}
48+
49+
@Test
50+
public void testFieldSingleDeclarationNok() throws Exception {
51+
final DefaultConfiguration checkConfig =
52+
createModuleConfig(FieldSingleDeclarationCheck.class);
53+
54+
checkConfig.addAttribute("fullyQualifiedClassName", "org.slf4j.Logger");
55+
56+
final String[] expected = {
57+
"10: " + getCheckMessage(FieldSingleDeclarationCheck.MSG_KEY, "org.slf4j.Logger"),
58+
};
59+
60+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckNok.java"), expected);
61+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckOk.java"), new String[] {});
62+
}
63+
64+
@Test
65+
public void testFieldSingleDeclarationFqnNok() throws Exception {
66+
final DefaultConfiguration checkConfig =
67+
createModuleConfig(FieldSingleDeclarationCheck.class);
68+
69+
checkConfig.addAttribute("fullyQualifiedClassName", "org.slf4j.Logger");
70+
71+
final String[] expected = {
72+
"7: " + getCheckMessage(FieldSingleDeclarationCheck.MSG_KEY, "org.slf4j.Logger"),
73+
};
74+
75+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckFqnNok.java"), expected);
76+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckOk.java"), new String[] {});
77+
}
78+
79+
@Test
80+
public void testNoClassNameConfigured() throws Exception {
81+
final DefaultConfiguration checkConfig =
82+
createModuleConfig(FieldSingleDeclarationCheck.class);
83+
84+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckOk.java"), new String[] {});
85+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckNok.java"), new String[] {});
86+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckFqnNok.java"),
87+
new String[] {});
88+
}
89+
90+
@Test
91+
public void testCustomTokens() throws Exception {
92+
final DefaultConfiguration checkConfig =
93+
createModuleConfig(FieldSingleDeclarationCheck.class);
94+
95+
checkConfig.addAttribute("fullyQualifiedClassName", "org.slf4j.Logger");
96+
// This is required just so that getAcceptableTokens() gets coverage
97+
checkConfig.addAttribute("tokens", "VARIABLE_DEF");
98+
99+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckOk.java"), new String[] {});
100+
}
101+
102+
@Test
103+
public void testUnknownClassName() throws Exception {
104+
final DefaultConfiguration checkConfig =
105+
createModuleConfig(FieldSingleDeclarationCheck.class);
106+
107+
checkConfig.addAttribute("fullyQualifiedClassName", "SomeClass");
108+
// This is required just so that getAcceptableTokens() gets coverage
109+
checkConfig.addAttribute("tokens", "VARIABLE_DEF");
110+
111+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckOk.java"), new String[] {});
112+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckNok.java"), new String[] {});
113+
verify(checkConfig, getPath("InputFieldSingleDeclarationCheckFqnNok.java"),
114+
new String[] {});
115+
}
116+
117+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.github.sevntu.checkstyle.checks.coding;
2+
3+
public class InputFieldSingleDeclarationCheckFqnNok {
4+
5+
// even twice a FQN type names work:
6+
org.slf4j.Logger logger1;
7+
org.slf4j.Logger logger2; // <= Checkstyle violation raised here!
8+
9+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.github.sevntu.checkstyle.checks.coding;
2+
3+
import java.io.File;
4+
import java.util.logging.Logger;
5+
6+
public class InputFieldSingleDeclarationCheckNok {
7+
8+
// both FQN and import'ed type names work:
9+
Logger logger1;
10+
org.slf4j.Logger logger2; // <= Checkstyle violation raised here!
11+
12+
// some field of another type (req. for test coverage)
13+
File file;
14+
15+
// a method, so that there are not only fields here (req. for test coverage)
16+
void foo() { }
17+
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.github.sevntu.checkstyle.checks.coding;
2+
3+
public class InputFieldSingleDeclarationCheckOk {
4+
5+
org.slf4j.Logger logger;
6+
7+
}

0 commit comments

Comments
 (0)