From 211e2414ae57765ee25e99ab89372ee293f59e38 Mon Sep 17 00:00:00 2001 From: Oliver Burn Date: Sun, 8 Mar 2009 04:21:41 +0000 Subject: [PATCH] Added the SuppressWithNearbyCommentFilter filter that uses nearby comments to suppress audit events. Thanks to Mick Killianey for providing patch #2354424. --- build.xml | 3 +- .../SuppressWithNearbyCommentFilter.java | 523 ++++++++++++++++++ .../InputSuppressWithNearbyCommentFilter.java | 77 +++ .../SuppressWithNearbyCommentFilterTest.java | 222 ++++++++ src/xdocs/config.xml | 144 ++++- src/xdocs/releasenotes.xml | 5 + suppressions.xml | 7 +- 7 files changed, 977 insertions(+), 4 deletions(-) create mode 100644 src/checkstyle/com/puppycrawl/tools/checkstyle/filters/SuppressWithNearbyCommentFilter.java create mode 100644 src/testinputs/com/puppycrawl/tools/checkstyle/filters/InputSuppressWithNearbyCommentFilter.java create mode 100644 src/tests/com/puppycrawl/tools/checkstyle/filters/SuppressWithNearbyCommentFilterTest.java diff --git a/build.xml b/build.xml index b1d780e0e..f81981839 100644 --- a/build.xml +++ b/build.xml @@ -149,12 +149,13 @@ + - + diff --git a/src/checkstyle/com/puppycrawl/tools/checkstyle/filters/SuppressWithNearbyCommentFilter.java b/src/checkstyle/com/puppycrawl/tools/checkstyle/filters/SuppressWithNearbyCommentFilter.java new file mode 100644 index 000000000..ccd4300a0 --- /dev/null +++ b/src/checkstyle/com/puppycrawl/tools/checkstyle/filters/SuppressWithNearbyCommentFilter.java @@ -0,0 +1,523 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2008 Oliver Burn +// +// 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.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.commons.beanutils.ConversionException; + +import com.puppycrawl.tools.checkstyle.api.AuditEvent; +import com.puppycrawl.tools.checkstyle.api.AutomaticBean; +import com.puppycrawl.tools.checkstyle.api.FileContents; +import com.puppycrawl.tools.checkstyle.api.Filter; +import com.puppycrawl.tools.checkstyle.api.TextBlock; +import com.puppycrawl.tools.checkstyle.api.Utils; +import com.puppycrawl.tools.checkstyle.checks.FileContentsHolder; + +/** + *

+ * A filter that uses nearby comments to suppress audit events. + *

+ *

+ * This check is philosophically similar to {@link SuppressionCommentFilter}. + * Unlike {@link SuppressionCommentFilter}, this filter does not require + * pairs of comments. This check may be used to suppress warnings in the + * current line: + *

+ *    offendingLine(for, whatever, reason); // SUPPRESS ParameterNumberCheck
+ * 
+ * or it may be configured to span multiple lines, either forward: + *
+ *    // PERMIT MultipleVariableDeclarations NEXT 3 LINES
+ *    double x1 = 1.0, y1 = 0.0, z1 = 0.0;
+ *    double x2 = 0.0, y2 = 1.0, z2 = 0.0;
+ *    double x3 = 0.0, y3 = 0.0, z3 = 1.0;
+ * 
+ * or reverse: + *
+ *   try {
+ *     thirdPartyLibrary.method();
+ *   } catch (RuntimeException e) {
+ *     // ALLOW ILLEGAL CATCH BECAUSE third party API wraps everything
+ *     // in RuntimeExceptions.
+ *     ...
+ *   }
+ * 
+ *

+ *

+ * See {@link SuppressionCommentFilter} for usage notes. + *

