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 does not declare a static nested class, since a
14   * static nested class is a self-contained abstraction and belongs in
15   * a file of its own. Non-static (inner) nested classes are always
16   * allowed, since they keep a reference to their enclosing instance
17   * and are not self-contained.
18   *
19   * <p>Only files whose names do not match the configured pattern (by
20   * default {@code *Test.java}, {@code *IT.java}, {@code *ITCase.java})
21   * are inspected, since fake/stub helpers nested inside test classes
22   * are a normal pattern (see
23   * {@link ProhibitFieldsInTestClassesCheck}).</p>
24   *
25   * @since 0.73.4
26   */
27  public final class ProhibitStaticNestedClassesCheck extends AbstractCheck {
28  
29      /**
30       * File names of test classes.
31       */
32      private static final Pattern TESTS =
33          Pattern.compile(".*(Test|IT|ITCase)\\.java$");
34  
35      /**
36       * File names that this check does not apply to.
37       */
38      private Pattern exclude;
39  
40      /**
41       * Default constructor.
42       */
43      public ProhibitStaticNestedClassesCheck() {
44          this.exclude = ProhibitStaticNestedClassesCheck.TESTS;
45      }
46  
47      /**
48       * Do not apply this check to files matching the given pattern.
49       *
50       * @param regex Regex of file names to exclude
51       */
52      public void setExcludeFileNamePattern(final String regex) {
53          this.exclude = Pattern.compile(regex);
54      }
55  
56      @Override
57      public int[] getDefaultTokens() {
58          return new int[] {TokenTypes.CLASS_DEF};
59      }
60  
61      @Override
62      public int[] getAcceptableTokens() {
63          return this.getDefaultTokens();
64      }
65  
66      @Override
67      public int[] getRequiredTokens() {
68          return this.getDefaultTokens();
69      }
70  
71      @Override
72      public void visitToken(final DetailAST ast) {
73          if (!this.exclude.matcher(this.getFilePath()).find()
74              && ProhibitStaticNestedClassesCheck.isStaticNestedClass(ast)) {
75              final DetailAST name = ast.findFirstToken(TokenTypes.IDENT);
76              this.log(
77                  name.getLineNo(),
78                  String.format(
79                      "Static class \"%s\" must be moved into its own file",
80                      name.getText()
81                  )
82              );
83          }
84      }
85  
86      private static boolean isStaticNestedClass(final DetailAST ast) {
87          final DetailAST parent = ast.getParent();
88          return parent != null && parent.getType() == TokenTypes.OBJBLOCK
89              && ast.findFirstToken(TokenTypes.MODIFIERS)
90                  .findFirstToken(TokenTypes.LITERAL_STATIC) != null;
91      }
92  }