View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.filters;
21  
22  import java.util.HashSet;
23  import java.util.Objects;
24  import java.util.Set;
25  
26  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean;
27  import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
28  import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
29  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
30  import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
31  import com.puppycrawl.tools.checkstyle.utils.FilterUtil;
32  
33  /**
34   * <div>
35   * Filter {@code SuppressionXpathFilter} works as
36   * <a href="https://checkstyle.org/filters/suppressionfilter.html">
37   * SuppressionFilter</a>, but also processes {@code suppress-xpath} elements,
38   * which contain xpath-expressions. Xpath-expressions are queries for
39   * suppressed nodes inside the AST tree.
40   * </div>
41   *
42   * <p>
43   * Currently, filter does not support the following checks:
44   * </p>
45   * <ul id="IncompatibleChecks">
46   * <li>
47   * NoCodeInFile (reason is that AST is not generated for a file not containing code)
48   * </li>
49   * <li>
50   * Regexp (reason is at
51   * <a href="https://github.com/checkstyle/checkstyle/issues/7759#issuecomment-605525287"> #7759</a>)
52   * </li>
53   * <li>
54   * RegexpSinglelineJava (reason is at
55   * <a href="https://github.com/checkstyle/checkstyle/issues/7759#issuecomment-605525287"> #7759</a>)
56   * </li>
57   * </ul>
58   *
59   * <p>
60   * Also, the filter does not support suppressions inside javadoc reported by Javadoc checks:
61   * </p>
62   * <ul id="JavadocChecks">
63   * <li>
64   * AtclauseOrder
65   * </li>
66   * <li>
67   * JavadocBlockTagLocation
68   * </li>
69   * <li>
70   * JavadocMethod
71   * </li>
72   * <li>
73   * JavadocMissingLeadingAsterisk
74   * </li>
75   * <li>
76   * JavadocMissingWhitespaceAfterAsterisk
77   * </li>
78   * <li>
79   * JavadocParagraph
80   * </li>
81   * <li>
82   * JavadocTagContinuationIndentation
83   * </li>
84   * <li>
85   * JavadocType
86   * </li>
87   * <li>
88   * MissingDeprecated
89   * </li>
90   * <li>
91   * NonEmptyAtclauseDescription
92   * </li>
93   * <li>
94   * RequireEmptyLineBeforeBlockTagGroup
95   * </li>
96   * <li>
97   * SingleLineJavadoc
98   * </li>
99   * <li>
100  * SummaryJavadoc
101  * </li>
102  * <li>
103  * WriteTag
104  * </li>
105  * </ul>
106  *
107  * <p>
108  * Note, that support for these Checks will be available after resolving issue
109  * <a href="https://github.com/checkstyle/checkstyle/issues/5770">#5770</a>.
110  * </p>
111  *
112  * <p>
113  * Currently, filter supports the following xpath axes:
114  * </p>
115  * <ul>
116  * <li>
117  * ancestor
118  * </li>
119  * <li>
120  * ancestor-or-self
121  * </li>
122  * <li>
123  * attribute
124  * </li>
125  * <li>
126  * child
127  * </li>
128  * <li>
129  * descendant
130  * </li>
131  * <li>
132  * descendant-or-self
133  * </li>
134  * <li>
135  * following
136  * </li>
137  * <li>
138  * following-sibling
139  * </li>
140  * <li>
141  * parent
142  * </li>
143  * <li>
144  * preceding
145  * </li>
146  * <li>
147  * preceding-sibling
148  * </li>
149  * <li>
150  * self
151  * </li>
152  * </ul>
153  *
154  * <p>
155  * You can use the command line helper tool to generate xpath suppressions based on your
156  * configuration file and input files. See <a href="https://checkstyle.org/cmdline.html">here</a>
157  * for more details.
158  * </p>
159  *
160  * <p>
161  * Notes:
162  * The suppression file location is checked in following order:
163  * </p>
164  * <ol>
165  * <li>
166  * as a filesystem location
167  * </li>
168  * <li>
169  * if no file found, and the location starts with either {@code http://} or {@code https://},
170  * then it is interpreted as a URL
171  * </li>
172  * <li>
173  * if no file found, then passed to the {@code ClassLoader.getResource()} method.
174  * </li>
175  * </ol>
176  *
177  * <p>
178  * SuppressionXpathFilter can suppress Checks that have Treewalker as parent module.
179  * </p>
180  *
181  * <p>
182  * A <a href="/dtds/suppressions_1_2_xpath.dtd"><em>suppressions XML
183  * document</em></a> contains a set
184  * of {@code suppress} and {@code suppress-xpath} elements, where
185  * each {@code suppress-xpath} element can have the
186  * following attributes:
187  * </p>
188  * <ul>
189  * <li>
190  * {@code files} -
191  * a <a href="https://checkstyle.org/property-types.html#Pattern">Pattern</a>
192  * matched against the file name associated with an audit
193  * event. It is optional. If unmatched, all Unix path separators (/)
194  * are converted to Windows separators (\) and retried.
195  * </li>
196  * <li>
197  * {@code checks} -
198  * a <a href="https://checkstyle.org/property-types.html#Pattern">Pattern</a>
199  * matched against the name of the check associated with an audit
200  * event. Optional as long as {@code id} or {@code message} is specified.
201  * </li>
202  * <li>
203  * {@code message} -
204  * a <a href="https://checkstyle.org/property-types.html#Pattern">Pattern</a>
205  * matched against the message of the check associated with an audit
206  * event. Optional as long as {@code checks} or {@code id} is specified.
207  * </li>
208  * <li>
209  * {@code id} -
210  * a <a href="https://checkstyle.org/property-types.html#String">String</a>
211  * matched against the ID of the check associated with an audit
212  * event. Optional as long as {@code checks} or {@code message} is specified.
213  * </li>
214  * <li>
215  * {@code query} -
216  * a <a href="https://checkstyle.org/property-types.html#String">String</a>
217  * xpath query. It is optional.
218  * </li>
219  * </ul>
220  *
221  * <p>
222  * Each audit event is checked against
223  * each {@code suppress} and {@code suppress-xpath} element. It is
224  * suppressed if all specified attributes match against the audit
225  * event.
226  * </p>
227  *
228  * <p>
229  * ATTENTION: filtering by message is dependent on runtime locale. If project is running
230  * in different languages it is better to avoid filtering by message.
231  * </p>
232  *
233  * @since 8.6
234  */
235 public class SuppressionXpathFilter extends AbstractAutomaticBean implements
236         TreeWalkerFilter, ExternalResourceHolder {
237 
238     /** Set of individual xpath suppresses. */
239     private final Set<TreeWalkerFilter> filters = new HashSet<>();
240 
241     /** Specify the location of the <em>suppressions XML document</em> file. */
242     private String file;
243     /**
244      * Control what to do when the file is not existing.
245      * If optional is set to false the file must exist, or else it ends with error.
246      * On the other hand if optional is true and file is not found,
247      * the filter accepts all audit events.
248      */
249     private boolean optional;
250 
251     /**
252      * Creates a new {@code SuppressionXpathFilter} instance.
253      */
254     public SuppressionXpathFilter() {
255         // no code by default
256     }
257 
258     /**
259      * Setter to specify the location of the <em>suppressions XML document</em> file.
260      *
261      * @param fileName name of the suppressions file.
262      * @since 8.6
263      */
264     public void setFile(String fileName) {
265         file = fileName;
266     }
267 
268     /**
269      * Setter to control what to do when the file is not existing.
270      * If optional is set to false the file must exist, or else it ends with error.
271      * On the other hand if optional is true and file is not found,
272      * the filter accepts all audit events.
273      *
274      * @param optional tells if config file existence is optional.
275      * @since 8.6
276      */
277     public void setOptional(boolean optional) {
278         this.optional = optional;
279     }
280 
281     @Override
282     public boolean equals(Object obj) {
283         if (this == obj) {
284             return true;
285         }
286         if (obj == null || getClass() != obj.getClass()) {
287             return false;
288         }
289         final SuppressionXpathFilter suppressionXpathFilter = (SuppressionXpathFilter) obj;
290         return Objects.equals(filters, suppressionXpathFilter.filters);
291     }
292 
293     @Override
294     public int hashCode() {
295         return Objects.hash(filters);
296     }
297 
298     @Override
299     public boolean accept(TreeWalkerAuditEvent treeWalkerAuditEvent) {
300         boolean result = true;
301         for (TreeWalkerFilter filter : filters) {
302             if (!filter.accept(treeWalkerAuditEvent)) {
303                 result = false;
304                 break;
305             }
306         }
307         return result;
308     }
309 
310     @Override
311     public Set<String> getExternalResourceLocations() {
312         return Set.of(file);
313     }
314 
315     @Override
316     protected void finishLocalSetup() throws CheckstyleException {
317         if (file != null) {
318             if (optional) {
319                 if (FilterUtil.isFileExists(file)) {
320                     filters.addAll(SuppressionsLoader.loadXpathSuppressions(file));
321                 }
322             }
323             else {
324                 filters.addAll(SuppressionsLoader.loadXpathSuppressions(file));
325             }
326         }
327     }
328 
329 }