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.Matcher;
11  import java.util.regex.Pattern;
12  
13  /**
14   * Checks that every {@code @checkstyle} suppression names an enabled check.
15   *
16   * <p>A comment like {@code @checkstyle LineLength (N lines)} tells the
17   * suppression filters to ignore violations of {@code LineLength} nearby.
18   * When the name belongs to no enabled check, because it is misspelled or
19   * because the check has left {@code checks.xml} since, the comment
20   * suppresses nothing and stays in the source as a lie about the code.
21   * PMD reports the same mistake in {@code @SuppressWarnings} annotations.
22   * The other half, a suppression that names an enabled check but covers no
23   * violation of it, is reported by {@link UnusedSuppressions}.</p>
24   *
25   * @since 1.0
26   */
27  public final class UnknownSuppressionCheck extends AbstractCheck {
28  
29      /**
30       * The forms of suppression that the filters of {@code checks.xml} honor.
31       */
32      private static final Pattern TAG = Pattern.compile(
33          "@checkstyle (\\w+) (?:\\(\\d+ lines?\\)|disable|enable)"
34      );
35  
36      /**
37       * Checks that {@code checks.xml} enables.
38       */
39      private final ConfiguredChecks checks;
40  
41      /**
42       * Default constructor.
43       */
44      public UnknownSuppressionCheck() {
45          this.checks = new ConfiguredChecks();
46      }
47  
48      @Override
49      public boolean isCommentNodesRequired() {
50          return true;
51      }
52  
53      @Override
54      public int[] getDefaultTokens() {
55          return new int[]{TokenTypes.COMMENT_CONTENT};
56      }
57  
58      @Override
59      public int[] getAcceptableTokens() {
60          return this.getDefaultTokens();
61      }
62  
63      @Override
64      public int[] getRequiredTokens() {
65          return this.getDefaultTokens();
66      }
67  
68      @Override
69      public void visitToken(final DetailAST ast) {
70          final String text = ast.getText();
71          final Matcher matcher = UnknownSuppressionCheck.TAG.matcher(text);
72          while (matcher.find()) {
73              final String name = matcher.group(1);
74              if (!this.checks.covers(name)) {
75                  this.log(
76                      ast.getLineNo()
77                          + UnknownSuppressionCheck.breaks(text, matcher.start()),
78                      String.format(
79                          "Check \"%s\" is not enabled, this suppression has no effect",
80                          name
81                      )
82                  );
83              }
84          }
85      }
86  
87      private static int breaks(final String text, final int position) {
88          int count = 0;
89          for (int idx = 0; idx < position; ++idx) {
90              if (text.charAt(idx) == '\n') {
91                  ++count;
92              }
93          }
94          return count;
95      }
96  }