001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks; 021 022import java.util.Arrays; 023import java.util.Set; 024 025import antlr.collections.AST; 026import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 030import com.puppycrawl.tools.checkstyle.utils.TokenUtils; 031 032/** 033 * <p> 034 * Checks for restricted tokens beneath other tokens. 035 * </p> 036 * <p> 037 * Examples of how to configure the check: 038 * </p> 039 * <pre> 040 * <!-- String literal equality check --> 041 * <module name="DescendantToken"> 042 * <property name="tokens" value="EQUAL,NOT_EQUAL"/> 043 * <property name="limitedTokens" value="STRING_LITERAL"/> 044 * <property name="maximumNumber" value="0"/> 045 * <property name="maximumDepth" value="1"/> 046 * </module> 047 * 048 * <!-- Switch with no default --> 049 * <module name="DescendantToken"> 050 * <property name="tokens" value="LITERAL_SWITCH"/> 051 * <property name="maximumDepth" value="2"/> 052 * <property name="limitedTokens" value="LITERAL_DEFAULT"/> 053 * <property name="minimumNumber" value="1"/> 054 * </module> 055 * 056 * <!-- Assert statement may have side effects --> 057 * <module name="DescendantToken"> 058 * <property name="tokens" value="LITERAL_ASSERT"/> 059 * <property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC, 060 * POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN, 061 * BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN, 062 * METHOD_CALL"/> 063 * <property name="maximumNumber" value="0"/> 064 * </module> 065 * 066 * <!-- Initializer in for performs no setup - use while instead? --> 067 * <module name="DescendantToken"> 068 * <property name="tokens" value="FOR_INIT"/> 069 * <property name="limitedTokens" value="EXPR"/> 070 * <property name="minimumNumber" value="1"/> 071 * </module> 072 * 073 * <!-- Condition in for performs no check --> 074 * <module name="DescendantToken"> 075 * <property name="tokens" value="FOR_CONDITION"/> 076 * <property name="limitedTokens" value="EXPR"/> 077 * <property name="minimumNumber" value="1"/> 078 * </module> 079 * 080 * <!-- Switch within switch --> 081 * <module name="DescendantToken"> 082 * <property name="tokens" value="LITERAL_SWITCH"/> 083 * <property name="limitedTokens" value="LITERAL_SWITCH"/> 084 * <property name="maximumNumber" value="0"/> 085 * <property name="minimumDepth" value="1"/> 086 * </module> 087 * 088 * <!-- Return from within a catch or finally block --> 089 * <module name="DescendantToken"> 090 * <property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/> 091 * <property name="limitedTokens" value="LITERAL_RETURN"/> 092 * <property name="maximumNumber" value="0"/> 093 * </module> 094 * 095 * <!-- Try within catch or finally block --> 096 * <module name="DescendantToken"> 097 * <property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/> 098 * <property name="limitedTokens" value="LITERAL_TRY"/> 099 * <property name="maximumNumber" value="0"/> 100 * </module> 101 * 102 * <!-- Too many cases within a switch --> 103 * <module name="DescendantToken"> 104 * <property name="tokens" value="LITERAL_SWITCH"/> 105 * <property name="limitedTokens" value="LITERAL_CASE"/> 106 * <property name="maximumDepth" value="2"/> 107 * <property name="maximumNumber" value="10"/> 108 * </module> 109 * 110 * <!-- Too many local variables within a method --> 111 * <module name="DescendantToken"> 112 * <property name="tokens" value="METHOD_DEF"/> 113 * <property name="limitedTokens" value="VARIABLE_DEF"/> 114 * <property name="maximumDepth" value="2"/> 115 * <property name="maximumNumber" value="10"/> 116 * </module> 117 * 118 * <!-- Too many returns from within a method --> 119 * <module name="DescendantToken"> 120 * <property name="tokens" value="METHOD_DEF"/> 121 * <property name="limitedTokens" value="LITERAL_RETURN"/> 122 * <property name="maximumNumber" value="3"/> 123 * </module> 124 * 125 * <!-- Too many fields within an interface --> 126 * <module name="DescendantToken"> 127 * <property name="tokens" value="INTERFACE_DEF"/> 128 * <property name="limitedTokens" value="VARIABLE_DEF"/> 129 * <property name="maximumDepth" value="2"/> 130 * <property name="maximumNumber" value="0"/> 131 * </module> 132 * 133 * <!-- Limit the number of exceptions a method can throw --> 134 * <module name="DescendantToken"> 135 * <property name="tokens" value="LITERAL_THROWS"/> 136 * <property name="limitedTokens" value="IDENT"/> 137 * <property name="maximumNumber" value="1"/> 138 * </module> 139 * 140 * <!-- Limit the number of expressions in a method --> 141 * <module name="DescendantToken"> 142 * <property name="tokens" value="METHOD_DEF"/> 143 * <property name="limitedTokens" value="EXPR"/> 144 * <property name="maximumNumber" value="200"/> 145 * </module> 146 * 147 * <!-- Disallow empty statements --> 148 * <module name="DescendantToken"> 149 * <property name="tokens" value="EMPTY_STAT"/> 150 * <property name="limitedTokens" value="EMPTY_STAT"/> 151 * <property name="maximumNumber" value="0"/> 152 * <property name="maximumDepth" value="0"/> 153 * <property name="maximumMessage" 154 * value="Empty statement is not allowed."/> 155 * </module> 156 * 157 * <!-- Too many fields within a class --> 158 * <module name="DescendantToken"> 159 * <property name="tokens" value="CLASS_DEF"/> 160 * <property name="limitedTokens" value="VARIABLE_DEF"/> 161 * <property name="maximumDepth" value="2"/> 162 * <property name="maximumNumber" value="10"/> 163 * </module> 164 * </pre> 165 * 166 * @author Tim Tyler <tim@tt1.org> 167 * @author Rick Giles 168 */ 169@FileStatefulCheck 170public class DescendantTokenCheck extends AbstractCheck { 171 172 /** 173 * A key is pointing to the warning message text in "messages.properties" 174 * file. 175 */ 176 public static final String MSG_KEY_MIN = "descendant.token.min"; 177 178 /** 179 * A key is pointing to the warning message text in "messages.properties" 180 * file. 181 */ 182 public static final String MSG_KEY_MAX = "descendant.token.max"; 183 184 /** 185 * A key is pointing to the warning message text in "messages.properties" 186 * file. 187 */ 188 public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min"; 189 190 /** 191 * A key is pointing to the warning message text in "messages.properties" 192 * file. 193 */ 194 public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max"; 195 196 /** Minimum depth. */ 197 private int minimumDepth; 198 /** Maximum depth. */ 199 private int maximumDepth = Integer.MAX_VALUE; 200 /** Minimum number. */ 201 private int minimumNumber; 202 /** Maximum number. */ 203 private int maximumNumber = Integer.MAX_VALUE; 204 /** Whether to sum the number of tokens found. */ 205 private boolean sumTokenCounts; 206 /** Limited tokens. */ 207 private int[] limitedTokens = CommonUtils.EMPTY_INT_ARRAY; 208 /** Error message when minimum count not reached. */ 209 private String minimumMessage; 210 /** Error message when maximum count exceeded. */ 211 private String maximumMessage; 212 213 /** 214 * Counts of descendant tokens. 215 * Indexed by (token ID - 1) for performance. 216 */ 217 private int[] counts = CommonUtils.EMPTY_INT_ARRAY; 218 219 @Override 220 public int[] getDefaultTokens() { 221 return getRequiredTokens(); 222 } 223 224 @Override 225 public int[] getRequiredTokens() { 226 return CommonUtils.EMPTY_INT_ARRAY; 227 } 228 229 @Override 230 public void visitToken(DetailAST ast) { 231 //reset counts 232 Arrays.fill(counts, 0); 233 countTokens(ast, 0); 234 235 if (sumTokenCounts) { 236 logAsTotal(ast); 237 } 238 else { 239 logAsSeparated(ast); 240 } 241 } 242 243 /** 244 * Log violations for each Token. 245 * @param ast token 246 */ 247 private void logAsSeparated(DetailAST ast) { 248 // name of this token 249 final String name = TokenUtils.getTokenName(ast.getType()); 250 251 for (int element : limitedTokens) { 252 final int tokenCount = counts[element - 1]; 253 if (tokenCount < minimumNumber) { 254 final String descendantName = TokenUtils.getTokenName(element); 255 256 if (minimumMessage == null) { 257 minimumMessage = MSG_KEY_MIN; 258 } 259 log(ast.getLineNo(), ast.getColumnNo(), 260 minimumMessage, 261 String.valueOf(tokenCount), 262 String.valueOf(minimumNumber), 263 name, 264 descendantName); 265 } 266 if (tokenCount > maximumNumber) { 267 final String descendantName = TokenUtils.getTokenName(element); 268 269 if (maximumMessage == null) { 270 maximumMessage = MSG_KEY_MAX; 271 } 272 log(ast.getLineNo(), ast.getColumnNo(), 273 maximumMessage, 274 String.valueOf(tokenCount), 275 String.valueOf(maximumNumber), 276 name, 277 descendantName); 278 } 279 } 280 } 281 282 /** 283 * Log validation as one violation. 284 * @param ast current token 285 */ 286 private void logAsTotal(DetailAST ast) { 287 // name of this token 288 final String name = TokenUtils.getTokenName(ast.getType()); 289 290 int total = 0; 291 for (int element : limitedTokens) { 292 total += counts[element - 1]; 293 } 294 if (total < minimumNumber) { 295 if (minimumMessage == null) { 296 minimumMessage = MSG_KEY_SUM_MIN; 297 } 298 log(ast.getLineNo(), ast.getColumnNo(), 299 minimumMessage, 300 String.valueOf(total), 301 String.valueOf(minimumNumber), name); 302 } 303 if (total > maximumNumber) { 304 if (maximumMessage == null) { 305 maximumMessage = MSG_KEY_SUM_MAX; 306 } 307 log(ast.getLineNo(), ast.getColumnNo(), 308 maximumMessage, 309 String.valueOf(total), 310 String.valueOf(maximumNumber), name); 311 } 312 } 313 314 /** 315 * Counts the number of occurrences of descendant tokens. 316 * @param ast the root token for descendants. 317 * @param depth the maximum depth of the counted descendants. 318 */ 319 private void countTokens(AST ast, int depth) { 320 if (depth <= maximumDepth) { 321 //update count 322 if (depth >= minimumDepth) { 323 final int type = ast.getType(); 324 if (type <= counts.length) { 325 counts[type - 1]++; 326 } 327 } 328 AST child = ast.getFirstChild(); 329 final int nextDepth = depth + 1; 330 while (child != null) { 331 countTokens(child, nextDepth); 332 child = child.getNextSibling(); 333 } 334 } 335 } 336 337 @Override 338 public int[] getAcceptableTokens() { 339 // Any tokens set by property 'tokens' are acceptable 340 final Set<String> tokenNames = getTokenNames(); 341 final int[] result = new int[tokenNames.size()]; 342 int index = 0; 343 for (String name : tokenNames) { 344 result[index] = TokenUtils.getTokenId(name); 345 index++; 346 } 347 return result; 348 } 349 350 /** 351 * Sets the tokens which occurrence as descendant is limited. 352 * @param limitedTokensParam - list of tokens to ignore. 353 */ 354 public void setLimitedTokens(String... limitedTokensParam) { 355 limitedTokens = new int[limitedTokensParam.length]; 356 357 int maxToken = 0; 358 for (int i = 0; i < limitedTokensParam.length; i++) { 359 limitedTokens[i] = TokenUtils.getTokenId(limitedTokensParam[i]); 360 if (limitedTokens[i] >= maxToken + 1) { 361 maxToken = limitedTokens[i]; 362 } 363 } 364 counts = new int[maxToken]; 365 } 366 367 /** 368 * Sets the minimum depth for descendant counts. 369 * @param minimumDepth the minimum depth for descendant counts. 370 */ 371 public void setMinimumDepth(int minimumDepth) { 372 this.minimumDepth = minimumDepth; 373 } 374 375 /** 376 * Sets the maximum depth for descendant counts. 377 * @param maximumDepth the maximum depth for descendant counts. 378 */ 379 public void setMaximumDepth(int maximumDepth) { 380 this.maximumDepth = maximumDepth; 381 } 382 383 /** 384 * Sets a minimum count for descendants. 385 * @param minimumNumber the minimum count for descendants. 386 */ 387 public void setMinimumNumber(int minimumNumber) { 388 this.minimumNumber = minimumNumber; 389 } 390 391 /** 392 * Sets a maximum count for descendants. 393 * @param maximumNumber the maximum count for descendants. 394 */ 395 public void setMaximumNumber(int maximumNumber) { 396 this.maximumNumber = maximumNumber; 397 } 398 399 /** 400 * Sets the error message for minimum count not reached. 401 * @param message the error message for minimum count not reached. 402 * Used as a {@code MessageFormat} pattern with arguments 403 * <ul> 404 * <li>{0} - token count</li> 405 * <li>{1} - minimum number</li> 406 * <li>{2} - name of token</li> 407 * <li>{3} - name of limited token</li> 408 * </ul> 409 */ 410 public void setMinimumMessage(String message) { 411 minimumMessage = message; 412 } 413 414 /** 415 * Sets the error message for maximum count exceeded. 416 * @param message the error message for maximum count exceeded. 417 * Used as a {@code MessageFormat} pattern with arguments 418 * <ul> 419 * <li>{0} - token count</li> 420 * <li>{1} - maximum number</li> 421 * <li>{2} - name of token</li> 422 * <li>{3} - name of limited token</li> 423 * </ul> 424 */ 425 426 public void setMaximumMessage(String message) { 427 maximumMessage = message; 428 } 429 430 /** 431 * Sets whether to use the sum of the tokens found, rather than the 432 * individual counts. 433 * @param sum whether to use the sum. 434 */ 435 public void setSumTokenCounts(boolean sum) { 436 sumTokenCounts = sum; 437 } 438 439}