View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 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}?
20   *
21   * 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.
24   *
25   * 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.
28   *
29   * @since 0.3
30   */
31  public final class NonStaticMethodCheck extends AbstractCheck {
32  
33      /**
34       * Files to exclude from this check.
35       * This is mostly to exclude JUnit tests.
36       */
37      private Pattern exclude = Pattern.compile("^$");
38  
39      /**
40       * Exclude files matching given pattern.
41       * @param excl Regexp of classes to exclude.
42       */
43      public void setExcludeFileNamePattern(final String excl) {
44          this.exclude = Pattern.compile(excl);
45      }
46  
47      @Override
48      public int[] getDefaultTokens() {
49          return new int[] {
50              TokenTypes.METHOD_DEF,
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      @SuppressWarnings("deprecation")
66      public void visitToken(final DetailAST ast) {
67          if (this.exclude.matcher(this.getFileContents().getFileName())
68              .find()) {
69              return;
70          }
71          if (TokenTypes.CLASS_DEF == ast.getParent().getParent().getType()) {
72              this.checkClassMethod(ast);
73          }
74      }
75  
76      /**
77       * Check that non static class method refer {@code this}. Methods that
78       * are {@code native}, {@code abstract} or annotated with {@code @Override}
79       * are excluded.  Additionally, if the method only throws an exception, it
80       * too is excluded.
81       * @param method DetailAST of method
82       */
83      private void checkClassMethod(final DetailAST method) {
84          final DetailAST modifiers = method
85              .findFirstToken(TokenTypes.MODIFIERS);
86          if (modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null) {
87              return;
88          }
89          final BranchContains checker = new BranchContains(method);
90          final boolean onlythrow =
91              checker.check(TokenTypes.LITERAL_THROW)
92                  && !checker.check(TokenTypes.LCURLY)
93                  && this.countSemiColons(method) == 1;
94          if (!AnnotationUtil.containsAnnotation(method, "Override")
95              && !isInAbstractOrNativeMethod(method)
96              && !checker.check(TokenTypes.LITERAL_THIS)
97              && !onlythrow) {
98              final int line = method.getLineNo();
99              this.log(
100                 line,
101                 "This method must be static, because it does not refer to \"this\""
102             );
103         }
104     }
105 
106     /**
107      * Determines whether a method is {@code abstract} or {@code native}.
108      * @param method Method to check.
109      * @return True if method is abstract or native.
110      */
111     private static boolean isInAbstractOrNativeMethod(final DetailAST method) {
112         final DetailAST modifiers = method.findFirstToken(TokenTypes.MODIFIERS);
113         final BranchContains checker = new BranchContains(modifiers);
114         return checker.check(TokenTypes.ABSTRACT)
115             || checker.check(TokenTypes.LITERAL_NATIVE);
116     }
117 
118     /**
119      * Determines the number semicolons in a method excluding those in
120      * comments.
121      * @param method Method to count
122      * @return The number of semicolons in the method as an int
123      */
124     @SuppressWarnings("deprecation")
125     private int countSemiColons(final DetailAST method) {
126         final DetailAST openingbrace = method.findFirstToken(TokenTypes.SLIST);
127         int count = 0;
128         if (openingbrace != null) {
129             final DetailAST closingbrace =
130                 openingbrace.findFirstToken(TokenTypes.RCURLY);
131             final int lastline = closingbrace.getLineNo();
132             final int firstline = openingbrace.getLineNo();
133             final FileContents contents = this.getFileContents();
134             for (int line = firstline - 1; line < lastline; line += 1) {
135                 if (!contents.lineIsBlank(line)
136                     && !contents.lineIsComment(line)
137                     && contents.getLine(line).contains(";")) {
138                     count += 1;
139                 }
140             }
141         }
142         return count;
143     }
144 }