1 package com.puppycrawl.tools.checkstyle.checks.javadoc; 20 21 import java.util.Stack ; 22 import java.util.List ; 23 import java.util.regex.Pattern ; 24 import java.util.regex.PatternSyntaxException ; 25 26 import com.puppycrawl.tools.checkstyle.api.Check; 27 import com.puppycrawl.tools.checkstyle.api.DetailAST; 28 import com.puppycrawl.tools.checkstyle.api.FileContents; 29 import com.puppycrawl.tools.checkstyle.api.Scope; 30 import com.puppycrawl.tools.checkstyle.api.ScopeUtils; 31 import com.puppycrawl.tools.checkstyle.api.TextBlock; 32 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 33 import com.puppycrawl.tools.checkstyle.checks.CheckUtils; 34 35 42 public class JavadocStyleCheck 43 extends Check 44 { 45 46 private static final String UNCLOSED_HTML = "javadoc.unclosedhtml"; 47 48 49 private static final String EXTRA_HTML = "javadoc.extrahtml"; 50 51 52 private static final String [] SINGLE_TAG = 53 {"p", "br", "li", "dt", "dd", "td", "hr", "img", "tr", "th", "td"}; 54 55 56 private Scope mScope = Scope.PRIVATE; 57 58 59 private Scope mExcludeScope; 60 61 62 private Pattern mEndOfSentencePattern; 63 64 68 private boolean mCheckFirstSentence = true; 69 70 73 private boolean mCheckHtml = true; 74 75 78 private boolean mCheckEmptyJavadoc; 79 80 81 public int[] getDefaultTokens() 82 { 83 return new int[] { 84 TokenTypes.INTERFACE_DEF, 85 TokenTypes.CLASS_DEF, 86 TokenTypes.ANNOTATION_DEF, 87 TokenTypes.ENUM_DEF, 88 TokenTypes.METHOD_DEF, 89 TokenTypes.CTOR_DEF, 90 TokenTypes.VARIABLE_DEF, 91 TokenTypes.ENUM_CONSTANT_DEF, 92 TokenTypes.ANNOTATION_FIELD_DEF, 93 }; 94 } 95 96 97 public void visitToken(DetailAST aAST) 98 { 99 if (shouldCheck(aAST)) { 100 final FileContents contents = getFileContents(); 101 final TextBlock cmt = 102 contents.getJavadocBefore(aAST.getLineNo()); 103 104 checkComment(aAST, cmt); 105 } 106 } 107 108 113 private boolean shouldCheck(final DetailAST aAST) 114 { 115 if (ScopeUtils.inCodeBlock(aAST)) { 116 return false; 117 } 118 119 final Scope declaredScope; 120 if (aAST.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 121 declaredScope = Scope.PUBLIC; 122 } 123 else { 124 declaredScope = ScopeUtils.getScopeFromMods( 125 aAST.findFirstToken(TokenTypes.MODIFIERS)); 126 } 127 128 final Scope scope = 129 ScopeUtils.inInterfaceOrAnnotationBlock(aAST) 130 ? Scope.PUBLIC : declaredScope; 131 final Scope surroundingScope = ScopeUtils.getSurroundingScope(aAST); 132 133 return scope.isIn(mScope) 134 && ((surroundingScope == null) || surroundingScope.isIn(mScope)) 135 && ((mExcludeScope == null) 136 || !scope.isIn(mExcludeScope) 137 || ((surroundingScope != null) 138 && !surroundingScope.isIn(mExcludeScope))); 139 } 140 141 150 private void checkComment(final DetailAST aAST, final TextBlock aComment) 151 { 152 if (aComment == null) { 153 return; 154 } 155 156 if (mCheckFirstSentence) { 157 checkFirstSentence(aComment); 158 } 159 160 if (mCheckHtml) { 161 checkHtml(aAST, aComment); 162 } 163 164 if (mCheckEmptyJavadoc) { 165 checkEmptyJavadoc(aComment); 166 } 167 } 168 169 178 private void checkFirstSentence(TextBlock aComment) 179 { 180 final String commentText = getCommentText(aComment.getText()); 181 182 if ((commentText.length() != 0) 183 && !getEndOfSentencePattern().matcher(commentText).find() 184 && !"{@inheritDoc}".equals(commentText)) 185 { 186 log(aComment.getStartLineNo(), "javadoc.noperiod"); 187 } 188 } 189 190 195 private void checkEmptyJavadoc(TextBlock aComment) 196 { 197 final String commentText = getCommentText(aComment.getText()); 198 199 if (commentText.length() == 0) { 200 log(aComment.getStartLineNo(), "javadoc.empty"); 201 } 202 } 203 204 209 private String getCommentText(String [] aComments) 210 { 211 final StringBuffer buffer = new StringBuffer (); 212 for (int i = 0; i < aComments.length; i++) { 213 final String line = aComments[i]; 214 final int textStart = findTextStart(line); 215 216 if (textStart != -1) { 217 if (line.charAt(textStart) == '@') { 218 break; 220 } 221 buffer.append(line.substring(textStart)); 222 trimTail(buffer); 223 buffer.append('\n'); 224 } 225 } 226 227 return buffer.toString().trim(); 228 } 229 230 238 private int findTextStart(String aLine) 239 { 240 int textStart = -1; 241 for (int i = 0; i < aLine.length(); i++) { 242 if (!Character.isWhitespace(aLine.charAt(i))) { 243 if (aLine.regionMatches(i, "/**", 0, "/**".length())) { 244 i += 2; 245 } 246 else if (aLine.regionMatches(i, "*/", 0, 2)) { 247 i++; 248 } 249 else if (aLine.charAt(i) != '*') { 250 textStart = i; 251 break; 252 } 253 } 254 } 255 return textStart; 256 } 257 258 262 private void trimTail(StringBuffer aBuffer) 263 { 264 for (int i = aBuffer.length() - 1; i >= 0; i--) { 265 if (Character.isWhitespace(aBuffer.charAt(i))) { 266 aBuffer.deleteCharAt(i); 267 } 268 else if ((i > 0) 269 && (aBuffer.charAt(i - 1) == '*') 270 && (aBuffer.charAt(i) == '/')) 271 { 272 aBuffer.deleteCharAt(i); 273 aBuffer.deleteCharAt(i - 1); 274 i--; 275 while (aBuffer.charAt(i - 1) == '*') { 276 aBuffer.deleteCharAt(i - 1); 277 i--; 278 } 279 } 280 else { 281 break; 282 } 283 } 284 } 285 286 295 private void checkHtml(final DetailAST aAST, final TextBlock aComment) 296 { 297 final int lineno = aComment.getStartLineNo(); 298 final Stack htmlStack = new Stack (); 299 final String [] text = aComment.getText(); 300 final List typeParameters = 301 CheckUtils.getTypeParameterNames(aAST); 302 303 TagParser parser = null; 304 parser = new TagParser(text, lineno); 305 306 while (parser.hasNextTag()) { 307 final HtmlTag tag = parser.nextTag(); 308 309 if (tag.isIncompleteTag()) { 310 log(tag.getLineno(), "javadoc.incompleteTag", 311 new Object [] {text[tag.getLineno() - lineno]}); 312 return; 313 } 314 if (tag.isClosedTag()) { 315 continue; 317 } 318 if (!tag.isCloseTag()) { 319 htmlStack.push(tag); 320 } 321 else { 322 if (isExtraHtml(tag.getId(), htmlStack)) { 324 log(tag.getLineno(), 326 tag.getPosition(), 327 EXTRA_HTML, 328 tag); 329 } 330 else { 331 checkUnclosedTags(htmlStack, tag.getId()); 334 } 335 } 336 } 337 338 String lastFound = ""; for (int i = 0; i < htmlStack.size(); i++) { 341 final HtmlTag htag = (HtmlTag) htmlStack.elementAt(i); 342 if (!isSingleTag(htag) 343 && !htag.getId().equals(lastFound) 344 && !typeParameters.contains(htag.getId())) 345 { 346 log(htag.getLineno(), htag.getPosition(), UNCLOSED_HTML, htag); 347 lastFound = htag.getId(); 348 } 349 } 350 } 351 352 361 private void checkUnclosedTags(Stack aHtmlStack, String aToken) 362 { 363 final Stack unclosedTags = new Stack (); 364 HtmlTag lastOpenTag = (HtmlTag) aHtmlStack.pop(); 365 while (!aToken.equalsIgnoreCase(lastOpenTag.getId())) { 366 if (isSingleTag(lastOpenTag)) { 369 lastOpenTag = (HtmlTag) aHtmlStack.pop(); 370 } 371 else { 372 unclosedTags.push(lastOpenTag); 373 lastOpenTag = (HtmlTag) aHtmlStack.pop(); 374 } 375 } 376 377 String lastFound = ""; for (int i = 0; i < unclosedTags.size(); i++) { 380 lastOpenTag = (HtmlTag) unclosedTags.get(i); 381 if (lastOpenTag.getId().equals(lastFound)) { 382 continue; 383 } 384 lastFound = lastOpenTag.getId(); 385 log(lastOpenTag.getLineno(), 386 lastOpenTag.getPosition(), 387 UNCLOSED_HTML, 388 lastOpenTag); 389 } 390 } 391 392 398 private boolean isSingleTag(HtmlTag aTag) 399 { 400 boolean isSingleTag = false; 401 for (int i = 0; i < SINGLE_TAG.length; i++) { 402 if (aTag.getId().equalsIgnoreCase(SINGLE_TAG[i])) { 407 isSingleTag = true; 408 } 409 } 410 return isSingleTag; 411 } 412 413 422 private boolean isExtraHtml(String aToken, Stack aHtmlStack) 423 { 424 boolean isExtra = true; 425 for (int i = 0; i < aHtmlStack.size(); i++) { 426 final HtmlTag td = (HtmlTag) aHtmlStack.elementAt(i); 431 if (aToken.equalsIgnoreCase(td.getId())) { 432 isExtra = false; 433 break; 434 } 435 } 436 437 return isExtra; 438 } 439 440 444 public void setScope(String aFrom) 445 { 446 mScope = Scope.getInstance(aFrom); 447 } 448 449 453 public void setExcludeScope(String aScope) 454 { 455 mExcludeScope = Scope.getInstance(aScope); 456 } 457 458 463 private Pattern getEndOfSentencePattern() 464 { 465 if (mEndOfSentencePattern == null) { 466 try { 467 mEndOfSentencePattern = 468 Pattern.compile("([.?!][ \t\n\r\f<])|([.?!]$)"); 469 } 470 catch (final PatternSyntaxException e) { 471 e.printStackTrace(); 473 } 474 } 475 return mEndOfSentencePattern; 476 } 477 478 483 public void setCheckFirstSentence(boolean aFlag) 484 { 485 mCheckFirstSentence = aFlag; 486 } 487 488 492 public void setCheckHtml(boolean aFlag) 493 { 494 mCheckHtml = aFlag; 495 } 496 497 501 public void setCheckEmptyJavadoc(boolean aFlag) 502 { 503 mCheckEmptyJavadoc = aFlag; 504 } 505 } 506 | Popular Tags |