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.whitespace; 021 022import java.util.Locale; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029 030/** 031 * <div> 032 * Checks the empty line right after the opening brace and right before the closing 033 * brace of a block. 034 * </div> 035 * 036 * <p> 037 * {@code topSeparator} controls the line after the opening brace and 038 * {@code bottomSeparator} controls the line before the closing brace. 039 * Both accept the following values: 040 * </p> 041 * <ul> 042 * <li>{@code empty_line} - exactly one empty line is required;</li> 043 * <li>{@code empty_line_allowed} - not validated, an empty line may be present or absent;</li> 044 * <li>{@code no_empty_line} - an empty line is forbidden.</li> 045 * </ul> 046 * 047 * @since 14.4.0 048 */ 049@StatelessCheck 050public class EmptyLineWrappingInBlockCheck extends AbstractCheck { 051 052 /** 053 * A key is pointing to the warning message empty.line.wrapping.top.no in "messages.properties" 054 * file. 055 */ 056 public static final String MSG_EMPTY_LINE_WRAPPING_TOP_NO = "empty.line.wrapping.top.no"; 057 058 /** 059 * A key is pointing to the warning message empty.line.wrapping.top.one in "messages.properties" 060 * file. 061 */ 062 public static final String MSG_EMPTY_LINE_WRAPPING_TOP_ONE = "empty.line.wrapping.top.one"; 063 064 /** 065 * A key is pointing to the warning message empty.line.wrapping.bottom.no in 066 * "messages.properties" file. 067 */ 068 public static final String MSG_EMPTY_LINE_WRAPPING_BOTTOM_NO = "empty.line.wrapping.bottom.no"; 069 070 /** 071 * A key is pointing to the warning message empty.line.wrapping.bottom.one in 072 * "messages.properties" file. 073 */ 074 public static final String MSG_EMPTY_LINE_WRAPPING_BOTTOM_ONE = 075 "empty.line.wrapping.bottom.one"; 076 077 /** Policy for empty lines after the opening brace of the block. */ 078 private SeparatorOption topSeparator = SeparatorOption.EMPTY_LINE; 079 080 /** Policy for empty lines before the closing brace of the block. */ 081 private SeparatorOption bottomSeparator = SeparatorOption.EMPTY_LINE; 082 083 /** 084 * Creates a new {@code EmptyLineWrappingInBlockCheck} instance. 085 */ 086 public EmptyLineWrappingInBlockCheck() { 087 // no code by default 088 } 089 090 /** 091 * Specify the policy for the empty line after the opening brace. 092 * 093 * @param value one of {@code empty_line}, {@code empty_line_allowed}, {@code no_empty_line} 094 * @throws IllegalArgumentException if the value is not recognized 095 * @since 14.4.0 096 */ 097 public void setTopSeparator(String value) { 098 topSeparator = SeparatorOption.valueOf(value.trim().toUpperCase(Locale.ENGLISH)); 099 } 100 101 /** 102 * Specify the policy for the empty line before the closing brace. 103 * 104 * @param value one of {@code empty_line}, {@code empty_line_allowed}, {@code no_empty_line} 105 * @throws IllegalArgumentException if the value is not recognized 106 * @since 14.4.0 107 */ 108 public void setBottomSeparator(String value) { 109 bottomSeparator = SeparatorOption.valueOf(value.trim().toUpperCase(Locale.ENGLISH)); 110 } 111 112 /** 113 * Returns the default token set that this check validates. 114 * 115 * @return the default tokens 116 */ 117 @Override 118 public int[] getDefaultTokens() { 119 return new int[] { 120 TokenTypes.CLASS_DEF, 121 TokenTypes.INTERFACE_DEF, 122 TokenTypes.ANNOTATION_DEF, 123 TokenTypes.ENUM_DEF, 124 TokenTypes.ENUM_CONSTANT_DEF, 125 }; 126 } 127 128 /** 129 * Returns the set of tokens that can be configured for this check. 130 * 131 * @return the acceptable tokens 132 */ 133 @Override 134 public int[] getAcceptableTokens() { 135 return new int[] { 136 TokenTypes.ANNOTATION_ARRAY_INIT, 137 TokenTypes.ANNOTATION_DEF, 138 TokenTypes.ARRAY_INIT, 139 TokenTypes.CASE_GROUP, 140 TokenTypes.CLASS_DEF, 141 TokenTypes.CTOR_DEF, 142 TokenTypes.ENUM_CONSTANT_DEF, 143 TokenTypes.ENUM_DEF, 144 TokenTypes.INSTANCE_INIT, 145 TokenTypes.INTERFACE_DEF, 146 TokenTypes.LAMBDA, 147 TokenTypes.LITERAL_CASE, 148 TokenTypes.LITERAL_CATCH, 149 TokenTypes.LITERAL_DEFAULT, 150 TokenTypes.LITERAL_DO, 151 TokenTypes.LITERAL_ELSE, 152 TokenTypes.LITERAL_FINALLY, 153 TokenTypes.LITERAL_FOR, 154 TokenTypes.LITERAL_IF, 155 TokenTypes.LITERAL_NEW, 156 TokenTypes.LITERAL_SWITCH, 157 TokenTypes.LITERAL_SYNCHRONIZED, 158 TokenTypes.LITERAL_TRY, 159 TokenTypes.LITERAL_WHILE, 160 TokenTypes.METHOD_DEF, 161 TokenTypes.SLIST, 162 TokenTypes.STATIC_INIT, 163 }; 164 } 165 166 /** 167 * Returns the tokens that this check must be registered for. 168 * 169 * @return the empty array as this check has no required tokens 170 */ 171 @Override 172 public int[] getRequiredTokens() { 173 return CommonUtil.EMPTY_INT_ARRAY; 174 } 175 176 /** 177 * Called to process a token. Validates empty line wrapping after the opening brace 178 * and before the closing brace of the block according to the configured policies. 179 * 180 * @param ast the token to process 181 */ 182 @Override 183 public void visitToken(DetailAST ast) { 184 final DetailAST leftCurly = findLeftBrace(ast); 185 if (leftCurly != null) { 186 final DetailAST rightCurly = findRightBrace(leftCurly); 187 if (topSeparator == SeparatorOption.NO_EMPTY_LINE 188 && hasBlankLineAfter(leftCurly, rightCurly)) { 189 log(leftCurly, MSG_EMPTY_LINE_WRAPPING_TOP_NO, leftCurly.getText()); 190 } 191 else if (topSeparator == SeparatorOption.EMPTY_LINE 192 && !hasOneEmptyLineAfter(leftCurly, rightCurly)) { 193 log(leftCurly, MSG_EMPTY_LINE_WRAPPING_TOP_ONE, leftCurly.getText()); 194 } 195 if (bottomSeparator == SeparatorOption.NO_EMPTY_LINE 196 && hasBlankLineBefore(leftCurly, rightCurly)) { 197 log(rightCurly, MSG_EMPTY_LINE_WRAPPING_BOTTOM_NO, rightCurly.getText()); 198 } 199 else if (bottomSeparator == SeparatorOption.EMPTY_LINE 200 && !hasOneEmptyLineBefore(leftCurly, rightCurly)) { 201 log(rightCurly, MSG_EMPTY_LINE_WRAPPING_BOTTOM_ONE, rightCurly.getText()); 202 } 203 } 204 } 205 206 /** 207 * Finds the left (opening) brace of the block for the given AST node. 208 * The result depends on the token type (e.g. SLIST for statements, 209 * OBJBLOCK for type bodies, or the node itself for array inits). 210 * 211 * @param ast the AST node representing a block or block-containing construct 212 * @return the AST of the opening brace, or null if not found 213 */ 214 private static DetailAST findLeftBrace(DetailAST ast) { 215 DetailAST leftBrace = null; 216 switch (ast.getType()) { 217 case TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT -> leftBrace = ast; 218 case TokenTypes.INTERFACE_DEF, TokenTypes.CLASS_DEF, TokenTypes.ANNOTATION_DEF, 219 TokenTypes.ENUM_DEF, TokenTypes.ENUM_CONSTANT_DEF, TokenTypes.LITERAL_NEW -> { 220 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 221 if (objBlock != null) { 222 leftBrace = objBlock.getFirstChild(); 223 } 224 } 225 case TokenTypes.SLIST -> { 226 if (ast.getParent().getType() == TokenTypes.SLIST) { 227 leftBrace = ast; 228 } 229 } 230 case TokenTypes.CASE_GROUP -> 231 leftBrace = ast.getLastChild().findFirstToken(TokenTypes.SLIST); 232 case TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT -> 233 leftBrace = ast.getParent().getLastChild().findFirstToken(TokenTypes.SLIST); 234 default -> 235 leftBrace = ast.findFirstToken(TokenTypes.SLIST); 236 } 237 return leftBrace; 238 } 239 240 /** 241 * Finds the right (closing) brace of the block for the given opening brace. 242 * The result depends on the token type: for SLIST/array-init the closing brace 243 * is the last child; for OBJBLOCK etc. it is the last child of the parent. 244 * 245 * @param leftBrace the opening brace AST, non-null 246 * @return the closing brace AST 247 */ 248 private static DetailAST findRightBrace(DetailAST leftBrace) { 249 return switch (leftBrace.getType()) { 250 case TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT, TokenTypes.SLIST -> 251 leftBrace.getLastChild(); 252 default -> leftBrace.getParent().getLastChild(); 253 }; 254 } 255 256 /** 257 * Checks whether the line immediately after the opening brace is blank. 258 * 259 * @param leftCurly the opening brace token 260 * @param rightCurly the closing brace token 261 * @return true if the braces are on different lines and the next line is blank 262 */ 263 private boolean hasBlankLineAfter(DetailAST leftCurly, DetailAST rightCurly) { 264 return leftCurly.getLineNo() != rightCurly.getLineNo() 265 && CommonUtil.isBlank(getLine(leftCurly.getLineNo())); 266 } 267 268 /** 269 * Checks whether there is exactly one empty line after the opening brace. 270 * Used when topSeparator is EMPTY_LINE. Two or more consecutive empty lines are rejected. 271 * 272 * @param leftCurly the opening brace token 273 * @param rightCurly the closing brace token 274 * @return true if the next line is blank and the line after that is not blank 275 */ 276 private boolean hasOneEmptyLineAfter(DetailAST leftCurly, DetailAST rightCurly) { 277 boolean result = false; 278 final int nextLineIndex = leftCurly.getLineNo(); 279 if (leftCurly.getLineNo() != rightCurly.getLineNo() 280 && CommonUtil.isBlank(getLine(nextLineIndex))) { 281 final int lineAfterNext = nextLineIndex + 1; 282 result = !CommonUtil.isBlank(getLine(lineAfterNext)); 283 } 284 return result; 285 } 286 287 /** 288 * Checks whether the line immediately before the closing brace is blank. 289 * Used when bottomSeparator is NO_EMPTY_LINE. Always false for same-line braces. 290 * 291 * @param leftCurly the opening brace token 292 * @param rightCurly the closing brace token 293 * @return true if the braces are on different lines and the previous line is blank 294 */ 295 private boolean hasBlankLineBefore(DetailAST leftCurly, DetailAST rightCurly) { 296 return leftCurly.getLineNo() != rightCurly.getLineNo() 297 && CommonUtil.isBlank(getLine(rightCurly.getLineNo() - 2)); 298 } 299 300 /** 301 * Checks whether there is exactly one empty line before the closing brace. 302 * Used when bottomSeparator is EMPTY_LINE. Two or more consecutive empty lines are rejected. 303 * 304 * @param leftCurly the opening brace token 305 * @param rightCurly the closing brace token 306 * @return true if the previous line is blank and the line before that is not blank 307 */ 308 private boolean hasOneEmptyLineBefore(DetailAST leftCurly, DetailAST rightCurly) { 309 boolean result = false; 310 final int prevLineIndex = rightCurly.getLineNo() - 2; 311 if (leftCurly.getLineNo() != rightCurly.getLineNo() 312 && CommonUtil.isBlank(getLine(prevLineIndex))) { 313 result = !CommonUtil.isBlank(getLine(prevLineIndex - 1)); 314 } 315 return result; 316 } 317 318}