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