001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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; 023 024import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 028import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 029 030/** 031 * <p> 032 * Checks for restricted tokens beneath other tokens. 033 * </p> 034 * <p> 035 * WARNING: This is a very powerful and flexible check, but, at the same time, 036 * it is low-level and very implementation-dependent because its results depend 037 * on the grammar we use to build abstract syntax trees. Thus we recommend using 038 * other checks when they provide the desired functionality. Essentially, this 039 * check just works on the level of an abstract syntax tree and knows nothing 040 * about language structures. 041 * </p> 042 * <ul> 043 * <li> 044 * Property {@code limitedTokens} - Specify set of tokens with limited occurrences as descendants. 045 * Type is {@code java.lang.String[]}. 046 * Validation type is {@code tokenTypesSet}. 047 * Default value is {@code ""}. 048 * </li> 049 * <li> 050 * Property {@code minimumDepth} - Specify the minimum depth for descendant counts. 051 * Type is {@code int}. 052 * Default value is {@code 0}. 053 * </li> 054 * <li> 055 * Property {@code maximumDepth} - Specify the maximum depth for descendant counts. 056 * Type is {@code int}. 057 * Default value is {@code 2147483647}. 058 * </li> 059 * <li> 060 * Property {@code minimumNumber} - Specify a minimum count for descendants. 061 * Type is {@code int}. 062 * Default value is {@code 0}. 063 * </li> 064 * <li> 065 * Property {@code maximumNumber} - Specify a maximum count for descendants. 066 * Type is {@code int}. 067 * Default value is {@code 2147483647}. 068 * </li> 069 * <li> 070 * Property {@code sumTokenCounts} - Control whether the number of tokens found 071 * should be calculated from the sum of the individual token counts. 072 * Type is {@code boolean}. 073 * Default value is {@code false}. 074 * </li> 075 * <li> 076 * Property {@code minimumMessage} - Define the violation message 077 * when the minimum count is not reached. 078 * Type is {@code java.lang.String}. 079 * Default value is {@code null}. 080 * </li> 081 * <li> 082 * Property {@code maximumMessage} - Define the violation message 083 * when the maximum count is exceeded. 084 * Type is {@code java.lang.String}. 085 * Default value is {@code null}. 086 * </li> 087 * <li> 088 * Property {@code tokens} - tokens to check 089 * Type is {@code anyTokenTypesSet}. 090 * Default value is {@code ""}. 091 * </li> 092 * </ul> 093 * <p> 094 * To configure the check to produce a violation on a switch statement with no default case: 095 * </p> 096 * <pre> 097 * <module name="DescendantToken"> 098 * <property name="tokens" value="LITERAL_SWITCH"/> 099 * <property name="maximumDepth" value="2"/> 100 * <property name="limitedTokens" value="LITERAL_DEFAULT"/> 101 * <property name="minimumNumber" value="1"/> 102 * </module> 103 * </pre> 104 * <p> 105 * To configure the check to produce a violation on a condition in {@code for} 106 * which performs no check: 107 * </p> 108 * <pre> 109 * <module name="DescendantToken"> 110 * <property name="tokens" value="FOR_CONDITION"/> 111 * <property name="limitedTokens" value="EXPR"/> 112 * <property name="minimumNumber" value="1"/> 113 * </module> 114 * </pre> 115 * <p> 116 * To configure the check to produce a violation on comparing {@code this} with 117 * {@code null}(i.e. {@code this == null} and {@code this != null}): 118 * </p> 119 * <pre> 120 * <module name="DescendantToken"> 121 * <property name="tokens" value="EQUAL,NOT_EQUAL"/> 122 * <property name="limitedTokens" value="LITERAL_THIS,LITERAL_NULL"/> 123 * <property name="maximumNumber" value="1"/> 124 * <property name="maximumDepth" value="1"/> 125 * <property name="sumTokenCounts" value="true"/> 126 * </module> 127 * </pre> 128 * <p> 129 * To configure the check to produce a violation on a {@code String} literal equality check: 130 * </p> 131 * <pre> 132 * <module name="DescendantToken"> 133 * <property name="tokens" value="EQUAL,NOT_EQUAL"/> 134 * <property name="limitedTokens" value="STRING_LITERAL"/> 135 * <property name="maximumNumber" value="0"/> 136 * <property name="maximumDepth" value="1"/> 137 * </module> 138 * </pre> 139 * <p> 140 * To configure the check to produce a violation on an assert statement that may 141 * have side effects (formatted for browser display): 142 * </p> 143 * <pre> 144 * <module name="DescendantToken"> 145 * <property name="tokens" value="LITERAL_ASSERT"/> 146 * <property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC, 147 * POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN, 148 * BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN, 149 * METHOD_CALL"/> 150 * <property name="maximumNumber" value="0"/> 151 * </module> 152 * </pre> 153 * <p> 154 * To configure the check to produce a violation on an initializer in {@code for} 155 * performs no setup (where a {@code while} statement could be used instead): 156 * </p> 157 * <pre> 158 * <module name="DescendantToken"> 159 * <property name="tokens" value="FOR_INIT"/> 160 * <property name="limitedTokens" value="EXPR"/> 161 * <property name="minimumNumber" value="1"/> 162 * </module> 163 * </pre> 164 * <p> 165 * To configure the check to produce a violation on a switch that is nested in another switch: 166 * </p> 167 * <pre> 168 * <module name="DescendantToken"> 169 * <property name="tokens" value="LITERAL_SWITCH"/> 170 * <property name="limitedTokens" value="LITERAL_SWITCH"/> 171 * <property name="maximumNumber" value="0"/> 172 * <property name="minimumDepth" value="1"/> 173 * </module> 174 * </pre> 175 * <p> 176 * To configure the check to produce a violation on a return statement from 177 * within a catch or finally block: 178 * </p> 179 * <pre> 180 * <module name="DescendantToken"> 181 * <property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/> 182 * <property name="limitedTokens" value="LITERAL_RETURN"/> 183 * <property name="maximumNumber" value="0"/> 184 * </module> 185 * </pre> 186 * <p> 187 * To configure the check to produce a violation on a try statement within a catch or finally block: 188 * </p> 189 * <pre> 190 * <module name="DescendantToken"> 191 * <property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/> 192 * <property name="limitedTokens" value="LITERAL_TRY"/> 193 * <property name="maximumNumber" value="0"/> 194 * </module> 195 * </pre> 196 * <p> 197 * To configure the check to produce a violation on a switch with too many cases: 198 * </p> 199 * <pre> 200 * <module name="DescendantToken"> 201 * <property name="tokens" value="LITERAL_SWITCH"/> 202 * <property name="limitedTokens" value="LITERAL_CASE"/> 203 * <property name="maximumDepth" value="2"/> 204 * <property name="maximumNumber" value="10"/> 205 * </module> 206 * </pre> 207 * <p> 208 * To configure the check to produce a violation on a method with too many local variables: 209 * </p> 210 * <pre> 211 * <module name="DescendantToken"> 212 * <property name="tokens" value="METHOD_DEF"/> 213 * <property name="limitedTokens" value="VARIABLE_DEF"/> 214 * <property name="maximumDepth" value="2"/> 215 * <property name="maximumNumber" value="10"/> 216 * </module> 217 * </pre> 218 * <p> 219 * To configure the check to produce a violation on a method with too many returns: 220 * </p> 221 * <pre> 222 * <module name="DescendantToken"> 223 * <property name="tokens" value="METHOD_DEF"/> 224 * <property name="limitedTokens" value="LITERAL_RETURN"/> 225 * <property name="maximumNumber" value="3"/> 226 * </module> 227 * </pre> 228 * <p> 229 * To configure the check to produce a violation on an interface with too many fields: 230 * </p> 231 * <pre> 232 * <module name="DescendantToken"> 233 * <property name="tokens" value="INTERFACE_DEF"/> 234 * <property name="limitedTokens" value="VARIABLE_DEF"/> 235 * <property name="maximumDepth" value="2"/> 236 * <property name="maximumNumber" value="0"/> 237 * </module> 238 * </pre> 239 * <p> 240 * To configure the check to produce a violation on a method which throws too many exceptions: 241 * </p> 242 * <pre> 243 * <module name="DescendantToken"> 244 * <property name="tokens" value="LITERAL_THROWS"/> 245 * <property name="limitedTokens" value="IDENT"/> 246 * <property name="maximumNumber" value="1"/> 247 * </module> 248 * </pre> 249 * <p> 250 * To configure the check to produce a violation on a method with too many expressions: 251 * </p> 252 * <pre> 253 * <module name="DescendantToken"> 254 * <property name="tokens" value="METHOD_DEF"/> 255 * <property name="limitedTokens" value="EXPR"/> 256 * <property name="maximumNumber" value="200"/> 257 * </module> 258 * </pre> 259 * <p> 260 * To configure the check to produce a violation on an empty statement: 261 * </p> 262 * <pre> 263 * <module name="DescendantToken"> 264 * <property name="tokens" value="EMPTY_STAT"/> 265 * <property name="limitedTokens" value="EMPTY_STAT"/> 266 * <property name="maximumNumber" value="0"/> 267 * <property name="maximumDepth" value="0"/> 268 * <property name="maximumMessage" 269 * value="Empty statement is not allowed."/> 270 * </module> 271 * </pre> 272 * <p> 273 * To configure the check to produce a violation on a class with too many fields: 274 * </p> 275 * <pre> 276 * <module name="DescendantToken"> 277 * <property name="tokens" value="CLASS_DEF"/> 278 * <property name="limitedTokens" value="VARIABLE_DEF"/> 279 * <property name="maximumDepth" value="2"/> 280 * <property name="maximumNumber" value="10"/> 281 * </module> 282 * </pre> 283 * <p> 284 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 285 * </p> 286 * <p> 287 * Violation Message Keys: 288 * </p> 289 * <ul> 290 * <li> 291 * {@code descendant.token.max} 292 * </li> 293 * <li> 294 * {@code descendant.token.min} 295 * </li> 296 * <li> 297 * {@code descendant.token.sum.max} 298 * </li> 299 * <li> 300 * {@code descendant.token.sum.min} 301 * </li> 302 * </ul> 303 * 304 * @since 3.2 305 */ 306@FileStatefulCheck 307public class DescendantTokenCheck extends AbstractCheck { 308 309 /** 310 * A key is pointing to the warning message text in "messages.properties" 311 * file. 312 */ 313 public static final String MSG_KEY_MIN = "descendant.token.min"; 314 315 /** 316 * A key is pointing to the warning message text in "messages.properties" 317 * file. 318 */ 319 public static final String MSG_KEY_MAX = "descendant.token.max"; 320 321 /** 322 * A key is pointing to the warning message text in "messages.properties" 323 * file. 324 */ 325 public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min"; 326 327 /** 328 * A key is pointing to the warning message text in "messages.properties" 329 * file. 330 */ 331 public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max"; 332 333 /** Specify the minimum depth for descendant counts. */ 334 private int minimumDepth; 335 /** Specify the maximum depth for descendant counts. */ 336 private int maximumDepth = Integer.MAX_VALUE; 337 /** Specify a minimum count for descendants. */ 338 private int minimumNumber; 339 /** Specify a maximum count for descendants. */ 340 private int maximumNumber = Integer.MAX_VALUE; 341 /** 342 * Control whether the number of tokens found should be calculated from 343 * the sum of the individual token counts. 344 */ 345 private boolean sumTokenCounts; 346 /** Specify set of tokens with limited occurrences as descendants. */ 347 private int[] limitedTokens = CommonUtil.EMPTY_INT_ARRAY; 348 /** Define the violation message when the minimum count is not reached. */ 349 private String minimumMessage; 350 /** Define the violation message when the maximum count is exceeded. */ 351 private String maximumMessage; 352 353 /** 354 * Counts of descendant tokens. 355 * Indexed by (token ID - 1) for performance. 356 */ 357 private int[] counts = CommonUtil.EMPTY_INT_ARRAY; 358 359 @Override 360 public int[] getAcceptableTokens() { 361 return TokenUtil.getAllTokenIds(); 362 } 363 364 @Override 365 public int[] getDefaultTokens() { 366 return getRequiredTokens(); 367 } 368 369 @Override 370 public int[] getRequiredTokens() { 371 return CommonUtil.EMPTY_INT_ARRAY; 372 } 373 374 @Override 375 public void visitToken(DetailAST ast) { 376 // reset counts 377 Arrays.fill(counts, 0); 378 countTokens(ast, 0); 379 380 if (sumTokenCounts) { 381 logAsTotal(ast); 382 } 383 else { 384 logAsSeparated(ast); 385 } 386 } 387 388 /** 389 * Log violations for each Token. 390 * 391 * @param ast token 392 */ 393 private void logAsSeparated(DetailAST ast) { 394 // name of this token 395 final String name = TokenUtil.getTokenName(ast.getType()); 396 397 for (int element : limitedTokens) { 398 final int tokenCount = counts[element - 1]; 399 if (tokenCount < minimumNumber) { 400 final String descendantName = TokenUtil.getTokenName(element); 401 402 if (minimumMessage == null) { 403 minimumMessage = MSG_KEY_MIN; 404 } 405 log(ast, 406 minimumMessage, 407 String.valueOf(tokenCount), 408 String.valueOf(minimumNumber), 409 name, 410 descendantName); 411 } 412 if (tokenCount > maximumNumber) { 413 final String descendantName = TokenUtil.getTokenName(element); 414 415 if (maximumMessage == null) { 416 maximumMessage = MSG_KEY_MAX; 417 } 418 log(ast, 419 maximumMessage, 420 String.valueOf(tokenCount), 421 String.valueOf(maximumNumber), 422 name, 423 descendantName); 424 } 425 } 426 } 427 428 /** 429 * Log validation as one violation. 430 * 431 * @param ast current token 432 */ 433 private void logAsTotal(DetailAST ast) { 434 // name of this token 435 final String name = TokenUtil.getTokenName(ast.getType()); 436 437 int total = 0; 438 for (int element : limitedTokens) { 439 total += counts[element - 1]; 440 } 441 if (total < minimumNumber) { 442 if (minimumMessage == null) { 443 minimumMessage = MSG_KEY_SUM_MIN; 444 } 445 log(ast, 446 minimumMessage, 447 String.valueOf(total), 448 String.valueOf(minimumNumber), name); 449 } 450 if (total > maximumNumber) { 451 if (maximumMessage == null) { 452 maximumMessage = MSG_KEY_SUM_MAX; 453 } 454 log(ast, 455 maximumMessage, 456 String.valueOf(total), 457 String.valueOf(maximumNumber), name); 458 } 459 } 460 461 /** 462 * Counts the number of occurrences of descendant tokens. 463 * 464 * @param ast the root token for descendants. 465 * @param depth the maximum depth of the counted descendants. 466 */ 467 private void countTokens(DetailAST ast, int depth) { 468 if (depth <= maximumDepth) { 469 // update count 470 if (depth >= minimumDepth) { 471 final int type = ast.getType(); 472 if (type <= counts.length) { 473 counts[type - 1]++; 474 } 475 } 476 DetailAST child = ast.getFirstChild(); 477 final int nextDepth = depth + 1; 478 while (child != null) { 479 countTokens(child, nextDepth); 480 child = child.getNextSibling(); 481 } 482 } 483 } 484 485 /** 486 * Setter to specify set of tokens with limited occurrences as descendants. 487 * 488 * @param limitedTokensParam - list of tokens to ignore. 489 */ 490 public void setLimitedTokens(String... limitedTokensParam) { 491 limitedTokens = new int[limitedTokensParam.length]; 492 493 int maxToken = 0; 494 for (int i = 0; i < limitedTokensParam.length; i++) { 495 limitedTokens[i] = TokenUtil.getTokenId(limitedTokensParam[i]); 496 if (limitedTokens[i] >= maxToken + 1) { 497 maxToken = limitedTokens[i]; 498 } 499 } 500 counts = new int[maxToken]; 501 } 502 503 /** 504 * Setter to specify the minimum depth for descendant counts. 505 * 506 * @param minimumDepth the minimum depth for descendant counts. 507 */ 508 public void setMinimumDepth(int minimumDepth) { 509 this.minimumDepth = minimumDepth; 510 } 511 512 /** 513 * Setter to specify the maximum depth for descendant counts. 514 * 515 * @param maximumDepth the maximum depth for descendant counts. 516 */ 517 public void setMaximumDepth(int maximumDepth) { 518 this.maximumDepth = maximumDepth; 519 } 520 521 /** 522 * Setter to specify a minimum count for descendants. 523 * 524 * @param minimumNumber the minimum count for descendants. 525 */ 526 public void setMinimumNumber(int minimumNumber) { 527 this.minimumNumber = minimumNumber; 528 } 529 530 /** 531 * Setter to specify a maximum count for descendants. 532 * 533 * @param maximumNumber the maximum count for descendants. 534 */ 535 public void setMaximumNumber(int maximumNumber) { 536 this.maximumNumber = maximumNumber; 537 } 538 539 /** 540 * Setter to define the violation message when the minimum count is not reached. 541 * 542 * @param message the violation message for minimum count not reached. 543 * Used as a {@code MessageFormat} pattern with arguments 544 * <ul> 545 * <li>{0} - token count</li> 546 * <li>{1} - minimum number</li> 547 * <li>{2} - name of token</li> 548 * <li>{3} - name of limited token</li> 549 * </ul> 550 */ 551 public void setMinimumMessage(String message) { 552 minimumMessage = message; 553 } 554 555 /** 556 * Setter to define the violation message when the maximum count is exceeded. 557 * 558 * @param message the violation message for maximum count exceeded. 559 * Used as a {@code MessageFormat} pattern with arguments 560 * <ul> 561 * <li>{0} - token count</li> 562 * <li>{1} - maximum number</li> 563 * <li>{2} - name of token</li> 564 * <li>{3} - name of limited token</li> 565 * </ul> 566 */ 567 568 public void setMaximumMessage(String message) { 569 maximumMessage = message; 570 } 571 572 /** 573 * Setter to control whether the number of tokens found should be calculated 574 * from the sum of the individual token counts. 575 * 576 * @param sum whether to use the sum. 577 */ 578 public void setSumTokenCounts(boolean sum) { 579 sumTokenCounts = sum; 580 } 581 582}