View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8   import com.puppycrawl.tools.checkstyle.api.DetailAST;
9   import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10  import java.util.regex.Pattern;
11  
12  /**
13   * Checks that a class, interface, enum or annotation declaration is followed
14   * by an empty line before its first member.
15   *
16   * <p>Some developers add an empty line after the opening brace of a
17   * class, interface or enum for aesthetic reasons, while others consider it
18   * wasted vertical space. This check enforces the presence of such a blank
19   * line so that the style is consistent across the whole codebase.</p>
20   *
21   * <p>The following code will be reported as a violation because the first
22   * member is not preceded by an empty line:</p>
23   * <pre>
24   * class Foo {
25   *     private int bar;
26   * }
27   * </pre>
28   *
29   * <p>Empty type bodies and one-line declarations are not reported.</p>
30   *
31   * @since 0.24
32   */
33  public final class EmptyLineBeforeFirstMemberCheck extends AbstractCheck {
34  
35      /**
36       * Pattern matching an empty (whitespace-only) line.
37       */
38      private static final Pattern BLANK = Pattern.compile("^\\s*$");
39  
40      /**
41       * Default constructor.
42       */
43      public EmptyLineBeforeFirstMemberCheck() {
44          // nothing to initialize
45      }
46  
47      @Override
48      public int[] getDefaultTokens() {
49          return new int[] {
50              TokenTypes.CLASS_DEF,
51              TokenTypes.INTERFACE_DEF,
52              TokenTypes.ENUM_DEF,
53              TokenTypes.ANNOTATION_DEF,
54          };
55      }
56  
57      @Override
58      public int[] getAcceptableTokens() {
59          return this.getDefaultTokens();
60      }
61  
62      @Override
63      public int[] getRequiredTokens() {
64          return this.getDefaultTokens();
65      }
66  
67      @Override
68      public void visitToken(final DetailAST ast) {
69          final DetailAST block = ast.findFirstToken(TokenTypes.OBJBLOCK);
70          if (block != null) {
71              this.visitBlock(block);
72          }
73      }
74  
75      private void visitBlock(final DetailAST block) {
76          final DetailAST left = block.findFirstToken(TokenTypes.LCURLY);
77          final DetailAST right = block.findFirstToken(TokenTypes.RCURLY);
78          if (left != null && right != null
79              && right.getLineNo() - left.getLineNo() >= 2
80              && !EmptyLineBeforeFirstMemberCheck.BLANK
81                  .matcher(this.getLines()[left.getLineNo()]).find()) {
82              this.log(
83                  left.getLineNo() + 1,
84                  "Expected empty line before first member"
85              );
86          }
87      }
88  }