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:
parent
326e3ae67b
commit
211e2414ae
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()]);
|
||||
}
|
||||
}
|
||||
|
|
@ -807,6 +807,7 @@
|
|||
|
||||
</subsection>
|
||||
|
||||
<subsection name="SuppressionCommentFilter">
|
||||
<h4>SuppressionCommentFilter</h4>
|
||||
|
||||
<p>
|
||||
|
|
@ -963,7 +964,148 @@
|
|||
<property name="checkFormat" value="$1"/>
|
||||
</module>
|
||||
</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>
|
||||
<module name="TreeWalker">
|
||||
...
|
||||
<module name="FileContentsHolder"/>
|
||||
...
|
||||
</module>
|
||||
</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>
|
||||
<module name="SuppressWithNearbyCommentFilter"/>
|
||||
</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>
|
||||
<module name="SuppressionCommentFilter">
|
||||
<property name="commentFormat" value="CHECKSTYLE IGNORE THIS LINE"/>
|
||||
<property name="checkFormat" value=".*"/>
|
||||
<property name="influenceFormat" value="0"/>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</source>
|
||||
|
||||
</subsection>
|
||||
</section>
|
||||
|
||||
<section name="AuditListeners">
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue