Added the SuppressWithNearbyCommentFilter filter that uses nearby comments to suppress audit events. Thanks to Mick Killianey for providing patch #2354424.

This commit is contained in:
Oliver Burn 2009-03-08 04:21:41 +00:00
parent 326e3ae67b
commit 211e2414ae
7 changed files with 977 additions and 4 deletions

View File

@ -149,12 +149,13 @@
<entry key="checkstyle.compile.timestamp" type="date" value="now" pattern="E MMMM dd yyyy, HH:mm z"/>
</propertyfile>
<!-- had problems with following using lastest JDK, where this hung -->
<native2ascii src="src/checkstyle"
dest="${checkstyle.dest}"
encoding="EUC-JP"
includes="**/*_ja.properties" />
<copy todir="${checkstyle.dest}">
<copy todir="${checkstyle.dest}">
<fileset dir="src/checkstyle" includes="**/*.properties"/>
<fileset dir="src/checkstyle" includes="**/*.xml"/>
<fileset dir="src/checkstyle" includes="**/*.dtd"/>

View File

@ -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;
/**
* <p>
* A filter that uses nearby comments to suppress audit events.
* </p>
* <p>
* 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:
* <pre>
* offendingLine(for, whatever, reason); // SUPPRESS ParameterNumberCheck
* </pre>
* or it may be configured to span multiple lines, either forward:
* <pre>
* // 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;
* </pre>
* or reverse:
* <pre>
* try {
* thirdPartyLibrary.method();
* } catch (RuntimeException e) {
* // ALLOW ILLEGAL CATCH BECAUSE third party API wraps everything
* // in RuntimeExceptions.
* ...
* }
* </pre>
* </p>
* <p>
* See {@link SuppressionCommentFilter} for usage notes.
* </p>
*
* @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 <code>AuditEvent</code> 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 <code>String</code> 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 <code>String</code> 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 <code>String</code> 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 <code>String</code> 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 <code>true</code> if C++ comments are checked.
*/
public void setCheckCPP(boolean aCheckCPP)
{
mCheckCPP = aCheckCPP;
}
/**
* Set whether to look in C comments.
* @param aCheckC <code>true</code> 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 <code>Tag</code> 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);
}
}

View File

@ -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.
}
}
}

View File

@ -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<String> coll =
Lists.newArrayList(Arrays.asList(aFrom));
coll.removeAll(Arrays.asList(aRemove));
return coll.toArray(new String[coll.size()]);
}
}

View File

@ -807,6 +807,7 @@
</subsection>
<subsection name="SuppressionCommentFilter">
<h4>SuppressionCommentFilter</h4>
<p>
@ -963,7 +964,148 @@
&lt;property name=&quot;checkFormat&quot; value=&quot;$1&quot;/&gt;
&lt;/module&gt;
</source>
</section>
</subsection>
<subsection name="SuppressWithNearbyCommentFilter">
<h4>SuppressWithNearbyCommentFilter</h4>
<p>
Filter <span class="code">SuppressWithNearbyCommentFilter</span> uses
individual comments to suppress audit events.
</p>
<p>
Rationale: Same as <span class="code">SuppressionCommentFilter</span>.
Whereas the SuppressionCommentFilter uses matched pairs of
filters to turn on/off comment matching,
<span class="code">SuppressWithNearbyCommentFilter</span> uses
single comments. This requires fewer lines to mark a region, and
may be aesthetically preferable in some contexts.
</p>
<p>
Usage: This filter only works in conjunction with a <span
class="code">FileContentsHolder</span>, since that check makes
the suppression comments in the .java files available <i>sub
rosa</i>. A configuration that includes this filter must
configure <span class="code">FileContentsHolder</span> as a
child module of <span class="code">TreeWalker</span>.
</p>
<h5>SuppressWithNearbyCommentFilter Properties</h5>
<table width="100%" border="1" cellpadding="5" class="body">
<tr class="header">
<th>name</th>
<th>description</th>
<th>type</th>
<th>default value</th>
</tr>
<tr>
<td>commentFormat</td>
<td>comment pattern to trigger filter to begin suppression</td>
<td><a href="property_types.html#regexp">regular expression</a></td>
<td><span class="default">SUPPRESS CHECKSTYLE (\w+)</span></td>
</tr>
<tr>
<td>checkFormat</td>
<td>check pattern to suppress</td>
<td><a href="property_types.html#regexp">regular expression</a></td>
<td><span class="default">$1</span></td>
</tr>
<tr>
<td>messageFormat</td>
<td>message pattern to suppress</td>
<td><a href="property_types.html#regexp">regular expression</a></td>
<td>none</td>
</tr>
<tr>
<td>influenceFormat</td>
<td>a negative/zero/positive value that defines the number of
lines preceding/at/following the suppression comment</td>
<td><a href="property_types.html#regexp">regular expression</a></td>
<td><span class="default">0</span> (the line containing the comment)</td>
</tr>
<tr>
<td>checkCPP</td>
<td>whether to check C++ style comments (<code>//</code>)</td>
<td><a href="property_types.html#boolean">boolean</a></td>
<td><span class="default">true</span></td>
</tr>
<tr>
<td>checkC</td>
<td>whether to check C style comments (<code>/* ... */</code>)</td>
<td><a href="property_types.html#boolean">boolean</a></td>
<td><span class="default">true</span></td>
</tr>
</table>
<h5>Examples</h5>
<p>
To configure the check that makes comments available to the filter:
</p>
<source>
&lt;module name=&quot;TreeWalker&quot;&gt;
...
&lt;module name=&quot;FileContentsHolder&quot;/&gt;
...
&lt;/module&gt;
</source>
<p>
To configure a filter to suppress audit events for <i>check</i>
on any line with a comment <code>SUPPRESS CHECKSTYLE <i>check</i></code>:
</p>
<source>
&lt;module name=&quot;SuppressWithNearbyCommentFilter&quot;/&gt;
</source>
<p>
To configure a filter to suppress all audit events on any line
containing the comment <code>CHECKSTYLE IGNORE THIS LINE</code>:
</p>
<source>
&lt;module name=&quot;SuppressionCommentFilter&quot;&gt;
&lt;property name=&quot;commentFormat&quot; value=&quot;CHECKSTYLE IGNORE THIS LINE&quot;/&gt;
&lt;property name=&quot;checkFormat&quot; value=&quot;.*&quot;/&gt;
&lt;property name=&quot;influenceFormat&quot; value=&quot;0&quot;/&gt;
&lt;/module>
</source>
<p>
To configure a filter so that
<code>// Ok to catch (Throwable|Exception|RuntimeException) here</code>
permits the current and previous line to avoid generating an IllegalCatch
audit event:
</p>
<source>
&lt;module name=&quot;SuppressionCommentFilter&quot;&gt;
&lt;property name=&quot;commentFormat&quot; value=&quot;Ok to catch (\w+) here&quot;/&gt;
&lt;property name=&quot;checkFormat&quot; value=&quot;IllegalCatchCheck&quot;/&gt;
&lt;property name=&quot;messageFormat&quot; value=&quot;$1&quot;/&gt;
&lt;property name=&quot;influenceFormat&quot; value=&quot;-1&quot;/&gt;
&lt;/module>
</source>
<p>
To configure a filter so that <code>CHECKSTYLE IGNORE <i>check</i> FOR NEXT <i>var</i> LINES</code>
avoids triggering any audits for the given check for the current line and the next <i>var</i> lines
(for a total of <i>var</i>+1 lines):
</p>
<source>
&lt;module name=&quot;SuppressionCommentFilter&quot;&gt;
&lt;property name=&quot;commentFormat&quot; value=&quot;CHECKSTYLE IGNORE (\w+) FOR NEXT (\d+) LINES&quot;/&gt;
&lt;property name=&quot;checkFormat&quot; value=&quot;$1&quot;/&gt;
&lt;property name=&quot;messageFormat&quot; value=&quot;$2&quot;/&gt;
&lt;/module&gt;
</source>
</subsection>
</section>
<section name="AuditListeners">

View File

@ -93,6 +93,11 @@
comment. Thanks to Travis Schneeberger for providing patch
#2294029.
</li>
<li>
Added the <a href="config.html#Filters">SuppressWithNearbyCommentFilter</a>
filter that uses nearby comments to suppress audit events. Thanks
to Mick Killianey for providing patch #2354424.
</li>
</ul>
<p>Fixed Bugs:</p>

View File

@ -20,9 +20,12 @@
<suppress checks="ImportControl"
files="SuppressionCommentFilter.java"
lines="28"/>
<suppress checks="ImportControl"
files="SuppressWithNearbyCommentFilter.java"
lines="40"/>
<suppress id="paramNum"
files="LocalizedMessage.java"
lines="141,145,178,210"/>
files="LocalizedMessage.java"
lines="141,145,178,210"/>
<!--
Turn off all checks for Generated and Test code. Fixes issues with using
Eclipse plug-in.