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