1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks; 21 22 import java.util.Arrays; 23 import java.util.Set; 24 25 import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 27 import com.puppycrawl.tools.checkstyle.api.DetailAST; 28 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 29 import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 30 31 /** 32 * <p> 33 * Checks for restricted tokens beneath other tokens. 34 * </p> 35 * <p> 36 * Examples of how to configure the check: 37 * </p> 38 * <pre> 39 * <!-- String literal equality check --> 40 * <module name="DescendantToken"> 41 * <property name="tokens" value="EQUAL,NOT_EQUAL"/> 42 * <property name="limitedTokens" value="STRING_LITERAL"/> 43 * <property name="maximumNumber" value="0"/> 44 * <property name="maximumDepth" value="1"/> 45 * </module> 46 * 47 * <!-- Switch with no default --> 48 * <module name="DescendantToken"> 49 * <property name="tokens" value="LITERAL_SWITCH"/> 50 * <property name="maximumDepth" value="2"/> 51 * <property name="limitedTokens" value="LITERAL_DEFAULT"/> 52 * <property name="minimumNumber" value="1"/> 53 * </module> 54 * 55 * <!-- Assert statement may have side effects --> 56 * <module name="DescendantToken"> 57 * <property name="tokens" value="LITERAL_ASSERT"/> 58 * <property name="limitedTokens" value="ASSIGN,DEC,INC,POST_DEC, 59 * POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN, 60 * BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN, 61 * METHOD_CALL"/> 62 * <property name="maximumNumber" value="0"/> 63 * </module> 64 * 65 * <!-- Initializer in for performs no setup - use while instead? --> 66 * <module name="DescendantToken"> 67 * <property name="tokens" value="FOR_INIT"/> 68 * <property name="limitedTokens" value="EXPR"/> 69 * <property name="minimumNumber" value="1"/> 70 * </module> 71 * 72 * <!-- Condition in for performs no check --> 73 * <module name="DescendantToken"> 74 * <property name="tokens" value="FOR_CONDITION"/> 75 * <property name="limitedTokens" value="EXPR"/> 76 * <property name="minimumNumber" value="1"/> 77 * </module> 78 * 79 * <!-- Switch within switch --> 80 * <module name="DescendantToken"> 81 * <property name="tokens" value="LITERAL_SWITCH"/> 82 * <property name="limitedTokens" value="LITERAL_SWITCH"/> 83 * <property name="maximumNumber" value="0"/> 84 * <property name="minimumDepth" value="1"/> 85 * </module> 86 * 87 * <!-- Return from within a catch or finally block --> 88 * <module name="DescendantToken"> 89 * <property name="tokens" value="LITERAL_FINALLY,LITERAL_CATCH"/> 90 * <property name="limitedTokens" value="LITERAL_RETURN"/> 91 * <property name="maximumNumber" value="0"/> 92 * </module> 93 * 94 * <!-- Try within catch or finally block --> 95 * <module name="DescendantToken"> 96 * <property name="tokens" value="LITERAL_CATCH,LITERAL_FINALLY"/> 97 * <property name="limitedTokens" value="LITERAL_TRY"/> 98 * <property name="maximumNumber" value="0"/> 99 * </module> 100 * 101 * <!-- Too many cases within a switch --> 102 * <module name="DescendantToken"> 103 * <property name="tokens" value="LITERAL_SWITCH"/> 104 * <property name="limitedTokens" value="LITERAL_CASE"/> 105 * <property name="maximumDepth" value="2"/> 106 * <property name="maximumNumber" value="10"/> 107 * </module> 108 * 109 * <!-- Too many local variables within a method --> 110 * <module name="DescendantToken"> 111 * <property name="tokens" value="METHOD_DEF"/> 112 * <property name="limitedTokens" value="VARIABLE_DEF"/> 113 * <property name="maximumDepth" value="2"/> 114 * <property name="maximumNumber" value="10"/> 115 * </module> 116 * 117 * <!-- Too many returns from within a method --> 118 * <module name="DescendantToken"> 119 * <property name="tokens" value="METHOD_DEF"/> 120 * <property name="limitedTokens" value="LITERAL_RETURN"/> 121 * <property name="maximumNumber" value="3"/> 122 * </module> 123 * 124 * <!-- Too many fields within an interface --> 125 * <module name="DescendantToken"> 126 * <property name="tokens" value="INTERFACE_DEF"/> 127 * <property name="limitedTokens" value="VARIABLE_DEF"/> 128 * <property name="maximumDepth" value="2"/> 129 * <property name="maximumNumber" value="0"/> 130 * </module> 131 * 132 * <!-- Limit the number of exceptions a method can throw --> 133 * <module name="DescendantToken"> 134 * <property name="tokens" value="LITERAL_THROWS"/> 135 * <property name="limitedTokens" value="IDENT"/> 136 * <property name="maximumNumber" value="1"/> 137 * </module> 138 * 139 * <!-- Limit the number of expressions in a method --> 140 * <module name="DescendantToken"> 141 * <property name="tokens" value="METHOD_DEF"/> 142 * <property name="limitedTokens" value="EXPR"/> 143 * <property name="maximumNumber" value="200"/> 144 * </module> 145 * 146 * <!-- Disallow empty statements --> 147 * <module name="DescendantToken"> 148 * <property name="tokens" value="EMPTY_STAT"/> 149 * <property name="limitedTokens" value="EMPTY_STAT"/> 150 * <property name="maximumNumber" value="0"/> 151 * <property name="maximumDepth" value="0"/> 152 * <property name="maximumMessage" 153 * value="Empty statement is not allowed."/> 154 * </module> 155 * 156 * <!-- Too many fields within a class --> 157 * <module name="DescendantToken"> 158 * <property name="tokens" value="CLASS_DEF"/> 159 * <property name="limitedTokens" value="VARIABLE_DEF"/> 160 * <property name="maximumDepth" value="2"/> 161 * <property name="maximumNumber" value="10"/> 162 * </module> 163 * </pre> 164 * 165 */ 166 @FileStatefulCheck 167 public class DescendantTokenCheck extends AbstractCheck { 168 169 /** 170 * A key is pointing to the warning message text in "messages.properties" 171 * file. 172 */ 173 public static final String MSG_KEY_MIN = "descendant.token.min"; 174 175 /** 176 * A key is pointing to the warning message text in "messages.properties" 177 * file. 178 */ 179 public static final String MSG_KEY_MAX = "descendant.token.max"; 180 181 /** 182 * A key is pointing to the warning message text in "messages.properties" 183 * file. 184 */ 185 public static final String MSG_KEY_SUM_MIN = "descendant.token.sum.min"; 186 187 /** 188 * A key is pointing to the warning message text in "messages.properties" 189 * file. 190 */ 191 public static final String MSG_KEY_SUM_MAX = "descendant.token.sum.max"; 192 193 /** Minimum depth. */ 194 private int minimumDepth; 195 /** Maximum depth. */ 196 private int maximumDepth = Integer.MAX_VALUE; 197 /** Minimum number. */ 198 private int minimumNumber; 199 /** Maximum number. */ 200 private int maximumNumber = Integer.MAX_VALUE; 201 /** Whether to sum the number of tokens found. */ 202 private boolean sumTokenCounts; 203 /** Limited tokens. */ 204 private int[] limitedTokens = CommonUtil.EMPTY_INT_ARRAY; 205 /** Error message when minimum count not reached. */ 206 private String minimumMessage; 207 /** Error message when maximum count exceeded. */ 208 private String maximumMessage; 209 210 /** 211 * Counts of descendant tokens. 212 * Indexed by (token ID - 1) for performance. 213 */ 214 private int[] counts = CommonUtil.EMPTY_INT_ARRAY; 215 216 @Override 217 public int[] getDefaultTokens() { 218 return getRequiredTokens(); 219 } 220 221 @Override 222 public int[] getRequiredTokens() { 223 return CommonUtil.EMPTY_INT_ARRAY; 224 } 225 226 @Override 227 public void visitToken(DetailAST ast) { 228 //reset counts 229 Arrays.fill(counts, 0); 230 countTokens(ast, 0); 231 232 if (sumTokenCounts) { 233 logAsTotal(ast); 234 } 235 else { 236 logAsSeparated(ast); 237 } 238 } 239 240 /** 241 * Log violations for each Token. 242 * @param ast token 243 */ 244 private void logAsSeparated(DetailAST ast) { 245 // name of this token 246 final String name = TokenUtil.getTokenName(ast.getType()); 247 248 for (int element : limitedTokens) { 249 final int tokenCount = counts[element - 1]; 250 if (tokenCount < minimumNumber) { 251 final String descendantName = TokenUtil.getTokenName(element); 252 253 if (minimumMessage == null) { 254 minimumMessage = MSG_KEY_MIN; 255 } 256 log(ast, 257 minimumMessage, 258 String.valueOf(tokenCount), 259 String.valueOf(minimumNumber), 260 name, 261 descendantName); 262 } 263 if (tokenCount > maximumNumber) { 264 final String descendantName = TokenUtil.getTokenName(element); 265 266 if (maximumMessage == null) { 267 maximumMessage = MSG_KEY_MAX; 268 } 269 log(ast, 270 maximumMessage, 271 String.valueOf(tokenCount), 272 String.valueOf(maximumNumber), 273 name, 274 descendantName); 275 } 276 } 277 } 278 279 /** 280 * Log validation as one violation. 281 * @param ast current token 282 */ 283 private void logAsTotal(DetailAST ast) { 284 // name of this token 285 final String name = TokenUtil.getTokenName(ast.getType()); 286 287 int total = 0; 288 for (int element : limitedTokens) { 289 total += counts[element - 1]; 290 } 291 if (total < minimumNumber) { 292 if (minimumMessage == null) { 293 minimumMessage = MSG_KEY_SUM_MIN; 294 } 295 log(ast, 296 minimumMessage, 297 String.valueOf(total), 298 String.valueOf(minimumNumber), name); 299 } 300 if (total > maximumNumber) { 301 if (maximumMessage == null) { 302 maximumMessage = MSG_KEY_SUM_MAX; 303 } 304 log(ast, 305 maximumMessage, 306 String.valueOf(total), 307 String.valueOf(maximumNumber), name); 308 } 309 } 310 311 /** 312 * Counts the number of occurrences of descendant tokens. 313 * @param ast the root token for descendants. 314 * @param depth the maximum depth of the counted descendants. 315 */ 316 private void countTokens(DetailAST ast, int depth) { 317 if (depth <= maximumDepth) { 318 //update count 319 if (depth >= minimumDepth) { 320 final int type = ast.getType(); 321 if (type <= counts.length) { 322 counts[type - 1]++; 323 } 324 } 325 DetailAST child = ast.getFirstChild(); 326 final int nextDepth = depth + 1; 327 while (child != null) { 328 countTokens(child, nextDepth); 329 child = child.getNextSibling(); 330 } 331 } 332 } 333 334 @Override 335 public int[] getAcceptableTokens() { 336 // Any tokens set by property 'tokens' are acceptable 337 final Set<String> tokenNames = getTokenNames(); 338 final int[] result = new int[tokenNames.size()]; 339 int index = 0; 340 for (String name : tokenNames) { 341 result[index] = TokenUtil.getTokenId(name); 342 index++; 343 } 344 return result; 345 } 346 347 /** 348 * Sets the tokens which occurrence as descendant is limited. 349 * @param limitedTokensParam - list of tokens to ignore. 350 */ 351 public void setLimitedTokens(String... limitedTokensParam) { 352 limitedTokens = new int[limitedTokensParam.length]; 353 354 int maxToken = 0; 355 for (int i = 0; i < limitedTokensParam.length; i++) { 356 limitedTokens[i] = TokenUtil.getTokenId(limitedTokensParam[i]); 357 if (limitedTokens[i] >= maxToken + 1) { 358 maxToken = limitedTokens[i]; 359 } 360 } 361 counts = new int[maxToken]; 362 } 363 364 /** 365 * Sets the minimum depth for descendant counts. 366 * @param minimumDepth the minimum depth for descendant counts. 367 */ 368 public void setMinimumDepth(int minimumDepth) { 369 this.minimumDepth = minimumDepth; 370 } 371 372 /** 373 * Sets the maximum depth for descendant counts. 374 * @param maximumDepth the maximum depth for descendant counts. 375 */ 376 public void setMaximumDepth(int maximumDepth) { 377 this.maximumDepth = maximumDepth; 378 } 379 380 /** 381 * Sets a minimum count for descendants. 382 * @param minimumNumber the minimum count for descendants. 383 */ 384 public void setMinimumNumber(int minimumNumber) { 385 this.minimumNumber = minimumNumber; 386 } 387 388 /** 389 * Sets a maximum count for descendants. 390 * @param maximumNumber the maximum count for descendants. 391 */ 392 public void setMaximumNumber(int maximumNumber) { 393 this.maximumNumber = maximumNumber; 394 } 395 396 /** 397 * Sets the error message for minimum count not reached. 398 * @param message the error message for minimum count not reached. 399 * Used as a {@code MessageFormat} pattern with arguments 400 * <ul> 401 * <li>{0} - token count</li> 402 * <li>{1} - minimum number</li> 403 * <li>{2} - name of token</li> 404 * <li>{3} - name of limited token</li> 405 * </ul> 406 */ 407 public void setMinimumMessage(String message) { 408 minimumMessage = message; 409 } 410 411 /** 412 * Sets the error message for maximum count exceeded. 413 * @param message the error message for maximum count exceeded. 414 * Used as a {@code MessageFormat} pattern with arguments 415 * <ul> 416 * <li>{0} - token count</li> 417 * <li>{1} - maximum number</li> 418 * <li>{2} - name of token</li> 419 * <li>{3} - name of limited token</li> 420 * </ul> 421 */ 422 423 public void setMaximumMessage(String message) { 424 maximumMessage = message; 425 } 426 427 /** 428 * Sets whether to use the sum of the tokens found, rather than the 429 * individual counts. 430 * @param sum whether to use the sum. 431 */ 432 public void setSumTokenCounts(boolean sum) { 433 sumTokenCounts = sum; 434 } 435 436 }