View Javadoc
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 }