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}