1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2020 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.Collections;
23 import java.util.HashSet;
24 import java.util.Objects;
25 import java.util.Set;
26
27 import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
28 import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
29 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
30 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
31 import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
32 import com.puppycrawl.tools.checkstyle.utils.FilterUtil;
33
34 /**
35 * <p>
36 * Filter {@code SuppressionXpathFilter} works as
37 * <a href="https://checkstyle.org/config_filters.html#SuppressionFilter">SuppressionFilter</a>.
38 * Additionally, filter processes {@code suppress-xpath} elements,
39 * which contains xpath-expressions. Xpath-expressions are queries for
40 * suppressed nodes inside the AST tree.
41 * </p>
42 * <p>
43 * Currently, filter does not support the following checks:
44 * </p>
45 * <ul id="SuppressionXpathFilter_IncompatibleChecks">
46 * <li>
47 * EmptyLineSeparator
48 * </li>
49 * <li>
50 * Indentation
51 * </li>
52 * <li>
53 * JavadocMethod
54 * </li>
55 * <li>
56 * MissingJavadocType
57 * </li>
58 * <li>
59 * Regexp (reason is at
60 * <a href="https://github.com/checkstyle/checkstyle/issues/7759#issuecomment-605525287"> #7759</a>)
61 * </li>
62 * <li>
63 * RegexpSinglelineJava (reason is at
64 * <a href="https://github.com/checkstyle/checkstyle/issues/7759#issuecomment-605525287"> #7759</a>)
65 * </li>
66 * <li>
67 * TodoComment
68 * </li>
69 * <li>
70 * TrailingComment
71 * </li>
72 * <li>
73 * UnnecessaryParentheses
74 * </li>
75 * <li>
76 * VariableDeclarationUsageDistance
77 * </li>
78 * </ul>
79 * <p>
80 * Also, the filter does not support suppressions inside javadoc reported by Javadoc checks:
81 * </p>
82 * <ul id="SuppressionXpathFilter_JavadocChecks">
83 * <li>
84 * AtclauseOrder
85 * </li>
86 * <li>
87 * JavadocBlockTagLocation
88 * </li>
89 * <li>
90 * JavadocMissingWhitespaceAfterAsterisk
91 * </li>
92 * <li>
93 * JavadocParagraph
94 * </li>
95 * <li>
96 * JavadocStyle
97 * </li>
98 * <li>
99 * JavadocTagContinuationIndentation
100 * </li>
101 * <li>
102 * JavadocType
103 * </li>
104 * <li>
105 * MissingDeprecated
106 * </li>
107 * <li>
108 * NonEmptyAtclauseDescription
109 * </li>
110 * <li>
111 * SingleLineJavadoc
112 * </li>
113 * <li>
114 * SummaryJavadoc
115 * </li>
116 * <li>
117 * WriteTag
118 * </li>
119 * </ul>
120 * <p>
121 * Note, that support for these Checks will be available after resolving issues
122 * <a href="https://github.com/checkstyle/checkstyle/issues/5770">#5770</a> and
123 * <a href="https://github.com/checkstyle/checkstyle/issues/5777">#5777</a>.
124 * Support for Indentation check will be available after resolving issue
125 * <a href="https://github.com/checkstyle/checkstyle/issues/7734">#7734</a>.
126 * </p>
127 * <p>
128 * Currently, filter supports the following xpath axes:
129 * </p>
130 * <ul>
131 * <li>
132 * ancestor
133 * </li>
134 * <li>
135 * ancestor-or-self
136 * </li>
137 * <li>
138 * attribute
139 * </li>
140 * <li>
141 * child
142 * </li>
143 * <li>
144 * descendant
145 * </li>
146 * <li>
147 * descendant-or-self
148 * </li>
149 * <li>
150 * following
151 * </li>
152 * <li>
153 * following-sibling
154 * </li>
155 * <li>
156 * parent
157 * </li>
158 * <li>
159 * preceding
160 * </li>
161 * <li>
162 * preceding-sibling
163 * </li>
164 * <li>
165 * self
166 * </li>
167 * </ul>
168 * <p>
169 * You can use the command line helper tool to generate xpath suppressions based on your
170 * configuration file and input files. See <a href="https://checkstyle.org/cmdline.html">here</a>
171 * for more details.
172 * </p>
173 * <p>
174 * The suppression file location is checked in following order:
175 * </p>
176 * <ol>
177 * <li>
178 * as a filesystem location
179 * </li>
180 * <li>
181 * if no file found, and the location starts with either {@code http://} or {@code https://},
182 * then it is interpreted as a URL
183 * </li>
184 * <li>
185 * if no file found, then passed to the {@code ClassLoader.getResource()} method.
186 * </li>
187 * </ol>
188 * <p>
189 * SuppressionXpathFilter can suppress Checks that have Treewalker as parent module.
190 * </p>
191 * <ul>
192 * <li>
193 * Property {@code file} - Specify the location of the <em>suppressions XML document</em> file.
194 * Default value is {@code null}.
195 * </li>
196 * <li>
197 * Property {@code optional} - Control what to do when the file is not existing.
198 * If optional is set to false the file must exist, or else it ends with error.
199 * On the other hand if optional is true and file is not found, the filter accepts all audit events.
200 * Default value is {@code false}.
201 * </li>
202 * </ul>
203 * <p>
204 * For example, the following configuration fragment directs the Checker to use a
205 * {@code SuppressionXpathFilter} with suppressions file {@code config/suppressions.xml}:
206 * </p>
207 * <pre>
208 * <module name="SuppressionXpathFilter">
209 * <property name="file" value="config/suppressions.xml"/>
210 * <property name="optional" value="false"/>
211 * </module>
212 * </pre>
213 * <p>
214 * A <a href="https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd"><em>
215 * suppressions XML document</em></a>
216 * contains a set of {@code suppress} and {@code suppress-xpath} elements,
217 * where each {@code suppress-xpath} element can have the following attributes:
218 * </p>
219 * <ul>
220 * <li>
221 * {@code files} - a <a href="https://checkstyle.org/property_types.html#regexp">Regular Expression</a>
222 * matched against the file name associated with an audit event. It is optional.
223 * </li>
224 * <li>
225 * {@code checks} - a <a href="https://checkstyle.org/property_types.html#regexp">Regular Expression</a>
226 * matched against the name of the check associated with an audit event.
227 * Optional as long as {@code id} or {@code message} is specified.
228 * </li>
229 * <li>
230 * {@code message} - a <a href="https://checkstyle.org/property_types.html#regexp">Regular Expression</a>
231 * matched against the message of the check associated with an audit event.
232 * Optional as long as {@code checks} or {@code id} is specified.
233 * </li>
234 * <li>
235 * {@code id} - a <a href="https://checkstyle.org/property_types.html#string">string</a> matched against
236 * the ID of the check associated with an audit event.
237 * Optional as long as {@code checks} or {@code message} is specified.
238 * </li>
239 * <li>
240 * {@code query} - a <a href="https://checkstyle.org/property_types.html#string">string</a> xpath query. It is optional.
241 * </li>
242 * </ul>
243 * <p>
244 * Each audit event is checked against each {@code suppress} and {@code suppress-xpath} element.
245 * It is suppressed if all specified attributes match against the audit event.
246 * </p>
247 * <p>
248 * ATTENTION: filtering by message is dependant on runtime locale.
249 * If project is running in different languages it is better to avoid filtering by message.
250 * </p>
251 * <p>
252 * The following suppressions XML document directs a {@code SuppressionXpathFilter} to reject
253 * {@code CyclomaticComplexity} violations for all methods with name <i>sayHelloWorld</i> inside
254 * <i>FileOne</i> and <i>FileTwo</i> files:
255 * </p>
256 * <p>
257 * Currently, xpath queries support one type of attribute {@code @text}. {@code @text} -
258 * addresses to the text value of the node. For example: variable name, annotation name,
259 * text content and etc. Only the following token types support {@code @text} attribute:
260 * {@code TokenTypes.IDENT}, {@code TokenTypes.STRING_LITERAL}, {@code TokenTypes.CHAR_LITERAL},
261 * {@code TokenTypes.NUM_LONG}, {@code TokenTypes.NUM_INT}, {@code TokenTypes.NUM_DOUBLE},
262 * {@code TokenTypes.NUM_FLOAT}.
263 * These token types were selected because only their text values are different
264 * in content from token type and represent text value from file and can be used
265 * in xpath queries for more accurate results. Other token types always have constant values.
266 * </p>
267 * <pre>
268 * <?xml version="1.0"?>
269 *
270 * <!DOCTYPE suppressions PUBLIC
271 * "-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2//EN"
272 * "https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd">
273 *
274 * <suppressions>
275 * <suppress-xpath checks="CyclomaticComplexity"
276 * files="FileOne.java,FileTwo.java"
277 * query="//METHOD_DEF[./IDENT[@text='sayHelloWorld']]"/>
278 * </suppressions>
279 * </pre>
280 * <p>
281 * Suppress checks for package definitions:
282 * </p>
283 * <pre>
284 * <suppress-xpath checks=".*" query="/PACKAGE_DEF"/>
285 * </pre>
286 * <p>
287 * Suppress checks for parent element of the first variable definition:
288 * </p>
289 * <pre>
290 * <suppress-xpath checks=".*" query="(//VARIABLE_DEF)[1]/.."/>
291 * </pre>
292 * <p>
293 * Suppress checks for elements which are either class definitions, either method definitions.
294 * </p>
295 * <pre>
296 * <suppress-xpath checks=".*" query="//CLASS_DEF | //METHOD_DEF"/>
297 * </pre>
298 * <p>
299 * Suppress checks for certain methods:
300 * </p>
301 * <pre>
302 * <suppress-xpath checks=".*" query="//METHOD_DEF[./IDENT[@text='getSomeVar'
303 * or @text='setSomeVar']]"/>
304 * </pre>
305 * <p>
306 * Suppress checks for variable <i>testVariable</i> inside <i>testMethod</i>
307 * method inside <i>TestClass</i> class.
308 * </p>
309 * <pre>
310 * <suppress-xpath checks=".*" query="/CLASS_DEF[@text='TestClass']
311 * //METHOD_DEF[./IDENT[@text='testMethod']]
312 * //VARIABLE_DEF[./IDENT[@text='testVariable']]"/>
313 * </pre>
314 * <p>
315 * In the following sample, violations for {@code LeftCurly} check will be suppressed
316 * for classes with name <i>Main</i> or for methods with name <i>calculate</i>.
317 * </p>
318 * <pre>
319 * <suppress-xpath checks="LeftCurly" query="/CLASS_DEF[./IDENT[@text='Main']]//*
320 * | //METHOD_DEF[./IDENT[@text='calculate']]/*"/>
321 * </pre>
322 * <p>
323 * The following example demonstrates how to suppress {@code RequireThis} violations
324 * for variable <i>age</i> inside <i>changeAge</i> method.
325 * </p>
326 * <pre>
327 * <suppress-xpath checks="RequireThis"
328 * query="/CLASS_DEF[./IDENT[@text='InputTest']]
329 * //METHOD_DEF[./IDENT[@text='changeAge']]//ASSIGN/IDENT[@text='age']"/>
330 * </pre>
331 * <pre>
332 * public class InputTest {
333 * private int age = 23;
334 *
335 * public void changeAge() {
336 * age = 24; //violation will be suppressed
337 * }
338 * }
339 * </pre>
340 * <p>
341 * Suppress {@code IllegalThrows} violations only for methods with name <i>throwsMethod</i>
342 * and only for {@code RuntimeException} exceptions. Double colon is used for axis iterations.
343 * In the following example {@code ancestor} axis is used to iterate all ancestor nodes
344 * of the current node with type {@code METHOD_DEF} and name <i>throwsMethod</i>.
345 * Please read more about xpath axes at <a href="https://www.w3schools.com/xml/xpath_axes.asp">
346 * W3Schools Xpath Axes</a>.
347 * </p>
348 * <pre>
349 * <suppress-xpath checks="IllegalThrows" query="//LITERAL_THROWS
350 * /IDENT[@text='RuntimeException' and
351 * ./ancestor::METHOD_DEF[./IDENT[@text='throwsMethod']]]"/>
352 * </pre>
353 * <pre>
354 * public class InputTest {
355 * public void throwsMethod() throws RuntimeException { // violation will be suppressed
356 * }
357 *
358 * public void sampleMethod() throws RuntimeException { // will throw violation here
359 * }
360 * }
361 * </pre>
362 * <p>
363 * The following sample demonstrates how to suppress all violations for method
364 * itself and all descendants. {@code descendant-or-self} axis iterates through
365 * current node and all children nodes at any level. Keyword {@code node()}
366 * selects node elements. Please read more about xpath syntax at
367 * <a href="https://www.w3schools.com/xml/xpath_syntax.asp">W3Schools Xpath Syntax</a>.
368 * </p>
369 * <pre>
370 * <suppress-xpath checks=".*" query="//METHOD_DEF[./IDENT[@text='legacyMethod']]
371 * /descendant-or-self::node()"/>
372 * </pre>
373 * <p>
374 * Some elements can be suppressed in different ways. For example, to suppress
375 * violation on variable {@code wordCount} in following code:
376 * </p>
377 * <pre>
378 * public class InputTest {
379 * private int wordCount = 11;
380 * }
381 * </pre>
382 * <p>
383 * You need to look at AST of such code by our CLI tool:
384 * </p>
385 * <pre>
386 * $ java -jar checkstyle-X.XX-all.jar -t InputTest.java
387 * CLASS_DEF -> CLASS_DEF [1:0]
388 * |--MODIFIERS -> MODIFIERS [1:0]
389 * | `--LITERAL_PUBLIC -> public [1:0]
390 * |--LITERAL_CLASS -> class [1:7]
391 * |--IDENT -> InputTest [1:13]
392 * `--OBJBLOCK -> OBJBLOCK [1:23]
393 * |--LCURLY -> { [1:23]
394 * |--VARIABLE_DEF -> VARIABLE_DEF [2:4]
395 * | |--MODIFIERS -> MODIFIERS [2:4]
396 * | | `--LITERAL_PRIVATE -> private [2:4]
397 * | |--TYPE -> TYPE [2:12]
398 * | | `--LITERAL_INT -> int [2:12]
399 * | |--IDENT -> wordCount [2:16]
400 * | |--ASSIGN -> = [2:26]
401 * | | `--EXPR -> EXPR [2:28]
402 * | | `--NUM_INT -> 11 [2:28]
403 * | `--SEMI -> ; [2:30]
404 * `--RCURLY -> } [3:0]
405 * </pre>
406 * <p>
407 * The easiest way is to suppress by variable name. As you can see {@code VARIABLE_DEF}
408 * node refers to variable declaration statement and has child node with token type
409 * {@code IDENT} which is used for storing class, method, variable names.
410 * </p>
411 * <p>
412 * The following example demonstrates how variable can be queried by its name:
413 * </p>
414 * <pre>
415 * <suppress-xpath checks="." query="//VARIABLE_DEF[
416 * ./IDENT[@text='wordCount']]"/>
417 * </pre>
418 * <p>
419 * Another way is to suppress by variable value. Again, if you look at the printed
420 * AST tree above, you will notice that one of the grandchildren of {@code VARIABLE_DEF}
421 * node is responsible for storing variable value -{@code NUM_INT} with value <b>11</b>.
422 * </p>
423 * <p>
424 * The following example demonstrates how variable can be queried by its value,
425 * same approach applies to {@code String, char, float, double, int, long} data types:
426 * </p>
427 * <pre>
428 * <suppress-xpath checks="." query="//VARIABLE_DEF[.//NUM_INT[@text=11]]"/>
429 * </pre>
430 * <p>
431 * Next example is about suppressing method with certain annotation by its name and element value.
432 * </p>
433 * <pre>
434 * public class InputTest {
435 * @Generated("first") // should not be suppressed
436 * public void test1() {
437 * }
438 *
439 * @Generated("second") // should be suppressed
440 * public void test2() {
441 * }
442 * }
443 * </pre>
444 * <p>
445 * First of all we need to look at AST tree printed by our CLI tool:
446 * </p>
447 * <pre>
448 * $ java -jar checkstyle-X.XX-all.jar -t InputTest.java
449 * CLASS_DEF -> CLASS_DEF [1:0]
450 * |--MODIFIERS -> MODIFIERS [1:0]
451 * | `--LITERAL_PUBLIC -> public [1:0]
452 * |--LITERAL_CLASS -> class [1:7]
453 * |--IDENT -> InputTest [1:13]
454 * `--OBJBLOCK -> OBJBLOCK [1:23]
455 * |--LCURLY -> { [1:23]
456 * |--METHOD_DEF -> METHOD_DEF [2:4]
457 * | |--MODIFIERS -> MODIFIERS [2:4]
458 * | | |--ANNOTATION -> ANNOTATION [2:4]
459 * | | | |--AT -> @ [2:4]
460 * | | | |--IDENT -> Generated [2:5]
461 * | | | |--LPAREN -> ( [2:14]
462 * | | | |--EXPR -> EXPR [2:15]
463 * | | | | `--STRING_LITERAL -> "first" [2:15]
464 * | | | `--RPAREN -> ) [2:22]
465 * | | `--LITERAL_PUBLIC -> public [3:4]
466 * | |--TYPE -> TYPE [3:11]
467 * | | `--LITERAL_VOID -> void [3:11]
468 * | |--IDENT -> test1 [3:16]
469 * | |--LPAREN -> ( [3:21]
470 * | |--PARAMETERS -> PARAMETERS [3:22]
471 * | |--RPAREN -> ) [3:22]
472 * | `--SLIST -> { [3:24]
473 * | `--RCURLY -> } [4:4]
474 * |--METHOD_DEF -> METHOD_DEF [6:4]
475 * | |--MODIFIERS -> MODIFIERS [6:4]
476 * | | |--ANNOTATION -> ANNOTATION [6:4]
477 * | | | |--AT -> @ [6:4]
478 * | | | |--IDENT -> Generated [6:5]
479 * | | | |--LPAREN -> ( [6:14]
480 * | | | |--EXPR -> EXPR [6:15]
481 * | | | | `--STRING_LITERAL -> "second" [6:15]
482 * | | | `--RPAREN -> ) [6:23]
483 * | | `--LITERAL_PUBLIC -> public [7:4]
484 * | |--TYPE -> TYPE [7:11]
485 * | | `--LITERAL_VOID -> void [7:11]
486 * | |--IDENT -> test2 [7:16]
487 * | |--LPAREN -> ( [7:21]
488 * | |--PARAMETERS -> PARAMETERS [7:22]
489 * | |--RPAREN -> ) [7:22]
490 * | `--SLIST -> { [7:24]
491 * | `--RCURLY -> } [8:4]
492 * `--RCURLY -> } [9:0]
493 * </pre>
494 * <p>
495 * AST node {@code ANNOTATION -> ANNOTATION [6:4]} has direct child
496 * {@code IDENT -> Generated [6:5]}, therefore can be queried by {@code IDENT} value:
497 * </p>
498 * <pre>
499 * <suppress-xpath checks="." query="//METHOD_DEF[
500 * .//ANNOTATION/IDENT[@text='Generated']]"/>
501 * </pre>
502 * <p>
503 * The problem with query above that it will suppress violations for all methods
504 * with annotation {@code @Generated}. In order to suppress methods with
505 * {@code @Generated("second")} annotations only, you need to look at AST tree again.
506 * Value of the {@code ANNOTATION} node is stored inside sub-node with token type
507 * {@code STRING_LITERAL}. Use the following query to suppress methods with
508 * {@code @Generated("second")} annotation:
509 * </p>
510 * <pre>
511 * <suppress-xpath checks="." query="//METHOD_DEF[.//ANNOTATION[
512 * ./IDENT[@text='Generated'] and ./EXPR/STRING_LITERAL[@text='second']]]"/>
513 * </pre>
514 *
515 * @since 8.6
516 * @noinspection NonFinalFieldReferenceInEquals, NonFinalFieldReferencedInHashCode
517 */
518 public class SuppressionXpathFilter extends AutomaticBean implements
519 TreeWalkerFilter, ExternalResourceHolder {
520
521 /** Specify the location of the <em>suppressions XML document</em> file. */
522 private String file;
523 /**
524 * Control what to do when the file is not existing.
525 * If optional is set to false the file must exist, or else it ends with error.
526 * On the other hand if optional is true and file is not found,
527 * the filter accepts all audit events.
528 */
529 private boolean optional;
530 /** Set of individual xpath suppresses. */
531 private Set<TreeWalkerFilter> filters = new HashSet<>();
532
533 /**
534 * Setter to specify the location of the <em>suppressions XML document</em> file.
535 *
536 * @param fileName name of the suppressions file.
537 */
538 public void setFile(String fileName) {
539 file = fileName;
540 }
541
542 /**
543 * Setter to control what to do when the file is not existing.
544 * If optional is set to false the file must exist, or else it ends with error.
545 * On the other hand if optional is true and file is not found,
546 * the filter accepts all audit events.
547 *
548 * @param optional tells if config file existence is optional.
549 */
550 public void setOptional(boolean optional) {
551 this.optional = optional;
552 }
553
554 @Override
555 public boolean equals(Object obj) {
556 if (this == obj) {
557 return true;
558 }
559 if (obj == null || getClass() != obj.getClass()) {
560 return false;
561 }
562 final SuppressionXpathFilterols/checkstyle/filters/SuppressionXpathFilter.html#SuppressionXpathFilter">SuppressionXpathFilter suppressionXpathFilter = (SuppressionXpathFilter) obj;
563 return Objects.equals(filters, suppressionXpathFilter.filters);
564 }
565
566 @Override
567 public int hashCode() {
568 return Objects.hash(filters);
569 }
570
571 @Override
572 public boolean accept(TreeWalkerAuditEvent treeWalkerAuditEvent) {
573 boolean result = true;
574 for (TreeWalkerFilter filter : filters) {
575 if (!filter.accept(treeWalkerAuditEvent)) {
576 result = false;
577 break;
578 }
579 }
580 return result;
581 }
582
583 @Override
584 public Set<String> getExternalResourceLocations() {
585 return Collections.singleton(file);
586 }
587
588 @Override
589 protected void finishLocalSetup() throws CheckstyleException {
590 if (file != null) {
591 if (optional) {
592 if (FilterUtil.isFileExists(file)) {
593 filters = SuppressionsLoader.loadXpathSuppressions(file);
594 }
595 else {
596 filters = new HashSet<>();
597 }
598 }
599 else {
600 filters = SuppressionsLoader.loadXpathSuppressions(file);
601 }
602 }
603 }
604
605 }