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