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.ArrayDeque;
11  import java.util.Deque;
12  import java.util.HashSet;
13  import java.util.Set;
14  
15  /**
16   * Checks that static members are not accessed through an instance reference.
17   *
18   * <p>A static method or field must be accessed through the declaring class
19   * (for example {@code MyClass.staticMethod()}), not through
20   * {@code this.staticMethod()}. Accessing a static member through an instance
21   * reference is misleading because it looks like a polymorphic call while the
22   * dispatch is actually resolved statically at compile time.</p>
23   *
24   * <p>This check scans every class, enum and interface in the file, collects
25   * the names of all declared static methods and fields, and reports every
26   * {@code this.name} expression where {@code name} is in that set.</p>
27   *
28   * @since 0.24
29   */
30  public final class StaticAccessViaInstanceCheck extends AbstractCheck {
31  
32      /**
33       * Stack of static member name sets, one per enclosing class-like scope.
34       */
35      private final Deque<Set<String>> scopes;
36  
37      /**
38       * Default constructor.
39       */
40      public StaticAccessViaInstanceCheck() {
41          this.scopes = new ArrayDeque<>();
42      }
43  
44      @Override
45      public int[] getDefaultTokens() {
46          return new int[] {
47              TokenTypes.CLASS_DEF,
48              TokenTypes.ENUM_DEF,
49              TokenTypes.INTERFACE_DEF,
50              TokenTypes.DOT,
51          };
52      }
53  
54      @Override
55      public int[] getAcceptableTokens() {
56          return this.getDefaultTokens();
57      }
58  
59      @Override
60      public int[] getRequiredTokens() {
61          return this.getDefaultTokens();
62      }
63  
64      @Override
65      public void beginTree(final DetailAST root) {
66          this.scopes.clear();
67      }
68  
69      @Override
70      public void visitToken(final DetailAST ast) {
71          final int type = ast.getType();
72          if (type == TokenTypes.DOT) {
73              this.checkDot(ast);
74          } else {
75              this.scopes.push(collectStatic(ast));
76          }
77      }
78  
79      @Override
80      public void leaveToken(final DetailAST ast) {
81          if (ast.getType() != TokenTypes.DOT) {
82              this.scopes.pop();
83          }
84      }
85  
86      private void checkDot(final DetailAST dot) {
87          final DetailAST left = dot.getFirstChild();
88          if (!this.scopes.isEmpty()
89              && left != null
90              && left.getType() == TokenTypes.LITERAL_THIS
91              && isStaticIdent(left.getNextSibling(), this.scopes.peek())) {
92              this.log(
93                  dot,
94                  "Static member must be accessed via class name, not via instance"
95              );
96          }
97      }
98  
99      private static boolean isStaticIdent(
100         final DetailAST node, final Set<String> names) {
101         return node != null
102             && node.getType() == TokenTypes.IDENT
103             && names.contains(node.getText());
104     }
105 
106     private static Set<String> collectStatic(final DetailAST clazz) {
107         final Set<String> names = new HashSet<>(0);
108         final DetailAST body = clazz.findFirstToken(TokenTypes.OBJBLOCK);
109         for (DetailAST child = body.getFirstChild();
110             child != null; child = child.getNextSibling()) {
111             if (isStaticMember(child)) {
112                 names.add(child.findFirstToken(TokenTypes.IDENT).getText());
113             }
114         }
115         return names;
116     }
117 
118     private static boolean isStaticMember(final DetailAST node) {
119         final int type = node.getType();
120         final boolean member = type == TokenTypes.METHOD_DEF
121             || type == TokenTypes.VARIABLE_DEF;
122         boolean result = false;
123         if (member) {
124             final DetailAST modifiers =
125                 node.findFirstToken(TokenTypes.MODIFIERS);
126             result = modifiers != null
127                 && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
128         }
129         return result;
130     }
131 }