Skip to content

Commit 57428dc

Browse files
committed
Issue #464: Check for trailing commas on enums
1 parent 0c7b77a commit 57428dc

File tree

11 files changed

+302
-4
lines changed

11 files changed

+302
-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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,5 +556,12 @@
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+
</rule-metadata>
565+
559566
</rule-group-metadata>
560567
</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: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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. This is similar to
31+
* <a href="https://checkstyle.org/config_coding.html#ArrayTrailingComma">ArrayTrailingComma</a>.
32+
*
33+
* <p>The following is a normal enum type declaration:
34+
* <pre>
35+
* enum Type {
36+
* ALPHA,
37+
* BETA,
38+
* GAMMA
39+
* }
40+
* </pre>
41+
*
42+
* <p>However, if you want to append something to the list, you would need to change the line
43+
* containing the last enum constant:
44+
*
45+
* <pre>
46+
* enum Type {
47+
* ALPHA,
48+
* BETA,
49+
* GAMMA, // changed due to the ','
50+
* DELTA // new line
51+
* }
52+
* </pre>
53+
*
54+
* <p>This check makes sure that also the last enum constant has a trailing comma, which is
55+
* valid according to the Java Spec (see <a
56+
* href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.1">Enum Constants</a>)
57+
*
58+
* <pre>
59+
* enum Type {
60+
* ALPHA,
61+
* BETA,
62+
* GAMMA,
63+
* DELTA, // removing this comma will result in a violation with the check activated
64+
* }
65+
* </pre>
66+
*
67+
* <p>However, you could also add a semicolon behind that comma on the same line, which would raise
68+
* a violation
69+
*
70+
* <pre>
71+
* enum Type {
72+
* ALPHA,
73+
* BETA,
74+
* GAMMA,
75+
* DELTA,; // violation
76+
* }
77+
* </pre>
78+
* In this case the semicolon should be removed. However, if there is more in the enum body, the
79+
* semicolon should be placed on a line by itself.
80+
*
81+
* <p>An example of how to configure the check is:
82+
* <pre>
83+
* &lt;module name="EnumTrailingComma"/&gt;
84+
* </pre>
85+
*
86+
* <p>Please note that using this check together with {@code NoWhitespaceBefore} or
87+
* {@code SeparatorWrap} may create conflicts with enums that contain a body:
88+
* this check enforces the semicolon on a separate line while {@code NoWhiteSpaceBefore} does
89+
* not allow the semicolon to be preceded with whitespace and {@code SeparatorWrap} expects the
90+
* semicolon to be on the same line as the last enum constant.
91+
*
92+
* @author <a href="[email protected]">Kariem Hussein</a>
93+
*/
94+
public class EnumTrailingCommaCheck extends AbstractCheck {
95+
96+
/** Key for warning message text in "messages.properties" file. */
97+
public static final String MSG_KEY = "enum.trailing.comma";
98+
99+
@Override
100+
public int[] getDefaultTokens() {
101+
return getAcceptableTokens();
102+
}
103+
104+
@Override
105+
public int[] getAcceptableTokens() {
106+
return new int[] {TokenTypes.ENUM_DEF};
107+
}
108+
109+
@Override
110+
public int[] getRequiredTokens() {
111+
return getAcceptableTokens();
112+
}
113+
114+
@Override
115+
public void visitToken(DetailAST enumDef) {
116+
final DetailAST enumConstBlock = enumDef.findFirstToken(TokenTypes.OBJBLOCK);
117+
118+
final DetailAST enumConstLeft = enumConstBlock.findFirstToken(TokenTypes.LCURLY);
119+
120+
DetailAST enumConstEnd = enumConstBlock.findFirstToken(TokenTypes.SEMI);
121+
if (enumConstEnd == null) {
122+
enumConstEnd = enumConstBlock.findFirstToken(TokenTypes.RCURLY);
123+
}
124+
125+
// Only check, if block is multi-line and there are more than one enum constants
126+
if (enumConstLeft.getLineNo() != enumConstEnd.getLineNo()
127+
&& enumConstBlock.getChildCount(TokenTypes.ENUM_CONSTANT_DEF) > 1) {
128+
129+
final DetailAST lastSibling = enumConstEnd.getPreviousSibling();
130+
if (lastSibling.getType() != TokenTypes.COMMA) {
131+
// constant definition does not end in a comma
132+
log(lastSibling, MSG_KEY);
133+
}
134+
else if (enumConstEnd.getType() == TokenTypes.SEMI
135+
&& enumConstEnd.getLineNo() == lastSibling.getLineNo()) {
136+
// constant definition ends in a comma, but is followed by semi on same line
137+
log(enumConstEnd, MSG_KEY);
138+
}
139+
}
140+
}
141+
142+
}

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ 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 be followed by only a trailing comma in the line.
1718
finalize.implementation.missed.try.finally = finalize() method should contain try-finally block with super.finalize() call inside finally block.
1819
finalize.implementation.public = finalize() method should have a "protected" visibility.
1920
finalize.implementation.useless = finalize() method is useless: it does nothing except for calling super.finalize().
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
24+
import org.junit.Assert;
25+
import org.junit.Test;
26+
27+
import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
28+
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
29+
30+
public class EnumTrailingCommaCheckTest extends AbstractModuleTestSupport {
31+
32+
@Override
33+
protected String getPackageLocation() {
34+
return "com/github/sevntu/checkstyle/checks/coding";
35+
}
36+
37+
@Test
38+
public void testDefault() throws Exception {
39+
final DefaultConfiguration checkConfig =
40+
createModuleConfig(EnumTrailingCommaCheck.class);
41+
final String[] expected = {
42+
"14:9: " + getCheckMessage(MSG_KEY),
43+
"20:9: " + getCheckMessage(MSG_KEY),
44+
"26:15: " + getCheckMessage(MSG_KEY),
45+
};
46+
verify(checkConfig, getPath("InputEnumTrailingCommaCheck.java"), expected);
47+
}
48+
49+
@Test
50+
public void testTokensNotNull() {
51+
final EnumTrailingCommaCheck check = new EnumTrailingCommaCheck();
52+
Assert.assertNotNull(check.getAcceptableTokens());
53+
Assert.assertNotNull(check.getDefaultTokens());
54+
Assert.assertNotNull(check.getRequiredTokens());
55+
}
56+
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ 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+
// conflicting with NoWhitespaceBefore, SeparatorWrap
41+
"EnumTrailingCommaCheck",
42+
43+
"ReturnCountExtendedCheck"
44+
);
4045

4146
// -@cs[CyclomaticComplexity] Can't split
4247
@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: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
62+
// check ignores comments
63+
enum E9_1 {
64+
ONE, // this is ok
65+
TWO,
66+
THREE,
67+
}
68+
enum E9_2 {
69+
ONE,
70+
TWO /* this is also ok */ ,
71+
THREE,
72+
;
73+
}
74+
}

0 commit comments

Comments
 (0)