Coverage Report - com.puppycrawl.tools.checkstyle.filters.SuppressionCommentFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
SuppressionCommentFilter
100%
71/71
100%
30/30
0
SuppressionCommentFilter$Tag
100%
60/60
100%
32/32
0
SuppressionCommentFilter$TagType
100%
3/3
N/A
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.Collections;
 26  
 import java.util.List;
 27  
 import java.util.Objects;
 28  
 import java.util.regex.Matcher;
 29  
 import java.util.regex.Pattern;
 30  
 import java.util.regex.PatternSyntaxException;
 31  
 
 32  
 import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
 33  
 import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
 34  
 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
 35  
 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
 36  
 import com.puppycrawl.tools.checkstyle.api.FileContents;
 37  
 import com.puppycrawl.tools.checkstyle.api.TextBlock;
 38  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 39  
 
 40  
 /**
 41  
  * <p>
 42  
  * A filter that uses comments to suppress audit events.
 43  
  * </p>
 44  
  * <p>
 45  
  * Rationale:
 46  
  * Sometimes there are legitimate reasons for violating a check.  When
 47  
  * this is a matter of the code in question and not personal
 48  
  * preference, the best place to override the policy is in the code
 49  
  * itself.  Semi-structured comments can be associated with the check.
 50  
  * This is sometimes superior to a separate suppressions file, which
 51  
  * must be kept up-to-date as the source file is edited.
 52  
  * </p>
 53  
  * @author Mike McMahon
 54  
  * @author Rick Giles
 55  
  */
 56  175
 public class SuppressionCommentFilter
 57  
     extends AutomaticBean
 58  
     implements TreeWalkerFilter {
 59  
 
 60  
     /**
 61  
      * Enum to be used for switching checkstyle reporting for tags.
 62  
      */
 63  4
     public enum TagType {
 64  
         /**
 65  
          * Switch reporting on.
 66  
          */
 67  1
         ON,
 68  
         /**
 69  
          * Switch reporting off.
 70  
          */
 71  1
         OFF
 72  
     }
 73  
 
 74  
     /** Turns checkstyle reporting off. */
 75  
     private static final String DEFAULT_OFF_FORMAT = "CHECKSTYLE:OFF";
 76  
 
 77  
     /** Turns checkstyle reporting on. */
 78  
     private static final String DEFAULT_ON_FORMAT = "CHECKSTYLE:ON";
 79  
 
 80  
     /** Control all checks. */
 81  
     private static final String DEFAULT_CHECK_FORMAT = ".*";
 82  
 
 83  
     /** Tagged comments. */
 84  28
     private final List<Tag> tags = new ArrayList<>();
 85  
 
 86  
     /** Whether to look in comments of the C type. */
 87  28
     private boolean checkC = true;
 88  
 
 89  
     /** Whether to look in comments of the C++ type. */
 90  
     // -@cs[AbbreviationAsWordInName] we can not change it as,
 91  
     // Check property is a part of API (used in configurations)
 92  28
     private boolean checkCPP = true;
 93  
 
 94  
     /** Parsed comment regexp that turns checkstyle reporting off. */
 95  28
     private Pattern offCommentFormat = Pattern.compile(DEFAULT_OFF_FORMAT);
 96  
 
 97  
     /** Parsed comment regexp that turns checkstyle reporting on. */
 98  28
     private Pattern onCommentFormat = Pattern.compile(DEFAULT_ON_FORMAT);
 99  
 
 100  
     /** The check format to suppress. */
 101  28
     private String checkFormat = DEFAULT_CHECK_FORMAT;
 102  
 
 103  
     /** The message format to suppress. */
 104  
     private String messageFormat;
 105  
 
 106  
     /**
 107  
      * References the current FileContents for this filter.
 108  
      * Since this is a weak reference to the FileContents, the FileContents
 109  
      * can be reclaimed as soon as the strong references in TreeWalker
 110  
      * are reassigned to the next FileContents, at which time filtering for
 111  
      * the current FileContents is finished.
 112  
      */
 113  28
     private WeakReference<FileContents> fileContentsReference = new WeakReference<>(null);
 114  
 
 115  
     /**
 116  
      * Set the format for a comment that turns off reporting.
 117  
      * @param pattern a pattern.
 118  
      */
 119  
     public final void setOffCommentFormat(Pattern pattern) {
 120  14
         offCommentFormat = pattern;
 121  14
     }
 122  
 
 123  
     /**
 124  
      * Set the format for a comment that turns on reporting.
 125  
      * @param pattern a pattern.
 126  
      */
 127  
     public final void setOnCommentFormat(Pattern pattern) {
 128  13
         onCommentFormat = pattern;
 129  13
     }
 130  
 
 131  
     /**
 132  
      * Returns FileContents for this filter.
 133  
      * @return the FileContents for this filter.
 134  
      */
 135  
     private FileContents getFileContents() {
 136  163
         return fileContentsReference.get();
 137  
     }
 138  
 
 139  
     /**
 140  
      * Set the FileContents for this filter.
 141  
      * @param fileContents the FileContents for this filter.
 142  
      * @noinspection WeakerAccess
 143  
      */
 144  
     public void setFileContents(FileContents fileContents) {
 145  15
         fileContentsReference = new WeakReference<>(fileContents);
 146  15
     }
 147  
 
 148  
     /**
 149  
      * Set the format for a check.
 150  
      * @param format a {@code String} value
 151  
      */
 152  
     public final void setCheckFormat(String format) {
 153  13
         checkFormat = format;
 154  13
     }
 155  
 
 156  
     /**
 157  
      * Set the format for a message.
 158  
      * @param format a {@code String} value
 159  
      */
 160  
     public void setMessageFormat(String format) {
 161  5
         messageFormat = format;
 162  5
     }
 163  
 
 164  
     /**
 165  
      * Set whether to look in C++ comments.
 166  
      * @param checkCpp {@code true} if C++ comments are checked.
 167  
      */
 168  
     // -@cs[AbbreviationAsWordInName] We can not change it as,
 169  
     // check's property is a part of API (used in configurations).
 170  
     public void setCheckCPP(boolean checkCpp) {
 171  2
         checkCPP = checkCpp;
 172  2
     }
 173  
 
 174  
     /**
 175  
      * Set whether to look in C comments.
 176  
      * @param checkC {@code true} if C comments are checked.
 177  
      */
 178  
     public void setCheckC(boolean checkC) {
 179  1
         this.checkC = checkC;
 180  1
     }
 181  
 
 182  
     @Override
 183  
     protected void finishLocalSetup() throws CheckstyleException {
 184  
         // No code by default
 185  22
     }
 186  
 
 187  
     @Override
 188  
     public boolean accept(TreeWalkerAuditEvent event) {
 189  149
         boolean accepted = true;
 190  
 
 191  149
         if (event.getLocalizedMessage() != null) {
 192  
             // Lazy update. If the first event for the current file, update file
 193  
             // contents and tag suppressions
 194  148
             final FileContents currentContents = event.getFileContents();
 195  
 
 196  148
             if (getFileContents() != currentContents) {
 197  15
                 setFileContents(currentContents);
 198  15
                 tagSuppressions();
 199  
             }
 200  146
             final Tag matchTag = findNearestMatch(event);
 201  146
             accepted = matchTag == null || matchTag.getTagType() == TagType.ON;
 202  
         }
 203  147
         return accepted;
 204  
     }
 205  
 
 206  
     /**
 207  
      * Finds the nearest comment text tag that matches an audit event.
 208  
      * The nearest tag is before the line and column of the event.
 209  
      * @param event the {@code TreeWalkerAuditEvent} to match.
 210  
      * @return The {@code Tag} nearest event.
 211  
      */
 212  
     private Tag findNearestMatch(TreeWalkerAuditEvent event) {
 213  146
         Tag result = null;
 214  146
         for (Tag tag : tags) {
 215  366
             if (tag.getLine() > event.getLine()
 216  258
                 || tag.getLine() == event.getLine()
 217  7
                     && tag.getColumn() > event.getColumn()) {
 218  3
                 break;
 219  
             }
 220  255
             if (tag.isMatch(event)) {
 221  185
                 result = tag;
 222  
             }
 223  255
         }
 224  146
         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  15
         tags.clear();
 233  15
         final FileContents contents = getFileContents();
 234  15
         if (checkCPP) {
 235  13
             tagSuppressions(contents.getSingleLineComments().values());
 236  
         }
 237  13
         if (checkC) {
 238  12
             final Collection<List<TextBlock>> cComments = contents
 239  12
                     .getBlockComments().values();
 240  12
             cComments.forEach(this::tagSuppressions);
 241  
         }
 242  13
         Collections.sort(tags);
 243  13
     }
 244  
 
 245  
     /**
 246  
      * Appends the suppressions in a collection of comments to the full
 247  
      * set of suppression tags.
 248  
      * @param comments the set of comments.
 249  
      */
 250  
     private void tagSuppressions(Collection<TextBlock> comments) {
 251  50
         for (TextBlock comment : comments) {
 252  217
             final int startLineNo = comment.getStartLineNo();
 253  217
             final String[] text = comment.getText();
 254  217
             tagCommentLine(text[0], startLineNo, comment.getStartColNo());
 255  250
             for (int i = 1; i < text.length; i++) {
 256  35
                 tagCommentLine(text[i], startLineNo + i, 0);
 257  
             }
 258  215
         }
 259  48
     }
 260  
 
 261  
     /**
 262  
      * Tags a string if it matches the format for turning
 263  
      * checkstyle reporting on or the format for turning reporting off.
 264  
      * @param text the string to tag.
 265  
      * @param line the line number of text.
 266  
      * @param column the column number of text.
 267  
      */
 268  
     private void tagCommentLine(String text, int line, int column) {
 269  252
         final Matcher offMatcher = offCommentFormat.matcher(text);
 270  252
         if (offMatcher.find()) {
 271  23
             addTag(offMatcher.group(0), line, column, TagType.OFF);
 272  
         }
 273  
         else {
 274  229
             final Matcher onMatcher = onCommentFormat.matcher(text);
 275  229
             if (onMatcher.find()) {
 276  22
                 addTag(onMatcher.group(0), line, column, TagType.ON);
 277  
             }
 278  
         }
 279  250
     }
 280  
 
 281  
     /**
 282  
      * Adds a {@code Tag} to the list of all tags.
 283  
      * @param text the text of the tag.
 284  
      * @param line the line number of the tag.
 285  
      * @param column the column number of the tag.
 286  
      * @param reportingOn {@code true} if the tag turns checkstyle reporting on.
 287  
      */
 288  
     private void addTag(String text, int line, int column, TagType reportingOn) {
 289  45
         final Tag tag = new Tag(line, column, text, reportingOn, this);
 290  43
         tags.add(tag);
 291  43
     }
 292  
 
 293  
     /**
 294  
      * A Tag holds a suppression comment and its location, and determines
 295  
      * whether the suppression turns checkstyle reporting on or off.
 296  
      * @author Rick Giles
 297  
      */
 298  57
     public static class Tag
 299  
         implements Comparable<Tag> {
 300  
         /** The text of the tag. */
 301  
         private final String text;
 302  
 
 303  
         /** The line number of the tag. */
 304  
         private final int line;
 305  
 
 306  
         /** The column number of the tag. */
 307  
         private final int column;
 308  
 
 309  
         /** Determines whether the suppression turns checkstyle reporting on. */
 310  
         private final TagType tagType;
 311  
 
 312  
         /** The parsed check regexp, expanded for the text of this tag. */
 313  
         private final Pattern tagCheckRegexp;
 314  
 
 315  
         /** The parsed message regexp, expanded for the text of this tag. */
 316  
         private final Pattern tagMessageRegexp;
 317  
 
 318  
         /**
 319  
          * Constructs a tag.
 320  
          * @param line the line number.
 321  
          * @param column the column number.
 322  
          * @param text the text of the suppression.
 323  
          * @param tagType {@code ON} if the tag turns checkstyle reporting.
 324  
          * @param filter the {@code SuppressionCommentFilter} with the context
 325  
          * @throws IllegalArgumentException if unable to parse expanded text.
 326  
          */
 327  
         public Tag(int line, int column, String text, TagType tagType,
 328  46
                    SuppressionCommentFilter filter) {
 329  46
             this.line = line;
 330  46
             this.column = column;
 331  46
             this.text = text;
 332  46
             this.tagType = tagType;
 333  
 
 334  
             //Expand regexp for check and message
 335  
             //Does not intern Patterns with Utils.getPattern()
 336  46
             String format = "";
 337  
             try {
 338  46
                 if (this.tagType == TagType.ON) {
 339  44
                     format = CommonUtils.fillTemplateWithStringsByRegexp(
 340  22
                             filter.checkFormat, text, filter.onCommentFormat);
 341  22
                     tagCheckRegexp = Pattern.compile(format);
 342  21
                     if (filter.messageFormat == null) {
 343  18
                         tagMessageRegexp = null;
 344  
                     }
 345  
                     else {
 346  6
                         format = CommonUtils.fillTemplateWithStringsByRegexp(
 347  3
                                 filter.messageFormat, text, filter.onCommentFormat);
 348  3
                         tagMessageRegexp = Pattern.compile(format);
 349  
                     }
 350  
                 }
 351  
                 else {
 352  48
                     format = CommonUtils.fillTemplateWithStringsByRegexp(
 353  24
                             filter.checkFormat, text, filter.offCommentFormat);
 354  24
                     tagCheckRegexp = Pattern.compile(format);
 355  24
                     if (filter.messageFormat == null) {
 356  22
                         tagMessageRegexp = null;
 357  
                     }
 358  
                     else {
 359  4
                         format = CommonUtils.fillTemplateWithStringsByRegexp(
 360  2
                                 filter.messageFormat, text, filter.offCommentFormat);
 361  2
                         tagMessageRegexp = Pattern.compile(format);
 362  
                     }
 363  
                 }
 364  
             }
 365  2
             catch (final PatternSyntaxException ex) {
 366  2
                 throw new IllegalArgumentException(
 367  
                     "unable to parse expanded comment " + format, ex);
 368  44
             }
 369  44
         }
 370  
 
 371  
         /**
 372  
          * Returns line number of the tag in the source file.
 373  
          * @return the line number of the tag in the source file.
 374  
          */
 375  
         public int getLine() {
 376  624
             return line;
 377  
         }
 378  
 
 379  
         /**
 380  
          * Determines the column number of the tag in the source file.
 381  
          * Will be 0 for all lines of multiline comment, except the
 382  
          * first line.
 383  
          * @return the column number of the tag in the source file.
 384  
          */
 385  
         public int getColumn() {
 386  7
             return column;
 387  
         }
 388  
 
 389  
         /**
 390  
          * Determines whether the suppression turns checkstyle reporting on or
 391  
          * off.
 392  
          * @return {@code ON} if the suppression turns reporting on.
 393  
          */
 394  
         public TagType getTagType() {
 395  67
             return tagType;
 396  
         }
 397  
 
 398  
         /**
 399  
          * Compares the position of this tag in the file
 400  
          * with the position of another tag.
 401  
          * @param object the tag to compare with this one.
 402  
          * @return a negative number if this tag is before the other tag,
 403  
          *     0 if they are at the same position, and a positive number if this
 404  
          *     tag is after the other tag.
 405  
          */
 406  
         @Override
 407  
         public int compareTo(Tag object) {
 408  
             final int result;
 409  57
             if (line == object.line) {
 410  2
                 result = Integer.compare(column, object.column);
 411  
             }
 412  
             else {
 413  55
                 result = Integer.compare(line, object.line);
 414  
             }
 415  57
             return result;
 416  
         }
 417  
 
 418  
         @Override
 419  
         public boolean equals(Object other) {
 420  121
             if (this == other) {
 421  2
                 return true;
 422  
             }
 423  119
             if (other == null || getClass() != other.getClass()) {
 424  5
                 return false;
 425  
             }
 426  114
             final Tag tag = (Tag) other;
 427  228
             return Objects.equals(line, tag.line)
 428  95
                     && Objects.equals(column, tag.column)
 429  87
                     && Objects.equals(tagType, tag.tagType)
 430  78
                     && Objects.equals(text, tag.text)
 431  69
                     && Objects.equals(tagCheckRegexp, tag.tagCheckRegexp)
 432  60
                     && Objects.equals(tagMessageRegexp, tag.tagMessageRegexp);
 433  
         }
 434  
 
 435  
         @Override
 436  
         public int hashCode() {
 437  35
             return Objects.hash(text, line, column, tagType, tagCheckRegexp, tagMessageRegexp);
 438  
         }
 439  
 
 440  
         /**
 441  
          * Determines whether the source of an audit event
 442  
          * matches the text of this tag.
 443  
          * @param event the {@code TreeWalkerAuditEvent} to check.
 444  
          * @return true if the source of event matches the text of this tag.
 445  
          */
 446  
         public boolean isMatch(TreeWalkerAuditEvent event) {
 447  255
             boolean match = false;
 448  255
             final Matcher tagMatcher = tagCheckRegexp.matcher(event.getSourceName());
 449  255
             if (tagMatcher.find()) {
 450  182
                 if (tagMessageRegexp == null) {
 451  178
                     match = true;
 452  
                 }
 453  
                 else {
 454  4
                     final Matcher messageMatcher = tagMessageRegexp.matcher(event.getMessage());
 455  4
                     match = messageMatcher.find();
 456  4
                 }
 457  
             }
 458  73
             else if (event.getModuleId() != null) {
 459  22
                 final Matcher idMatcher = tagCheckRegexp.matcher(event.getModuleId());
 460  22
                 match = idMatcher.find();
 461  
             }
 462  255
             return match;
 463  
         }
 464  
 
 465  
         @Override
 466  
         public String toString() {
 467  1
             return "Tag[text='" + text + '\''
 468  
                     + ", line=" + line
 469  
                     + ", column=" + column
 470  
                     + ", type=" + tagType
 471  
                     + ", tagCheckRegexp=" + tagCheckRegexp
 472  
                     + ", tagMessageRegexp=" + tagMessageRegexp + ']';
 473  
         }
 474  
     }
 475  
 }