001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import java.util.Arrays; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Locale; 026import java.util.Map; 027import java.util.Set; 028import java.util.stream.Collectors; 029 030import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser; 031import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseErrorMessage; 032import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseStatus; 033import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 034import com.puppycrawl.tools.checkstyle.api.DetailAST; 035import com.puppycrawl.tools.checkstyle.api.DetailNode; 036import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 037import com.puppycrawl.tools.checkstyle.api.LineColumn; 038import com.puppycrawl.tools.checkstyle.api.TokenTypes; 039import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 040import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 041 042/** 043 * Base class for Checks that process Javadoc comments. 044 * 045 * @noinspection NoopMethodInAbstractClass 046 * @noinspectionreason NoopMethodInAbstractClass - we allow each 047 * check to define these methods, as needed. They 048 * should be overridden only by demand in subclasses 049 */ 050public abstract class AbstractJavadocCheck extends AbstractCheck { 051 052 /** 053 * Message key of error message. Missed close HTML tag breaks structure 054 * of parse tree, so parser stops parsing and generates such error 055 * message. This case is special because parser prints error like 056 * {@code "no viable alternative at input 'b \n *\n'"} and it is not 057 * clear that error is about missed close HTML tag. 058 */ 059 public static final String MSG_JAVADOC_MISSED_HTML_CLOSE = 060 JavadocDetailNodeParser.MSG_JAVADOC_MISSED_HTML_CLOSE; 061 062 /** 063 * Message key of error message. 064 */ 065 public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG = 066 JavadocDetailNodeParser.MSG_JAVADOC_WRONG_SINGLETON_TAG; 067 068 /** 069 * Parse error while rule recognition. 070 */ 071 public static final String MSG_JAVADOC_PARSE_RULE_ERROR = 072 JavadocDetailNodeParser.MSG_JAVADOC_PARSE_RULE_ERROR; 073 074 /** 075 * Message key of error message. 076 */ 077 public static final String MSG_KEY_UNCLOSED_HTML_TAG = 078 JavadocDetailNodeParser.MSG_UNCLOSED_HTML_TAG; 079 080 /** 081 * Key is "line:column". Value is {@link DetailNode} tree. Map is stored in {@link ThreadLocal} 082 * to guarantee basic thread safety and avoid shared, mutable state when not necessary. 083 */ 084 private static final ThreadLocal<Map<LineColumn, ParseStatus>> TREE_CACHE = 085 ThreadLocal.withInitial(HashMap::new); 086 087 /** 088 * The file context. 089 * 090 * @noinspection ThreadLocalNotStaticFinal 091 * @noinspectionreason ThreadLocalNotStaticFinal - static context is 092 * problematic for multithreading 093 */ 094 private final ThreadLocal<FileContext> context = ThreadLocal.withInitial(FileContext::new); 095 096 /** The javadoc tokens the check is interested in. */ 097 private final Set<Integer> javadocTokens = new HashSet<>(); 098 099 /** 100 * This property determines if a check should log a violation upon encountering javadoc with 101 * non-tight html. The default return value for this method is set to false since checks 102 * generally tend to be fine with non-tight html. It can be set through config file if a check 103 * is to log violation upon encountering non-tight HTML in javadoc. 104 * 105 * @see ParseStatus#isNonTight() 106 * @see <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules"> 107 * Tight HTML rules</a> 108 */ 109 private boolean violateExecutionOnNonTightHtml; 110 111 /** 112 * Returns the default javadoc token types a check is interested in. 113 * 114 * @return the default javadoc token types 115 * @see JavadocTokenTypes 116 */ 117 public abstract int[] getDefaultJavadocTokens(); 118 119 /** 120 * Called to process a Javadoc token. 121 * 122 * @param ast 123 * the token to process 124 */ 125 public abstract void visitJavadocToken(DetailNode ast); 126 127 /** 128 * The configurable javadoc token set. 129 * Used to protect Checks against malicious users who specify an 130 * unacceptable javadoc token set in the configuration file. 131 * The default implementation returns the check's default javadoc tokens. 132 * 133 * @return the javadoc token set this check is designed for. 134 * @see JavadocTokenTypes 135 */ 136 public int[] getAcceptableJavadocTokens() { 137 final int[] defaultJavadocTokens = getDefaultJavadocTokens(); 138 final int[] copy = new int[defaultJavadocTokens.length]; 139 System.arraycopy(defaultJavadocTokens, 0, copy, 0, defaultJavadocTokens.length); 140 return copy; 141 } 142 143 /** 144 * The javadoc tokens that this check must be registered for. 145 * 146 * @return the javadoc token set this must be registered for. 147 * @see JavadocTokenTypes 148 */ 149 public int[] getRequiredJavadocTokens() { 150 return CommonUtil.EMPTY_INT_ARRAY; 151 } 152 153 /** 154 * This method determines if a check should process javadoc containing non-tight html tags. 155 * This method must be overridden in checks extending {@code AbstractJavadocCheck} which 156 * are not supposed to process javadoc containing non-tight html tags. 157 * 158 * @return true if the check should or can process javadoc containing non-tight html tags; 159 * false otherwise 160 * @see ParseStatus#isNonTight() 161 * @see <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules"> 162 * Tight HTML rules</a> 163 */ 164 public boolean acceptJavadocWithNonTightHtml() { 165 return true; 166 } 167 168 /** 169 * Setter to control when to print violations if the Javadoc being examined by this check 170 * violates the tight html rules defined at 171 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules"> 172 * Tight-HTML Rules</a>. 173 * 174 * @param shouldReportViolation value to which the field shall be set to 175 */ 176 public final void setViolateExecutionOnNonTightHtml(boolean shouldReportViolation) { 177 violateExecutionOnNonTightHtml = shouldReportViolation; 178 } 179 180 /** 181 * Adds a set of tokens the check is interested in. 182 * 183 * @param strRep the string representation of the tokens interested in 184 */ 185 public final void setJavadocTokens(String... strRep) { 186 for (String str : strRep) { 187 javadocTokens.add(JavadocUtil.getTokenId(str)); 188 } 189 } 190 191 @Override 192 public void init() { 193 validateDefaultJavadocTokens(); 194 if (javadocTokens.isEmpty()) { 195 javadocTokens.addAll( 196 Arrays.stream(getDefaultJavadocTokens()).boxed().collect(Collectors.toList())); 197 } 198 else { 199 final int[] acceptableJavadocTokens = getAcceptableJavadocTokens(); 200 Arrays.sort(acceptableJavadocTokens); 201 for (Integer javadocTokenId : javadocTokens) { 202 if (Arrays.binarySearch(acceptableJavadocTokens, javadocTokenId) < 0) { 203 final String message = String.format(Locale.ROOT, "Javadoc Token \"%s\" was " 204 + "not found in Acceptable javadoc tokens list in check %s", 205 JavadocUtil.getTokenName(javadocTokenId), getClass().getName()); 206 throw new IllegalStateException(message); 207 } 208 } 209 } 210 } 211 212 /** 213 * Validates that check's required javadoc tokens are subset of default javadoc tokens. 214 * 215 * @throws IllegalStateException when validation of default javadoc tokens fails 216 */ 217 private void validateDefaultJavadocTokens() { 218 if (getRequiredJavadocTokens().length != 0) { 219 final int[] defaultJavadocTokens = getDefaultJavadocTokens(); 220 Arrays.sort(defaultJavadocTokens); 221 for (final int javadocToken : getRequiredJavadocTokens()) { 222 if (Arrays.binarySearch(defaultJavadocTokens, javadocToken) < 0) { 223 final String message = String.format(Locale.ROOT, 224 "Javadoc Token \"%s\" from required javadoc " 225 + "tokens was not found in default " 226 + "javadoc tokens list in check %s", 227 javadocToken, getClass().getName()); 228 throw new IllegalStateException(message); 229 } 230 } 231 } 232 } 233 234 /** 235 * Called before the starting to process a tree. 236 * 237 * @param rootAst 238 * the root of the tree 239 * @noinspection WeakerAccess 240 * @noinspectionreason WeakerAccess - we avoid 'protected' when possible 241 */ 242 public void beginJavadocTree(DetailNode rootAst) { 243 // No code by default, should be overridden only by demand at subclasses 244 } 245 246 /** 247 * Called after finished processing a tree. 248 * 249 * @param rootAst 250 * the root of the tree 251 * @noinspection WeakerAccess 252 * @noinspectionreason WeakerAccess - we avoid 'protected' when possible 253 */ 254 public void finishJavadocTree(DetailNode rootAst) { 255 // No code by default, should be overridden only by demand at subclasses 256 } 257 258 /** 259 * Called after all the child nodes have been process. 260 * 261 * @param ast 262 * the token leaving 263 */ 264 public void leaveJavadocToken(DetailNode ast) { 265 // No code by default, should be overridden only by demand at subclasses 266 } 267 268 /** 269 * Defined final to not allow JavadocChecks to change default tokens. 270 * 271 * @return default tokens 272 */ 273 @Override 274 public final int[] getDefaultTokens() { 275 return getRequiredTokens(); 276 } 277 278 @Override 279 public final int[] getAcceptableTokens() { 280 return getRequiredTokens(); 281 } 282 283 @Override 284 public final int[] getRequiredTokens() { 285 return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN }; 286 } 287 288 /** 289 * Defined final because all JavadocChecks require comment nodes. 290 * 291 * @return true 292 */ 293 @Override 294 public final boolean isCommentNodesRequired() { 295 return true; 296 } 297 298 @Override 299 public final void beginTree(DetailAST rootAST) { 300 TREE_CACHE.get().clear(); 301 } 302 303 @Override 304 public final void finishTree(DetailAST rootAST) { 305 // No code, prevent override in subclasses 306 } 307 308 @Override 309 public final void visitToken(DetailAST blockCommentNode) { 310 if (JavadocUtil.isJavadocComment(blockCommentNode)) { 311 // store as field, to share with child Checks 312 context.get().blockCommentAst = blockCommentNode; 313 314 final LineColumn treeCacheKey = new LineColumn(blockCommentNode.getLineNo(), 315 blockCommentNode.getColumnNo()); 316 317 final ParseStatus result = TREE_CACHE.get().computeIfAbsent(treeCacheKey, key -> { 318 return context.get().parser.parseJavadocAsDetailNode(blockCommentNode); 319 }); 320 321 if (result.getParseErrorMessage() == null) { 322 if (acceptJavadocWithNonTightHtml() || !result.isNonTight()) { 323 processTree(result.getTree()); 324 } 325 326 if (violateExecutionOnNonTightHtml && result.isNonTight()) { 327 log(result.getFirstNonTightHtmlTag().getLine(), 328 MSG_KEY_UNCLOSED_HTML_TAG, 329 result.getFirstNonTightHtmlTag().getText()); 330 } 331 } 332 else { 333 final ParseErrorMessage parseErrorMessage = result.getParseErrorMessage(); 334 log(parseErrorMessage.getLineNumber(), 335 parseErrorMessage.getMessageKey(), 336 parseErrorMessage.getMessageArguments()); 337 } 338 } 339 } 340 341 /** 342 * Getter for block comment in Java language syntax tree. 343 * 344 * @return A block comment in the syntax tree. 345 */ 346 protected DetailAST getBlockCommentAst() { 347 return context.get().blockCommentAst; 348 } 349 350 /** 351 * Processes JavadocAST tree notifying Check. 352 * 353 * @param root 354 * root of JavadocAST tree. 355 */ 356 private void processTree(DetailNode root) { 357 beginJavadocTree(root); 358 walk(root); 359 finishJavadocTree(root); 360 } 361 362 /** 363 * Processes a node calling Check at interested nodes. 364 * 365 * @param root 366 * the root of tree for process 367 */ 368 private void walk(DetailNode root) { 369 DetailNode curNode = root; 370 while (curNode != null) { 371 boolean waitsForProcessing = shouldBeProcessed(curNode); 372 373 if (waitsForProcessing) { 374 visitJavadocToken(curNode); 375 } 376 DetailNode toVisit = JavadocUtil.getFirstChild(curNode); 377 while (curNode != null && toVisit == null) { 378 if (waitsForProcessing) { 379 leaveJavadocToken(curNode); 380 } 381 382 toVisit = JavadocUtil.getNextSibling(curNode); 383 if (toVisit == null) { 384 curNode = curNode.getParent(); 385 if (curNode != null) { 386 waitsForProcessing = shouldBeProcessed(curNode); 387 } 388 } 389 } 390 curNode = toVisit; 391 } 392 } 393 394 /** 395 * Checks whether the current node should be processed by the check. 396 * 397 * @param curNode current node. 398 * @return true if the current node should be processed by the check. 399 */ 400 private boolean shouldBeProcessed(DetailNode curNode) { 401 return javadocTokens.contains(curNode.getType()); 402 } 403 404 @Override 405 public void destroy() { 406 super.destroy(); 407 context.remove(); 408 TREE_CACHE.remove(); 409 } 410 411 /** 412 * The file context holder. 413 */ 414 private static final class FileContext { 415 416 /** 417 * Parses content of Javadoc comment as DetailNode tree. 418 */ 419 private final JavadocDetailNodeParser parser = new JavadocDetailNodeParser(); 420 421 /** 422 * DetailAST node of considered Javadoc comment that is just a block comment 423 * in Java language syntax tree. 424 */ 425 private DetailAST blockCommentAst; 426 427 } 428 429}