1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.indentation;
21
22 import javax.annotation.Nullable;
23
24 import com.puppycrawl.tools.checkstyle.api.DetailAST;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
27
28 /**
29 * Handler for lambda expressions.
30 *
31 */
32 public class LambdaHandler extends AbstractExpressionHandler {
33 /**
34 * Checks whether the lambda is correctly indented, this variable get its value from checking
35 * the lambda handler's indentation, and it is being used in aligning the lambda's children.
36 * A true value depicts lambda is correctly aligned without giving any errors.
37 * This is updated to false where there is any Indentation error log.
38 */
39 private boolean isLambdaCorrectlyIndented = true;
40
41 /**
42 * Construct an instance of this handler with the given indentation check,
43 * abstract syntax tree, and parent handler.
44 *
45 * @param indentCheck the indentation check
46 * @param ast the abstract syntax tree
47 * @param parent the parent handler
48 */
49 public LambdaHandler(IndentationCheck indentCheck,
50 DetailAST ast, AbstractExpressionHandler parent) {
51 super(indentCheck, "lambda", ast, parent);
52 }
53
54 @Override
55 public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
56 IndentLevel childIndent = getIndent();
57 if (isLambdaCorrectlyIndented) {
58 // If the lambda is correctly indented, include its line start as acceptable to
59 // avoid false positives. When "forceStrictCondition" is off, we allow indents
60 // larger than expected (e.g., 12 instead of 6 or 8). These larger indents are
61 // accepted but not recorded, so child indent suggestions may be inaccurate.
62 // Adding the actual line start ensures the tool recognizes the lambda’s real indent
63 // context.
64 childIndent = IndentLevel.addAcceptable(childIndent, getLineStart(getMainAst()));
65
66 if (isSameLineAsSwitch(child.getMainAst()) || child instanceof SlistHandler) {
67 // Lambda with block body (enclosed in {})
68 childIndent = IndentLevel.addAcceptable(childIndent,
69 getLineStart(getMainAst().getFirstChild()));
70 }
71 else {
72 // Single-expression lambda (no {} block):
73 // assume line wrapping and add additional indentation
74 // for the statement in the next line.
75 childIndent = new IndentLevel(childIndent,
76 getIndentCheck().getLineWrappingIndentation());
77 }
78 }
79
80 return childIndent;
81 }
82
83 /**
84 * {@inheritDoc}.
85 *
86 * @noinspection MethodWithMultipleReturnPoints
87 * @noinspectionreason MethodWithMultipleReturnPoints - indentation is complex and
88 * tightly coupled, thus making this method difficult to refactor
89 */
90 @Override
91 protected IndentLevel getIndentImpl() {
92 if (getParent() instanceof MethodCallHandler) {
93 return getParent().getSuggestedChildIndent(this);
94 }
95
96 final IndentLevel result;
97 final DetailAST enumConstDef = findParentEnumConstantDef();
98 if (enumConstDef != null) {
99 result = getEnumConstantBasedIndent(enumConstDef);
100 }
101 else {
102 DetailAST parent = getMainAst().getParent();
103 if (getParent() instanceof NewHandler) {
104 parent = parent.getParent();
105 }
106
107 // Use the start of the parent's line as the reference indentation level.
108 IndentLevel level = new IndentLevel(getLineStart(parent));
109
110 // If the start of the lambda is the first element on the line;
111 // assume line wrapping with respect to its parent.
112 final DetailAST firstChild = getMainAst().getFirstChild();
113 if (getLineStart(firstChild) == expandedTabsColumnNo(firstChild)) {
114 level = new IndentLevel(level, getIndentCheck().getLineWrappingIndentation());
115 }
116 result = level;
117 }
118
119 return result;
120 }
121
122 @Override
123 public void checkIndentation() {
124 final DetailAST mainAst = getMainAst();
125 final DetailAST firstChild = mainAst.getFirstChild();
126
127 // If the "->" has no children, it is a switch
128 // rule lambda (i.e. 'case ONE -> 1;')
129 final boolean isSwitchRuleLambda = firstChild == null;
130
131 if (!isSwitchRuleLambda
132 && getLineStart(firstChild) == expandedTabsColumnNo(firstChild)) {
133 final int firstChildColumnNo = expandedTabsColumnNo(firstChild);
134 final IndentLevel level = getIndent();
135
136 if (isNonAcceptableIndent(firstChildColumnNo, level)) {
137 isLambdaCorrectlyIndented = false;
138 logError(firstChild, "arguments", firstChildColumnNo, level);
139 }
140 }
141
142 // If the "->" is the first element on the line, assume line wrapping.
143 final int mainAstColumnNo = expandedTabsColumnNo(mainAst);
144 final boolean isLineWrappedLambda = mainAstColumnNo == getLineStart(mainAst);
145 if (isLineWrappedLambda) {
146 checkLineWrappedLambda(isSwitchRuleLambda, mainAstColumnNo);
147 }
148
149 final DetailAST nextSibling = mainAst.getNextSibling();
150
151 if (isSwitchRuleLambda
152 && nextSibling.getType() == TokenTypes.EXPR
153 && !TokenUtil.areOnSameLine(mainAst, nextSibling)) {
154 // Likely a single-statement switch rule lambda without curly braces, e.g.:
155 // case ONE ->
156 // 1;
157 checkSingleStatementSwitchRuleIndentation(isLineWrappedLambda);
158 }
159 }
160
161 /**
162 * Checks that given indent is acceptable or not.
163 *
164 * @param astColumnNo indent value to check
165 * @param level indent level
166 * @return true if indent is not acceptable
167 */
168 private boolean isNonAcceptableIndent(int astColumnNo, IndentLevel level) {
169 return astColumnNo < level.getFirstIndentLevel()
170 || getIndentCheck().isForceStrictCondition()
171 && !level.isAcceptable(astColumnNo);
172 }
173
174 /**
175 * This method checks a line wrapped lambda, whether it is a lambda
176 * expression or switch rule lambda.
177 *
178 * @param isSwitchRuleLambda if mainAst is a switch rule lambda
179 * @param mainAstColumnNo the column number of the lambda we are checking
180 */
181 private void checkLineWrappedLambda(final boolean isSwitchRuleLambda,
182 final int mainAstColumnNo) {
183 final IndentLevel level;
184 final DetailAST mainAst = getMainAst();
185
186 if (isSwitchRuleLambda) {
187 // We check the indentation of the case literal or default literal
188 // on the previous line and use that to determine the correct
189 // indentation for the line wrapped "->"
190 final DetailAST previousSibling = mainAst.getPreviousSibling();
191 final int previousLineStart = getLineStart(previousSibling);
192
193 level = new IndentLevel(new IndentLevel(previousLineStart),
194 getIndentCheck().getLineWrappingIndentation());
195 }
196 else {
197 level = new IndentLevel(getIndent(),
198 getIndentCheck().getLineWrappingIndentation());
199 }
200
201 if (isNonAcceptableIndent(mainAstColumnNo, level)) {
202 isLambdaCorrectlyIndented = false;
203 logError(mainAst, "", mainAstColumnNo, level);
204 }
205 }
206
207 /**
208 * Checks the indentation of statements inside a single-statement switch rule
209 * when the statement is not on the same line as the lambda operator ({@code ->}).
210 * This applies to single-statement switch rules without curly braces {@code {}}.
211 * Example:
212 * <pre>
213 * case ONE {@code ->}
214 * 1;
215 * </pre>
216 *
217 * @param isLambdaFirstInLine if {@code ->} is the first element on the line
218 */
219 private void checkSingleStatementSwitchRuleIndentation(boolean isLambdaFirstInLine) {
220 final DetailAST mainAst = getMainAst();
221 IndentLevel level = getParent().getSuggestedChildIndent(this);
222
223 if (isLambdaFirstInLine) {
224 // If the lambda operator (`->`) is at the start of the line, assume line wrapping
225 // and add additional indentation for the statement in the next line.
226 level = new IndentLevel(level, getIndentCheck().getLineWrappingIndentation());
227 }
228
229 // The first line should not match if the switch rule statement starts on the same line
230 // as "->" but continues onto the next lines as part of a single logical expression.
231 final DetailAST nextSibling = mainAst.getNextSibling();
232 final boolean firstLineMatches = getFirstLine(nextSibling) != mainAst.getLineNo();
233 checkExpressionSubtree(nextSibling, level, firstLineMatches, false);
234 }
235
236 /**
237 * Checks if the current LAMBDA node is placed on the same line
238 * as the given SWITCH_LITERAL node.
239 *
240 * @param node the SWITCH_LITERAL node to compare with
241 * @return true if the current LAMBDA node is on the same line
242 * as the given SWITCH_LITERAL node
243 */
244 private boolean isSameLineAsSwitch(DetailAST node) {
245 return node.getType() == TokenTypes.LITERAL_SWITCH
246 && TokenUtil.areOnSameLine(getMainAst(), node);
247 }
248
249 /**
250 * Finds the parent ENUM_CONSTANT_DEF node if this lambda is an argument of an enum constant.
251 *
252 * @return the ENUM_CONSTANT_DEF node if found, null otherwise
253 */
254 @Nullable
255 private DetailAST findParentEnumConstantDef() {
256 DetailAST result = null;
257 DetailAST parent = getMainAst().getParent();
258 while (parent != null) {
259 if (parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
260 result = parent;
261 break;
262 }
263 parent = parent.getParent();
264 }
265 return result;
266 }
267
268 /**
269 * Calculates the expected indentation for a lambda inside enum constant arguments.
270 * The expected indent is the enum constant's indent plus line wrapping indentation.
271 *
272 * @param enumConstDef the ENUM_CONSTANT_DEF node
273 * @return the expected indentation level
274 */
275 private IndentLevel getEnumConstantBasedIndent(DetailAST enumConstDef) {
276 final int enumConstIndent = getLineStart(enumConstDef);
277 final IndentLevel baseLevel = new IndentLevel(enumConstIndent);
278 return new IndentLevel(baseLevel, getIndentCheck().getLineWrappingIndentation());
279 }
280 }