001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code 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 com.puppycrawl.tools.checkstyle.StatelessCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026 027/** 028 * <p> 029 * Checks lambda body length. 030 * </p> 031 * <p> 032 * Rationale: Similar to anonymous inner classes, if lambda body becomes very long 033 * it is hard to understand and to see the flow of the method 034 * where the lambda is defined. Therefore long lambda body 035 * should usually be extracted to method. 036 * </p> 037 * <ul> 038 * <li> 039 * Property {@code max} - Specify the maximum number of lines allowed. 040 * Type is {@code int}. 041 * Default value is {@code 10}. 042 * </li> 043 * </ul> 044 * <p> 045 * To configure the check to accept lambda bodies with up to 10 lines: 046 * </p> 047 * <pre> 048 * <module name="LambdaBodyLength"/> 049 * </pre> 050 * <p> 051 * Example: 052 * </p> 053 * <pre> 054 * class Test { 055 * Runnable r = () -> { // violation, 11 lines 056 * System.out.println(2); // line 2 of lambda 057 * System.out.println(3); 058 * System.out.println(4); 059 * System.out.println(5); 060 * System.out.println(6); 061 * System.out.println(7); 062 * System.out.println(8); 063 * System.out.println(9); 064 * System.out.println(10); 065 * }; // line 11 066 * 067 * Runnable r2 = () -> // violation, 11 lines 068 * "someString".concat("1") // line 1 of lambda 069 * .concat("2") 070 * .concat("3") 071 * .concat("4") 072 * .concat("5") 073 * .concat("6") 074 * .concat("7") 075 * .concat("8") 076 * .concat("9") 077 * .concat("10") 078 * .concat("11"); // line 11 079 * 080 * Runnable r3 = () -> { // ok, 10 lines 081 * System.out.println(2); // line 2 of lambda 082 * System.out.println(3); 083 * System.out.println(4); 084 * System.out.println(5); 085 * System.out.println(6); 086 * System.out.println(7); 087 * System.out.println(8); 088 * System.out.println(9); 089 * }; // line 10 090 * } 091 * </pre> 092 * <p> 093 * To configure the check to accept lambda bodies with max 5 lines: 094 * </p> 095 * <pre> 096 * <module name="LambdaBodyLength"> 097 * <property name="max" value="5"/> 098 * </module> 099 * </pre> 100 * <p> 101 * Example: 102 * </p> 103 * <pre> 104 * class Test { 105 * Runnable r = () -> { // violation, 6 lines 106 * System.out.println(2); // line 2 of lambda 107 * System.out.println(3); 108 * System.out.println(4); 109 * System.out.println(5); 110 * }; 111 * 112 * Runnable r2 = () -> // violation, 6 lines 113 * "someString".concat("1") 114 * .concat("2") 115 * .concat("3") 116 * .concat("4") 117 * .concat("5") 118 * .concat("6"); 119 * 120 * Runnable r3 = () -> { // ok, 5 lines 121 * System.out.println(2); 122 * System.out.println(3); 123 * System.out.println(4); 124 * }; 125 * } 126 * </pre> 127 * <p> 128 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 129 * </p> 130 * <p> 131 * Violation Message Keys: 132 * </p> 133 * <ul> 134 * <li> 135 * {@code maxLen.lambdaBody} 136 * </li> 137 * </ul> 138 * 139 * @since 8.37 140 */ 141@StatelessCheck 142public class LambdaBodyLengthCheck extends AbstractCheck { 143 144 /** 145 * A key is pointing to the warning message text in "messages.properties" 146 * file. 147 */ 148 public static final String MSG_KEY = "maxLen.lambdaBody"; 149 150 /** Default maximum number of lines. */ 151 private static final int DEFAULT_MAX = 10; 152 153 /** Specify the maximum number of lines allowed. */ 154 private int max = DEFAULT_MAX; 155 156 /** 157 * Setter to specify the maximum number of lines allowed. 158 * 159 * @param length the maximum length of lambda body. 160 */ 161 public void setMax(int length) { 162 max = length; 163 } 164 165 @Override 166 public int[] getDefaultTokens() { 167 return getRequiredTokens(); 168 } 169 170 @Override 171 public int[] getAcceptableTokens() { 172 return getRequiredTokens(); 173 } 174 175 @Override 176 public int[] getRequiredTokens() { 177 return new int[] {TokenTypes.LAMBDA}; 178 } 179 180 @Override 181 public void visitToken(DetailAST ast) { 182 if (ast.getParent().getType() != TokenTypes.SWITCH_RULE) { 183 final int length = getLength(ast); 184 if (length > max) { 185 log(ast, MSG_KEY, length, max); 186 } 187 } 188 } 189 190 /** 191 * Get length of lambda body. 192 * 193 * @param ast lambda body node. 194 * @return length of lambda body. 195 */ 196 private static int getLength(DetailAST ast) { 197 final DetailAST lambdaBody = ast.getLastChild(); 198 final int length; 199 if (lambdaBody.getType() == TokenTypes.SLIST) { 200 length = lambdaBody.getLastChild().getLineNo() - lambdaBody.getLineNo(); 201 } 202 else { 203 length = getLastNodeLineNumber(lambdaBody) - getFirstNodeLineNumber(lambdaBody); 204 } 205 return length + 1; 206 } 207 208 /** 209 * Get last child node in the tree line number. 210 * 211 * @param lambdaBody lambda body node. 212 * @return last child node in the tree line number. 213 */ 214 private static int getLastNodeLineNumber(DetailAST lambdaBody) { 215 DetailAST node = lambdaBody; 216 int result; 217 do { 218 result = node.getLineNo(); 219 node = node.getLastChild(); 220 } while (node != null); 221 return result; 222 } 223 224 /** 225 * Get first child node in the tree line number. 226 * 227 * @param lambdaBody lambda body node. 228 * @return first child node in the tree line number. 229 */ 230 private static int getFirstNodeLineNumber(DetailAST lambdaBody) { 231 DetailAST node = lambdaBody; 232 int result; 233 do { 234 result = node.getLineNo(); 235 node = node.getFirstChild(); 236 } while (node != null); 237 return result; 238 } 239 240}