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}?
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          final boolean skip = AnnotationUtil.containsAnnotation(method, "Override")
95              || isInAbstractOrNativeMethod(method)
96              || onlythrow;
97          if (!skip
98              && !checker.check(TokenTypes.LITERAL_THIS)
99              && !checker.check(TokenTypes.LITERAL_SUPER)) {
100             this.log(
101                 method.getLineNo(),
102                 "This method must be static, because it does not refer to \"this\""
103             );
104         }
105     }
106 
107     /**
108      * Determines whether a method is {@code abstract} or {@code native}.
109      * @param method Method to check
110      * @return True if method is abstract or native
111      */
112     private static boolean isInAbstractOrNativeMethod(final DetailAST method) {
113         final BranchContains checker = new BranchContains(
114             method.findFirstToken(TokenTypes.MODIFIERS)
115         );
116         return checker.check(TokenTypes.ABSTRACT)
117             || checker.check(TokenTypes.LITERAL_NATIVE);
118     }
119 
120     /**
121      * Determines the number semicolons in a method excluding those in
122      * comments.
123      * @param method Method to count
124      * @return The number of semicolons in the method as an int
125      */
126     @SuppressWarnings("deprecation")
127     private int countSemiColons(final DetailAST method) {
128         final DetailAST openingbrace = method.findFirstToken(TokenTypes.SLIST);
129         int count = 0;
130         if (openingbrace != null) {
131             final DetailAST closingbrace =
132                 openingbrace.findFirstToken(TokenTypes.RCURLY);
133             final int lastline = closingbrace.getLineNo();
134             final int firstline = openingbrace.getLineNo();
135             final FileContents contents = this.getFileContents();
136             for (int line = firstline - 1; line < lastline; line += 1) {
137                 if (!contents.lineIsBlank(line)
138                     && !contents.lineIsComment(line)
139                     && contents.getLine(line).contains(";")) {
140                     count += 1;
141                 }
142             }
143         }
144         return count;
145     }
146 }