View Javadoc
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 }