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