- * Note: a suppression filter is needed because + * Note: a + * suppression xpath single filter is needed because * IDEA has no blank line between "javax" and "java". * ImportOrder has a limitation by design to enforce an empty line between groups ("java", "javax"). * There is no flexibility to enforce empty lines between some groups and no empty lines between @@ -230,17 +231,10 @@ * <property name="option" value="bottom"/> * <property name="sortStaticImportsAlphabetically" value="true"/> * </module> - * - *
- * <?xml version="1.0"?> - * <!DOCTYPE suppressions PUBLIC - * "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" - * "https://checkstyle.org/dtds/suppressions_1_2.dtd"> - * - * <suppressions> - * <!-- message contains no message text to work well in multi-language environments --> - * <suppress checks="ImportOrder" message="^'java\..*'.*"/> - * </suppressions> + * <module name="SuppressionXpathSingleFilter"> + * <property name="checks" value="ImportOrder"/> + * <property name="message" value="^'java\..*'.*"/> + * </module> **
* To configure the check so that it matches default NetBeans formatter configuration
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/filters/SuppressionXpathSingleFilter.java b/src/main/java/com/puppycrawl/tools/checkstyle/filters/SuppressionXpathSingleFilter.java
new file mode 100644
index 00000000000..20654c667b3
--- /dev/null
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/filters/SuppressionXpathSingleFilter.java
@@ -0,0 +1,127 @@
+////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code for adherence to a set of rules.
+// Copyright (C) 2001-2019 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.filters;
+
+import java.util.regex.Pattern;
+
+import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
+import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
+import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
+
+/**
+ * Filter {@code SuppressionXpathSingleFilter} suppresses audit events for
+ * Checks violations in the specified file, class, checks, message, module id,
+ * and xpath.
+ * Attention: This filter only supports single suppression, and will need
+ * multiple instances if users wants to suppress multiple violations.
+ */
+public class SuppressionXpathSingleFilter extends AutomaticBean implements
+ TreeWalkerFilter {
+ /**
+ * XpathFilter instance.
+ */
+ private XpathFilter xpathFilter;
+ /**
+ * The pattern for file names.
+ */
+ private Pattern files;
+ /**
+ * The pattern for check class names.
+ */
+ private Pattern checks;
+ /**
+ * The pattern for message names.
+ */
+ private Pattern message;
+ /**
+ * Module id of filter.
+ */
+ private String id;
+ /**
+ * Xpath query.
+ */
+ private String query;
+
+ /**
+ * Set the regular expression to specify names of files to suppress.
+ * @param files the name of the file
+ */
+ public void setFiles(String files) {
+ if (files == null) {
+ this.files = null;
+ }
+ else {
+ this.files = Pattern.compile(files);
+ }
+ }
+
+ /**
+ * Set the regular expression to specify the name of the check to suppress.
+ * @param checks the name of the check
+ */
+ public void setChecks(String checks) {
+ if (checks == null) {
+ this.checks = null;
+ }
+ else {
+ this.checks = Pattern.compile(checks);
+ }
+ }
+
+ /**
+ * Set the regular expression to specify the message of the check to suppress.
+ * @param message the message of the check
+ */
+ public void setMessage(String message) {
+ if (message == null) {
+ this.message = null;
+ }
+ else {
+ this.message = Pattern.compile(message);
+ }
+ }
+
+ /**
+ * Set the ID of the check to suppress.
+ * @param id the ID of the check
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Set the xpath query.
+ * @param query the xpath query
+ */
+ public void setQuery(String query) {
+ this.query = query;
+ }
+
+ @Override
+ protected void finishLocalSetup() {
+ xpathFilter = new XpathFilter(files, checks, message, id, query);
+ }
+
+ @Override
+ public boolean accept(TreeWalkerAuditEvent treeWalkerAuditEvent) {
+ return xpathFilter.accept(treeWalkerAuditEvent);
+ }
+
+}
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/filters/XpathFilter.java b/src/main/java/com/puppycrawl/tools/checkstyle/filters/XpathFilter.java
index 202883f4a8f..f7931e42041 100644
--- a/src/main/java/com/puppycrawl/tools/checkstyle/filters/XpathFilter.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/filters/XpathFilter.java
@@ -111,7 +111,57 @@ public XpathFilter(String files, String checks,
xpathExpression = xpathEvaluator.createExpression(xpathQuery);
}
catch (XPathException ex) {
- throw new IllegalStateException("Unexpected xpath query: " + xpathQuery, ex);
+ throw new IllegalArgumentException("Unexpected xpath query: " + xpathQuery, ex);
+ }
+ }
+ }
+
+ /**
+ * Creates a {@code XpathElement} instance.
+ * @param files regular expression for names of filtered files
+ * @param checks regular expression for filtered check classes
+ * @param message regular expression for messages.
+ * @param moduleId the module id
+ * @param query the xpath query
+ */
+ public XpathFilter(Pattern files, Pattern checks, Pattern message,
+ String moduleId, String query) {
+ if (files == null) {
+ filePattern = null;
+ fileRegexp = null;
+ }
+ else {
+ filePattern = files.pattern();
+ fileRegexp = files;
+ }
+ if (checks == null) {
+ checkPattern = null;
+ checkRegexp = null;
+ }
+ else {
+ checkPattern = checks.pattern();
+ checkRegexp = checks;
+ }
+ if (message == null) {
+ messagePattern = null;
+ messageRegexp = null;
+ }
+ else {
+ messagePattern = message.pattern();
+ messageRegexp = message;
+ }
+ this.moduleId = moduleId;
+ xpathQuery = query;
+ if (xpathQuery == null) {
+ xpathExpression = null;
+ }
+ else {
+ final XPathEvaluator xpathEvaluator = new XPathEvaluator();
+ try {
+ xpathExpression = xpathEvaluator.createExpression(xpathQuery);
+ }
+ catch (XPathException ex) {
+ throw new IllegalArgumentException("Incorrect xpath query: " + xpathQuery, ex);
}
}
}
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/filters/SuppressionXpathSingleFilterTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/filters/SuppressionXpathSingleFilterTest.java
new file mode 100644
index 00000000000..b97e83d421f
--- /dev/null
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/filters/SuppressionXpathSingleFilterTest.java
@@ -0,0 +1,303 @@
+////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code for adherence to a set of rules.
+// Copyright (C) 2001-2019 the original author or authors.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+////////////////////////////////////////////////////////////////////////////////
+
+package com.puppycrawl.tools.checkstyle.filters;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
+import com.puppycrawl.tools.checkstyle.JavaParser;
+import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
+import com.puppycrawl.tools.checkstyle.api.FileContents;
+import com.puppycrawl.tools.checkstyle.api.FileText;
+import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
+import com.puppycrawl.tools.checkstyle.api.TokenTypes;
+
+public class SuppressionXpathSingleFilterTest
+ extends AbstractModuleTestSupport {
+
+ private File file;
+ private FileContents fileContents;
+
+ @Before
+ public void setUp() throws Exception {
+ file = new File(getPath("InputSuppressionXpathSingleFilter.java"));
+ fileContents = new FileContents(new FileText(file,
+ StandardCharsets.UTF_8.name()));
+ }
+
+ @Override
+ protected String getPackageLocation() {
+ return "com/puppycrawl/tools/checkstyle/filters/suppressionxpathsinglefilter";
+ }
+
+ @Test
+ public void testMatching() throws Exception {
+ final String xpath = "/CLASS_DEF[@text='InputSuppressionXpathSingleFilter']";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, null, xpath);
+ final TreeWalkerAuditEvent ev = createEvent(3, 0,
+ TokenTypes.CLASS_DEF);
+ assertFalse("Event should be rejected", filter.accept(ev));
+ }
+
+ @Test
+ public void testNonMatchingTokenType() throws Exception {
+ final String xpath = "//METHOD_DEF[@text='countTokens']";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, null, xpath);
+ final TreeWalkerAuditEvent ev = createEvent(3, 0,
+ TokenTypes.CLASS_DEF);
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testNonMatchingLineNumber() throws Exception {
+ final String xpath = "/CLASS_DEF[@text='InputSuppressionXpathSingleFilter']";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, null, xpath);
+ final TreeWalkerAuditEvent ev = createEvent(100, 0,
+ TokenTypes.CLASS_DEF);
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testNonMatchingColumnNumber() throws Exception {
+ final String xpath = "/CLASS_DEF[@text='InputSuppressionXpathSingleFilter']";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, null, xpath);
+ final TreeWalkerAuditEvent ev = createEvent(3, 100,
+ TokenTypes.CLASS_DEF);
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testComplexQuery() throws Exception {
+ final String xpath = "//VARIABLE_DEF[@text='pi' and "
+ + "../..[@text='countTokens']] "
+ + "| //VARIABLE_DEF[@text='someVariable' and ../..[@text='sum']]";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, null, xpath);
+ final TreeWalkerAuditEvent eventOne = createEvent(5, 8,
+ TokenTypes.VARIABLE_DEF);
+ final TreeWalkerAuditEvent eventTwo = createEvent(10, 4,
+ TokenTypes.VARIABLE_DEF);
+ final TreeWalkerAuditEvent eventThree = createEvent(15, 8,
+ TokenTypes.VARIABLE_DEF);
+ assertFalse("Event should be rejected", filter.accept(eventOne));
+ assertTrue("Event should be accepted", filter.accept(eventTwo));
+ assertFalse("Event should be rejected", filter.accept(eventThree));
+ }
+
+ @Test
+ public void testIncorrectQuery() {
+ final String xpath = "1@#";
+ try {
+ final Object test = createSuppressionXpathSingleFilter(
+ "InputSuppressionXpathSingleFilter", "Test",
+ null, null, xpath);
+ fail("Exception was expected but got " + test);
+ }
+ catch (IllegalArgumentException ex) {
+ assertTrue("Message should be: Unexpected xpath query",
+ ex.getMessage().contains("Incorrect xpath query"));
+ }
+ }
+
+ @Test
+ public void testNoQuery() throws Exception {
+ final TreeWalkerAuditEvent event = createEvent(15, 8,
+ TokenTypes.VARIABLE_DEF);
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, null, null);
+ assertFalse("Event should be accepted", filter.accept(event));
+ }
+
+ @Test
+ public void testNullFileName() {
+ final String xpath = "NON_MATCHING_QUERY";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, null, xpath);
+ final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(null,
+ null, null, null);
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testNonMatchingFileRegexp() throws Exception {
+ final String xpath = "NON_MATCHING_QUERY";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("NonMatchingRegexp", "Test",
+ null, null, xpath);
+ final TreeWalkerAuditEvent ev = createEvent(3, 0,
+ TokenTypes.CLASS_DEF);
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testNullLocalizedMessage() {
+ final String xpath = "NON_MATCHING_QUERY";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, null, xpath);
+ final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(null,
+ file.getName(), null, null);
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testNonMatchingModuleId() throws Exception {
+ final String xpath = "NON_MATCHING_QUERY";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, "id19", xpath);
+ final LocalizedMessage message =
+ new LocalizedMessage(3, 0, TokenTypes.CLASS_DEF, "", "",
+ null, null, "id20",
+ getClass(), null);
+ final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(fileContents, file.getName(),
+ message, JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS));
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testMatchingModuleId() throws Exception {
+ final String xpath = "/CLASS_DEF[@text='InputSuppressionXpathSingleFilter']";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter", "Test",
+ null, "id19", xpath);
+ final LocalizedMessage message =
+ new LocalizedMessage(3, 0, TokenTypes.CLASS_DEF, "",
+ "", null, null, "id19",
+ getClass(), null);
+ final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(fileContents, file.getName(),
+ message, JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS));
+ assertFalse("Event should be rejected", filter.accept(ev));
+ }
+
+ @Test
+ public void testNonMatchingChecks() throws Exception {
+ final String xpath = "NON_MATCHING_QUERY";
+ final SuppressionXpathSingleFilter filter = createSuppressionXpathSingleFilter(
+ "InputSuppressionXpathSingleFilter", "NonMatchingRegexp",
+ null, "id19", xpath);
+ final LocalizedMessage message =
+ new LocalizedMessage(3, 0, TokenTypes.CLASS_DEF, "",
+ "", null, null, "id19",
+ getClass(), null);
+ final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(fileContents, file.getName(),
+ message, JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS));
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testNonMatchingFileNameModuleIdAndCheck() throws Exception {
+ final String xpath = "NON_MATCHING_QUERY";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter",
+ null, null, null, xpath);
+ final TreeWalkerAuditEvent ev = createEvent(3, 0,
+ TokenTypes.CLASS_DEF);
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testNullModuleIdAndNonMatchingChecks() throws Exception {
+ final String xpath = "NON_MATCHING_QUERY";
+ final SuppressionXpathSingleFilter filter = createSuppressionXpathSingleFilter(
+ "InputSuppressionXpathSingleFilter", "NonMatchingRegexp", null, null, xpath);
+ final TreeWalkerAuditEvent ev = createEvent(3, 0,
+ TokenTypes.CLASS_DEF);
+ assertTrue("Event should be accepted", filter.accept(ev));
+ }
+
+ @Test
+ public void testDecideByMessage() throws Exception {
+ final LocalizedMessage message = new LocalizedMessage(0, 0,
+ TokenTypes.CLASS_DEF, "", "",
+ null, null, null, getClass(), "Test");
+ final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(fileContents, file.getName(),
+ message, JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS));
+ final SuppressionXpathSingleFilter filter1 = createSuppressionXpathSingleFilter(
+ null, null, "Test", null, null);
+ final SuppressionXpathSingleFilter filter2 = createSuppressionXpathSingleFilter(
+ null, null, "Bad", null, null);
+ assertFalse("Message match", filter1.accept(ev));
+ assertTrue("Message not match", filter2.accept(ev));
+ }
+
+ @Test
+ public void testThrowException() {
+ final String xpath = "/CLASS_DEF[@text='InputSuppressionXpathSingleFilter']";
+ final SuppressionXpathSingleFilter filter =
+ createSuppressionXpathSingleFilter("InputSuppressionXpathSingleFilter",
+ "Test", null, null, xpath);
+ final LocalizedMessage message =
+ new LocalizedMessage(3, 0, TokenTypes.CLASS_DEF, "",
+ "", null, null, "id19",
+ getClass(), null);
+ final TreeWalkerAuditEvent ev = new TreeWalkerAuditEvent(fileContents,
+ file.getName(), message, null);
+ try {
+ filter.accept(ev);
+ fail("Exception is expected");
+ }
+ catch (IllegalStateException ex) {
+ assertTrue("Exception message does not match expected one",
+ ex.getMessage().contains("Cannot initialize context and evaluate query"));
+ }
+ }
+
+ private static SuppressionXpathSingleFilter createSuppressionXpathSingleFilter(
+ String files, String checks, String message, String moduleId, String query) {
+ final SuppressionXpathSingleFilter filter = new SuppressionXpathSingleFilter();
+ filter.setFiles(files);
+ filter.setChecks(checks);
+ filter.setMessage(message);
+ filter.setId(moduleId);
+ filter.setQuery(query);
+ filter.finishLocalSetup();
+ return filter;
+ }
+
+ private TreeWalkerAuditEvent createEvent(int line, int column, int tokenType)
+ throws Exception {
+ final LocalizedMessage message =
+ new LocalizedMessage(line, column, tokenType, "", "", null, null, null,
+ getClass(), null);
+ return new TreeWalkerAuditEvent(fileContents, file.getName(), message,
+ JavaParser.parseFile(file, JavaParser.Options.WITHOUT_COMMENTS));
+ }
+
+}
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/filters/XpathFilterTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/filters/XpathFilterTest.java
index 29547d0d7ed..7f5fb536329 100644
--- a/src/test/java/com/puppycrawl/tools/checkstyle/filters/XpathFilterTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/filters/XpathFilterTest.java
@@ -125,7 +125,7 @@ public void testIncorrectQuery() {
null, xpath);
fail("Exception was expected but got " + test);
}
- catch (IllegalStateException ex) {
+ catch (IllegalArgumentException ex) {
assertTrue("Message should be: Unexpected xpath query",
ex.getMessage().contains("Unexpected xpath query"));
}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/filters/suppressionxpathsinglefilter/InputSuppressionXpathSingleFilter.java b/src/test/resources/com/puppycrawl/tools/checkstyle/filters/suppressionxpathsinglefilter/InputSuppressionXpathSingleFilter.java
new file mode 100644
index 00000000000..50389332766
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/filters/suppressionxpathsinglefilter/InputSuppressionXpathSingleFilter.java
@@ -0,0 +1,19 @@
+package com.puppycrawl.tools.checkstyle.filters.suppressionxpathsinglefilter;
+
+public class InputSuppressionXpathSingleFilter {
+ private int countTokens() {
+ double pi = 3.14;
+ return 123;
+ }
+
+ public String getName() {
+ int someVariable = 123;
+ return "InputSuppressByXpathThree";
+ }
+
+ public int sum(int a, int b) {
+ String someVariable = "Hello World";
+ return a + b;
+ }
+
+}
diff --git a/src/xdocs/config_filters.xml b/src/xdocs/config_filters.xml
index d079a10f32a..c20f0acf9d7 100644
--- a/src/xdocs/config_filters.xml
+++ b/src/xdocs/config_filters.xml
@@ -887,7 +887,7 @@ public class UserService {
a SuppressionXpathFilter
to
reject CyclomaticComplexity
errors for
all methods with name sayHelloWorld inside FileOne
- and FileTwo classes:
+ and FileTwo files:
To configure the check so that it matches default NetBeans formatter configuration