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, MSG_METHOD, count, methodMaximum); 170 } 171 } 172 else if (tokenType == TokenTypes.CLASS_DEF) { 173 //pop counter from the stack 174 final Counter counter = counters.pop(); 175 176 final int count = counter.getCount(); 177 if (count > classMaximum) { 178 log(ast, MSG_CLASS, count, classMaximum); 179 } 180 } 181 } 182 183 @Override 184 public void finishTree(DetailAST rootAST) { 185 //pop counter from the stack 186 final Counter counter = counters.pop(); 187 188 final int count = counter.getCount(); 189 if (count > fileMaximum) { 190 log(rootAST, MSG_FILE, count, fileMaximum); 191 } 192 } 193 194 /** 195 * Sets the maximum ncss for a file. 196 * 197 * @param fileMaximum 198 * the maximum ncss 199 */ 200 public void setFileMaximum(int fileMaximum) { 201 this.fileMaximum = fileMaximum; 202 } 203 204 /** 205 * Sets the maximum ncss for a class. 206 * 207 * @param classMaximum 208 * the maximum ncss 209 */ 210 public void setClassMaximum(int classMaximum) { 211 this.classMaximum = classMaximum; 212 } 213 214 /** 215 * Sets the maximum ncss for a method. 216 * 217 * @param methodMaximum 218 * the maximum ncss 219 */ 220 public void setMethodMaximum(int methodMaximum) { 221 this.methodMaximum = methodMaximum; 222 } 223 224 /** 225 * Checks if a token is countable for the ncss metric. 226 * 227 * @param ast 228 * the AST 229 * @return true if the token is countable 230 */ 231 private static boolean isCountable(DetailAST ast) { 232 boolean countable = true; 233 234 final int tokenType = ast.getType(); 235 236 //check if an expression is countable 237 if (tokenType == TokenTypes.EXPR) { 238 countable = isExpressionCountable(ast); 239 } 240 //check if an variable definition is countable 241 else if (tokenType == TokenTypes.VARIABLE_DEF) { 242 countable = isVariableDefCountable(ast); 243 } 244 return countable; 245 } 246 247 /** 248 * Checks if a variable definition is countable. 249 * 250 * @param ast the AST 251 * @return true if the variable definition is countable, false otherwise 252 */ 253 private static boolean isVariableDefCountable(DetailAST ast) { 254 boolean countable = false; 255 256 //count variable definitions only if they are direct child to a slist or 257 // object block 258 final int parentType = ast.getParent().getType(); 259 260 if (parentType == TokenTypes.SLIST 261 || parentType == TokenTypes.OBJBLOCK) { 262 final DetailAST prevSibling = ast.getPreviousSibling(); 263 264 //is countable if no previous sibling is found or 265 //the sibling is no COMMA. 266 //This is done because multiple assignment on one line are counted 267 // as 1 268 countable = prevSibling == null 269 || prevSibling.getType() != TokenTypes.COMMA; 270 } 271 272 return countable; 273 } 274 275 /** 276 * Checks if an expression is countable for the ncss metric. 277 * 278 * @param ast the AST 279 * @return true if the expression is countable, false otherwise 280 */ 281 private static boolean isExpressionCountable(DetailAST ast) { 282 final boolean countable; 283 284 //count expressions only if they are direct child to a slist (method 285 // body, for loop...) 286 //or direct child of label,if,else,do,while,for 287 final int parentType = ast.getParent().getType(); 288 switch (parentType) { 289 case TokenTypes.SLIST : 290 case TokenTypes.LABELED_STAT : 291 case TokenTypes.LITERAL_FOR : 292 case TokenTypes.LITERAL_DO : 293 case TokenTypes.LITERAL_WHILE : 294 case TokenTypes.LITERAL_IF : 295 case TokenTypes.LITERAL_ELSE : 296 //don't count if or loop conditions 297 final DetailAST prevSibling = ast.getPreviousSibling(); 298 countable = prevSibling == null 299 || prevSibling.getType() != TokenTypes.LPAREN; 300 break; 301 default : 302 countable = false; 303 break; 304 } 305 return countable; 306 } 307 308 /** 309 * Class representing a counter. 310 * 311 */ 312 private static class Counter { 313 314 /** The counters internal integer. */ 315 private int count; 316 317 /** 318 * Increments the counter. 319 */ 320 public void increment() { 321 count++; 322 } 323 324 /** 325 * Gets the counters value. 326 * 327 * @return the counter 328 */ 329 public int getCount() { 330 return count; 331 } 332 333 } 334 335}