Skip to content

Commit fbe2857

Browse files
committed
Issue sevntu-checkstyle#464: Check for trailing commas on enums
1 parent 0c7b77a commit fbe2857

File tree

11 files changed

+307
-4
lines changed

11 files changed

+307
-4
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ EitherLogOrThrowCheck.loggingMethodNames = Logging method names separated with c
183183
WhitespaceBeforeArrayInitializerCheck.name = Whitespace Before Array Initializer
184184
WhitespaceBeforeArrayInitializerCheck.desc = This checks enforces whitespace before array initializer.
185185
186+
EnumTrailingCommaCheck.name = Enum Trailing Comma
187+
EnumTrailingCommaCheck.desc = This checks enforces a trailing comma at the end of a line in an enum constant definition.
188+
186189
MoveVariableInsideIfCheck.name = Move Variable Inside If Check
187190
MoveVariableInsideIfCheck.desc = Checks if a variable is only used inside if statements and asks for it's declaration to be moved there too.
188191

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,5 +556,13 @@
556556
<message-key key="require.fail"/>
557557
</rule-metadata>
558558

559+
<rule-metadata name="%EnumTrailingCommaCheck.name" internal-name="EnumTrailingCommaCheck" parent="TreeWalker">
560+
<alternative-name internal-name="com.github.sevntu.checkstyle.checks.coding.EnumTrailingCommaCheck"/>
561+
<description>%EnumTrailingCommaCheck.desc</description>
562+
563+
<message-key key="enum.trailing.comma"/>
564+
<message-key key="enum.trailing.comma.semi"/>
565+
</rule-metadata>
566+
559567
</rule-group-metadata>
560568
</checkstyle-metadata>

