001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.filters; 021 022import java.util.Collections; 023import java.util.HashSet; 024import java.util.Objects; 025import java.util.Set; 026 027import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent; 028import com.puppycrawl.tools.checkstyle.TreeWalkerFilter; 029import com.puppycrawl.tools.checkstyle.api.AutomaticBean; 030import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 031import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder; 032import com.puppycrawl.tools.checkstyle.utils.FilterUtil; 033 034/** 035 * <p> 036 * Filter {@code SuppressionXpathFilter} works as 037 * <a href="https://checkstyle.org/config_filters.html#SuppressionFilter">SuppressionFilter</a>. 038 * Additionally, filter processes {@code suppress-xpath} elements, 039 * which contains xpath-expressions. Xpath-expressions are queries for 040 * suppressed nodes inside the AST tree. 041 * </p> 042 * <p> 043 * Currently, filter does not support the following checks: 044 * </p> 045 * <ul id="SuppressionXpathFilter_IncompatibleChecks"> 046 * <li> 047 * NoCodeInFile (reason is that AST is not generated for a file not containing code) 048 * </li> 049 * <li> 050 * Regexp (reason is at 051 * <a href="https://github.com/checkstyle/checkstyle/issues/7759#issuecomment-605525287"> #7759</a>) 052 * </li> 053 * <li> 054 * RegexpSinglelineJava (reason is at 055 * <a href="https://github.com/checkstyle/checkstyle/issues/7759#issuecomment-605525287"> #7759</a>) 056 * </li> 057 * </ul> 058 * <p> 059 * Also, the filter does not support suppressions inside javadoc reported by Javadoc checks: 060 * </p> 061 * <ul id="SuppressionXpathFilter_JavadocChecks"> 062 * <li> 063 * AtclauseOrder 064 * </li> 065 * <li> 066 * JavadocBlockTagLocation 067 * </li> 068 * <li> 069 * JavadocMethod 070 * </li> 071 * <li> 072 * JavadocMissingLeadingAsterisk 073 * </li> 074 * <li> 075 * JavadocMissingWhitespaceAfterAsterisk 076 * </li> 077 * <li> 078 * JavadocParagraph 079 * </li> 080 * <li> 081 * JavadocStyle 082 * </li> 083 * <li> 084 * JavadocTagContinuationIndentation 085 * </li> 086 * <li> 087 * JavadocType 088 * </li> 089 * <li> 090 * MissingDeprecated 091 * </li> 092 * <li> 093 * NonEmptyAtclauseDescription 094 * </li> 095 * <li> 096 * RequireEmptyLineBeforeBlockTagGroup 097 * </li> 098 * <li> 099 * SingleLineJavadoc 100 * </li> 101 * <li> 102 * SummaryJavadoc 103 * </li> 104 * <li> 105 * WriteTag 106 * </li> 107 * </ul> 108 * <p> 109 * Note, that support for these Checks will be available after resolving issues 110 * <a href="https://github.com/checkstyle/checkstyle/issues/5770">#5770</a> and 111 * <a href="https://github.com/checkstyle/checkstyle/issues/5777">#5777</a>. 112 * </p> 113 * <p> 114 * Currently, filter supports the following xpath axes: 115 * </p> 116 * <ul> 117 * <li> 118 * ancestor 119 * </li> 120 * <li> 121 * ancestor-or-self 122 * </li> 123 * <li> 124 * attribute 125 * </li> 126 * <li> 127 * child 128 * </li> 129 * <li> 130 * descendant 131 * </li> 132 * <li> 133 * descendant-or-self 134 * </li> 135 * <li> 136 * following 137 * </li> 138 * <li> 139 * following-sibling 140 * </li> 141 * <li> 142 * parent 143 * </li> 144 * <li> 145 * preceding 146 * </li> 147 * <li> 148 * preceding-sibling 149 * </li> 150 * <li> 151 * self 152 * </li> 153 * </ul> 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 * <p> 160 * The suppression file location is checked in following order: 161 * </p> 162 * <ol> 163 * <li> 164 * as a filesystem location 165 * </li> 166 * <li> 167 * if no file found, and the location starts with either {@code http://} or {@code https://}, 168 * then it is interpreted as a URL 169 * </li> 170 * <li> 171 * if no file found, then passed to the {@code ClassLoader.getResource()} method. 172 * </li> 173 * </ol> 174 * <p> 175 * SuppressionXpathFilter can suppress Checks that have Treewalker as parent module. 176 * </p> 177 * <ul> 178 * <li> 179 * Property {@code file} - Specify the location of the <em>suppressions XML document</em> file. 180 * Type is {@code java.lang.String}. 181 * Default value is {@code null}. 182 * </li> 183 * <li> 184 * Property {@code optional} - Control what to do when the file is not existing. 185 * If optional is set to false the file must exist, or else it ends with error. 186 * On the other hand if optional is true and file is not found, the filter accepts all audit events. 187 * Type is {@code boolean}. 188 * Default value is {@code false}. 189 * </li> 190 * </ul> 191 * <p> 192 * For example, the following configuration fragment directs the Checker to use a 193 * {@code SuppressionXpathFilter} with suppressions file {@code config/suppressions.xml}: 194 * </p> 195 * <pre> 196 * <module name="SuppressionXpathFilter"> 197 * <property name="file" value="config/suppressions.xml"/> 198 * <property name="optional" value="false"/> 199 * </module> 200 * </pre> 201 * <p> 202 * A <a href="https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd"><em> 203 * suppressions XML document</em></a> 204 * contains a set of {@code suppress} and {@code suppress-xpath} elements, 205 * where each {@code suppress-xpath} element can have the following attributes: 206 * </p> 207 * <ul> 208 * <li> 209 * {@code files} - a <a href="https://checkstyle.org/property_types.html#Pattern">Pattern</a> 210 * matched against the file name associated with an audit event. It is optional. 211 * </li> 212 * <li> 213 * {@code checks} - a <a href="https://checkstyle.org/property_types.html#Pattern">Pattern</a> 214 * matched against the name of the check associated with an audit event. 215 * Optional as long as {@code id} or {@code message} is specified. 216 * </li> 217 * <li> 218 * {@code message} - a <a href="https://checkstyle.org/property_types.html#Pattern">Pattern</a> 219 * matched against the message of the check associated with an audit event. 220 * Optional as long as {@code checks} or {@code id} is specified. 221 * </li> 222 * <li> 223 * {@code id} - a <a href="https://checkstyle.org/property_types.html#String">String</a> matched against 224 * the ID of the check associated with an audit event. 225 * Optional as long as {@code checks} or {@code message} is specified. 226 * </li> 227 * <li> 228 * {@code query} - a <a href="https://checkstyle.org/property_types.html#String">String</a> xpath query. It is optional. 229 * </li> 230 * </ul> 231 * <p> 232 * Each audit event is checked against each {@code suppress} and {@code suppress-xpath} element. 233 * It is suppressed if all specified attributes match against the audit event. 234 * </p> 235 * <p> 236 * ATTENTION: filtering by message is dependant on runtime locale. 237 * If project is running in different languages it is better to avoid filtering by message. 238 * </p> 239 * <p> 240 * The following suppressions XML document directs a {@code SuppressionXpathFilter} to reject 241 * {@code CyclomaticComplexity} violations for all methods with name <i>sayHelloWorld</i> inside 242 * <i>FileOne</i> and <i>FileTwo</i> files: 243 * </p> 244 * <p> 245 * Currently, xpath queries support one type of attribute {@code @text}. {@code @text} - 246 * addresses to the text value of the node. For example: variable name, annotation name, 247 * text content and etc. Only the following token types support {@code @text} attribute: 248 * {@code TokenTypes.IDENT}, {@code TokenTypes.STRING_LITERAL}, {@code TokenTypes.CHAR_LITERAL}, 249 * {@code TokenTypes.NUM_LONG}, {@code TokenTypes.NUM_INT}, {@code TokenTypes.NUM_DOUBLE}, 250 * {@code TokenTypes.NUM_FLOAT}. 251 * These token types were selected because only their text values are different 252 * in content from token type and represent text value from file and can be used 253 * in xpath queries for more accurate results. Other token types always have constant values. 254 * </p> 255 * <pre> 256 * <?xml version="1.0"?> 257 * 258 * <!DOCTYPE suppressions PUBLIC 259 * "-//Checkstyle//DTD SuppressionXpathFilter Experimental Configuration 1.2//EN" 260 * "https://checkstyle.org/dtds/suppressions_1_2_xpath_experimental.dtd"> 261 * 262 * <suppressions> 263 * <suppress-xpath checks="CyclomaticComplexity" 264 * files="FileOne.java,FileTwo.java" 265 * query="//METHOD_DEF[./IDENT[@text='sayHelloWorld']]"/> 266 * </suppressions> 267 * </pre> 268 * <p> 269 * Suppress checks for package definitions: 270 * </p> 271 * <pre> 272 * <suppress-xpath checks=".*" query="/PACKAGE_DEF"/> 273 * </pre> 274 * <p> 275 * Suppress checks for parent element of the first variable definition: 276 * </p> 277 * <pre> 278 * <suppress-xpath checks=".*" query="(//VARIABLE_DEF)[1]/.."/> 279 * </pre> 280 * <p> 281 * Suppress checks for elements which are either class definitions, either method definitions. 282 * </p> 283 * <pre> 284 * <suppress-xpath checks=".*" query="//CLASS_DEF | //METHOD_DEF"/> 285 * </pre> 286 * <p> 287 * Suppress checks for certain methods: 288 * </p> 289 * <pre> 290 * <suppress-xpath checks=".*" query="//METHOD_DEF[./IDENT[@text='getSomeVar' 291 * or @text='setSomeVar']]"/> 292 * </pre> 293 * <p> 294 * Suppress checks for variable <i>testVariable</i> inside <i>testMethod</i> 295 * method inside <i>TestClass</i> class. 296 * </p> 297 * <pre> 298 * <suppress-xpath checks=".*" query="/CLASS_DEF[@text='TestClass'] 299 * //METHOD_DEF[./IDENT[@text='testMethod']] 300 * //VARIABLE_DEF[./IDENT[@text='testVariable']]"/> 301 * </pre> 302 * <p> 303 * In the following sample, violations for {@code LeftCurly} check will be suppressed 304 * for classes with name <i>Main</i> or for methods with name <i>calculate</i>. 305 * </p> 306 * <pre> 307 * <suppress-xpath checks="LeftCurly" query="/CLASS_DEF[./IDENT[@text='Main']]//* 308 * | //METHOD_DEF[./IDENT[@text='calculate']]/*"/> 309 * </pre> 310 * <p> 311 * The following example demonstrates how to suppress {@code RequireThis} violations 312 * for variable <i>age</i> inside <i>changeAge</i> method. 313 * </p> 314 * <pre> 315 * <suppress-xpath checks="RequireThis" 316 * query="/CLASS_DEF[./IDENT[@text='InputTest']] 317 * //METHOD_DEF[./IDENT[@text='changeAge']]//ASSIGN/IDENT[@text='age']"/> 318 * </pre> 319 * <pre> 320 * public class InputTest { 321 * private int age = 23; 322 * 323 * public void changeAge() { 324 * age = 24; //violation will be suppressed 325 * } 326 * } 327 * </pre> 328 * <p> 329 * Suppress {@code IllegalThrows} violations only for methods with name <i>throwsMethod</i> 330 * and only for {@code RuntimeException} exceptions. Double colon is used for axis iterations. 331 * In the following example {@code ancestor} axis is used to iterate all ancestor nodes 332 * of the current node with type {@code METHOD_DEF} and name <i>throwsMethod</i>. 333 * Please read more about xpath axes at <a href="https://www.w3schools.com/xml/xpath_axes.asp"> 334 * W3Schools Xpath Axes</a>. 335 * </p> 336 * <pre> 337 * <suppress-xpath checks="IllegalThrows" query="//LITERAL_THROWS 338 * /IDENT[@text='RuntimeException' and 339 * ./ancestor::METHOD_DEF[./IDENT[@text='throwsMethod']]]"/> 340 * </pre> 341 * <pre> 342 * public class InputTest { 343 * public void throwsMethod() throws RuntimeException { // violation will be suppressed 344 * } 345 * 346 * public void sampleMethod() throws RuntimeException { // will throw violation here 347 * } 348 * } 349 * </pre> 350 * <p> 351 * The following sample demonstrates how to suppress all violations for method 352 * itself and all descendants. {@code descendant-or-self} axis iterates through 353 * current node and all children nodes at any level. Keyword {@code node()} 354 * selects node elements. Please read more about xpath syntax at 355 * <a href="https://www.w3schools.com/xml/xpath_syntax.asp">W3Schools Xpath Syntax</a>. 356 * </p> 357 * <pre> 358 * <suppress-xpath checks=".*" query="//METHOD_DEF[./IDENT[@text='legacyMethod']] 359 * /descendant-or-self::node()"/> 360 * </pre> 361 * <p> 362 * Some elements can be suppressed in different ways. For example, to suppress 363 * violation on variable {@code wordCount} in following code: 364 * </p> 365 * <pre> 366 * public class InputTest { 367 * private int wordCount = 11; 368 * } 369 * </pre> 370 * <p> 371 * You need to look at AST of such code by our CLI tool: 372 * </p> 373 * <pre> 374 * $ java -jar checkstyle-X.XX-all.jar -t InputTest.java 375 * CLASS_DEF -> CLASS_DEF [1:0] 376 * |--MODIFIERS -> MODIFIERS [1:0] 377 * | `--LITERAL_PUBLIC -> public [1:0] 378 * |--LITERAL_CLASS -> class [1:7] 379 * |--IDENT -> InputTest [1:13] 380 * `--OBJBLOCK -> OBJBLOCK [1:23] 381 * |--LCURLY -> { [1:23] 382 * |--VARIABLE_DEF -> VARIABLE_DEF [2:4] 383 * | |--MODIFIERS -> MODIFIERS [2:4] 384 * | | `--LITERAL_PRIVATE -> private [2:4] 385 * | |--TYPE -> TYPE [2:12] 386 * | | `--LITERAL_INT -> int [2:12] 387 * | |--IDENT -> wordCount [2:16] 388 * | |--ASSIGN -> = [2:26] 389 * | | `--EXPR -> EXPR [2:28] 390 * | | `--NUM_INT -> 11 [2:28] 391 * | `--SEMI -> ; [2:30] 392 * `--RCURLY -> } [3:0] 393 * </pre> 394 * <p> 395 * The easiest way is to suppress by variable name. As you can see {@code VARIABLE_DEF} 396 * node refers to variable declaration statement and has child node with token type 397 * {@code IDENT} which is used for storing class, method, variable names. 398 * </p> 399 * <p> 400 * The following example demonstrates how variable can be queried by its name: 401 * </p> 402 * <pre> 403 * <suppress-xpath checks="." query="//VARIABLE_DEF[ 404 * ./IDENT[@text='wordCount']]"/> 405 * </pre> 406 * <p> 407 * Another way is to suppress by variable value. Again, if you look at the printed 408 * AST tree above, you will notice that one of the grandchildren of {@code VARIABLE_DEF} 409 * node is responsible for storing variable value -{@code NUM_INT} with value <b>11</b>. 410 * </p> 411 * <p> 412 * The following example demonstrates how variable can be queried by its value, 413 * same approach applies to {@code String, char, float, double, int, long} data types: 414 * </p> 415 * <pre> 416 * <suppress-xpath checks="." query="//VARIABLE_DEF[.//NUM_INT[@text=11]]"/> 417 * </pre> 418 * <p> 419 * Next example is about suppressing method with certain annotation by its name and element value. 420 * </p> 421 * <pre> 422 * public class InputTest { 423 * @Generated("first") // should not be suppressed 424 * public void test1() { 425 * } 426 * 427 * @Generated("second") // should be suppressed 428 * public void test2() { 429 * } 430 * } 431 * </pre> 432 * <p> 433 * First of all we need to look at AST tree printed by our CLI tool: 434 * </p> 435 * <pre> 436 * $ java -jar checkstyle-X.XX-all.jar -t InputTest.java 437 * CLASS_DEF -> CLASS_DEF [1:0] 438 * |--MODIFIERS -> MODIFIERS [1:0] 439 * | `--LITERAL_PUBLIC -> public [1:0] 440 * |--LITERAL_CLASS -> class [1:7] 441 * |--IDENT -> InputTest [1:13] 442 * `--OBJBLOCK -> OBJBLOCK [1:23] 443 * |--LCURLY -> { [1:23] 444 * |--METHOD_DEF -> METHOD_DEF [2:4] 445 * | |--MODIFIERS -> MODIFIERS [2:4] 446 * | | |--ANNOTATION -> ANNOTATION [2:4] 447 * | | | |--AT -> @ [2:4] 448 * | | | |--IDENT -> Generated [2:5] 449 * | | | |--LPAREN -> ( [2:14] 450 * | | | |--EXPR -> EXPR [2:15] 451 * | | | | `--STRING_LITERAL -> "first" [2:15] 452 * | | | `--RPAREN -> ) [2:22] 453 * | | `--LITERAL_PUBLIC -> public [3:4] 454 * | |--TYPE -> TYPE [3:11] 455 * | | `--LITERAL_VOID -> void [3:11] 456 * | |--IDENT -> test1 [3:16] 457 * | |--LPAREN -> ( [3:21] 458 * | |--PARAMETERS -> PARAMETERS [3:22] 459 * | |--RPAREN -> ) [3:22] 460 * | `--SLIST -> { [3:24] 461 * | `--RCURLY -> } [4:4] 462 * |--METHOD_DEF -> METHOD_DEF [6:4] 463 * | |--MODIFIERS -> MODIFIERS [6:4] 464 * | | |--ANNOTATION -> ANNOTATION [6:4] 465 * | | | |--AT -> @ [6:4] 466 * | | | |--IDENT -> Generated [6:5] 467 * | | | |--LPAREN -> ( [6:14] 468 * | | | |--EXPR -> EXPR [6:15] 469 * | | | | `--STRING_LITERAL -> "second" [6:15] 470 * | | | `--RPAREN -> ) [6:23] 471 * | | `--LITERAL_PUBLIC -> public [7:4] 472 * | |--TYPE -> TYPE [7:11] 473 * | | `--LITERAL_VOID -> void [7:11] 474 * | |--IDENT -> test2 [7:16] 475 * | |--LPAREN -> ( [7:21] 476 * | |--PARAMETERS -> PARAMETERS [7:22] 477 * | |--RPAREN -> ) [7:22] 478 * | `--SLIST -> { [7:24] 479 * | `--RCURLY -> } [8:4] 480 * `--RCURLY -> } [9:0] 481 * </pre> 482 * <p> 483 * AST node {@code ANNOTATION -> ANNOTATION [6:4]} has direct child 484 * {@code IDENT -> Generated [6:5]}, therefore can be queried by {@code IDENT} value: 485 * </p> 486 * <pre> 487 * <suppress-xpath checks="." query="//METHOD_DEF[ 488 * .//ANNOTATION/IDENT[@text='Generated']]"/> 489 * </pre> 490 * <p> 491 * The problem with query above that it will suppress violations for all methods 492 * with annotation {@code @Generated}. In order to suppress methods with 493 * {@code @Generated("second")} annotations only, you need to look at AST tree again. 494 * Value of the {@code ANNOTATION} node is stored inside sub-node with token type 495 * {@code STRING_LITERAL}. Use the following query to suppress methods with 496 * {@code @Generated("second")} annotation: 497 * </p> 498 * <pre> 499 * <suppress-xpath checks="." query="//METHOD_DEF[.//ANNOTATION[ 500 * ./IDENT[@text='Generated'] and ./EXPR/STRING_LITERAL[@text='second']]]"/> 501 * </pre> 502 * <p> 503 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 504 * </p> 505 * 506 * @since 8.6 507 * @noinspection NonFinalFieldReferenceInEquals, NonFinalFieldReferencedInHashCode 508 */ 509public class SuppressionXpathFilter extends AutomaticBean implements 510 TreeWalkerFilter, ExternalResourceHolder { 511 512 /** Specify the location of the <em>suppressions XML document</em> file. */ 513 private String file; 514 /** 515 * Control what to do when the file is not existing. 516 * If optional is set to false the file must exist, or else it ends with error. 517 * On the other hand if optional is true and file is not found, 518 * the filter accepts all audit events. 519 */ 520 private boolean optional; 521 /** Set of individual xpath suppresses. */ 522 private Set<TreeWalkerFilter> filters = new HashSet<>(); 523 524 /** 525 * Setter to specify the location of the <em>suppressions XML document</em> file. 526 * 527 * @param fileName name of the suppressions file. 528 */ 529 public void setFile(String fileName) { 530 file = fileName; 531 } 532 533 /** 534 * Setter to control what to do when the file is not existing. 535 * If optional is set to false the file must exist, or else it ends with error. 536 * On the other hand if optional is true and file is not found, 537 * the filter accepts all audit events. 538 * 539 * @param optional tells if config file existence is optional. 540 */ 541 public void setOptional(boolean optional) { 542 this.optional = optional; 543 } 544 545 @Override 546 public boolean equals(Object obj) { 547 if (this == obj) { 548 return true; 549 } 550 if (obj == null || getClass() != obj.getClass()) { 551 return false; 552 } 553 final SuppressionXpathFilter suppressionXpathFilter = (SuppressionXpathFilter) obj; 554 return Objects.equals(filters, suppressionXpathFilter.filters); 555 } 556 557 @Override 558 public int hashCode() { 559 return Objects.hash(filters); 560 } 561 562 @Override 563 public boolean accept(TreeWalkerAuditEvent treeWalkerAuditEvent) { 564 boolean result = true; 565 for (TreeWalkerFilter filter : filters) { 566 if (!filter.accept(treeWalkerAuditEvent)) { 567 result = false; 568 break; 569 } 570 } 571 return result; 572 } 573 574 @Override 575 public Set<String> getExternalResourceLocations() { 576 return Collections.singleton(file); 577 } 578 579 @Override 580 protected void finishLocalSetup() throws CheckstyleException { 581 if (file != null) { 582 if (optional) { 583 if (FilterUtil.isFileExists(file)) { 584 filters = SuppressionsLoader.loadXpathSuppressions(file); 585 } 586 else { 587 filters = new HashSet<>(); 588 } 589 } 590 else { 591 filters = SuppressionsLoader.loadXpathSuppressions(file); 592 } 593 } 594 } 595 596}