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