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