001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.sizes; 021 022import java.util.ArrayDeque; 023import java.util.BitSet; 024import java.util.Deque; 025import java.util.Objects; 026import java.util.stream.Stream; 027 028import com.puppycrawl.tools.checkstyle.StatelessCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 033 034/** 035 * <p> 036 * Checks for long methods and constructors. 037 * </p> 038 * <p> 039 * Rationale: If a method becomes very long it is hard to understand. 040 * Therefore, long methods should usually be refactored into several 041 * individual methods that focus on a specific task. 042 * </p> 043 * <ul> 044 * <li> 045 * Property {@code max} - Specify the maximum number of lines allowed. 046 * Type is {@code int}. 047 * Default value is {@code 150}. 048 * </li> 049 * <li> 050 * Property {@code countEmpty} - Control whether to count empty lines and comments. 051 * Type is {@code boolean}. 052 * Default value is {@code true}. 053 * </li> 054 * <li> 055 * Property {@code tokens} - tokens to check 056 * Type is {@code java.lang.String[]}. 057 * Validation type is {@code tokenSet}. 058 * Default value is: 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 060 * METHOD_DEF</a>, 061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 062 * CTOR_DEF</a>, 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 064 * COMPACT_CTOR_DEF</a>. 065 * </li> 066 * </ul> 067 * <p> 068 * To configure the check: 069 * </p> 070 * <pre> 071 * <module name="MethodLength"/> 072 * </pre> 073 * <p> 074 * Example: 075 * </p> 076 * <pre> 077 * public class MyClass { 078 * public MyClass() { // constructor (line 1) 079 * /* line 2 080 * ... 081 * line 150 */ 082 * } // line 151, violation, as it is over 150 083 * 084 * public void firstExample() { // line 1 085 * 086 * // line 3 087 * System.out.println("line 4"); 088 * /* line 5 089 * line 6 */ 090 * } // line 7, OK, as it is less than 150 091 * 092 * public void secondExample() { // line 1 093 * // line 2 094 * System.out.println("line 3"); 095 * 096 * /* line 5 097 * ... 098 * line 150 */ 099 * } // line 151, violation, as it is over 150 100 * } 101 * </pre> 102 * <p> 103 * To configure the check so that it accepts methods with at most 4 lines: 104 * </p> 105 * <pre> 106 * <module name="MethodLength"> 107 * <property name="tokens" value="METHOD_DEF"/> 108 * <property name="max" value="4"/> 109 * </module> 110 * </pre> 111 * <p> 112 * Example: 113 * </p> 114 * <pre> 115 * public class MyTest { 116 * public MyTest() { // constructor (line 1) 117 * int var1 = 2; // line 2 118 * int var2 = 4; // line 3 119 * int sum = var1 + var2; // line 4 120 * } // line 5, OK, constructor is not mentioned in the tokens 121 * 122 * public void firstMethod() { // line 1 123 * // comment (line 2) 124 * System.out.println("line 3"); 125 * } // line 4, OK, as it allows at most 4 lines 126 * 127 * public void secondMethod() { // line 1 128 * int index = 0; // line 2 129 * if (index < 5) { // line 3 130 * index++; // line 4 131 * } // line 5 132 * } // line 6, violation, as it is over 4 lines 133 * 134 * public void thirdMethod() { // line 1 135 * 136 * // comment (line 3) 137 * System.out.println("line 4"); 138 * } // line 5, violation, as it is over 4 lines 139 * } 140 * </pre> 141 * <p> 142 * To configure the check so that it accepts methods with at most 4 lines, 143 * not counting empty lines and comments: 144 * </p> 145 * <pre> 146 * <module name="MethodLength"> 147 * <property name="tokens" value="METHOD_DEF"/> 148 * <property name="max" value="4"/> 149 * <property name="countEmpty" value="false"/> 150 * </module> 151 * </pre> 152 * <p> 153 * Example: 154 * </p> 155 * <pre> 156 * public class MyTest { 157 * public MyTest() { // constructor (line 1) 158 * int var1 = 2; // line 2 159 * int var2 = 4; // line 3 160 * int sum = var1 + var2; // line 4 161 * } // line 5, OK, constructor is not mentioned in the tokens 162 * 163 * public void firstMethod() { // line 1 164 * // comment - not counted as line 165 * System.out.println("line 2"); 166 * } // line 3, OK, as it allows at most 4 lines 167 * 168 * public void secondMethod() { // line 1 169 * int index = 0; // line 2 170 * if (index < 5) { // line 3 171 * index++; // line 4 172 * } // line 5 173 * } // line 6, violation, as it is over 4 lines 174 * 175 * public void thirdMethod() { // line 1 176 * 177 * // comment - not counted as line 178 * System.out.println("line 2"); 179 * } // line 3, OK, as it allows at most 4 lines 180 * } 181 * </pre> 182 * <p> 183 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 184 * </p> 185 * <p> 186 * Violation Message Keys: 187 * </p> 188 * <ul> 189 * <li> 190 * {@code maxLen.method} 191 * </li> 192 * </ul> 193 * 194 * @since 3.0 195 */ 196@StatelessCheck 197public class MethodLengthCheck extends AbstractCheck { 198 199 /** 200 * A key is pointing to the warning message text in "messages.properties" 201 * file. 202 */ 203 public static final String MSG_KEY = "maxLen.method"; 204 205 /** Default maximum number of lines. */ 206 private static final int DEFAULT_MAX_LINES = 150; 207 208 /** Control whether to count empty lines and comments. */ 209 private boolean countEmpty = true; 210 211 /** Specify the maximum number of lines allowed. */ 212 private int max = DEFAULT_MAX_LINES; 213 214 @Override 215 public int[] getDefaultTokens() { 216 return getAcceptableTokens(); 217 } 218 219 @Override 220 public int[] getAcceptableTokens() { 221 return new int[] { 222 TokenTypes.METHOD_DEF, 223 TokenTypes.CTOR_DEF, 224 TokenTypes.COMPACT_CTOR_DEF, 225 }; 226 } 227 228 @Override 229 public int[] getRequiredTokens() { 230 return CommonUtil.EMPTY_INT_ARRAY; 231 } 232 233 @Override 234 public void visitToken(DetailAST ast) { 235 final DetailAST openingBrace = ast.findFirstToken(TokenTypes.SLIST); 236 if (openingBrace != null) { 237 final int length; 238 if (countEmpty) { 239 final DetailAST closingBrace = openingBrace.findFirstToken(TokenTypes.RCURLY); 240 length = getLengthOfBlock(openingBrace, closingBrace); 241 } 242 else { 243 length = countUsedLines(openingBrace); 244 } 245 if (length > max) { 246 final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText(); 247 log(ast, MSG_KEY, length, max, methodName); 248 } 249 } 250 } 251 252 /** 253 * Returns length of code. 254 * 255 * @param openingBrace block opening brace 256 * @param closingBrace block closing brace 257 * @return number of lines with code for current block 258 */ 259 private static int getLengthOfBlock(DetailAST openingBrace, DetailAST closingBrace) { 260 final int startLineNo = openingBrace.getLineNo(); 261 final int endLineNo = closingBrace.getLineNo(); 262 return endLineNo - startLineNo + 1; 263 } 264 265 /** 266 * Count number of used code lines without comments. 267 * 268 * @param ast start ast 269 * @return number of used lines of code 270 */ 271 private static int countUsedLines(DetailAST ast) { 272 final Deque<DetailAST> nodes = new ArrayDeque<>(); 273 nodes.add(ast); 274 final BitSet usedLines = new BitSet(); 275 final int startLineNo = ast.getLineNo(); 276 while (!nodes.isEmpty()) { 277 final DetailAST node = nodes.removeFirst(); 278 final int lineIndex = node.getLineNo() - startLineNo; 279 // text block requires special treatment, 280 // since it is the only non-comment token that can span more than one line 281 if (node.getType() == TokenTypes.TEXT_BLOCK_LITERAL_BEGIN) { 282 final int endLineIndex = node.getLastChild().getLineNo() - startLineNo; 283 usedLines.set(lineIndex, endLineIndex + 1); 284 } 285 else { 286 usedLines.set(lineIndex); 287 Stream.iterate( 288 node.getLastChild(), Objects::nonNull, DetailAST::getPreviousSibling 289 ).forEach(nodes::addFirst); 290 } 291 } 292 return usedLines.cardinality(); 293 } 294 295 /** 296 * Setter to specify the maximum number of lines allowed. 297 * 298 * @param length the maximum length of a method. 299 */ 300 public void setMax(int length) { 301 max = length; 302 } 303 304 /** 305 * Setter to control whether to count empty lines and comments. 306 * 307 * @param countEmpty whether to count empty and comments. 308 */ 309 public void setCountEmpty(boolean countEmpty) { 310 this.countEmpty = countEmpty; 311 } 312 313}