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 }