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 com.puppycrawl.tools.checkstyle.api.DetailAST; 023import com.puppycrawl.tools.checkstyle.api.TokenTypes; 024import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 025 026/** 027 * Handler for method definitions. 028 * 029 */ 030public class MethodDefHandler extends BlockParentHandler { 031 032 /** 033 * Construct an instance of this handler with the given indentation check, 034 * abstract syntax tree, and parent handler. 035 * 036 * @param indentCheck the indentation check 037 * @param ast the abstract syntax tree 038 * @param parent the parent handler 039 */ 040 public MethodDefHandler(IndentationCheck indentCheck, 041 DetailAST ast, AbstractExpressionHandler parent) { 042 super(indentCheck, getHandlerName(ast), ast, parent); 043 } 044 045 @Override 046 protected DetailAST getTopLevelAst() { 047 // we check this stuff ourselves below 048 return null; 049 } 050 051 /** 052 * Checks modifiers for method definitions. 053 */ 054 private void checkModifiers() { 055 final DetailAST modifier = getMainAst().findFirstToken(TokenTypes.MODIFIERS); 056 if (isOnStartOfLine(modifier) 057 && !getIndent().isAcceptable(expandedTabsColumnNo(modifier))) { 058 logError(modifier, "modifier", expandedTabsColumnNo(modifier)); 059 } 060 } 061 062 /** 063 * Check the indentation level of the throws clause. 064 */ 065 private void checkThrows() { 066 final DetailAST throwsAst = getMainAst().findFirstToken(TokenTypes.LITERAL_THROWS); 067 068 if (throwsAst != null) { 069 final LineWrappingHandler.LineWrappingOptions ignoreFirstLine; 070 if (isOnStartOfLine(throwsAst)) { 071 ignoreFirstLine = LineWrappingHandler.LineWrappingOptions.NONE; 072 } 073 else { 074 ignoreFirstLine = LineWrappingHandler.LineWrappingOptions.IGNORE_FIRST_LINE; 075 } 076 checkWrappingIndentation(throwsAst, throwsAst.getNextSibling(), getIndentCheck() 077 .getThrowsIndent(), getLineStart(getMethodDefLineStart(getMainAst())), 078 ignoreFirstLine); 079 } 080 } 081 082 /** 083 * Gets the start line of the method, excluding any annotations. This is required because the 084 * current {@link TokenTypes#METHOD_DEF} may not always be the start as seen in 085 * <a href="https://github.com/checkstyle/checkstyle/issues/3145">#3145</a>. 086 * 087 * @param mainAst 088 * The method definition ast. 089 * @return The start column position of the method. 090 */ 091 private static int getMethodDefLineStart(DetailAST mainAst) { 092 // get first type position 093 int lineStart = mainAst.findFirstToken(TokenTypes.IDENT).getLineNo(); 094 095 // check if there is a type before the indent 096 final DetailAST typeNode = mainAst.findFirstToken(TokenTypes.TYPE); 097 if (typeNode != null) { 098 lineStart = getFirstLine(typeNode); 099 } 100 101 // check if there is a modifier before the type 102 for (DetailAST node = mainAst.findFirstToken(TokenTypes.MODIFIERS).getFirstChild(); 103 node != null; 104 node = node.getNextSibling()) { 105 // skip annotations as we check them else where as outside the method 106 if (node.getType() == TokenTypes.ANNOTATION) { 107 continue; 108 } 109 110 lineStart = Math.min(lineStart, node.getLineNo()); 111 } 112 113 return lineStart; 114 } 115 116 @Override 117 public void checkIndentation() { 118 checkModifiers(); 119 checkThrows(); 120 121 if (getMethodDefParamRightParen(getMainAst()) != null) { 122 checkWrappingIndentation(getMainAst(), getMethodDefParamRightParen(getMainAst())); 123 } 124 // abstract method def -- no body 125 if (getLeftCurly() != null) { 126 super.checkIndentation(); 127 } 128 } 129 130 @Override 131 protected boolean shouldCheckLeftParen(DetailAST leftParen) { 132 return !isMethodParenOnItsOwnLineWithMatchingRightParen(leftParen, 133 getMethodDefParamRightParen(getMainAst())); 134 } 135 136 @Override 137 protected boolean shouldCheckRightParen(DetailAST leftParen, DetailAST rightParen) { 138 return !isMethodParenOnItsOwnLineWithMatchingRightParen(leftParen, rightParen); 139 } 140 141 /** 142 * Checks if method definition parenthesis are wrapped on separate lines and aligned. 143 * 144 * @param leftParen left parenthesis of method definition 145 * @param rightParen right parenthesis of method definition 146 * @return true if both parenthesis start their own lines and are aligned 147 */ 148 private boolean isMethodParenOnItsOwnLineWithMatchingRightParen(DetailAST leftParen, 149 DetailAST rightParen) { 150 return rightParen != null 151 && !TokenUtil.areOnSameLine(leftParen, rightParen) 152 && isOnStartOfLine(leftParen) 153 && isOnStartOfLine(rightParen) 154 && expandedTabsColumnNo(leftParen) == expandedTabsColumnNo(rightParen); 155 } 156 157 /** 158 * Returns right parenthesis of method definition parameter list. 159 * 160 * @param methodDefAst 161 * method definition ast node(TokenTypes.LITERAL_IF) 162 * @return right parenthesis of method definition parameter list. 163 */ 164 private static DetailAST getMethodDefParamRightParen(DetailAST methodDefAst) { 165 return methodDefAst.findFirstToken(TokenTypes.RPAREN); 166 } 167 168 /** 169 * Creates a handler name for this class according to ast type. 170 * 171 * @param ast the abstract syntax tree. 172 * @return handler name for this class. 173 */ 174 private static String getHandlerName(DetailAST ast) { 175 176 return switch (ast.getType()) { 177 case TokenTypes.CTOR_DEF -> "ctor def"; 178 case TokenTypes.ANNOTATION_FIELD_DEF -> "annotation field def"; 179 case TokenTypes.COMPACT_CTOR_DEF -> "compact ctor def"; 180 default -> "method def"; 181 }; 182 } 183 184}