001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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 * This check calculates the Non Commenting Source Statements (NCSS) metric for 032 * java source files and methods. The check adheres to the <a 033 * href="http://www.kclee.com/clemens/java/javancss">JavaNCSS specification 034 * </a> and gives the same results as the JavaNCSS tool. 035 * 036 * <p>The NCSS-metric tries to determine complexity of methods, classes and files 037 * by counting the non commenting lines. Roughly said this is (nearly) 038 * equivalent to counting the semicolons and opening curly braces. 039 * 040 */ 041// -@cs[AbbreviationAsWordInName] We can not change it as, 042// check's name is a part of API (used in configurations). 043@FileStatefulCheck 044public class JavaNCSSCheck extends AbstractCheck { 045 046 /** 047 * A key is pointing to the warning message text in "messages.properties" 048 * file. 049 */ 050 public static final String MSG_METHOD = "ncss.method"; 051 052 /** 053 * A key is pointing to the warning message text in "messages.properties" 054 * file. 055 */ 056 public static final String MSG_CLASS = "ncss.class"; 057 058 /** 059 * A key is pointing to the warning message text in "messages.properties" 060 * file. 061 */ 062 public static final String MSG_FILE = "ncss.file"; 063 064 /** Default constant for max file ncss. */ 065 private static final int FILE_MAX_NCSS = 2000; 066 067 /** Default constant for max file ncss. */ 068 private static final int CLASS_MAX_NCSS = 1500; 069 070 /** Default constant for max method ncss. */ 071 private static final int METHOD_MAX_NCSS = 50; 072 073 /** Maximum ncss for a complete source file. */ 074 private int fileMaximum = FILE_MAX_NCSS; 075 076 /** Maximum ncss for a class. */ 077 private int classMaximum = CLASS_MAX_NCSS; 078 079 /** Maximum ncss for a method. */ 080 private int methodMaximum = METHOD_MAX_NCSS; 081 082 /** List containing the stacked counters. */ 083 private Deque<Counter> counters; 084 085 @Override 086 public int[] getDefaultTokens() { 087 return getRequiredTokens(); 088 } 089 090 @Override 091 public int[] getRequiredTokens() { 092 return new int[] { 093 TokenTypes.CLASS_DEF, 094 TokenTypes.INTERFACE_DEF, 095 TokenTypes.METHOD_DEF, 096 TokenTypes.CTOR_DEF, 097 TokenTypes.INSTANCE_INIT, 098 TokenTypes.STATIC_INIT, 099 TokenTypes.PACKAGE_DEF, 100 TokenTypes.IMPORT, 101 TokenTypes.VARIABLE_DEF, 102 TokenTypes.CTOR_CALL, 103 TokenTypes.SUPER_CTOR_CALL, 104 TokenTypes.LITERAL_IF, 105 TokenTypes.LITERAL_ELSE, 106 TokenTypes.LITERAL_WHILE, 107 TokenTypes.LITERAL_DO, 108 TokenTypes.LITERAL_FOR, 109 TokenTypes.LITERAL_SWITCH, 110 TokenTypes.LITERAL_BREAK, 111 TokenTypes.LITERAL_CONTINUE, 112 TokenTypes.LITERAL_RETURN, 113 TokenTypes.LITERAL_THROW, 114 TokenTypes.LITERAL_SYNCHRONIZED, 115 TokenTypes.LITERAL_CATCH, 116 TokenTypes.LITERAL_FINALLY, 117 TokenTypes.EXPR, 118 TokenTypes.LABELED_STAT, 119 TokenTypes.LITERAL_CASE, 120 TokenTypes.LITERAL_DEFAULT, 121 }; 122 } 123 124 @Override 125 public int[] getAcceptableTokens() { 126 return getRequiredTokens(); 127 } 128 129 @Override 130 public void beginTree(DetailAST rootAST) { 131 counters = new ArrayDeque<>(); 132 133 //add a counter for the file 134 counters.push(new Counter()); 135 } 136 137 @Override 138 public void visitToken(DetailAST ast) { 139 final int tokenType = ast.getType(); 140 141 if (tokenType == TokenTypes.CLASS_DEF 142 || tokenType == TokenTypes.METHOD_DEF 143 || tokenType == TokenTypes.CTOR_DEF 144 || tokenType == TokenTypes.STATIC_INIT 145 || tokenType == TokenTypes.INSTANCE_INIT) { 146 //add a counter for this class/method 147 counters.push(new Counter()); 148 } 149 150 //check if token is countable 151 if (isCountable(ast)) { 152 //increment the stacked counters 153 counters.forEach(Counter::increment); 154 } 155 } 156 157 @Override 158 public void leaveToken(DetailAST ast) { 159 final int tokenType = ast.getType(); 160 if (tokenType == TokenTypes.METHOD_DEF 161 || tokenType == TokenTypes.CTOR_DEF 162 || tokenType == TokenTypes.STATIC_INIT 163 || tokenType == TokenTypes.INSTANCE_INIT) { 164 //pop counter from the stack 165 final Counter counter = counters.pop(); 166 167 final int count = counter.getCount(); 168 if (count > methodMaximum) { 169 log(ast.getLineNo(), ast.getColumnNo(), MSG_METHOD, 170 count, methodMaximum); 171 } 172 } 173 else if (tokenType == TokenTypes.CLASS_DEF) { 174 //pop counter from the stack 175 final Counter counter = counters.pop(); 176 177 final int count = counter.getCount(); 178 if (count > classMaximum) { 179 log(ast.getLineNo(), ast.getColumnNo(), MSG_CLASS, 180 count, classMaximum); 181 } 182 } 183 } 184 185 @Override 186 public void finishTree(DetailAST rootAST) { 187 //pop counter from the stack 188 final Counter counter = counters.pop(); 189 190 final int count = counter.getCount(); 191 if (count > fileMaximum) { 192 log(rootAST.getLineNo(), rootAST.getColumnNo(), MSG_FILE, 193 count, fileMaximum); 194 } 195 } 196 197 /** 198 * Sets the maximum ncss for a file. 199 * 200 * @param fileMaximum 201 * the maximum ncss 202 */ 203 public void setFileMaximum(int fileMaximum) { 204 this.fileMaximum = fileMaximum; 205 } 206 207 /** 208 * Sets the maximum ncss for a class. 209 * 210 * @param classMaximum 211 * the maximum ncss 212 */ 213 public void setClassMaximum(int classMaximum) { 214 this.classMaximum = classMaximum; 215 } 216 217 /** 218 * Sets the maximum ncss for a method. 219 * 220 * @param methodMaximum 221 * the maximum ncss 222 */ 223 public void setMethodMaximum(int methodMaximum) { 224 this.methodMaximum = methodMaximum; 225 } 226 227 /** 228 * Checks if a token is countable for the ncss metric. 229 * 230 * @param ast 231 * the AST 232 * @return true if the token is countable 233 */ 234 private static boolean isCountable(DetailAST ast) { 235 boolean countable = true; 236 237 final int tokenType = ast.getType(); 238 239 //check if an expression is countable 240 if (tokenType == TokenTypes.EXPR) { 241 countable = isExpressionCountable(ast); 242 } 243 //check if an variable definition is countable 244 else if (tokenType == TokenTypes.VARIABLE_DEF) { 245 countable = isVariableDefCountable(ast); 246 } 247 return countable; 248 } 249 250 /** 251 * Checks if a variable definition is countable. 252 * 253 * @param ast the AST 254 * @return true if the variable definition is countable, false otherwise 255 */ 256 private static boolean isVariableDefCountable(DetailAST ast) { 257 boolean countable = false; 258 259 //count variable definitions only if they are direct child to a slist or 260 // object block 261 final int parentType = ast.getParent().getType(); 262 263 if (parentType == TokenTypes.SLIST 264 || parentType == TokenTypes.OBJBLOCK) { 265 final DetailAST prevSibling = ast.getPreviousSibling(); 266 267 //is countable if no previous sibling is found or 268 //the sibling is no COMMA. 269 //This is done because multiple assignment on one line are counted 270 // as 1 271 countable = prevSibling == null 272 || prevSibling.getType() != TokenTypes.COMMA; 273 } 274 275 return countable; 276 } 277 278 /** 279 * Checks if an expression is countable for the ncss metric. 280 * 281 * @param ast the AST 282 * @return true if the expression is countable, false otherwise 283 */ 284 private static boolean isExpressionCountable(DetailAST ast) { 285 final boolean countable; 286 287 //count expressions only if they are direct child to a slist (method 288 // body, for loop...) 289 //or direct child of label,if,else,do,while,for 290 final int parentType = ast.getParent().getType(); 291 switch (parentType) { 292 case TokenTypes.SLIST : 293 case TokenTypes.LABELED_STAT : 294 case TokenTypes.LITERAL_FOR : 295 case TokenTypes.LITERAL_DO : 296 case TokenTypes.LITERAL_WHILE : 297 case TokenTypes.LITERAL_IF : 298 case TokenTypes.LITERAL_ELSE : 299 //don't count if or loop conditions 300 final DetailAST prevSibling = ast.getPreviousSibling(); 301 countable = prevSibling == null 302 || prevSibling.getType() != TokenTypes.LPAREN; 303 break; 304 default : 305 countable = false; 306 break; 307 } 308 return countable; 309 } 310 311 /** 312 * Class representing a counter. 313 * 314 */ 315 private static class Counter { 316 317 /** The counters internal integer. */ 318 private int count; 319 320 /** 321 * Increments the counter. 322 */ 323 public void increment() { 324 count++; 325 } 326 327 /** 328 * Gets the counters value. 329 * 330 * @return the counter 331 */ 332 public int getCount() { 333 return count; 334 } 335 336 } 337 338}