001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.indentation;
021
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
025
026/**
027 * Handler for lambda expressions.
028 *
029 */
030public class LambdaHandler extends AbstractExpressionHandler {
031    /**
032     * Checks whether the lambda is correctly indented, this variable get its value from checking
033     * the lambda handler's indentation, and it is being used in aligning the lambda's children.
034     * A true value depicts lambda is correctly aligned without giving any errors.
035     * This is updated to false where there is any Indentation error log.
036     */
037    private boolean isLambdaCorrectlyIndented = true;
038
039    /**
040     * Construct an instance of this handler with the given indentation check,
041     * abstract syntax tree, and parent handler.
042     *
043     * @param indentCheck the indentation check
044     * @param ast the abstract syntax tree
045     * @param parent the parent handler
046     */
047    public LambdaHandler(IndentationCheck indentCheck,
048                         DetailAST ast, AbstractExpressionHandler parent) {
049        super(indentCheck, "lambda", ast, parent);
050    }
051
052    @Override
053    public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
054        IndentLevel childIndent = getIndent();
055        if (isLambdaCorrectlyIndented) {
056            // If the lambda is correctly indented, include its line start as acceptable to
057            // avoid false positives. When "forceStrictCondition" is off, we allow indents
058            // larger than expected (e.g., 12 instead of 6 or 8). These larger indents are
059            // accepted but not recorded, so child indent suggestions may be inaccurate.
060            // Adding the actual line start ensures the tool recognizes the lambda’s real indent
061            // context.
062            childIndent = IndentLevel.addAcceptable(childIndent, getLineStart(getMainAst()));
063
064            if (child instanceof SlistHandler) {
065                // Lambda with block body (enclosed in {})
066                childIndent = IndentLevel.addAcceptable(childIndent,
067                    getLineStart(getMainAst().getFirstChild()));
068            }
069            else {
070                // Single-expression lambda (no {} block):
071                // assume line wrapping and add additional indentation
072                // for the statement in the next line.
073                childIndent = new IndentLevel(childIndent,
074                        getIndentCheck().getLineWrappingIndentation());
075            }
076        }
077
078        return childIndent;
079    }
080
081    /**
082     * {@inheritDoc}.
083     *
084     * @noinspection MethodWithMultipleReturnPoints
085     * @noinspectionreason MethodWithMultipleReturnPoints - indentation is complex and
086     *      tightly coupled, thus making this method difficult to refactor
087     */
088    @Override
089    protected IndentLevel getIndentImpl() {
090        if (getParent() instanceof MethodCallHandler) {
091            return getParent().getSuggestedChildIndent(this);
092        }
093
094        DetailAST parent = getMainAst().getParent();
095        if (getParent() instanceof NewHandler) {
096            parent = parent.getParent();
097        }
098
099        // Use the start of the parent's line as the reference indentation level.
100        IndentLevel level = new IndentLevel(getLineStart(parent));
101
102        // If the start of the lambda is the first element on the line;
103        // assume line wrapping with respect to its parent.
104        final DetailAST firstChild = getMainAst().getFirstChild();
105        if (getLineStart(firstChild) == expandedTabsColumnNo(firstChild)) {
106            level = new IndentLevel(level, getIndentCheck().getLineWrappingIndentation());
107        }
108
109        return level;
110    }
111
112    @Override
113    public void checkIndentation() {
114        final DetailAST mainAst = getMainAst();
115        final DetailAST firstChild = mainAst.getFirstChild();
116
117        // If the "->" has no children, it is a switch
118        // rule lambda (i.e. 'case ONE -> 1;')
119        final boolean isSwitchRuleLambda = firstChild == null;
120
121        if (!isSwitchRuleLambda
122            && getLineStart(firstChild) == expandedTabsColumnNo(firstChild)) {
123            final int firstChildColumnNo = expandedTabsColumnNo(firstChild);
124            final IndentLevel level = getIndent();
125
126            if (isNonAcceptableIndent(firstChildColumnNo, level)) {
127                isLambdaCorrectlyIndented = false;
128                logError(firstChild, "arguments", firstChildColumnNo, level);
129            }
130        }
131
132        // If the "->" is the first element on the line, assume line wrapping.
133        final int mainAstColumnNo = expandedTabsColumnNo(mainAst);
134        final boolean isLineWrappedLambda = mainAstColumnNo == getLineStart(mainAst);
135        if (isLineWrappedLambda) {
136            checkLineWrappedLambda(isSwitchRuleLambda, mainAstColumnNo);
137        }
138
139        final DetailAST nextSibling = mainAst.getNextSibling();
140
141        if (isSwitchRuleLambda
142                && nextSibling.getType() == TokenTypes.EXPR
143                && !TokenUtil.areOnSameLine(mainAst, nextSibling)) {
144            // Likely a single-statement switch rule lambda without curly braces, e.g.:
145            // case ONE ->
146            //      1;
147            checkSingleStatementSwitchRuleIndentation(isLineWrappedLambda);
148        }
149    }
150
151    /**
152     * Checks that given indent is acceptable or not.
153     *
154     * @param astColumnNo indent value to check
155     * @param level indent level
156     * @return true if indent is not acceptable
157     */
158    private boolean isNonAcceptableIndent(int astColumnNo, IndentLevel level) {
159        return astColumnNo < level.getFirstIndentLevel()
160            || getIndentCheck().isForceStrictCondition()
161               && !level.isAcceptable(astColumnNo);
162    }
163
164    /**
165     * This method checks a line wrapped lambda, whether it is a lambda
166     * expression or switch rule lambda.
167     *
168     * @param isSwitchRuleLambda if mainAst is a switch rule lambda
169     * @param mainAstColumnNo the column number of the lambda we are checking
170     */
171    private void checkLineWrappedLambda(final boolean isSwitchRuleLambda,
172                                        final int mainAstColumnNo) {
173        final IndentLevel level;
174        final DetailAST mainAst = getMainAst();
175
176        if (isSwitchRuleLambda) {
177            // We check the indentation of the case literal or default literal
178            // on the previous line and use that to determine the correct
179            // indentation for the line wrapped "->"
180            final DetailAST previousSibling = mainAst.getPreviousSibling();
181            final int previousLineStart = getLineStart(previousSibling);
182
183            level = new IndentLevel(new IndentLevel(previousLineStart),
184                    getIndentCheck().getLineWrappingIndentation());
185        }
186        else {
187            level = new IndentLevel(getIndent(),
188                getIndentCheck().getLineWrappingIndentation());
189        }
190
191        if (isNonAcceptableIndent(mainAstColumnNo, level)) {
192            isLambdaCorrectlyIndented = false;
193            logError(mainAst, "", mainAstColumnNo, level);
194        }
195    }
196
197    /**
198     * Checks the indentation of statements inside a single-statement switch rule
199     * when the statement is not on the same line as the lambda operator ({@code ->}).
200     * This applies to single-statement switch rules without curly braces {@code {}}.
201     * Example:
202     * <pre>
203     * case ONE {@code ->}
204     *     1;
205     * </pre>
206     *
207     * @param isLambdaFirstInLine if {@code ->} is the first element on the line
208     */
209    private void checkSingleStatementSwitchRuleIndentation(boolean isLambdaFirstInLine) {
210        final DetailAST mainAst = getMainAst();
211        IndentLevel level = getParent().getSuggestedChildIndent(this);
212
213        if (isLambdaFirstInLine) {
214            // If the lambda operator (`->`) is at the start of the line, assume line wrapping
215            // and add additional indentation for the statement in the next line.
216            level = new IndentLevel(level, getIndentCheck().getLineWrappingIndentation());
217        }
218
219        // The first line should not match if the switch rule statement starts on the same line
220        // as "->" but continues onto the next lines as part of a single logical expression.
221        final DetailAST nextSibling = mainAst.getNextSibling();
222        final boolean firstLineMatches = getFirstLine(nextSibling) != mainAst.getLineNo();
223        checkExpressionSubtree(nextSibling, level, firstLineMatches, false);
224    }
225}