1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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.whitespace;
21
22 import com.puppycrawl.tools.checkstyle.StatelessCheck;
23 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24 import com.puppycrawl.tools.checkstyle.api.DetailAST;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
27 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
28 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
29
30 /**
31 * <div>
32 * Checks the padding of the body of type definitions (classes, interfaces,
33 * enums, records).
34 * That is, whether a blank line is required immediately after the opening brace
35 * and/or immediately before the closing brace of type bodies.
36 * Empty type bodies are exempt from this check by default.
37 * </div>
38 *
39 * <p>
40 * A blank line (padding) line is a line containing only whitespace characters.
41 * </p>
42 *
43 * @since 13.9.0
44 */
45 @StatelessCheck
46 public class TypeBodyPaddingCheck extends AbstractCheck {
47
48 /**
49 * A key pointing to the warning message text in "messages.properties" file.
50 * Emitted when a blank line is missing after the opening brace of a type body.
51 */
52 public static final String MSG_AFTER_LCURLY = "type.body.padding.after.lcurly";
53
54 /**
55 * A key pointing to the warning message text in "messages.properties" file.
56 * Emitted when a blank line is missing before the closing brace of a type body.
57 */
58 public static final String MSG_BEFORE_RCURLY = "type.body.padding.before.rcurly";
59
60 /**
61 * Require a blank line after the type body opening brace.
62 */
63 private boolean atStartOfBody = true;
64
65 /**
66 * Require a blank line before the type body closing brace.
67 */
68 private boolean atEndOfBody = true;
69
70 /**
71 * Allow empty type bodies (those with no members) to omit blank line padding.
72 */
73 private boolean allowEmpty = true;
74
75 /**
76 * Whether to skip inner types.
77 */
78 private boolean skipInner = true;
79
80 /**
81 * Whether to skip local types (types defined inside methods or constructors).
82 */
83 private boolean skipLocal = true;
84
85 /**
86 * Creates a new {@code TypeBodyPaddingCheck} instance.
87 */
88 public TypeBodyPaddingCheck() {
89 // no code by default
90 }
91
92 /**
93 * Setter to require a blank line after the type body opening brace.
94 *
95 * @param atStartOfBody the value to set.
96 * @since 13.9.0
97 */
98 public void setAtStartOfBody(boolean atStartOfBody) {
99 this.atStartOfBody = atStartOfBody;
100 }
101
102 /**
103 * Setter to require a blank line before the type body closing brace.
104 *
105 * @param atEndOfBody the value to set.
106 * @since 13.9.0
107 */
108 public void setAtEndOfBody(boolean atEndOfBody) {
109 this.atEndOfBody = atEndOfBody;
110 }
111
112 /**
113 * Setter to allow empty type bodies to omit blank line padding.
114 *
115 * @param allowEmpty the value to set.
116 * @since 13.9.0
117 */
118 public void setAllowEmpty(boolean allowEmpty) {
119 this.allowEmpty = allowEmpty;
120 }
121
122 /**
123 * Setter to control whether to skip checking of inner types.
124 *
125 * @param skipInner {@code false} to also check inner types.
126 * @since 13.9.0
127 */
128 public void setSkipInner(boolean skipInner) {
129 this.skipInner = skipInner;
130 }
131
132 /**
133 * Setter to control whether to skip checking of local types.
134 *
135 * @param skipLocal {@code false} to also check local types.
136 * @since 13.9.0
137 */
138 public void setSkipLocal(boolean skipLocal) {
139 this.skipLocal = skipLocal;
140 }
141
142 @Override
143 public int[] getDefaultTokens() {
144 return getAcceptableTokens();
145 }
146
147 @Override
148 public int[] getAcceptableTokens() {
149 return new int[] {
150 TokenTypes.CLASS_DEF,
151 TokenTypes.INTERFACE_DEF,
152 TokenTypes.ENUM_DEF,
153 TokenTypes.RECORD_DEF,
154 TokenTypes.ANNOTATION_DEF,
155 };
156 }
157
158 @Override
159 public int[] getRequiredTokens() {
160 return CommonUtil.EMPTY_INT_ARRAY;
161 }
162
163 @Override
164 public void visitToken(DetailAST ast) {
165 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
166 final DetailAST lcurly = objBlock.findFirstToken(TokenTypes.LCURLY);
167 final DetailAST rcurly = objBlock.findFirstToken(TokenTypes.RCURLY);
168
169 // A type body is "empty" when LCURLY and RCURLY are the only children
170 // (OBJBLOCK's only children are LCURLY and RCURLY).
171 final boolean isEmpty = lcurly.getNextSibling() == rcurly;
172
173 if (!shouldSkipType(ast, isEmpty)) {
174 if (requirePaddingAfterLcurly(lcurly, rcurly)) {
175 log(lcurly, MSG_AFTER_LCURLY);
176 }
177 if (requirePaddingBeforeRcurly(lcurly, rcurly)) {
178 log(rcurly, MSG_BEFORE_RCURLY);
179 }
180 }
181 }
182
183 /**
184 * Checks if padding is required after the left curly brace.
185 *
186 * @param lcurly the left curly brace.
187 * @param rcurly the right curly brace.
188 * @return true if padding is required.
189 */
190 private boolean requirePaddingAfterLcurly(DetailAST lcurly, DetailAST rcurly) {
191 return atStartOfBody
192 && (TokenUtil.areOnSameLine(lcurly, rcurly)
193 || !haveBlankLineAfterLeftCurly(lcurly));
194 }
195
196 /**
197 * Checks if padding is required before the right curly brace.
198 *
199 * @param lcurly the left curly brace.
200 * @param rcurly the right curly brace.
201 * @return true if padding is required.
202 */
203 private boolean requirePaddingBeforeRcurly(DetailAST lcurly, DetailAST rcurly) {
204 return atEndOfBody
205 && (TokenUtil.areOnSameLine(lcurly, rcurly)
206 || !haveBlankLineBeforeRightCurly(rcurly));
207 }
208
209 /**
210 * Checks that a blank line exists after the opening brace of the type body.
211 * The line immediately following the LCURLY line must be blank (contain only
212 * whitespace).
213 *
214 * @param lcurly the LCURLY token.
215 * @return true if there is a blank line after the opening brace.
216 */
217 private boolean haveBlankLineAfterLeftCurly(DetailAST lcurly) {
218 final int nextLineIndex = lcurly.getLineNo();
219
220 return CommonUtil.isBlank(getLine(nextLineIndex));
221 }
222
223 /**
224 * Checks that a blank line exists before the closing brace of the type body.
225 * The line immediately preceding the RCURLY line must be blank.
226 *
227 * @param rcurly the RCURLY token.
228 * @return true if there is a blank line before the closing brace.
229 */
230 private boolean haveBlankLineBeforeRightCurly(DetailAST rcurly) {
231 // The line right before the closing brace must be blank.
232 // getLine uses 0-based index; rcurlyLine is 1-based.
233 // Line before rcurly has 0-based index: rcurlyLine - 2.
234 final int prevLineIndex = rcurly.getLineNo() - 2;
235 return CommonUtil.isBlank(getLine(prevLineIndex));
236 }
237
238 /**
239 * Determines whether to skip checking the given AST node.
240 *
241 * @param ast the AST node to check
242 * @param isEmpty whether the type body is empty
243 * @return {@code true} if the node should be skipped, {@code false} otherwise
244 */
245 private boolean shouldSkipType(DetailAST ast, boolean isEmpty) {
246 final boolean result;
247 if (allowEmpty && isEmpty) {
248 result = true;
249 }
250 else if (ScopeUtil.isOuterMostType(ast)) {
251 result = false;
252 }
253 else if (ScopeUtil.isInCodeBlock(ast)) {
254 result = skipLocal;
255 }
256 else {
257 result = skipInner;
258 }
259 return result;
260 }
261
262 }