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