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.metrics; 021 022import java.util.ArrayDeque; 023import java.util.Deque; 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.api.TokenTypes; 029 030/** 031 * <p> 032 * Determines complexity of methods, classes and files by counting 033 * the Non Commenting Source Statements (NCSS). This check adheres to the 034 * <a href="http://www.kclee.de/clemens/java/javancss/#specification">specification</a> 035 * for the <a href="http://www.kclee.de/clemens/java/javancss/">JavaNCSS-Tool</a> 036 * written by <b>Chr. Clemens Lee</b>. 037 * </p> 038 * <p> 039 * Roughly said the NCSS metric is calculated by counting the source lines which are 040 * not comments, (nearly) equivalent to counting the semicolons and opening curly braces. 041 * </p> 042 * <p> 043 * The NCSS for a class is summarized from the NCSS of all its methods, the NCSS 044 * of its nested classes and the number of member variable declarations. 045 * </p> 046 * <p> 047 * The NCSS for a file is summarized from the ncss of all its top level classes, 048 * the number of imports and the package declaration. 049 * </p> 050 * <p> 051 * Rationale: Too large methods and classes are hard to read and costly to maintain. 052 * A large NCSS number often means that a method or class has too many responsibilities 053 * and/or functionalities which should be decomposed into smaller units. 054 * </p> 055 * <ul> 056 * <li> 057 * Property {@code methodMaximum} - Specify the maximum allowed number of 058 * non commenting lines in a method. 059 * Default value is {@code 50}. 060 * </li> 061 * <li> 062 * Property {@code classMaximum} - Specify the maximum allowed number of 063 * non commenting lines in a class. 064 * Default value is {@code 1500}. 065 * </li> 066 * <li> 067 * Property {@code fileMaximum} - Specify the maximum allowed number of 068 * non commenting lines in a file including all top level and nested classes. 069 * Default value is {@code 2000}. 070 * </li> 071 * </ul> 072 * <p> 073 * To configure the check: 074 * </p> 075 * <pre> 076 * <module name="JavaNCSS"/> 077 * </pre> 078 * <p> 079 * To configure the check with 40 allowed non commenting lines for a method: 080 * </p> 081 * <pre> 082 * <module name="JavaNCSS"> 083 * <property name="methodMaximum" value="40"/> 084 * </module> 085 * </pre> 086 * 087 * @since 3.5 088 */ 089// -@cs[AbbreviationAsWordInName] We can not change it as, 090// check's name is a part of API (used in configurations). 091@FileStatefulCheck 092public class JavaNCSSCheck extends AbstractCheck { 093 094 /** 095 * A key is pointing to the warning message text in "messages.properties" 096 * file. 097 */ 098 public static final String MSG_METHOD = "ncss.method"; 099 100 /** 101 * A key is pointing to the warning message text in "messages.properties" 102 * file. 103 */ 104 public static final String MSG_CLASS = "ncss.class"; 105 106 /** 107 * A key is pointing to the warning message text in "messages.properties" 108 * file. 109 */ 110 public static final String MSG_FILE = "ncss.file"; 111 112 /** Default constant for max file ncss. */ 113 private static final int FILE_MAX_NCSS = 2000; 114 115 /** Default constant for max file ncss. */ 116 private static final int CLASS_MAX_NCSS = 1500; 117 118 /** Default constant for max method ncss. */ 119 private static final int METHOD_MAX_NCSS = 50; 120 121 /** 122 * Specify the maximum allowed number of non commenting lines in a file 123 * including all top level and nested classes. 124 */ 125 private int fileMaximum = FILE_MAX_NCSS; 126 127 /** Specify the maximum allowed number of non commenting lines in a class. */ 128 private int classMaximum = CLASS_MAX_NCSS; 129 130 /** Specify the maximum allowed number of non commenting lines in a method. */ 131 private int methodMaximum = METHOD_MAX_NCSS; 132 133 /** List containing the stacked counters. */ 134 private Deque<Counter> counters; 135 136 @Override 137 public int[] getDefaultTokens() { 138 return getRequiredTokens(); 139 } 140 141 @Override 142 public int[] getRequiredTokens() { 143 return new int[] { 144 TokenTypes.CLASS_DEF, 145 TokenTypes.INTERFACE_DEF, 146 TokenTypes.METHOD_DEF, 147 TokenTypes.CTOR_DEF, 148 TokenTypes.INSTANCE_INIT, 149 TokenTypes.STATIC_INIT, 150 TokenTypes.PACKAGE_DEF, 151 TokenTypes.IMPORT, 152 TokenTypes.VARIABLE_DEF, 153 TokenTypes.CTOR_CALL, 154 TokenTypes.SUPER_CTOR_CALL, 155 TokenTypes.LITERAL_IF, 156 TokenTypes.LITERAL_ELSE, 157 TokenTypes.LITERAL_WHILE, 158 TokenTypes.LITERAL_DO, 159 TokenTypes.LITERAL_FOR, 160 TokenTypes.LITERAL_SWITCH, 161 TokenTypes.LITERAL_BREAK, 162 TokenTypes.LITERAL_CONTINUE, 163 TokenTypes.LITERAL_RETURN, 164 TokenTypes.LITERAL_THROW, 165 TokenTypes.LITERAL_SYNCHRONIZED, 166 TokenTypes.LITERAL_CATCH, 167 TokenTypes.LITERAL_FINALLY, 168 TokenTypes.EXPR, 169 TokenTypes.LABELED_STAT, 170 TokenTypes.LITERAL_CASE, 171 TokenTypes.LITERAL_DEFAULT, 172 }; 173 } 174 175 @Override 176 public int[] getAcceptableTokens() { 177 return getRequiredTokens(); 178 } 179 180 @Override 181 public void beginTree(DetailAST rootAST) { 182 counters = new ArrayDeque<>(); 183 184 //add a counter for the file 185 counters.push(new Counter()); 186 } 187 188 @Override 189 public void visitToken(DetailAST ast) { 190 final int tokenType = ast.getType(); 191 192 if (tokenType == TokenTypes.CLASS_DEF 193 || tokenType == TokenTypes.METHOD_DEF 194 || tokenType == TokenTypes.CTOR_DEF 195 || tokenType == TokenTypes.STATIC_INIT 196 || tokenType == TokenTypes.INSTANCE_INIT) { 197 //add a counter for this class/method 198 counters.push(new Counter()); 199 } 200 201 //check if token is countable 202 if (isCountable(ast)) { 203 //increment the stacked counters 204 counters.forEach(Counter::increment); 205 } 206 } 207 208 @Override 209 public void leaveToken(DetailAST ast) { 210 final int tokenType = ast.getType(); 211 if (tokenType == TokenTypes.METHOD_DEF 212 || tokenType == TokenTypes.CTOR_DEF 213 || tokenType == TokenTypes.STATIC_INIT 214 || tokenType == TokenTypes.INSTANCE_INIT) { 215 //pop counter from the stack 216 final Counter counter = counters.pop(); 217 218 final int count = counter.getCount(); 219 if (count > methodMaximum) { 220 log(ast, MSG_METHOD, count, methodMaximum); 221 } 222 } 223 else if (tokenType == TokenTypes.CLASS_DEF) { 224 //pop counter from the stack 225 final Counter counter = counters.pop(); 226 227 final int count = counter.getCount(); 228 if (count > classMaximum) { 229 log(ast, MSG_CLASS, count, classMaximum); 230 } 231 } 232 } 233 234 @Override 235 public void finishTree(DetailAST rootAST) { 236 //pop counter from the stack 237 final Counter counter = counters.pop(); 238 239 final int count = counter.getCount(); 240 if (count > fileMaximum) { 241 log(rootAST, MSG_FILE, count, fileMaximum); 242 } 243 } 244 245 /** 246 * Setter to specify the maximum allowed number of non commenting lines 247 * in a file including all top level and nested classes. 248 * 249 * @param fileMaximum 250 * the maximum ncss 251 */ 252 public void setFileMaximum(int fileMaximum) { 253 this.fileMaximum = fileMaximum; 254 } 255 256 /** 257 * Setter to specify the maximum allowed number of non commenting lines in a class. 258 * 259 * @param classMaximum 260 * the maximum ncss 261 */ 262 public void setClassMaximum(int classMaximum) { 263 this.classMaximum = classMaximum; 264 } 265 266 /** 267 * Setter to specify the maximum allowed number of non commenting lines in a method. 268 * 269 * @param methodMaximum 270 * the maximum ncss 271 */ 272 public void setMethodMaximum(int methodMaximum) { 273 this.methodMaximum = methodMaximum; 274 } 275 276 /** 277 * Checks if a token is countable for the ncss metric. 278 * 279 * @param ast 280 * the AST 281 * @return true if the token is countable 282 */ 283 private static boolean isCountable(DetailAST ast) { 284 boolean countable = true; 285 286 final int tokenType = ast.getType(); 287 288 //check if an expression is countable 289 if (tokenType == TokenTypes.EXPR) { 290 countable = isExpressionCountable(ast); 291 } 292 //check if an variable definition is countable 293 else if (tokenType == TokenTypes.VARIABLE_DEF) { 294 countable = isVariableDefCountable(ast); 295 } 296 return countable; 297 } 298 299 /** 300 * Checks if a variable definition is countable. 301 * 302 * @param ast the AST 303 * @return true if the variable definition is countable, false otherwise 304 */ 305 private static boolean isVariableDefCountable(DetailAST ast) { 306 boolean countable = false; 307 308 //count variable definitions only if they are direct child to a slist or 309 // object block 310 final int parentType = ast.getParent().getType(); 311 312 if (parentType == TokenTypes.SLIST 313 || parentType == TokenTypes.OBJBLOCK) { 314 final DetailAST prevSibling = ast.getPreviousSibling(); 315 316 //is countable if no previous sibling is found or 317 //the sibling is no COMMA. 318 //This is done because multiple assignment on one line are counted 319 // as 1 320 countable = prevSibling == null 321 || prevSibling.getType() != TokenTypes.COMMA; 322 } 323 324 return countable; 325 } 326 327 /** 328 * Checks if an expression is countable for the ncss metric. 329 * 330 * @param ast the AST 331 * @return true if the expression is countable, false otherwise 332 */ 333 private static boolean isExpressionCountable(DetailAST ast) { 334 final boolean countable; 335 336 //count expressions only if they are direct child to a slist (method 337 // body, for loop...) 338 //or direct child of label,if,else,do,while,for 339 final int parentType = ast.getParent().getType(); 340 switch (parentType) { 341 case TokenTypes.SLIST : 342 case TokenTypes.LABELED_STAT : 343 case TokenTypes.LITERAL_FOR : 344 case TokenTypes.LITERAL_DO : 345 case TokenTypes.LITERAL_WHILE : 346 case TokenTypes.LITERAL_IF : 347 case TokenTypes.LITERAL_ELSE : 348 //don't count if or loop conditions 349 final DetailAST prevSibling = ast.getPreviousSibling(); 350 countable = prevSibling == null 351 || prevSibling.getType() != TokenTypes.LPAREN; 352 break; 353 default : 354 countable = false; 355 break; 356 } 357 return countable; 358 } 359 360 /** 361 * Class representing a counter. 362 * 363 */ 364 private static class Counter { 365 366 /** The counters internal integer. */ 367 private int count; 368 369 /** 370 * Increments the counter. 371 */ 372 public void increment() { 373 count++; 374 } 375 376 /** 377 * Gets the counters value. 378 * 379 * @return the counter 380 */ 381 public int getCount() { 382 return count; 383 } 384 385 } 386 387}