sevntu-checks/sevntu-checks.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@
157157
<module name="com.github.sevntu.checkstyle.checks.coding.UselessSuperCtorCallCheck"/>
158158
<module name="com.github.sevntu.checkstyle.checks.coding.UselessSingleCatchCheck"/>
159159
<module name="com.github.sevntu.checkstyle.checks.coding.WhitespaceBeforeArrayInitializerCheck"/>
160+
<module name="com.github.sevntu.checkstyle.checks.coding.EnumTrailingCommaCheck"/>
160161
<module name="com.github.sevntu.checkstyle.checks.coding.MoveVariableInsideIfCheck" />
161162
<module name="com.github.sevntu.checkstyle.checks.coding.RequireFailForTryCatchInJunitCheck" />
162163

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
////////////////////////////////////////////////////////////////////////////////
2+
// checkstyle: Checks Java source code for adherence to a set of rules.
3+
// Copyright (C) 2001-2018 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.puppycrawl.tools.checkstyle.api.AbstractCheck;
23+
import com.puppycrawl.tools.checkstyle.api.DetailAST;
24+
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
25+
26+
/**
27+
* Checks if enum constant contains an optional trailing comma.
28+
*
29+
* <p>Rationale: Putting this comma in makes is easier to change the order of the elements or add
30+
* new elements at the end of the list.
31+
*
32+
* <p>The following is a normal enum type declaration:
33+
* <pre>
34+
* enum Type {
35+
* ALPHA,
36+
* BETA,
37+
* GAMMA
38+
* }
39+
* </pre>
40+
*
41+
* <p>However, if you want to append something to the list, you would need to change the line
42+
* containing the last enum constant:
43+
*
44+
* <pre>
45+
* enum Type {
46+
* ALPHA,
47+
* BETA,
48+
* GAMMA, // changed due to the ','
49+
* DELTA // new line
50+
* }
51+
* </pre>
52+
*
53+
* <p>This check makes sure that also the last enum constant has a trailing comma, which is
54+
* valid according to the Java Spec (see <a
55+
* href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.1">Enum Constants</a>)
56+
*
57+
* <pre>
58+
* enum Type {
59+
* ALPHA,
60+
* BETA,
61+
* GAMMA,
62+
* DELTA, // removing this comma will result in a violation with the check activated
63+
* }
64+
* </pre>
65+
*
66+
* <p>However, you could also add a semicolon behind that comma on the same line, which would raise
67+
* a violation
68+
*
69+
* <pre>
70+
* enum Type {
71+
* ALPHA,
72+
* BETA,
73+
* GAMMA,
74+
* DELTA,; // violation
75+
* }
76+
* </pre>
77+
* In this case the semicolon should be removed. However, if there is more in the enum body, the
78+
* semicolon should be placed on a line by itself.
79+
*
80+
* <p>An example of how to configure the check is:
81+
* <pre>
82+
* &lt;module name="EnumTrailingComma"/&gt;
83+
* </pre>
84+
*
85+
* <p>Please note that using this check together with {@code NoWhitespaceBefore} or
86+
* {@code SeparatorWrap} may create conflicts with enums that contain a body:
87+
* {@code EnumTrailingComma} enforces the semicolon on a separate line while
88+
* {@code NoWhiteSpaceBefore} does not allow the semicolon to be preceded with whitespace and
89+
* {@code SeparatorWrap} expects the semicolon to be on the same line as the last enum constant.
90+
*
91+
* @author <a href="[email protected]">Kariem Hussein</a>
92+
*/
93+
public class EnumTrailingCommaCheck extends AbstractCheck {
94+
95+
/** Key for warning message text in "messages.properties" file. */
96+
public static final String MSG_KEY = "enum.trailing.comma";
97+
/** Key for warning message text in "messages.properties" file. */
98+
public static final String MSG_KEY_SEMI = "enum.trailing.comma.semi";
99+
100+
@Override
101+
public int[] getDefaultTokens() {
102+
return getAcceptableTokens();
103+
}
104+
105+
@Override
106+
public int[] getAcceptableTokens() {
107+
return new int[] {TokenTypes.ENUM_DEF};
108+
}
109+
110+
@Override
111+
public int[] getRequiredTokens() {
112+
return getAcceptableTokens();
113+
}
114+
115+
@Override
116+
public void visitToken(DetailAST enumDef) {
117+
final DetailAST enumConstBlock = enumDef.findFirstToken(TokenTypes.OBJBLOCK);
118+
119+
final DetailAST enumConstLeft = enumConstBlock.findFirstToken(TokenTypes.LCURLY);
120+
final DetailAST enumConstRight = enumConstBlock.findFirstToken(TokenTypes.RCURLY);
121+
122+
// Only check, if block is multi-line and there are more than one enum constants
123+
if (enumConstLeft.getLineNo() != enumConstRight.getLineNo()
124+
&& enumConstBlock.getChildCount(TokenTypes.ENUM_CONSTANT_DEF) > 1) {
125+
final DetailAST constant = enumConstBlock.findFirstToken(TokenTypes.ENUM_CONSTANT_DEF);
126+
final DetailAST lastComma = getLastComma(constant);
127+
128+
final DetailAST next = lastComma.getNextSibling();
129+
final int type = next.getType();
130+
131+
if (type == TokenTypes.SEMI) {
132+
if (next.getLineNo() == lastComma.getLineNo()) {
133+
// semi on the same line as last comma
134+
log(next.getLineNo(), MSG_KEY_SEMI);
135+
}
136+
137+
}
138+
else if (type == TokenTypes.ENUM_CONSTANT_DEF) {
139+
log(next.getLineNo(), MSG_KEY);
140+
}
141+
}
142+
}
143+
144+
/**
145+
* Get the last comma in a series of siblings.
146+
*
147+
* @param start the first sibling
148+
* @return the AST containing the last comma
149+
*/
150+
private DetailAST getLastComma(DetailAST start) {
151+
DetailAST comma = null;
152+
for (DetailAST ast = start; ast != null; ast = ast.getNextSibling()) {
153+
if (ast.getType() == TokenTypes.COMMA) {
154+
comma = ast;
155+
}
156+
}
157+
return comma;
158+
}
159+
}

