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