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