+ * + * @author Mick Killianey + */ +public class SuppressWithNearbyCommentFilter + extends AutomaticBean + implements Filter +{ + /** + * A Tag holds a suppression comment and its location. + */ + public class Tag implements Comparable + { + /** The text of the tag. */ + private String mText; + + /** The first line where warnings may be suppressed. */ + private int mFirstLine; + + /** The last line where warnings may be suppressed. */ + private int mLastLine; + + /** The parsed check regexp, expanded for the text of this tag. */ + private Pattern mTagCheckRegexp; + + /** The parsed message regexp, expanded for the text of this tag. */ + private Pattern mTagMessageRegexp; + + /** + * Constructs a tag. + * @param aText the text of the suppression. + * @param aLine the line number. + * @throws ConversionException if unable to parse expanded aText. + * on. + */ + public Tag(String aText, int aLine) + throws ConversionException + { + mText = aText; + + mTagCheckRegexp = mCheckRegexp; + //Expand regexp for check and message + //Does not intern Patterns with Utils.getPattern() + String format = ""; + try { + format = expandFromComment(aText, mCheckFormat, mCommentRegexp); + mTagCheckRegexp = Pattern.compile(format); + if (mMessageFormat != null) { + format = expandFromComment( + aText, mMessageFormat, mCommentRegexp); + mTagMessageRegexp = Pattern.compile(format); + } + int influence = 0; + if (mInfluenceFormat != null) { + format = expandFromComment( + aText, mInfluenceFormat, mCommentRegexp); + try { + if (format.startsWith("+")) { + format = format.substring(1); + } + influence = Integer.parseInt(format); + } + catch (NumberFormatException e) { + throw new ConversionException( + "unable to parse influence from '" + aText + + "' using " + mInfluenceFormat, e); + } + } + if (influence >= 0) { + mFirstLine = aLine; + mLastLine = aLine + influence; + } + else { + mFirstLine = aLine + influence; + mLastLine = aLine; + } + } + catch (final PatternSyntaxException e) { + throw new ConversionException( + "unable to parse expanded comment " + format, + e); + } + } + + /** @return the text of the tag. */ + public String getText() + { + return mText; + } + + /** @return the line number of the first suppressed line. */ + public int getFirstLine() + { + return mFirstLine; + } + + /** @return the line number of the last suppressed line. */ + public int getLastLine() + { + return mLastLine; + } + + /** + * Compares the position of this tag in the file + * with the position of another tag. + * @param aObject the tag to compare with this one. + * @return a negative number if this tag is before the other tag, + * 0 if they are at the same position, and a positive number if this + * tag is after the other tag. + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Object aObject) + { + final Tag other = (Tag) aObject; + if (mFirstLine == other.mFirstLine) { + return mLastLine - other.mLastLine; + } + + return (mFirstLine - other.mFirstLine); + } + + /** + * Determines whether the source of an audit event + * matches the text of this tag. + * @param aEvent the AuditEvent to check. + * @return true if the source of aEvent matches the text of this tag. + */ + public boolean isMatch(AuditEvent aEvent) + { + final int line = aEvent.getLine(); + if (line < mFirstLine) { + return false; + } + if (line > mLastLine) { + return false; + } + final Matcher tagMatcher = + mTagCheckRegexp.matcher(aEvent.getSourceName()); + if (tagMatcher.find()) { + return true; + } + if (mTagMessageRegexp != null) { + final Matcher messageMatcher = + mTagMessageRegexp.matcher(aEvent.getMessage()); + return messageMatcher.find(); + } + return false; + } + + /** + * Expand based on a matching comment. + * @param aComment the comment. + * @param aString the string to expand. + * @param aRegexp the parsed expander. + * @return the expanded string + */ + private String expandFromComment( + String aComment, + String aString, + Pattern aRegexp) + { + final Matcher matcher = aRegexp.matcher(aComment); + // Match primarily for effect. + if (!matcher.find()) { + ///CLOVER:OFF + return aString; + ///CLOVER:ON + } + String result = aString; + for (int i = 0; i <= matcher.groupCount(); i++) { + // $n expands comment match like in Pattern.subst(). + result = result.replaceAll("\\$" + i, matcher.group(i)); + } + return result; + } + + /** {@inheritDoc} */ + public final String toString() + { + return "Tag[lines=[" + getFirstLine() + " to " + getLastLine() + + "]; text='" + getText() + "']"; + } + } + + /** Format to turns checkstyle reporting off. */ + private static final String DEFAULT_COMMENT_FORMAT = + "SUPPRESS CHECKSTYLE (\\w+)"; + + /** Default regex for checks that should be suppressed. */ + private static final String DEFAULT_CHECK_FORMAT = ".*"; + + /** Default regex for messages that should be suppressed. */ + private static final String DEFAULT_MESSAGE_FORMAT = null; + + /** Default regex for lines that should be suppressed. */ + private static final String DEFAULT_INFLUENCE_FORMAT = "0"; + + /** Whether to look for trigger in C-style comments. */ + private boolean mCheckC = true; + + /** Whether to look for trigger in C++-style comments. */ + private boolean mCheckCPP = true; + + /** Parsed comment regexp that marks checkstyle suppression region. */ + private Pattern mCommentRegexp; + + /** The comment pattern that triggers suppression. */ + private String mCheckFormat; + + /** The parsed check regexp. */ + private Pattern mCheckRegexp; + + /** The message format to suppress. */ + private String mMessageFormat; + + /** The influence of the suppression comment. */ + private String mInfluenceFormat; + + + //TODO: Investigate performance improvement with array + /** Tagged comments */ + private final List mTags = new ArrayList(); + + /** + * References the current FileContents for this filter. + * Since this is a weak reference to the FileContents, the FileContents + * can be reclaimed as soon as the strong references in TreeWalker + * and FileContentsHolder are reassigned to the next FileContents, + * at which time filtering for the current FileContents is finished. + */ + private WeakReference mFileContentsReference = new WeakReference(null); + + /** + * Constructs a SuppressionCommentFilter. + * Initializes comment on, comment off, and check formats + * to defaults. + */ + public SuppressWithNearbyCommentFilter() + { + if (DEFAULT_COMMENT_FORMAT != null) { + setCommentFormat(DEFAULT_COMMENT_FORMAT); + } + if (DEFAULT_CHECK_FORMAT != null) { + setCheckFormat(DEFAULT_CHECK_FORMAT); + } + if (DEFAULT_MESSAGE_FORMAT != null) { + setMessageFormat(DEFAULT_MESSAGE_FORMAT); + } + if (DEFAULT_INFLUENCE_FORMAT != null) { + setInfluenceFormat(DEFAULT_INFLUENCE_FORMAT); + } + } + + /** + * Set the format for a comment that turns off reporting. + * @param aFormat a String value. + * @throws ConversionException unable to parse aFormat. + */ + public void setCommentFormat(String aFormat) + throws ConversionException + { + try { + mCommentRegexp = Utils.getPattern(aFormat); + } + catch (final PatternSyntaxException e) { + throw new ConversionException("unable to parse " + aFormat, e); + } + } + + /** @return the FileContents for this filter. */ + public FileContents getFileContents() + { + return (FileContents) mFileContentsReference.get(); + } + + /** + * Set the FileContents for this filter. + * @param aFileContents the FileContents for this filter. + */ + public void setFileContents(FileContents aFileContents) + { + mFileContentsReference = new WeakReference(aFileContents); + } + + /** + * Set the format for a check. + * @param aFormat a String value + * @throws ConversionException unable to parse aFormat + */ + public void setCheckFormat(String aFormat) + throws ConversionException + { + try { + mCheckRegexp = Utils.getPattern(aFormat); + mCheckFormat = aFormat; + } + catch (final PatternSyntaxException e) { + throw new ConversionException("unable to parse " + aFormat, e); + } + } + + /** + * Set the format for a message. + * @param aFormat a String value + * @throws ConversionException unable to parse aFormat + */ + public void setMessageFormat(String aFormat) + throws ConversionException + { + // check that aFormat parses + try { + Utils.getPattern(aFormat); + } + catch (final PatternSyntaxException e) { + throw new ConversionException("unable to parse " + aFormat, e); + } + mMessageFormat = aFormat; + } + + /** + * Set the format for the influence of this check. + * @param aFormat a String value + * @throws ConversionException unable to parse aFormat + */ + public void setInfluenceFormat(String aFormat) + throws ConversionException + { + // check that aFormat parses + try { + Utils.getPattern(aFormat); + } + catch (final PatternSyntaxException e) { + throw new ConversionException("unable to parse " + aFormat, e); + } + mInfluenceFormat = aFormat; + } + + + /** + * Set whether to look in C++ comments. + * @param aCheckCPP true if C++ comments are checked. + */ + public void setCheckCPP(boolean aCheckCPP) + { + mCheckCPP = aCheckCPP; + } + + /** + * Set whether to look in C comments. + * @param aCheckC true if C comments are checked. + */ + public void setCheckC(boolean aCheckC) + { + mCheckC = aCheckC; + } + + /** {@inheritDoc} */ + public boolean accept(AuditEvent aEvent) + { + if (aEvent.getLocalizedMessage() == null) { + return true; // A special event. + } + + // Lazy update. If the first event for the current file, update file + // contents and tag suppressions + final FileContents currentContents = FileContentsHolder.getContents(); + if (currentContents == null) { + // we have no contents, so we can not filter. + // TODO: perhaps we should notify user somehow? + return true; + } + if (getFileContents() != currentContents) { + setFileContents(currentContents); + tagSuppressions(); + } + for (final Iterator iter = mTags.iterator(); iter.hasNext();) { + final Tag tag = (Tag) iter.next(); + if (tag.isMatch(aEvent)) { + return false; + } + } + return true; + } + + /** + * Collects all the suppression tags for all comments into a list and + * sorts the list. + */ + private void tagSuppressions() + { + mTags.clear(); + final FileContents contents = getFileContents(); + if (mCheckCPP) { + tagSuppressions(contents.getCppComments().values()); + } + if (mCheckC) { + final Collection cComments = contents.getCComments().values(); + final Iterator iter = cComments.iterator(); + while (iter.hasNext()) { + final Collection element = (Collection) iter.next(); + tagSuppressions(element); + } + } + Collections.sort(mTags); + } + + /** + * Appends the suppressions in a collection of comments to the full + * set of suppression tags. + * @param aComments the set of comments. + */ + private void tagSuppressions(Collection aComments) + { + for (final Iterator iter = aComments.iterator(); iter.hasNext();) { + final TextBlock comment = (TextBlock) iter.next(); + final int startLineNo = comment.getStartLineNo(); + final String[] text = comment.getText(); + tagCommentLine(text[0], startLineNo, comment.getStartColNo()); + for (int i = 1; i < text.length; i++) { + tagCommentLine(text[i], startLineNo + i, 0); + } + } + } + + /** + * Tags a string if it matches the format for turning + * checkstyle reporting on or the format for turning reporting off. + * @param aText the string to tag. + * @param aLine the line number of aText. + * @param aColumn the column number of aText. + */ + private void tagCommentLine(String aText, int aLine, int aColumn) + { + final Matcher matcher = mCommentRegexp.matcher(aText); + if (matcher.find()) { + addTag(matcher.group(0), aLine); + } + } + + /** + * Adds a comment suppression Tag to the list of all tags. + * @param aText the text of the tag. + * @param aLine the line number of the tag. + */ + private void addTag(String aText, int aLine) + { + final Tag tag = new Tag(aText, aLine); + mTags.add(tag); + } +} diff --git a/src/testinputs/com/puppycrawl/tools/checkstyle/filters/InputSuppressWithNearbyCommentFilter.java b/src/testinputs/com/puppycrawl/tools/checkstyle/filters/InputSuppressWithNearbyCommentFilter.java new file mode 100644 index 000000000..42fcf6ffe --- /dev/null +++ b/src/testinputs/com/puppycrawl/tools/checkstyle/filters/InputSuppressWithNearbyCommentFilter.java @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////////////////////////// +// Test case file for checkstyle. +//////////////////////////////////////////////////////////////////////////////// + +package com.puppycrawl.tools.checkstyle.filters; + +/** + * Test input for using comments to suppress errors. + * + * @author Mick Killianey + */ +class InputSuppressWithNearbyCommentFilter +{ + private int A1; // SUPPRESS CHECKSTYLE MemberNameCheck + private int A2; /* SUPPRESS CHECKSTYLE MemberNameCheck */ + /* SUPPRESS CHECKSTYLE MemberNameCheck */ private int A3; + + private int B1; // SUPPRESS CHECKSTYLE MemberNameCheck + private int B2; /* SUPPRESS CHECKSTYLE MemberNameCheck */ + /* SUPPRESS CHECKSTYLE MemberNameCheck */ private int B3; + + private int C1; + // ALLOW MemberName ON NEXT LINE + private int C2; + private int C3; + + private int D1; + private int D2; + // ALLOW MemberName ON PREVIOUS LINE + private int D3; + + private static final int e1 = 0; + private int E2; + private int E3; // ALLOW ConstantName UNTIL THIS LINE+2 + private static final int e4 = 0; + private int E5; + private static final int e6 = 0; + private int E7; + private int E8; /* ALLOW MemberName UNTIL THIS LINE-3 */ + private static final int e9 = 0; + + // ALLOW Unused UNTIL THIS LINE+5 + public static void doit1(int aInt) // this is +1 + { + } + + public static void doit2(int aInt) // this is +5 + { + } + + public static void doit3(int aInt) // this is +9 + { + } + + public void doit4() + { + try { + // blah blah blah + for(int i = 0; i < 10; i++) { + // blah blah blah + while(true) { + try { + // blah blah blah + } catch(Exception e) { + // bad bad bad + } catch (Throwable t) { + // ALLOW CATCH Throwable BECAUSE I threw this together. + } + } + // blah blah blah + } + // blah blah blah + } catch(Exception ex) { + // ALLOW CATCH Exception BECAUSE I am an exceptional person. + } + } +} diff --git a/src/tests/com/puppycrawl/tools/checkstyle/filters/SuppressWithNearbyCommentFilterTest.java b/src/tests/com/puppycrawl/tools/checkstyle/filters/SuppressWithNearbyCommentFilterTest.java new file mode 100644 index 000000000..7c756a5ec --- /dev/null +++ b/src/tests/com/puppycrawl/tools/checkstyle/filters/SuppressWithNearbyCommentFilterTest.java @@ -0,0 +1,222 @@ +//////////////////////////////////////////////////////////////////////////////// +// Test case for checkstyle. +//////////////////////////////////////////////////////////////////////////////// + +package com.puppycrawl.tools.checkstyle.filters; + +import com.google.common.collect.Lists; +import com.puppycrawl.tools.checkstyle.BaseCheckTestSupport; +import com.puppycrawl.tools.checkstyle.Checker; +import com.puppycrawl.tools.checkstyle.DefaultConfiguration; +import com.puppycrawl.tools.checkstyle.TreeWalker; +import com.puppycrawl.tools.checkstyle.api.CheckstyleException; +import com.puppycrawl.tools.checkstyle.api.Configuration; +import com.puppycrawl.tools.checkstyle.checks.FileContentsHolder; +import com.puppycrawl.tools.checkstyle.checks.coding.IllegalCatchCheck; +import com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck; +import com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck; +import java.util.Arrays; +import java.util.Collection; +import java.util.Locale; +import org.junit.Test; + +public class SuppressWithNearbyCommentFilterTest + extends BaseCheckTestSupport +{ + static String[] sAllMessages = { + "14:17: Name 'A1' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "15:17: Name 'A2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "16:59: Name 'A3' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + + "18:17: Name 'B1' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "19:17: Name 'B2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "20:59: Name 'B3' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + + "22:17: Name 'C1' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "24:17: Name 'C2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "25:17: Name 'C3' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + + "27:17: Name 'D1' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "28:17: Name 'D2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "30:17: Name 'D3' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + + "32:30: Name 'e1' must match pattern '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.", + "33:17: Name 'E2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "34:17: Name 'E3' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "35:30: Name 'e4' must match pattern '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.", + "36:17: Name 'E5' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "37:30: Name 'e6' must match pattern '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.", + "38:17: Name 'E7' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "39:17: Name 'E8' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "40:30: Name 'e9' must match pattern '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.", + + "64:23: Catching 'Exception' is not allowed.", + "66:23: Catching 'Throwable' is not allowed.", + "73:11: Catching 'Exception' is not allowed.", + }; + + @Test + public void testNone() + throws Exception + { + final DefaultConfiguration filterConfig = null; + final String[] suppressed = { + }; + verifySuppressed(filterConfig, suppressed); + } + + @Test + public void testDefault() throws Exception + { + final DefaultConfiguration filterConfig = + createFilterConfig(SuppressWithNearbyCommentFilter.class); + final String[] suppressed = { + "14:17: Name 'A1' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "15:17: Name 'A2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "16:59: Name 'A3' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + + "18:17: Name 'B1' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "19:17: Name 'B2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "20:59: Name 'B3' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + }; + verifySuppressed(filterConfig, suppressed); + } + + @Test + public void testCheckC() throws Exception + { + final DefaultConfiguration filterConfig = + createFilterConfig(SuppressWithNearbyCommentFilter.class); + filterConfig.addAttribute("checkC", "false"); + final String[] suppressed = { + "14:17: Name 'A1' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "18:17: Name 'B1' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + }; + verifySuppressed(filterConfig, suppressed); + } + + @Test + public void testCheckCPP() throws Exception + { + final DefaultConfiguration filterConfig = + createFilterConfig(SuppressWithNearbyCommentFilter.class); + filterConfig.addAttribute("checkCPP", "false"); + final String[] suppressed = { + "15:17: Name 'A2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "16:59: Name 'A3' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + + "19:17: Name 'B2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "20:59: Name 'B3' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + }; + verifySuppressed(filterConfig, suppressed); + } + + @Test + public void testUsingAVariableMessage() throws Exception + { + final DefaultConfiguration filterConfig = + createFilterConfig(SuppressWithNearbyCommentFilter.class); + filterConfig.addAttribute("commentFormat", "ALLOW CATCH (\\w+) BECAUSE"); + filterConfig.addAttribute("checkFormat", "IllegalCatchCheck"); + filterConfig.addAttribute("messageFormat", "$1"); + filterConfig.addAttribute("influenceFormat", "-1"); + final String[] suppressed = { + "66:23: Catching 'Throwable' is not allowed.", + "73:11: Catching 'Exception' is not allowed.", + }; + verifySuppressed(filterConfig, suppressed); + } + + @Test + public void testUsingAVariableCheckOnNextLine() throws Exception + { + final DefaultConfiguration filterConfig = + createFilterConfig(SuppressWithNearbyCommentFilter.class); + filterConfig.addAttribute("commentFormat", "ALLOW (\\w+) ON NEXT LINE"); + filterConfig.addAttribute("checkFormat", "$1"); + filterConfig.addAttribute("influenceFormat", "1"); + final String[] suppressed = { + "24:17: Name 'C2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + }; + verifySuppressed(filterConfig, suppressed); + } + + @Test + public void testUsingAVariableCheckOnPreviousLine() throws Exception + { + final DefaultConfiguration filterConfig = + createFilterConfig(SuppressWithNearbyCommentFilter.class); + filterConfig.addAttribute("commentFormat", "ALLOW (\\w+) ON PREVIOUS LINE"); + filterConfig.addAttribute("checkFormat", "$1"); + filterConfig.addAttribute("influenceFormat", "-1"); + final String[] suppressed = { + "28:17: Name 'D2' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + }; + verifySuppressed(filterConfig, suppressed); + } + + @Test + public void testVariableCheckOnVariableNumberOfLines() throws Exception + { + final DefaultConfiguration filterConfig = + createFilterConfig(SuppressWithNearbyCommentFilter.class); + filterConfig.addAttribute("commentFormat", "ALLOW (\\w+) UNTIL THIS LINE([+-]\\d+)"); + filterConfig.addAttribute("checkFormat", "$1"); + filterConfig.addAttribute("influenceFormat", "$2"); + final String[] suppressed = { + "35:30: Name 'e4' must match pattern '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.", + "36:17: Name 'E5' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "38:17: Name 'E7' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + "39:17: Name 'E8' must match pattern '^[a-z][a-zA-Z0-9]*$'.", + }; + verifySuppressed(filterConfig, suppressed); + } + + + public static DefaultConfiguration createFilterConfig(Class aClass) + { + return new DefaultConfiguration(aClass.getName()); + } + + protected void verifySuppressed(Configuration aFilterConfig, + String[] aSuppressed) + throws Exception + { + verify(createChecker(aFilterConfig), + getPath("filters/InputSuppressWithNearbyCommentFilter.java"), + removeSuppressed(sAllMessages, aSuppressed)); + } + + @Override + protected Checker createChecker(Configuration aFilterConfig) + throws CheckstyleException + { + final DefaultConfiguration checkerConfig = + new DefaultConfiguration("configuration"); + final DefaultConfiguration checksConfig = createCheckConfig(TreeWalker.class); + checksConfig.addChild(createCheckConfig(FileContentsHolder.class)); + checksConfig.addChild(createCheckConfig(MemberNameCheck.class)); + checksConfig.addChild(createCheckConfig(ConstantNameCheck.class)); + checksConfig.addChild(createCheckConfig(IllegalCatchCheck.class)); + checkerConfig.addChild(checksConfig); + if (aFilterConfig != null) { + checkerConfig.addChild(aFilterConfig); + } + final Checker checker = new Checker(); + final Locale locale = Locale.ENGLISH; + checker.setLocaleCountry(locale.getCountry()); + checker.setLocaleLanguage(locale.getLanguage()); + checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader()); + checker.configure(checkerConfig); + checker.addListener(new BriefLogger(mStream)); + return checker; + } + + private String[] removeSuppressed(String[] aFrom, String[] aRemove) + { + final Collection coll = + Lists.newArrayList(Arrays.asList(aFrom)); + coll.removeAll(Arrays.asList(aRemove)); + return coll.toArray(new String[coll.size()]); + } +} diff --git a/src/xdocs/config.xml b/src/xdocs/config.xml index 021ee0bbc..c256df78e 100755 --- a/src/xdocs/config.xml +++ b/src/xdocs/config.xml @@ -807,6 +807,7 @@ +

SuppressionCommentFilter

@@ -963,7 +964,148 @@ <property name="checkFormat" value="$1"/> </module> - + + + +

SuppressWithNearbyCommentFilter

+ +

+ Filter SuppressWithNearbyCommentFilter uses + individual comments to suppress audit events. +

+ +

+ Rationale: Same as SuppressionCommentFilter. + Whereas the SuppressionCommentFilter uses matched pairs of + filters to turn on/off comment matching, + SuppressWithNearbyCommentFilter uses + single comments. This requires fewer lines to mark a region, and + may be aesthetically preferable in some contexts. +

+ +

+ Usage: This filter only works in conjunction with a FileContentsHolder, since that check makes + the suppression comments in the .java files available sub + rosa. A configuration that includes this filter must + configure FileContentsHolder as a + child module of TreeWalker. +

+ +
SuppressWithNearbyCommentFilter Properties
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namedescriptiontypedefault value
commentFormatcomment pattern to trigger filter to begin suppressionregular expressionSUPPRESS CHECKSTYLE (\w+)
checkFormatcheck pattern to suppressregular expression$1
messageFormatmessage pattern to suppressregular expressionnone
influenceFormata negative/zero/positive value that defines the number of + lines preceding/at/following the suppression commentregular expression0 (the line containing the comment)
checkCPPwhether to check C++ style comments (//)booleantrue
checkCwhether to check C style comments (/* ... */)booleantrue
+ +
Examples
+

+ To configure the check that makes comments available to the filter: +

+ + +<module name="TreeWalker"> + ... + <module name="FileContentsHolder"/> + ... +</module> + + +

+ To configure a filter to suppress audit events for check + on any line with a comment SUPPRESS CHECKSTYLE check: +

+ + +<module name="SuppressWithNearbyCommentFilter"/> + + +

+ To configure a filter to suppress all audit events on any line + containing the comment CHECKSTYLE IGNORE THIS LINE: +

+ + +<module name="SuppressionCommentFilter"> + <property name="commentFormat" value="CHECKSTYLE IGNORE THIS LINE"/> + <property name="checkFormat" value=".*"/> + <property name="influenceFormat" value="0"/> +</module> + + +

+ To configure a filter so that + // Ok to catch (Throwable|Exception|RuntimeException) here + permits the current and previous line to avoid generating an IllegalCatch + audit event: +

+ + +<module name="SuppressionCommentFilter"> + <property name="commentFormat" value="Ok to catch (\w+) here"/> + <property name="checkFormat" value="IllegalCatchCheck"/> + <property name="messageFormat" value="$1"/> + <property name="influenceFormat" value="-1"/> +</module> + + +

+ To configure a filter so that CHECKSTYLE IGNORE check FOR NEXT var LINES + avoids triggering any audits for the given check for the current line and the next var lines + (for a total of var+1 lines): +

+ + +<module name="SuppressionCommentFilter"> + <property name="commentFormat" value="CHECKSTYLE IGNORE (\w+) FOR NEXT (\d+) LINES"/> + <property name="checkFormat" value="$1"/> + <property name="messageFormat" value="$2"/> +</module> + + +
+
diff --git a/src/xdocs/releasenotes.xml b/src/xdocs/releasenotes.xml index 99138d203..553bc4d77 100755 --- a/src/xdocs/releasenotes.xml +++ b/src/xdocs/releasenotes.xml @@ -93,6 +93,11 @@ comment. Thanks to Travis Schneeberger for providing patch #2294029. +
  • + Added the SuppressWithNearbyCommentFilter + filter that uses nearby comments to suppress audit events. Thanks + to Mick Killianey for providing patch #2354424. +
  • Fixed Bugs:

    diff --git a/suppressions.xml b/suppressions.xml index f468289d0..8dac962b9 100755 --- a/suppressions.xml +++ b/suppressions.xml @@ -20,9 +20,12 @@ + + files="LocalizedMessage.java" + lines="141,145,178,210"/>