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.checks.javadoc; 021 022import java.util.Arrays; 023import java.util.BitSet; 024import java.util.Map; 025import java.util.function.Function; 026import java.util.stream.Collectors; 027 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.Scope; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 032import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 033 034/** 035 * This enum defines the various Javadoc tags and their properties. 036 * 037 * <p> 038 * This class was modeled after documentation located at 039 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html"> 040 * javadoc</a> 041 * and 042 * <a href="https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html"> 043 * how to write</a>. 044 * </p> 045 * 046 * <p> 047 * Some of this documentation was a little incomplete (ex: valid placement of 048 * code, value, and literal tags). 049 * </p> 050 * 051 * <p> 052 * Whenever an inconsistency was found the author's judgment was used. 053 * </p> 054 * 055 * <p> 056 * For now, the number of required/optional tag arguments are not included 057 * because some Javadoc tags have very complex rules for determining this 058 * (ex: {@code {@value}} tag). 059 * </p> 060 * 061 * <p> 062 * Also, the {@link #isValidOn(DetailAST) isValidOn} method does not consider 063 * classes defined in a local code block (method, init block, etc.). 064 * </p> 065 * 066 */ 067public enum JavadocTagInfo { 068 069 /** 070 * {@code @author}. 071 */ 072 AUTHOR("@author", "author", Type.BLOCK) { 073 074 @Override 075 public boolean isValidOn(final DetailAST ast) { 076 final int astType = ast.getType(); 077 return astType == TokenTypes.PACKAGE_DEF 078 || TokenUtil.isTypeDeclaration(astType); 079 } 080 081 }, 082 083 /** 084 * {@code {@code}}. 085 */ 086 CODE("{@code}", "code", Type.INLINE) { 087 088 @Override 089 public boolean isValidOn(final DetailAST ast) { 090 final int astType = ast.getType(); 091 return DEF_TOKEN_TYPES.get(astType) 092 && !ScopeUtil.isLocalVariableDef(ast); 093 } 094 095 }, 096 097 /** 098 * {@code {@docRoot}}. 099 */ 100 DOC_ROOT("{@docRoot}", "docRoot", Type.INLINE) { 101 102 @Override 103 public boolean isValidOn(final DetailAST ast) { 104 final int astType = ast.getType(); 105 return DEF_TOKEN_TYPES.get(astType) 106 && !ScopeUtil.isLocalVariableDef(ast); 107 } 108 109 }, 110 111 /** 112 * {@code @deprecated}. 113 */ 114 DEPRECATED("@deprecated", "deprecated", Type.BLOCK) { 115 116 @Override 117 public boolean isValidOn(final DetailAST ast) { 118 final int astType = ast.getType(); 119 return DEF_TOKEN_TYPES_DEPRECATED.get(astType) 120 && !ScopeUtil.isLocalVariableDef(ast); 121 } 122 123 }, 124 125 /** 126 * {@code @exception}. 127 */ 128 EXCEPTION("@exception", "exception", Type.BLOCK) { 129 130 @Override 131 public boolean isValidOn(final DetailAST ast) { 132 final int astType = ast.getType(); 133 return astType == TokenTypes.METHOD_DEF || astType == TokenTypes.CTOR_DEF; 134 } 135 136 }, 137 138 /** 139 * {@code {@inheritDoc}}. 140 */ 141 INHERIT_DOC("{@inheritDoc}", "inheritDoc", Type.INLINE) { 142 143 @Override 144 public boolean isValidOn(final DetailAST ast) { 145 final int astType = ast.getType(); 146 147 return astType == TokenTypes.METHOD_DEF 148 && ast.findFirstToken(TokenTypes.MODIFIERS) 149 .findFirstToken(TokenTypes.LITERAL_STATIC) == null 150 && ScopeUtil.getScope(ast) != Scope.PRIVATE; 151 } 152 153 }, 154 155 /** 156 * {@code {@link}}. 157 */ 158 LINK("{@link}", "link", Type.INLINE) { 159 160 @Override 161 public boolean isValidOn(final DetailAST ast) { 162 final int astType = ast.getType(); 163 return DEF_TOKEN_TYPES.get(astType) 164 && !ScopeUtil.isLocalVariableDef(ast); 165 } 166 167 }, 168 169 /** 170 * {@code {@linkplain}}. 171 */ 172 LINKPLAIN("{@linkplain}", "linkplain", Type.INLINE) { 173 174 @Override 175 public boolean isValidOn(final DetailAST ast) { 176 final int astType = ast.getType(); 177 return DEF_TOKEN_TYPES.get(astType) 178 && !ScopeUtil.isLocalVariableDef(ast); 179 } 180 181 }, 182 183 /** 184 * {@code {@literal}}. 185 */ 186 LITERAL("{@literal}", "literal", Type.INLINE) { 187 188 @Override 189 public boolean isValidOn(final DetailAST ast) { 190 final int astType = ast.getType(); 191 return DEF_TOKEN_TYPES.get(astType) 192 && !ScopeUtil.isLocalVariableDef(ast); 193 } 194 195 }, 196 197 /** 198 * {@code @param}. 199 */ 200 PARAM("@param", "param", Type.BLOCK) { 201 202 @Override 203 public boolean isValidOn(final DetailAST ast) { 204 final int astType = ast.getType(); 205 return astType == TokenTypes.CLASS_DEF 206 || astType == TokenTypes.INTERFACE_DEF 207 || astType == TokenTypes.METHOD_DEF 208 || astType == TokenTypes.CTOR_DEF; 209 } 210 211 }, 212 213 /** 214 * {@code @return}. 215 */ 216 RETURN("@return", "return", Type.BLOCK) { 217 218 @Override 219 public boolean isValidOn(final DetailAST ast) { 220 final int astType = ast.getType(); 221 final DetailAST returnType = ast.findFirstToken(TokenTypes.TYPE); 222 223 return astType == TokenTypes.METHOD_DEF 224 && returnType.getFirstChild().getType() != TokenTypes.LITERAL_VOID; 225 } 226 227 }, 228 229 /** 230 * {@code @see}. 231 */ 232 SEE("@see", "see", Type.BLOCK) { 233 234 @Override 235 public boolean isValidOn(final DetailAST ast) { 236 final int astType = ast.getType(); 237 return DEF_TOKEN_TYPES.get(astType) 238 && !ScopeUtil.isLocalVariableDef(ast); 239 } 240 241 }, 242 243 /** 244 * {@code @serial}. 245 */ 246 SERIAL("@serial", "serial", Type.BLOCK) { 247 248 @Override 249 public boolean isValidOn(final DetailAST ast) { 250 final int astType = ast.getType(); 251 252 return astType == TokenTypes.VARIABLE_DEF 253 && !ScopeUtil.isLocalVariableDef(ast); 254 } 255 256 }, 257 258 /** 259 * {@code @serialData}. 260 */ 261 SERIAL_DATA("@serialData", "serialData", Type.BLOCK) { 262 263 @Override 264 public boolean isValidOn(final DetailAST ast) { 265 final int astType = ast.getType(); 266 final DetailAST methodNameAst = ast.findFirstToken(TokenTypes.IDENT); 267 final String methodName = methodNameAst.getText(); 268 269 return astType == TokenTypes.METHOD_DEF 270 && ("writeObject".equals(methodName) 271 || "readObject".equals(methodName) 272 || "writeExternal".equals(methodName) 273 || "readExternal".equals(methodName) 274 || "writeReplace".equals(methodName) 275 || "readResolve".equals(methodName)); 276 } 277 278 }, 279 280 /** 281 * {@code @serialField}. 282 */ 283 SERIAL_FIELD("@serialField", "serialField", Type.BLOCK) { 284 285 @Override 286 public boolean isValidOn(final DetailAST ast) { 287 final int astType = ast.getType(); 288 final DetailAST varType = ast.findFirstToken(TokenTypes.TYPE); 289 290 return astType == TokenTypes.VARIABLE_DEF 291 && varType.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR 292 && "ObjectStreamField".equals(varType.getFirstChild().getText()); 293 } 294 295 }, 296 297 /** 298 * {@code @since}. 299 */ 300 SINCE("@since", "since", Type.BLOCK) { 301 302 @Override 303 public boolean isValidOn(final DetailAST ast) { 304 final int astType = ast.getType(); 305 return DEF_TOKEN_TYPES.get(astType) 306 && !ScopeUtil.isLocalVariableDef(ast); 307 } 308 309 }, 310 311 /** 312 * {@code @throws}. 313 */ 314 THROWS("@throws", "throws", Type.BLOCK) { 315 316 @Override 317 public boolean isValidOn(final DetailAST ast) { 318 final int astType = ast.getType(); 319 return astType == TokenTypes.METHOD_DEF 320 || astType == TokenTypes.CTOR_DEF; 321 } 322 323 }, 324 325 /** 326 * {@code {@value}}. 327 */ 328 VALUE("{@value}", "value", Type.INLINE) { 329 330 @Override 331 public boolean isValidOn(final DetailAST ast) { 332 final int astType = ast.getType(); 333 return DEF_TOKEN_TYPES.get(astType) 334 && !ScopeUtil.isLocalVariableDef(ast); 335 } 336 337 }, 338 339 /** 340 * {@code @version}. 341 */ 342 VERSION("@version", "version", Type.BLOCK) { 343 344 @Override 345 public boolean isValidOn(final DetailAST ast) { 346 final int astType = ast.getType(); 347 return astType == TokenTypes.PACKAGE_DEF 348 || TokenUtil.isTypeDeclaration(astType); 349 } 350 351 }; 352 353 /** Default token types for DEPRECATED Javadoc tag.*/ 354 private static final BitSet DEF_TOKEN_TYPES_DEPRECATED = TokenUtil.asBitSet( 355 TokenTypes.CTOR_DEF, 356 TokenTypes.METHOD_DEF, 357 TokenTypes.VARIABLE_DEF, 358 TokenTypes.CLASS_DEF, 359 TokenTypes.INTERFACE_DEF, 360 TokenTypes.ENUM_DEF, 361 TokenTypes.ENUM_CONSTANT_DEF, 362 TokenTypes.ANNOTATION_DEF, 363 TokenTypes.ANNOTATION_FIELD_DEF 364 ); 365 366 /** Default token types.*/ 367 private static final BitSet DEF_TOKEN_TYPES = TokenUtil.asBitSet( 368 TokenTypes.CTOR_DEF, 369 TokenTypes.METHOD_DEF, 370 TokenTypes.VARIABLE_DEF, 371 TokenTypes.CLASS_DEF, 372 TokenTypes.INTERFACE_DEF, 373 TokenTypes.PACKAGE_DEF, 374 TokenTypes.ENUM_DEF, 375 TokenTypes.ANNOTATION_DEF 376 ); 377 378 /** Holds tag text to tag enum mappings. **/ 379 private static final Map<String, JavadocTagInfo> TEXT_TO_TAG; 380 /** Holds tag name to tag enum mappings. **/ 381 private static final Map<String, JavadocTagInfo> NAME_TO_TAG; 382 383 static { 384 final JavadocTagInfo[] values = values(); 385 TEXT_TO_TAG = Arrays.stream(values) 386 .collect(Collectors.toUnmodifiableMap(JavadocTagInfo::getText, Function.identity())); 387 NAME_TO_TAG = Arrays.stream(values) 388 .collect(Collectors.toUnmodifiableMap(JavadocTagInfo::getName, Function.identity())); 389 } 390 391 /** The tag text. **/ 392 private final String text; 393 /** The tag name. **/ 394 private final String name; 395 /** The tag type. **/ 396 private final Type type; 397 398 /** 399 * Sets the various properties of a Javadoc tag. 400 * 401 * @param text the tag text 402 * @param name the tag name 403 * @param type the type of tag 404 */ 405 JavadocTagInfo(final String text, final String name, 406 final Type type) { 407 this.text = text; 408 this.name = name; 409 this.type = type; 410 } 411 412 /** 413 * Checks if a particular Javadoc tag is valid within a Javadoc block of a 414 * given AST. 415 * 416 * <p> 417 * If passing in a DetailAST representing a non-void METHOD_DEF 418 * {@code true } would be returned. If passing in a DetailAST 419 * representing a CLASS_DEF {@code false } would be returned because 420 * CLASS_DEF's cannot return a value. 421 * </p> 422 * 423 * @param ast the AST representing a type that can be Javadoc'd 424 * @return true if tag is valid. 425 */ 426 public abstract boolean isValidOn(DetailAST ast); 427 428 /** 429 * Gets the tag text. 430 * 431 * @return the tag text 432 */ 433 public String getText() { 434 return text; 435 } 436 437 /** 438 * Gets the tag name. 439 * 440 * @return the tag name 441 */ 442 public String getName() { 443 return name; 444 } 445 446 /** 447 * Gets the Tag type defined by {@link Type Type}. 448 * 449 * @return the Tag type 450 */ 451 public Type getType() { 452 return type; 453 } 454 455 /** 456 * Returns a JavadocTag from the tag text. 457 * 458 * @param text String representing the tag text 459 * @return Returns a JavadocTag type from a String representing the tag 460 * @throws NullPointerException if the text is null 461 * @throws IllegalArgumentException if the text is not a valid tag 462 */ 463 public static JavadocTagInfo fromText(final String text) { 464 if (text == null) { 465 throw new IllegalArgumentException("the text is null"); 466 } 467 468 final JavadocTagInfo tag = TEXT_TO_TAG.get(text); 469 470 if (tag == null) { 471 throw new IllegalArgumentException("the text [" + text 472 + "] is not a valid Javadoc tag text"); 473 } 474 475 return tag; 476 } 477 478 /** 479 * Returns a JavadocTag from the tag name. 480 * 481 * @param name String name of the tag 482 * @return Returns a JavadocTag type from a String representing the tag 483 * @throws NullPointerException if the text is null 484 * @throws IllegalArgumentException if the text is not a valid tag. The name 485 * can be checked using {@link JavadocTagInfo#isValidName(String)} 486 */ 487 public static JavadocTagInfo fromName(final String name) { 488 if (name == null) { 489 throw new IllegalArgumentException("the name is null"); 490 } 491 492 final JavadocTagInfo tag = NAME_TO_TAG.get(name); 493 494 if (tag == null) { 495 throw new IllegalArgumentException("the name [" + name 496 + "] is not a valid Javadoc tag name"); 497 } 498 499 return tag; 500 } 501 502 /** 503 * Returns whether the provided name is for a valid tag. 504 * 505 * @param name the tag name to check. 506 * @return whether the provided name is for a valid tag. 507 */ 508 public static boolean isValidName(final String name) { 509 return NAME_TO_TAG.containsKey(name); 510 } 511 512 @Override 513 public String toString() { 514 return "text [" + text + "] name [" + name 515 + "] type [" + type + "]"; 516 } 517 518 /** 519 * The Javadoc Type. 520 * 521 * <p>For example a {@code @param} tag is a block tag while a 522 * {@code {@link}} tag is an inline tag. 523 * 524 */ 525 public enum Type { 526 527 /** Block type. **/ 528 BLOCK, 529 530 /** Inline type. **/ 531 INLINE 532 533 } 534 535}