sevntu-checks/src/main/java/com/github/sevntu/checkstyle/checks/coding/NumericLiteralNeedsUnderscoreCheck.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ protected enum NumericType {
166166
/**
167167
* Denotes a binary literal. For example, 0b0011
168168
*/
169-
BINARY;
169+
BINARY,
170170

171171
}
172172

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ custom.declaration.order.interface=Interface definition in wrong order. Expected
1212
custom.declaration.order.enum=Enum definition in wrong order. Expected ''{0}'' then ''{1}''.
1313
custom.declaration.order.invalid.setter=Setter ''{0}'' is in wrong order. Should be right after ''{1}''.
1414
diamond.operator.for.variable.definition = Diamond operator expected.
15-
empty.public.ctor=This empty public constructor is useless.
1615
either.log.or.throw=Either log or throw exception.
16+
empty.public.ctor=This empty public constructor is useless.
17+
enum.trailing.comma=Enum constant should contain trailing comma.
18+
enum.trailing.comma.semi=Enum constant should contain trailing comma without semi-colon afterwards.
1719
finalize.implementation.missed.try.finally = finalize() method should contain try-finally block with super.finalize() call inside finally block.
1820
finalize.implementation.public = finalize() method should have a "protected" visibility.
1921
finalize.implementation.useless = finalize() method is useless: it does nothing except for calling super.finalize().
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
////////////////////////////////////////////////////////////////////////////////
2+
// checkstyle: Checks Java source code for adherence to a set of rules.
3+
// Copyright (C) 2001-2018 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 static com.github.sevntu.checkstyle.checks.coding.EnumTrailingCommaCheck.MSG_KEY;
23+
import static com.github.sevntu.checkstyle.checks.coding.EnumTrailingCommaCheck.MSG_KEY_SEMI;
24+
25+
import org.junit.Assert;
26+
import org.junit.Test;
27+
28+
import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
29+
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
30+
31+
public class EnumTrailingCommaCheckTest extends AbstractModuleTestSupport {
32+
33+
@Override
34+
protected String getPackageLocation() {
35+
return "com/github/sevntu/checkstyle/checks/coding";
36+
}
37+
38+
@Test
39+
public void testDefault() throws Exception {
40+
final DefaultConfiguration checkConfig =
41+
createModuleConfig(EnumTrailingCommaCheck.class);
42+
final String[] expected = {
43+
"14: " + getCheckMessage(MSG_KEY),
44+
"20: " + getCheckMessage(MSG_KEY),
45+
"26: " + getCheckMessage(MSG_KEY_SEMI),
46+
};
47+
verify(checkConfig, getPath("InputEnumTrailingCommaCheck.java"), expected);
48+
}
49+
50+
@Test
51+
public void testTokensNotNull() {
52+
final EnumTrailingCommaCheck check = new EnumTrailingCommaCheck();
53+
Assert.assertNotNull(check.getAcceptableTokens());
54+
Assert.assertNotNull(check.getDefaultTokens());
55+
Assert.assertNotNull(check.getRequiredTokens());
56+
}
57+
}

sevntu-checks/src/test/java/com/github/sevntu/checkstyle/internal/CheckstyleRegressionTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ public class CheckstyleRegressionTest {
3636

3737
/** List of checks to suppress if we dynamically add it to the configuration. */
3838
private static final List<String> ADD_CHECK_SUPPRESSIONS = Arrays
39-
.asList("ReturnCountExtendedCheck");
39+
.asList(
40+
"EnumTrailingCommaCheck",
41+
"ReturnCountExtendedCheck"
42+
);
4043

4144
// -@cs[CyclomaticComplexity] Can't split
4245
@Test

sevntu-checks/src/test/java/com/github/sevntu/checkstyle/internal/CommitValidationTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ private static String getInvalidCommitMessageFormattingError(String commitId,
291291

292292
private enum CommitsResolutionMode {
293293

294-
BY_COUNTER, BY_LAST_COMMIT_AUTHOR
294+
BY_COUNTER,
295+
BY_LAST_COMMIT_AUTHOR,
295296

296297
}
297298

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.github.sevntu.checkstyle.checks.coding;
2+
3+
public interface InputEnumTrailingCommaCheck
4+
{
5+
enum E1 {
6+
ONE,
7+
TWO,
8+
THREE,
9+
}
10+
11+
enum E2 {
12+
ONE,
13+
TWO,
14+
THREE // violation
15+
}
16+
17+
enum E3 {
18+
ONE,
19+
TWO,
20+
THREE; // violation
21+
}
22+
23+
enum E4 {
24+
ONE,
25+
TWO,
26+
THREE,; // violation
27+
}
28+
29+
enum E5 {
30+
ONE,
31+
TWO,
32+
THREE,
33+
;
34+
}
35+
36+
// enums below are ignored by the check, but were added for completenes
37+
// Please don't remove, they are necessary for full cobertura branch coverage
38+
39+
// empty
40+
enum E6 {}
41+
42+
// single enum const, single-line block
43+
enum E7_1 { ONE }
44+
enum E7_2 { ONE; }
45+
enum E7_3 { ONE, }
46+
enum E7_4 { ONE,; }
47+
48+
// single enum const, multi-line block
49+
enum E8_1 {
50+
ONE
51+
}
52+
enum E8_2 {
53+
ONE;
54+
}
55+
enum E8_3 {
56+
ONE,
57+
}
58+
enum E8_4 {
59+
ONE,;
60+
}
61+
}

0 commit comments

Comments
 (0)