001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.utils; 021 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Map; 025import java.util.regex.Pattern; 026 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.DetailNode; 029import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 030import com.puppycrawl.tools.checkstyle.api.TextBlock; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag; 033import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag; 034import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo; 035import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTags; 036import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.BlockTagUtil; 037import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.InlineTagUtil; 038import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.TagInfo; 039 040/** 041 * Contains utility methods for working with Javadoc. 042 */ 043public final class JavadocUtil { 044 045 /** 046 * The type of Javadoc tag we want returned. 047 */ 048 public enum JavadocTagType { 049 050 /** Block type. */ 051 BLOCK, 052 /** Inline type. */ 053 INLINE, 054 /** All validTags. */ 055 ALL, 056 057 } 058 059 /** Maps from a token name to value. */ 060 private static final Map<String, Integer> TOKEN_NAME_TO_VALUE; 061 /** Maps from a token value to name. */ 062 private static final Map<Integer, String> TOKEN_VALUE_TO_NAME; 063 064 /** Exception message for unknown JavaDoc token id. */ 065 private static final String UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE = "Unknown javadoc" 066 + " token id. Given id: "; 067 068 /** Newline pattern. */ 069 private static final Pattern NEWLINE = Pattern.compile("\n"); 070 071 /** Return pattern. */ 072 private static final Pattern RETURN = Pattern.compile("\r"); 073 074 /** Tab pattern. */ 075 private static final Pattern TAB = Pattern.compile("\t"); 076 077 // initialise the constants 078 static { 079 TOKEN_NAME_TO_VALUE = TokenUtil.nameToValueMapFromPublicIntFields(JavadocTokenTypes.class); 080 TOKEN_VALUE_TO_NAME = TokenUtil.invertMap(TOKEN_NAME_TO_VALUE); 081 } 082 083 /** Prevent instantiation. */ 084 private JavadocUtil() { 085 } 086 087 /** 088 * Gets validTags from a given piece of Javadoc. 089 * 090 * @param textBlock 091 * the Javadoc comment to process. 092 * @param tagType 093 * the type of validTags we're interested in 094 * @return all standalone validTags from the given javadoc. 095 */ 096 public static JavadocTags getJavadocTags(TextBlock textBlock, 097 JavadocTagType tagType) { 098 final List<TagInfo> tags = new ArrayList<>(); 099 final boolean isBlockTags = tagType == JavadocTagType.ALL 100 || tagType == JavadocTagType.BLOCK; 101 if (isBlockTags) { 102 tags.addAll(BlockTagUtil.extractBlockTags(textBlock.getText())); 103 } 104 final boolean isInlineTags = tagType == JavadocTagType.ALL 105 || tagType == JavadocTagType.INLINE; 106 if (isInlineTags) { 107 tags.addAll(InlineTagUtil.extractInlineTags(textBlock.getText())); 108 } 109 110 final List<JavadocTag> validTags = new ArrayList<>(); 111 final List<InvalidJavadocTag> invalidTags = new ArrayList<>(); 112 113 for (TagInfo tag : tags) { 114 final int col = tag.getPosition().getColumn(); 115 116 // Add the starting line of the comment to the line number to get the actual line number 117 // in the source. 118 // Lines are one-indexed, so need an off-by-one correction. 119 final int line = textBlock.getStartLineNo() + tag.getPosition().getLine() - 1; 120 121 if (JavadocTagInfo.isValidName(tag.getName())) { 122 validTags.add( 123 new JavadocTag(line, col, tag.getName(), tag.getValue())); 124 } 125 else { 126 invalidTags.add(new InvalidJavadocTag(line, col, tag.getName())); 127 } 128 } 129 130 return new JavadocTags(validTags, invalidTags); 131 } 132 133 /** 134 * Checks that commentContent starts with '*' javadoc comment identifier. 135 * 136 * @param commentContent 137 * content of block comment 138 * @return true if commentContent starts with '*' javadoc comment 139 * identifier. 140 */ 141 public static boolean isJavadocComment(String commentContent) { 142 boolean result = false; 143 144 if (!commentContent.isEmpty()) { 145 final char docCommentIdentifier = commentContent.charAt(0); 146 result = docCommentIdentifier == '*'; 147 } 148 149 return result; 150 } 151 152 /** 153 * Checks block comment content starts with '*' javadoc comment identifier. 154 * 155 * @param blockCommentBegin 156 * block comment AST 157 * @return true if block comment content starts with '*' javadoc comment 158 * identifier. 159 */ 160 public static boolean isJavadocComment(DetailAST blockCommentBegin) { 161 final String commentContent = getBlockCommentContent(blockCommentBegin); 162 return isJavadocComment(commentContent) && isCorrectJavadocPosition(blockCommentBegin); 163 } 164 165 /** 166 * Gets content of block comment. 167 * 168 * @param blockCommentBegin 169 * block comment AST. 170 * @return content of block comment. 171 */ 172 public static String getBlockCommentContent(DetailAST blockCommentBegin) { 173 final DetailAST commentContent = blockCommentBegin.getFirstChild(); 174 return commentContent.getText(); 175 } 176 177 /** 178 * Get content of Javadoc comment. 179 * 180 * @param javadocCommentBegin 181 * Javadoc comment AST 182 * @return content of Javadoc comment. 183 */ 184 public static String getJavadocCommentContent(DetailAST javadocCommentBegin) { 185 final DetailAST commentContent = javadocCommentBegin.getFirstChild(); 186 return commentContent.getText().substring(1); 187 } 188 189 /** 190 * Returns the first child token that has a specified type. 191 * 192 * @param detailNode 193 * Javadoc AST node 194 * @param type 195 * the token type to match 196 * @return the matching token, or null if no match 197 */ 198 public static DetailNode findFirstToken(DetailNode detailNode, int type) { 199 DetailNode returnValue = null; 200 DetailNode node = getFirstChild(detailNode); 201 while (node != null) { 202 if (node.getType() == type) { 203 returnValue = node; 204 break; 205 } 206 node = getNextSibling(node); 207 } 208 return returnValue; 209 } 210 211 /** 212 * Gets first child node of specified node. 213 * 214 * @param node DetailNode 215 * @return first child 216 */ 217 public static DetailNode getFirstChild(DetailNode node) { 218 DetailNode resultNode = null; 219 220 if (node.getChildren().length > 0) { 221 resultNode = node.getChildren()[0]; 222 } 223 return resultNode; 224 } 225 226 /** 227 * Gets next sibling of specified node. 228 * 229 * @param node DetailNode 230 * @return next sibling. 231 */ 232 public static DetailNode getNextSibling(DetailNode node) { 233 DetailNode nextSibling = null; 234 final DetailNode parent = node.getParent(); 235 if (parent != null) { 236 final int nextSiblingIndex = node.getIndex() + 1; 237 final DetailNode[] children = parent.getChildren(); 238 if (nextSiblingIndex <= children.length - 1) { 239 nextSibling = children[nextSiblingIndex]; 240 } 241 } 242 return nextSibling; 243 } 244 245 /** 246 * Gets next sibling of specified node with the specified type. 247 * 248 * @param node DetailNode 249 * @param tokenType javadoc token type 250 * @return next sibling. 251 */ 252 public static DetailNode getNextSibling(DetailNode node, int tokenType) { 253 DetailNode nextSibling = getNextSibling(node); 254 while (nextSibling != null && nextSibling.getType() != tokenType) { 255 nextSibling = getNextSibling(nextSibling); 256 } 257 return nextSibling; 258 } 259 260 /** 261 * Gets previous sibling of specified node. 262 * 263 * @param node DetailNode 264 * @return previous sibling 265 */ 266 public static DetailNode getPreviousSibling(DetailNode node) { 267 DetailNode previousSibling = null; 268 final int previousSiblingIndex = node.getIndex() - 1; 269 if (previousSiblingIndex >= 0) { 270 final DetailNode parent = node.getParent(); 271 final DetailNode[] children = parent.getChildren(); 272 previousSibling = children[previousSiblingIndex]; 273 } 274 return previousSibling; 275 } 276 277 /** 278 * Returns the name of a token for a given ID. 279 * 280 * @param id 281 * the ID of the token name to get 282 * @return a token name 283 * @throws IllegalArgumentException if an unknown token ID was specified. 284 */ 285 public static String getTokenName(int id) { 286 final String name = TOKEN_VALUE_TO_NAME.get(id); 287 if (name == null) { 288 throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id); 289 } 290 return name; 291 } 292 293 /** 294 * Returns the ID of a token for a given name. 295 * 296 * @param name 297 * the name of the token ID to get 298 * @return a token ID 299 * @throws IllegalArgumentException if an unknown token name was specified. 300 */ 301 public static int getTokenId(String name) { 302 final Integer id = TOKEN_NAME_TO_VALUE.get(name); 303 if (id == null) { 304 throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name); 305 } 306 return id; 307 } 308 309 /** 310 * Gets tag name from javadocTagSection. 311 * 312 * @param javadocTagSection to get tag name from. 313 * @return name, of the javadocTagSection's tag. 314 */ 315 public static String getTagName(DetailNode javadocTagSection) { 316 final String javadocTagName; 317 if (javadocTagSection.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) { 318 javadocTagName = getNextSibling( 319 getFirstChild(javadocTagSection)).getText(); 320 } 321 else { 322 javadocTagName = getFirstChild(javadocTagSection).getText(); 323 } 324 return javadocTagName; 325 } 326 327 /** 328 * Replace all control chars with escaped symbols. 329 * 330 * @param text the String to process. 331 * @return the processed String with all control chars escaped. 332 */ 333 public static String escapeAllControlChars(String text) { 334 final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n"); 335 final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r"); 336 return TAB.matcher(textWithoutReturns).replaceAll("\\\\t"); 337 } 338 339 /** 340 * Checks Javadoc comment it's in right place. 341 * 342 * <p>From Javadoc util documentation: 343 * "Placement of comments - Documentation comments are recognized only when placed 344 * immediately before class, interface, constructor, method, field or annotation field 345 * declarations -- see the class example, method example, and field example. 346 * Documentation comments placed in the body of a method are ignored."</p> 347 * 348 * <p>If there are many documentation comments per declaration statement, 349 * only the last one will be recognized.</p> 350 * 351 * @param blockComment Block comment AST 352 * @return true if Javadoc is in right place 353 * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html"> 354 * Javadoc util documentation</a> 355 */ 356 public static boolean isCorrectJavadocPosition(DetailAST blockComment) { 357 // We must be sure that after this one there are no other documentation comments. 358 DetailAST sibling = blockComment.getNextSibling(); 359 while (sibling != null) { 360 if (sibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 361 if (isJavadocComment(getBlockCommentContent(sibling))) { 362 // Found another javadoc comment, so this one should be ignored. 363 break; 364 } 365 sibling = sibling.getNextSibling(); 366 } 367 else if (sibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 368 sibling = sibling.getNextSibling(); 369 } 370 else { 371 // Annotation, declaration or modifier is here. Do not check further. 372 sibling = null; 373 } 374 } 375 return sibling == null 376 && (BlockCommentPosition.isOnType(blockComment) 377 || BlockCommentPosition.isOnMember(blockComment) 378 || BlockCommentPosition.isOnPackage(blockComment)); 379 } 380 381}