1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks;
21
22 import java.util.Optional;
23 import java.util.Set;
24 import java.util.regex.Pattern;
25
26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.FullIdent;
30 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 @FileStatefulCheck
49 public class UncommentedMainCheck
50 extends AbstractCheck {
51
52
53
54
55
56 public static final String MSG_KEY = "uncommented.main";
57
58
59 private static final Set<String> STRING_PARAMETER_NAMES = Set.of(
60 String[].class.getCanonicalName(),
61 String.class.getCanonicalName(),
62 String[].class.getSimpleName(),
63 String.class.getSimpleName()
64 );
65
66
67
68
69
70 private Pattern excludedClasses = Pattern.compile("^$");
71
72 private String currentClass;
73
74 private FullIdent packageName;
75
76 private int classDepth;
77
78
79
80
81
82
83
84
85 public void setExcludedClasses(Pattern excludedClasses) {
86 this.excludedClasses = excludedClasses;
87 }
88
89 @Override
90 public int[] getAcceptableTokens() {
91 return getRequiredTokens();
92 }
93
94 @Override
95 public int[] getDefaultTokens() {
96 return getRequiredTokens();
97 }
98
99 @Override
100 public int[] getRequiredTokens() {
101 return new int[] {
102 TokenTypes.METHOD_DEF,
103 TokenTypes.CLASS_DEF,
104 TokenTypes.PACKAGE_DEF,
105 TokenTypes.RECORD_DEF,
106 };
107 }
108
109 @Override
110 public void beginTree(DetailAST rootAST) {
111 packageName = FullIdent.createFullIdent(null);
112 classDepth = 0;
113 }
114
115 @Override
116 public void leaveToken(DetailAST ast) {
117 if (ast.getType() == TokenTypes.CLASS_DEF) {
118 classDepth--;
119 }
120 }
121
122 @Override
123 public void visitToken(DetailAST ast) {
124 switch (ast.getType()) {
125 case TokenTypes.PACKAGE_DEF -> visitPackageDef(ast);
126 case TokenTypes.RECORD_DEF, TokenTypes.CLASS_DEF -> visitClassOrRecordDef(ast);
127 case TokenTypes.METHOD_DEF -> visitMethodDef(ast);
128 default -> throw new IllegalStateException(ast.toString());
129 }
130 }
131
132
133
134
135
136
137 private void visitPackageDef(DetailAST packageDef) {
138 packageName = FullIdent.createFullIdent(packageDef.getLastChild()
139 .getPreviousSibling());
140 }
141
142
143
144
145
146
147 private void visitClassOrRecordDef(DetailAST classOrRecordDef) {
148
149
150 if (classDepth == 0) {
151 final DetailAST ident =
152 NullUtil.notNull(classOrRecordDef.findFirstToken(TokenTypes.IDENT));
153 currentClass = packageName.getText() + "." + ident.getText();
154 classDepth++;
155 }
156 }
157
158
159
160
161
162
163
164 private void visitMethodDef(DetailAST method) {
165 if (classDepth == 1
166
167 && checkClassName()
168 && checkName(method)
169 && checkModifiers(method)
170 && checkType(method)
171 && checkParams(method)) {
172 log(method, MSG_KEY);
173 }
174 }
175
176
177
178
179
180
181 private boolean checkClassName() {
182 return !excludedClasses.matcher(currentClass).find();
183 }
184
185
186
187
188
189
190
191 private static boolean checkName(DetailAST method) {
192 final DetailAST ident = NullUtil.notNull(method.findFirstToken(TokenTypes.IDENT));
193 return "main".equals(ident.getText());
194 }
195
196
197
198
199
200
201
202 private static boolean checkModifiers(DetailAST method) {
203 final DetailAST modifiers =
204 NullUtil.notNull(method.findFirstToken(TokenTypes.MODIFIERS));
205
206 return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null
207 && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
208 }
209
210
211
212
213
214
215
216 private static boolean checkType(DetailAST method) {
217 final DetailAST type =
218 NullUtil.notNull(method.findFirstToken(TokenTypes.TYPE)).getFirstChild();
219 return type.getType() == TokenTypes.LITERAL_VOID;
220 }
221
222
223
224
225
226
227
228 private static boolean checkParams(DetailAST method) {
229 boolean checkPassed = false;
230 final DetailAST params =
231 NullUtil.notNull(method.findFirstToken(TokenTypes.PARAMETERS));
232
233 if (params.getChildCount() == 1) {
234 final DetailAST parameterType =
235 NullUtil.notNull(params.getFirstChild()
236 .findFirstToken(TokenTypes.TYPE));
237 final boolean isArrayDeclaration =
238 parameterType.findFirstToken(TokenTypes.ARRAY_DECLARATOR) != null;
239 final Optional<DetailAST> varargs = Optional.ofNullable(
240 params.getFirstChild().findFirstToken(TokenTypes.ELLIPSIS));
241
242 if (isArrayDeclaration || varargs.isPresent()) {
243 checkPassed = isStringType(parameterType.getFirstChild());
244 }
245 }
246 return checkPassed;
247 }
248
249
250
251
252
253
254
255 private static boolean isStringType(DetailAST typeAst) {
256 final FullIdent type = FullIdent.createFullIdent(typeAst);
257 return STRING_PARAMETER_NAMES.contains(type.getText());
258 }
259
260 }