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