Coverage Report - com.puppycrawl.tools.checkstyle.filters.SuppressWithNearbyCommentFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
SuppressWithNearbyCommentFilter
100%
64/64
100%
20/20
0
SuppressWithNearbyCommentFilter$Tag
100%
54/54
100%
32/32
0
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.filters;
 21  
 
 22  
 import java.lang.ref.WeakReference;
 23  
 import java.util.ArrayList;
 24  
 import java.util.Collection;
 25  
 import java.util.List;
 26  
 import java.util.Objects;
 27  
 import java.util.regex.Matcher;
 28  
 import java.util.regex.Pattern;
 29  
 import java.util.regex.PatternSyntaxException;
 30  
 
 31  
 import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
 32  
 import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
 33  
 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
 34  
 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
 35  
 import com.puppycrawl.tools.checkstyle.api.FileContents;
 36  
 import com.puppycrawl.tools.checkstyle.api.TextBlock;
 37  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 38  
 
 39  
 /**
 40  
  * <p>
 41  
  * A filter that uses nearby comments to suppress audit events.
 42  
  * </p>
 43  
  *
 44  
  * <p>This check is philosophically similar to {@link SuppressionCommentFilter}.
 45  
  * Unlike {@link SuppressionCommentFilter}, this filter does not require
 46  
  * pairs of comments.  This check may be used to suppress warnings in the
 47  
  * current line:
 48  
  * <pre>
 49  
  *    offendingLine(for, whatever, reason); // SUPPRESS ParameterNumberCheck
 50  
  * </pre>
 51  
  * or it may be configured to span multiple lines, either forward:
 52  
  * <pre>
 53  
  *    // PERMIT MultipleVariableDeclarations NEXT 3 LINES
 54  
  *    double x1 = 1.0, y1 = 0.0, z1 = 0.0;
 55  
  *    double x2 = 0.0, y2 = 1.0, z2 = 0.0;
 56  
  *    double x3 = 0.0, y3 = 0.0, z3 = 1.0;
 57  
  * </pre>
 58  
  * or reverse:
 59  
  * <pre>
 60  
  *   try {
 61  
  *     thirdPartyLibrary.method();
 62  
  *   } catch (RuntimeException ex) {
 63  
  *     // ALLOW ILLEGAL CATCH BECAUSE third party API wraps everything
 64  
  *     // in RuntimeExceptions.
 65  
  *     ...
 66  
  *   }
 67  
  * </pre>
 68  
  *
 69  
  * <p>See {@link SuppressionCommentFilter} for usage notes.
 70  
  *
 71  
  * @author Mick Killianey
 72  
  */
 73  324
 public class SuppressWithNearbyCommentFilter
 74  
     extends AutomaticBean
 75  
     implements TreeWalkerFilter {
 76  
 
 77  
     /** Format to turns checkstyle reporting off. */
 78  
     private static final String DEFAULT_COMMENT_FORMAT =
 79  
         "SUPPRESS CHECKSTYLE (\\w+)";
 80  
 
 81  
     /** Default regex for checks that should be suppressed. */
 82  
     private static final String DEFAULT_CHECK_FORMAT = ".*";
 83  
 
 84  
     /** Default regex for lines that should be suppressed. */
 85  
     private static final String DEFAULT_INFLUENCE_FORMAT = "0";
 86  
 
 87  
     /** Tagged comments. */
 88  26
     private final List<Tag> tags = new ArrayList<>();
 89  
 
 90  
     /** Whether to look for trigger in C-style comments. */
 91  26
     private boolean checkC = true;
 92  
 
 93  
     /** Whether to look for trigger in C++-style comments. */
 94  
     // -@cs[AbbreviationAsWordInName] We can not change it as,
 95  
     // check's property is a part of API (used in configurations).
 96  26
     private boolean checkCPP = true;
 97  
 
 98  
     /** Parsed comment regexp that marks checkstyle suppression region. */
 99  26
     private Pattern commentFormat = Pattern.compile(DEFAULT_COMMENT_FORMAT);
 100  
 
 101  
     /** The comment pattern that triggers suppression. */
 102  26
     private String checkFormat = DEFAULT_CHECK_FORMAT;
 103  
 
 104  
     /** The message format to suppress. */
 105  
     private String messageFormat;
 106  
 
 107  
     /** The influence of the suppression comment. */
 108  26
     private String influenceFormat = DEFAULT_INFLUENCE_FORMAT;
 109  
 
 110  
     /**
 111  
      * References the current FileContents for this filter.
 112  
      * Since this is a weak reference to the FileContents, the FileContents
 113  
      * can be reclaimed as soon as the strong references in TreeWalker
 114  
      * are reassigned to the next FileContents, at which time filtering for
 115  
      * the current FileContents is finished.
 116  
      */
 117  26
     private WeakReference<FileContents> fileContentsReference = new WeakReference<>(null);
 118  
 
 119  
     /**
 120  
      * Set the format for a comment that turns off reporting.
 121  
      * @param pattern a pattern.
 122  
      */
 123  
     public final void setCommentFormat(Pattern pattern) {
 124  13
         commentFormat = pattern;
 125  13
     }
 126  
 
 127  
     /**
 128  
      * Returns FileContents for this filter.
 129  
      * @return the FileContents for this filter.
 130  
      */
 131  
     private FileContents getFileContents() {
 132  257
         return fileContentsReference.get();
 133  
     }
 134  
 
 135  
     /**
 136  
      * Set the FileContents for this filter.
 137  
      * @param fileContents the FileContents for this filter.
 138  
      * @noinspection WeakerAccess
 139  
      */
 140  
     public void setFileContents(FileContents fileContents) {
 141  14
         fileContentsReference = new WeakReference<>(fileContents);
 142  14
     }
 143  
 
 144  
     /**
 145  
      * Set the format for a check.
 146  
      * @param format a {@code String} value
 147  
      */
 148  
     public final void setCheckFormat(String format) {
 149  14
         checkFormat = format;
 150  14
     }
 151  
 
 152  
     /**
 153  
      * Set the format for a message.
 154  
      * @param format a {@code String} value
 155  
      */
 156  
     public void setMessageFormat(String format) {
 157  3
         messageFormat = format;
 158  3
     }
 159  
 
 160  
     /**
 161  
      * Set the format for the influence of this check.
 162  
      * @param format a {@code String} value
 163  
      */
 164  
     public final void setInfluenceFormat(String format) {
 165  14
         influenceFormat = format;
 166  14
     }
 167  
 
 168  
     /**
 169  
      * Set whether to look in C++ comments.
 170  
      * @param checkCpp {@code true} if C++ comments are checked.
 171  
      */
 172  
     // -@cs[AbbreviationAsWordInName] We can not change it as,
 173  
     // check's property is a part of API (used in configurations).
 174  
     public void setCheckCPP(boolean checkCpp) {
 175  1
         checkCPP = checkCpp;
 176  1
     }
 177  
 
 178  
     /**
 179  
      * Set whether to look in C comments.
 180  
      * @param checkC {@code true} if C comments are checked.
 181  
      */
 182  
     public void setCheckC(boolean checkC) {
 183  1
         this.checkC = checkC;
 184  1
     }
 185  
 
 186  
     @Override
 187  
     protected void finishLocalSetup() throws CheckstyleException {
 188  
         // No code by default
 189  21
     }
 190  
 
 191  
     @Override
 192  
     public boolean accept(TreeWalkerAuditEvent event) {
 193  244
         boolean accepted = true;
 194  
 
 195  244
         if (event.getLocalizedMessage() != null) {
 196  
             // Lazy update. If the first event for the current file, update file
 197  
             // contents and tag suppressions
 198  243
             final FileContents currentContents = event.getFileContents();
 199  
 
 200  243
             if (getFileContents() != currentContents) {
 201  14
                 setFileContents(currentContents);
 202  14
                 tagSuppressions();
 203  
             }
 204  241
             if (matchesTag(event)) {
 205  34
                 accepted = false;
 206  
             }
 207  
         }
 208  242
         return accepted;
 209  
     }
 210  
 
 211  
     /**
 212  
      * Whether current event matches any tag from {@link #tags}.
 213  
      * @param event TreeWalkerAuditEvent to test match on {@link #tags}.
 214  
      * @return true if event matches any tag from {@link #tags}, false otherwise.
 215  
      */
 216  
     private boolean matchesTag(TreeWalkerAuditEvent event) {
 217  241
         boolean result = false;
 218  241
         for (final Tag tag : tags) {
 219  946
             if (tag.isMatch(event)) {
 220  34
                 result = true;
 221  34
                 break;
 222  
             }
 223  912
         }
 224  241
         return result;
 225  
     }
 226  
 
 227  
     /**
 228  
      * Collects all the suppression tags for all comments into a list and
 229  
      * sorts the list.
 230  
      */
 231  
     private void tagSuppressions() {
 232  14
         tags.clear();
 233  14
         final FileContents contents = getFileContents();
 234  14
         if (checkCPP) {
 235  13
             tagSuppressions(contents.getSingleLineComments().values());
 236  
         }
 237  12
         if (checkC) {
 238  11
             final Collection<List<TextBlock>> cComments =
 239  11
                 contents.getBlockComments().values();
 240  11
             cComments.forEach(this::tagSuppressions);
 241  
         }
 242  12
     }
 243  
 
 244  
     /**
 245  
      * Appends the suppressions in a collection of comments to the full
 246  
      * set of suppression tags.
 247  
      * @param comments the set of comments.
 248  
      */
 249  
     private void tagSuppressions(Collection<TextBlock> comments) {
 250  70
         for (final TextBlock comment : comments) {
 251  255
             final int startLineNo = comment.getStartLineNo();
 252  255
             final String[] text = comment.getText();
 253  255
             tagCommentLine(text[0], startLineNo);
 254  286
             for (int i = 1; i < text.length; i++) {
 255  33
                 tagCommentLine(text[i], startLineNo + i);
 256  
             }
 257  253
         }
 258  68
     }
 259  
 
 260  
     /**
 261  
      * Tags a string if it matches the format for turning
 262  
      * checkstyle reporting on or the format for turning reporting off.
 263  
      * @param text the string to tag.
 264  
      * @param line the line number of text.
 265  
      */
 266  
     private void tagCommentLine(String text, int line) {
 267  288
         final Matcher matcher = commentFormat.matcher(text);
 268  288
         if (matcher.find()) {
 269  46
             addTag(matcher.group(0), line);
 270  
         }
 271  286
     }
 272  
 
 273  
     /**
 274  
      * Adds a comment suppression {@code Tag} to the list of all tags.
 275  
      * @param text the text of the tag.
 276  
      * @param line the line number of the tag.
 277  
      */
 278  
     private void addTag(String text, int line) {
 279  46
         final Tag tag = new Tag(text, line, this);
 280  44
         tags.add(tag);
 281  44
     }
 282  
 
 283  
     /**
 284  
      * A Tag holds a suppression comment and its location.
 285  
      */
 286  
     public static class Tag {
 287  
         /** The text of the tag. */
 288  
         private final String text;
 289  
 
 290  
         /** The first line where warnings may be suppressed. */
 291  
         private final int firstLine;
 292  
 
 293  
         /** The last line where warnings may be suppressed. */
 294  
         private final int lastLine;
 295  
 
 296  
         /** The parsed check regexp, expanded for the text of this tag. */
 297  
         private final Pattern tagCheckRegexp;
 298  
 
 299  
         /** The parsed message regexp, expanded for the text of this tag. */
 300  
         private final Pattern tagMessageRegexp;
 301  
 
 302  
         /**
 303  
          * Constructs a tag.
 304  
          * @param text the text of the suppression.
 305  
          * @param line the line number.
 306  
          * @param filter the {@code SuppressWithNearbyCommentFilter} with the context
 307  
          * @throws IllegalArgumentException if unable to parse expanded text.
 308  
          */
 309  47
         public Tag(String text, int line, SuppressWithNearbyCommentFilter filter) {
 310  47
             this.text = text;
 311  
 
 312  
             //Expand regexp for check and message
 313  
             //Does not intern Patterns with Utils.getPattern()
 314  47
             String format = "";
 315  
             try {
 316  94
                 format = CommonUtils.fillTemplateWithStringsByRegexp(
 317  47
                         filter.checkFormat, text, filter.commentFormat);
 318  47
                 tagCheckRegexp = Pattern.compile(format);
 319  46
                 if (filter.messageFormat == null) {
 320  36
                     tagMessageRegexp = null;
 321  
                 }
 322  
                 else {
 323  20
                     format = CommonUtils.fillTemplateWithStringsByRegexp(
 324  10
                             filter.messageFormat, text, filter.commentFormat);
 325  10
                     tagMessageRegexp = Pattern.compile(format);
 326  
                 }
 327  92
                 format = CommonUtils.fillTemplateWithStringsByRegexp(
 328  46
                         filter.influenceFormat, text, filter.commentFormat);
 329  
 
 330  46
                 if (CommonUtils.startsWithChar(format, '+')) {
 331  2
                     format = format.substring(1);
 332  
                 }
 333  46
                 final int influence = parseInfluence(format, filter.influenceFormat, text);
 334  
 
 335  45
                 if (influence >= 1) {
 336  11
                     firstLine = line;
 337  11
                     lastLine = line + influence;
 338  
                 }
 339  
                 else {
 340  34
                     firstLine = line + influence;
 341  34
                     lastLine = line;
 342  
                 }
 343  
             }
 344  1
             catch (final PatternSyntaxException ex) {
 345  1
                 throw new IllegalArgumentException(
 346  
                     "unable to parse expanded comment " + format, ex);
 347  45
             }
 348  45
         }
 349  
 
 350  
         /**
 351  
          * Gets influence from suppress filter influence format param.
 352  
          *
 353  
          * @param format          influence format to parse
 354  
          * @param influenceFormat raw influence format
 355  
          * @param text            text of the suppression
 356  
          * @return parsed influence
 357  
          */
 358  
         private static int parseInfluence(String format, String influenceFormat, String text) {
 359  
             try {
 360  46
                 return Integer.parseInt(format);
 361  
             }
 362  1
             catch (final NumberFormatException ex) {
 363  1
                 throw new IllegalArgumentException("unable to parse influence from '" + text
 364  
                         + "' using " + influenceFormat, ex);
 365  
             }
 366  
         }
 367  
 
 368  
         @Override
 369  
         public boolean equals(Object other) {
 370  102
             if (this == other) {
 371  2
                 return true;
 372  
             }
 373  100
             if (other == null || getClass() != other.getClass()) {
 374  5
                 return false;
 375  
             }
 376  95
             final Tag tag = (Tag) other;
 377  190
             return Objects.equals(firstLine, tag.firstLine)
 378  78
                     && Objects.equals(lastLine, tag.lastLine)
 379  70
                     && Objects.equals(text, tag.text)
 380  61
                     && Objects.equals(tagCheckRegexp, tag.tagCheckRegexp)
 381  52
                     && Objects.equals(tagMessageRegexp, tag.tagMessageRegexp);
 382  
         }
 383  
 
 384  
         @Override
 385  
         public int hashCode() {
 386  30
             return Objects.hash(text, firstLine, lastLine, tagCheckRegexp, tagMessageRegexp);
 387  
         }
 388  
 
 389  
         /**
 390  
          * Determines whether the source of an audit event
 391  
          * matches the text of this tag.
 392  
          * @param event the {@code TreeWalkerAuditEvent} to check.
 393  
          * @return true if the source of event matches the text of this tag.
 394  
          */
 395  
         public boolean isMatch(TreeWalkerAuditEvent event) {
 396  946
             final int line = event.getLine();
 397  946
             boolean match = false;
 398  
 
 399  946
             if (line >= firstLine && line <= lastLine) {
 400  45
                 final Matcher tagMatcher = tagCheckRegexp.matcher(event.getSourceName());
 401  
 
 402  45
                 if (tagMatcher.find()) {
 403  31
                     match = true;
 404  
                 }
 405  14
                 else if (tagMessageRegexp == null) {
 406  6
                     if (event.getModuleId() != null) {
 407  5
                         final Matcher idMatcher = tagCheckRegexp.matcher(event.getModuleId());
 408  5
                         match = idMatcher.find();
 409  5
                     }
 410  
                 }
 411  
                 else {
 412  8
                     final Matcher messageMatcher = tagMessageRegexp.matcher(event.getMessage());
 413  8
                     match = messageMatcher.find();
 414  
                 }
 415  
             }
 416  946
             return match;
 417  
         }
 418  
 
 419  
         @Override
 420  
         public String toString() {
 421  1
             return "Tag[text='" + text + '\''
 422  
                     + ", firstLine=" + firstLine
 423  
                     + ", lastLine=" + lastLine
 424  
                     + ", tagCheckRegexp=" + tagCheckRegexp
 425  
                     + ", tagMessageRegexp=" + tagMessageRegexp
 426  
                     + ']';
 427  
         }
 428  
     }
 429  
 }