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" file. 053 */ 054 public static final String MSG_KEY = "expression.over.block.lambda"; 055 056 /** 057 * Creates a new {@code ExpressionOverBlockLambdaCheck} instance. 058 */ 059 public ExpressionOverBlockLambdaCheck() { 060 // no code by default 061 } 062 063 @Override 064 public int[] getDefaultTokens() { 065 return getRequiredTokens(); 066 } 067 068 @Override 069 public int[] getAcceptableTokens() { 070 return getRequiredTokens(); 071 } 072 073 @Override 074 public int[] getRequiredTokens() { 075 return new int[] {TokenTypes.LAMBDA}; 076 } 077 078 @Override 079 public void visitToken(DetailAST ast) { 080 if (!isSwitchRuleLambda(ast) 081 && isSingleLineLambda(ast)) { 082 final DetailAST body = ast.getLastChild(); 083 final DetailAST statement = 084 findSingleStatement(body); 085 if (statement != null 086 && isConvertibleToExpressionLambda( 087 statement)) { 088 log(ast, MSG_KEY); 089 } 090 } 091 } 092 093 /** 094 * Checks if the lambda is a switch rule lambda. 095 * 096 * @param lambda the lambda AST node 097 * @return true if the lambda is part of a switch rule 098 */ 099 private static boolean isSwitchRuleLambda(DetailAST lambda) { 100 return lambda.getParent().getType() == TokenTypes.SWITCH_RULE; 101 } 102 103 /** 104 * Checks if a lambda is single-line. 105 * 106 * @param lambda the lambda AST node 107 * @return true if the lambda fits on a single line 108 */ 109 private static boolean isSingleLineLambda(DetailAST lambda) { 110 final DetailAST lastLambdaToken = getLastLambdaToken(lambda); 111 return TokenUtil.areOnSameLine(lambda, lastLambdaToken); 112 } 113 114 /** 115 * Gets the last token in a lambda. 116 * 117 * @param lambda the lambda AST node 118 * @return the last token in the lambda 119 */ 120 private static DetailAST getLastLambdaToken(DetailAST lambda) { 121 DetailAST node = lambda; 122 do { 123 node = node.getLastChild(); 124 } while (node.getLastChild() != null); 125 return node; 126 } 127 128 /** 129 * Finds the single statement in a block lambda body, or returns null 130 * if there are zero or multiple statements. 131 * 132 * @param slist the SLIST node (lambda body) 133 * @return the single statement, or null if not exactly one 134 */ 135 private static DetailAST findSingleStatement(DetailAST slist) { 136 DetailAST singleStatement = null; 137 int count = 0; 138 for (DetailAST child = slist.getFirstChild(); child != null; 139 child = child.getNextSibling()) { 140 final int type = child.getType(); 141 if (type != TokenTypes.RCURLY 142 && type != TokenTypes.SEMI) { 143 singleStatement = child; 144 count++; 145 } 146 } 147 DetailAST result = null; 148 if (count == 1) { 149 result = singleStatement; 150 } 151 return result; 152 } 153 154 /** 155 * Checks if the statement in a block lambda can be rewritten as an expression lambda. 156 * 157 * @param statement the statement node 158 * @return true if the statement can be converted to an expression lambda 159 */ 160 private static boolean isConvertibleToExpressionLambda(DetailAST statement) { 161 boolean convertible = false; 162 if (statement.getType() == TokenTypes.EXPR) { 163 convertible = true; 164 } 165 else if (statement.getType() == TokenTypes.LITERAL_RETURN) { 166 convertible = hasReturnExpression(statement); 167 } 168 return convertible; 169 } 170 171 /** 172 * Checks if a return statement has an expression (not bare return). 173 * 174 * @param literalReturn the LITERAL_RETURN node 175 * @return true if the return statement has an expression 176 */ 177 private static boolean hasReturnExpression(DetailAST literalReturn) { 178 return literalReturn.findFirstToken(TokenTypes.EXPR) != null; 179 } 180 181}