001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 javax.annotation.Nullable; 023 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 027 028/** 029 * Handler for lambda expressions. 030 * 031 */ 032public class LambdaHandler extends AbstractExpressionHandler { 033 034 /** 035 * Checks whether the lambda is correctly indented, this variable get its value from checking 036 * the lambda handler's indentation, and it is being used in aligning the lambda's children. 037 * A true value depicts lambda is correctly aligned without giving any errors. 038 * This is updated to false where there is any Indentation error log. 039 */ 040 private boolean isLambdaCorrectlyIndented = true; 041 042 /** 043 * Construct an instance of this handler with the given indentation check, 044 * abstract syntax tree, and parent handler. 045 * 046 * @param indentCheck the indentation check 047 * @param ast the abstract syntax tree 048 * @param parent the parent handler 049 */ 050 public LambdaHandler(IndentationCheck indentCheck, 051 DetailAST ast, AbstractExpressionHandler parent) { 052 super(indentCheck, "lambda", ast, parent); 053 } 054 055 @Override 056 public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) { 057 IndentLevel childIndent = getIndent(); 058 if (isLambdaCorrectlyIndented) { 059 // If the lambda is correctly indented, include its line start as acceptable to 060 // avoid false positives. When "forceStrictCondition" is off, we allow indents 061 // larger than expected (e.g., 12 instead of 6 or 8). These larger indents are 062 // accepted but not recorded, so child indent suggestions may be inaccurate. 063 // Adding the actual line start ensures the tool recognizes the lambda’s real indent 064 // context. 065 childIndent = IndentLevel.addAcceptable(childIndent, getLineStart(getMainAst())); 066 067 if (isSameLineAsSwitch(child.getMainAst()) || child instanceof SlistHandler) { 068 // Lambda with block body (enclosed in {}) 069 childIndent = IndentLevel.addAcceptable(childIndent, 070 getLineStart(getMainAst().getFirstChild())); 071 } 072 else { 073 // Single-expression lambda (no {} block): 074 // assume line wrapping and add additional indentation 075 // for the statement in the next line. 076 childIndent = new IndentLevel(childIndent, 077 getIndentCheck().getLineWrappingIndentation()); 078 } 079 } 080 081 return childIndent; 082 } 083 084 /** 085 * {@inheritDoc}. 086 * 087 * @noinspection MethodWithMultipleReturnPoints 088 * @noinspectionreason MethodWithMultipleReturnPoints - indentation is complex and 089 * tightly coupled, thus making this method difficult to refactor 090 */ 091 @Override 092 protected IndentLevel getIndentImpl() { 093 if (getParent() instanceof MethodCallHandler) { 094 return getParent().getSuggestedChildIndent(this); 095 } 096 097 final IndentLevel result; 098 final DetailAST enumConstDef = findParentEnumConstantDef(); 099 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 * <pre> 214 * case ONE {@code ->} 215 * 1; 216 * </pre> 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}