001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.coding; 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; 026import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 027 028/** 029 * <div> 030 * Checks that expression lambdas are used instead of single-line block lambdas 031 * where possible. 032 * </div> 033 * 034 * <p> 035 * Rationale: According to the OpenJDK Java Style Guidelines (and general 036 * modern Java conventions), expression lambdas are preferred over single-line 037 * block lambdas for readability and conciseness. 038 * </p> 039 * 040 * <p> 041 * A single-line block lambda is a lambda whose body is a block ({@code {...}}) 042 * that fits on a single line and contains only one statement that could be 043 * written as an expression lambda. 044 * </p> 045 * 046 * @since 14.1.0 047 */ 048@StatelessCheck 049public class ExpressionOverBlockLambdaCheck extends AbstractCheck { 050 051 /** 052 * A key is pointing to the warning message text in "messages.properties" 053 * file. 054 */ 055 public static final String MSG_KEY = "expression.over.block.lambda"; 056 057 /** 058 * Creates a new {@code ExpressionOverBlockLambdaCheck} instance. 059 */ 060 public ExpressionOverBlockLambdaCheck() { 061 // no code by default 062 } 063 064 @Override 065 public int[] getDefaultTokens() { 066 return getRequiredTokens(); 067 } 068 069 @Override 070 public int[] getAcceptableTokens() { 071 return getRequiredTokens(); 072 } 073 074 @Override 075 public int[] getRequiredTokens() { 076 return new int[] {TokenTypes.LAMBDA}; 077 } 078 079 @Override 080 public void visitToken(DetailAST ast) { 081 if (!isSwitchRuleLambda(ast) 082 && isSingleLineLambda(ast)) { 083 final DetailAST body = ast.getLastChild(); 084 final DetailAST statement = 085 findSingleStatement(body); 086 if (statement != null 087 && isConvertibleToExpressionLambda( 088 statement)) { 089 log(ast, MSG_KEY); 090 } 091 } 092 } 093 094 /** 095 * Checks if the lambda is a switch rule lambda. 096 * 097 * @param lambda the lambda AST node 098 * @return true if the lambda is part of a switch rule 099 */ 100 private static boolean isSwitchRuleLambda(DetailAST lambda) { 101 return lambda.getParent().getType() == TokenTypes.SWITCH_RULE; 102 } 103 104 /** 105 * Checks if a lambda is single-line. 106 * 107 * @param lambda the lambda AST node 108 * @return true if the lambda fits on a single line 109 */ 110 private static boolean isSingleLineLambda(DetailAST lambda) { 111 final DetailAST lastLambdaToken = getLastLambdaToken(lambda); 112 return TokenUtil.areOnSameLine(lambda, lastLambdaToken); 113 } 114 115 /** 116 * Gets the last token in a lambda. 117 * 118 * @param lambda the lambda AST node 119 * @return the last token in the lambda 120 */ 121 private static DetailAST getLastLambdaToken(DetailAST lambda) { 122 DetailAST node = lambda; 123 do { 124 node = node.getLastChild(); 125 } while (node.getLastChild() != null); 126 return node; 127 } 128 129 /** 130 * Finds the single statement in a block lambda body, or returns null 131 * if there are zero or multiple statements. 132 * 133 * @param slist the SLIST node (lambda body) 134 * @return the single statement, or null if not exactly one 135 */ 136 private static DetailAST findSingleStatement(DetailAST slist) { 137 DetailAST singleStatement = null; 138 int count = 0; 139 for (DetailAST child = slist.getFirstChild(); child != null; 140 child = child.getNextSibling()) { 141 final int type = child.getType(); 142 if (type != TokenTypes.RCURLY 143 && type != TokenTypes.SEMI) { 144 singleStatement = child; 145 count++; 146 } 147 } 148 DetailAST result = null; 149 if (count == 1) { 150 result = singleStatement; 151 } 152 return result; 153 } 154 155 /** 156 * Checks if the statement in a block lambda can be rewritten 157 * as an expression lambda. 158 * 159 * @param statement the statement node 160 * @return true if the statement can be converted to an expression lambda 161 */ 162 private static boolean isConvertibleToExpressionLambda(DetailAST statement) { 163 boolean convertible = false; 164 if (statement.getType() == TokenTypes.EXPR) { 165 convertible = true; 166 } 167 else if (statement.getType() == TokenTypes.LITERAL_RETURN) { 168 convertible = hasReturnExpression(statement); 169 } 170 return convertible; 171 } 172 173 /** 174 * Checks if a return statement has an expression (not bare return). 175 * 176 * @param literalReturn the LITERAL_RETURN node 177 * @return true if the return statement has an expression 178 */ 179 private static boolean hasReturnExpression(DetailAST literalReturn) { 180 return literalReturn.findFirstToken(TokenTypes.EXPR) != null; 181 } 182 183}