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.FileContents;
10  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
11  import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
12  import java.util.regex.Pattern;
13  
14  /**
15   * Checks that non static method must contain at least one reference to
16   * {@code this}.
17   *
18   * <p>If your method doesn't need {@code this} than why it is not
19   * {@code static}?</p>
20   *
21   * <p>The exception here is when method has {@code @Override} annotation. There's
22   * no concept of inheritance and polymorphism for static methods even if they
23   * don't need {@code this} to perform the actual work.</p>
24   *
25   * <p>Another exception is when method is {@code abstract} or {@code native}.
26   * Such methods don't have body so detection based on {@code this} doesn't
27   * make sense for them.</p>
28   *
29   * @since 0.3
30   */
31  public final class NonStaticMethodCheck extends AbstractCheck {
32  
33      /**
34       * Pattern that matches an empty string only.
35       */
36      private static final Pattern EMPTY = Pattern.compile("^$");
37  
38      /**
39       * Files to exclude from this check.
40       * This is mostly to exclude JUnit tests.
41       */
42      private Pattern exclude;
43  
44      /**
45       * Default constructor.
46       */
47      public NonStaticMethodCheck() {
48          this.exclude = NonStaticMethodCheck.EMPTY;
49      }
50  
51      /**
52       * Exclude files matching given pattern.
53       *
54       * @param excl Regexp of classes to exclude
55       */
56      public void setExcludeFileNamePattern(final String excl) {
57          this.exclude = Pattern.compile(excl);
58      }
59  
60      @Override
61      public int[] getDefaultTokens() {
62          return new int[] {
63              TokenTypes.METHOD_DEF,
64          };
65      }
66  
67      @Override
68      public int[] getAcceptableTokens() {
69          return this.getDefaultTokens();
70      }
71  
72      @Override
73      public int[] getRequiredTokens() {
74          return this.getDefaultTokens();
75      }
76  
77      @Override
78      @SuppressWarnings("deprecation")
79      public void visitToken(final DetailAST ast) {
80          if (this.exclude.matcher(this.getFileContents().getFileName())
81              .find()) {
82              return;
83          }
84          if (TokenTypes.CLASS_DEF == ast.getParent().getParent().getType()) {
85              this.checkClassMethod(ast);
86          }
87      }
88  
89      private void checkClassMethod(final DetailAST method) {
90          final DetailAST modifiers = method
91              .findFirstToken(TokenTypes.MODIFIERS);
92          if (modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null) {
93              return;
94          }
95          final BranchContains checker = new BranchContains(method);
96          final boolean onlythrow =
97              checker.check(TokenTypes.LITERAL_THROW)
98                  && !checker.check(TokenTypes.LCURLY)
99                  && this.countSemiColons(method) == 1;
100         final boolean skip = AnnotationUtil.containsAnnotation(method, "Override")
101             || isInAbstractOrNativeMethod(method)
102             || onlythrow;
103         if (!skip
104             && !checker.check(TokenTypes.LITERAL_THIS)
105             && !checker.check(TokenTypes.LITERAL_SUPER)) {
106             this.log(
107                 method.getLineNo(),
108                 "This method must be static, because it does not refer to \"this\""
109             );
110         }
111     }
112 
113     private static boolean isInAbstractOrNativeMethod(final DetailAST method) {
114         final BranchContains checker = new BranchContains(
115             method.findFirstToken(TokenTypes.MODIFIERS)
116         );
117         return checker.check(TokenTypes.ABSTRACT)
118             || checker.check(TokenTypes.LITERAL_NATIVE);
119     }
120 
121     @SuppressWarnings("deprecation")
122     private int countSemiColons(final DetailAST method) {
123         final DetailAST openingbrace = method.findFirstToken(TokenTypes.SLIST);
124         int count = 0;
125         if (openingbrace != null) {
126             final DetailAST closingbrace =
127                 openingbrace.findFirstToken(TokenTypes.RCURLY);
128             final int lastline = closingbrace.getLineNo();
129             final int firstline = openingbrace.getLineNo();
130             final FileContents contents = this.getFileContents();
131             for (int line = firstline - 1; line < lastline; line += 1) {
132                 if (!contents.lineIsBlank(line)
133                     && !contents.lineIsComment(line)
134                     && contents.getLine(line).contains(";")) {
135                     count += 1;
136                 }
137             }
138         }
139         return count;
140     }
141 }