View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.comments;
5   
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.HashSet;
9   import java.util.List;
10  import java.util.Set;
11  
12  import net.sourceforge.pmd.PropertyDescriptor;
13  import net.sourceforge.pmd.PropertySource;
14  import net.sourceforge.pmd.Rule;
15  import net.sourceforge.pmd.RuleContext;
16  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
17  import net.sourceforge.pmd.lang.java.ast.Comment;
18  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
19  import net.sourceforge.pmd.lang.rule.properties.StringMultiProperty;
20  import net.sourceforge.pmd.util.CollectionUtil;
21  import net.sourceforge.pmd.util.StringUtil;
22  
23  /**
24   * A rule that checks for illegal words in the comment text.
25   *
26   * TODO implement regex option
27   *
28   * @author Brian Remedios
29   */
30  public class CommentContentRule extends AbstractCommentRule {
31  
32      private boolean caseSensitive;
33      private boolean wordsAreRegex;
34      private String[] originalBadWords;
35      private String[] currentBadWords;
36  
37      // FIXME need some better defaults (or none?)
38      private static final String[] BAD_WORDS = new String[] { "idiot", "jerk" };
39  
40      public static final BooleanProperty WORDS_ARE_REGEX_DESCRIPTOR = new BooleanProperty("wordsAreRegex",
41              "Use regular expressions", false, 1.0f);
42  
43      // ignored when property above == True
44      public static final BooleanProperty CASE_SENSITIVE_DESCRIPTOR = new BooleanProperty("caseSensitive",
45              "Case sensitive", false, 2.0f);
46  
47      public static final StringMultiProperty DISSALLOWED_TERMS_DESCRIPTOR = new StringMultiProperty("disallowedTerms",
48              "Illegal terms or phrases", BAD_WORDS, 3.0f, '|');
49  
50      private static final Set<PropertyDescriptor<?>> NON_REGEX_PROPERTIES;
51      static {
52          NON_REGEX_PROPERTIES = new HashSet<>(1);
53          NON_REGEX_PROPERTIES.add(CASE_SENSITIVE_DESCRIPTOR);
54      }
55  
56      public CommentContentRule() {
57          definePropertyDescriptor(WORDS_ARE_REGEX_DESCRIPTOR);
58          definePropertyDescriptor(CASE_SENSITIVE_DESCRIPTOR);
59          definePropertyDescriptor(DISSALLOWED_TERMS_DESCRIPTOR);
60      }
61  
62      /**
63       * Capture values and perform all the case-conversions once per run
64       */
65      @Override
66      public void start(RuleContext ctx) {
67          wordsAreRegex = getProperty(WORDS_ARE_REGEX_DESCRIPTOR);
68          originalBadWords = getProperty(DISSALLOWED_TERMS_DESCRIPTOR);
69          caseSensitive = getProperty(CASE_SENSITIVE_DESCRIPTOR);
70          if (caseSensitive) {
71              currentBadWords = originalBadWords;
72          } else {
73              currentBadWords = new String[originalBadWords.length];
74              for (int i = 0; i < currentBadWords.length; i++) {
75                  currentBadWords[i] = originalBadWords[i].toUpperCase();
76              }
77          }
78      }
79  
80      @Override
81      public Set<PropertyDescriptor<?>> ignoredProperties() {
82          return getProperty(WORDS_ARE_REGEX_DESCRIPTOR) ? NON_REGEX_PROPERTIES : Collections.EMPTY_SET;
83      }
84  
85      /**
86       * @see Rule#end(RuleContext)
87       */
88      @Override
89      public void end(RuleContext ctx) {
90          // Override as needed
91      }
92  
93      private List<String> illegalTermsIn(Comment comment) {
94  
95          if (currentBadWords.length == 0) {
96              return Collections.emptyList();
97          }
98  
99          String commentText = filteredCommentIn(comment);
100         if (StringUtil.isEmpty(commentText)) {
101             return Collections.emptyList();
102         }
103 
104         if (!caseSensitive) {
105             commentText = commentText.toUpperCase();
106         }
107 
108         List<String> foundWords = new ArrayList<>();
109 
110         for (int i = 0; i < currentBadWords.length; i++) {
111             if (commentText.indexOf(currentBadWords[i]) >= 0) {
112                 foundWords.add(originalBadWords[i]);
113             }
114         }
115 
116         return foundWords;
117     }
118 
119     private String errorMsgFor(List<String> badWords) {
120         StringBuilder msg = new StringBuilder(this.getMessage()).append(": ");
121         if (badWords.size() == 1) {
122             msg.append("Invalid term: '").append(badWords.get(0)).append('\'');
123         } else {
124             msg.append("Invalid terms: '");
125             msg.append(badWords.get(0));
126             for (int i = 1; i < badWords.size(); i++) {
127                 msg.append("', '").append(badWords.get(i));
128             }
129             msg.append('\'');
130         }
131         return msg.toString();
132     }
133 
134     @Override
135     public Object visit(ASTCompilationUnit cUnit, Object data) {
136 
137         // NPE patch: Eclipse plugin doesn't call start() at onset?
138         if (currentBadWords == null) {
139             start(null);
140         }
141 
142         for (Comment comment : cUnit.getComments()) {
143             List<String> badWords = illegalTermsIn(comment);
144             if (badWords.isEmpty()) {
145                 continue;
146             }
147 
148             addViolationWithMessage(data, cUnit, errorMsgFor(badWords), comment.getBeginLine(), comment.getEndLine());
149         }
150 
151         return super.visit(cUnit, data);
152     }
153 
154     public boolean hasDissallowedTerms() {
155         String[] terms = getProperty(DISSALLOWED_TERMS_DESCRIPTOR);
156         return CollectionUtil.isNotEmpty(terms);
157     }
158 
159     /**
160      * @see PropertySource#dysfunctionReason()
161      */
162     @Override
163     public String dysfunctionReason() {
164         return hasDissallowedTerms() ? null : "No disallowed terms specified";
165     }
166 }