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}