View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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 com.puppycrawl.tools.checkstyle.api.DetailAST;
23  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
24  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
25  
26  /**
27   * Handler for lambda expressions.
28   *
29   */
30  public class LambdaHandler extends AbstractExpressionHandler {
31      /**
32       * Checks whether the lambda is correctly indented, this variable get its value from checking
33       * the lambda handler's indentation, and it is being used in aligning the lambda's children.
34       * A true value depicts lambda is correctly aligned without giving any errors.
35       * This is updated to false where there is any Indentation error log.
36       */
37      private boolean isLambdaCorrectlyIndented = true;
38  
39      /**
40       * Construct an instance of this handler with the given indentation check,
41       * abstract syntax tree, and parent handler.
42       *
43       * @param indentCheck the indentation check
44       * @param ast the abstract syntax tree
45       * @param parent the parent handler
46       */
47      public LambdaHandler(IndentationCheck indentCheck,
48                           DetailAST ast, AbstractExpressionHandler parent) {
49          super(indentCheck, "lambda", ast, parent);
50      }
51  
52      @Override
53      public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
54          IndentLevel childIndent = getIndent();
55          if (isLambdaCorrectlyIndented) {
56              // If the lambda is correctly indented, include its line start as acceptable to
57              // avoid false positives. When "forceStrictCondition" is off, we allow indents
58              // larger than expected (e.g., 12 instead of 6 or 8). These larger indents are
59              // accepted but not recorded, so child indent suggestions may be inaccurate.
60              // Adding the actual line start ensures the tool recognizes the lambda’s real indent
61              // context.
62              childIndent = IndentLevel.addAcceptable(childIndent, getLineStart(getMainAst()));
63  
64              if (child instanceof SlistHandler) {
65                  // Lambda with block body (enclosed in {})
66                  childIndent = IndentLevel.addAcceptable(childIndent,
67                      getLineStart(getMainAst().getFirstChild()));
68              }
69              else {
70                  // Single-expression lambda (no {} block):
71                  // assume line wrapping and add additional indentation
72                  // for the statement in the next line.
73                  childIndent = new IndentLevel(childIndent,
74                          getIndentCheck().getLineWrappingIndentation());
75              }
76          }
77  
78          return childIndent;
79      }
80  
81      /**
82       * {@inheritDoc}.
83       *
84       * @noinspection MethodWithMultipleReturnPoints
85       * @noinspectionreason MethodWithMultipleReturnPoints - indentation is complex and
86       *      tightly coupled, thus making this method difficult to refactor
87       */
88      @Override
89      protected IndentLevel getIndentImpl() {
90          if (getParent() instanceof MethodCallHandler) {
91              return getParent().getSuggestedChildIndent(this);
92          }
93  
94          DetailAST parent = getMainAst().getParent();
95          if (getParent() instanceof NewHandler) {
96              parent = parent.getParent();
97          }
98  
99          // 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 }