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