001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code 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.Collections; 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 there 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 Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 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 Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 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 Arrays.binarySearch(DEF_TOKEN_TYPES_DEPRECATED, astType) >= 0 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 Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 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 Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 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 Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 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 Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 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 Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 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 Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 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 int[] DEF_TOKEN_TYPES_DEPRECATED = { 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 int[] DEF_TOKEN_TYPES = { 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 TEXT_TO_TAG = Collections.unmodifiableMap(Arrays.stream(values()) 387 .collect(Collectors.toMap(JavadocTagInfo::getText, Function.identity()))); 388 NAME_TO_TAG = Collections.unmodifiableMap(Arrays.stream(values()) 389 .collect(Collectors.toMap(JavadocTagInfo::getName, Function.identity()))); 390 391 // Arrays sorting for binary search 392 Arrays.sort(DEF_TOKEN_TYPES); 393 Arrays.sort(DEF_TOKEN_TYPES_DEPRECATED); 394 } 395 396 /** The tag text. **/ 397 private final String text; 398 /** The tag name. **/ 399 private final String name; 400 /** The tag type. **/ 401 private final Type type; 402 403 /** 404 * Sets the various properties of a Javadoc tag. 405 * 406 * @param text the tag text 407 * @param name the tag name 408 * @param type the type of tag 409 */ 410 JavadocTagInfo(final String text, final String name, 411 final Type type) { 412 this.text = text; 413 this.name = name; 414 this.type = type; 415 } 416 417 /** 418 * Checks if a particular Javadoc tag is valid within a Javadoc block of a 419 * given AST. 420 * 421 * <p> 422 * If passing in a DetailAST representing a non-void METHOD_DEF 423 * {@code true } would be returned. If passing in a DetailAST 424 * representing a CLASS_DEF {@code false } would be returned because 425 * CLASS_DEF's cannot return a value. 426 * </p> 427 * 428 * @param ast the AST representing a type that can be Javadoc'd 429 * @return true if tag is valid. 430 */ 431 public abstract boolean isValidOn(DetailAST ast); 432 433 /** 434 * Gets the tag text. 435 * 436 * @return the tag text 437 */ 438 public String getText() { 439 return text; 440 } 441 442 /** 443 * Gets the tag name. 444 * 445 * @return the tag name 446 */ 447 public String getName() { 448 return name; 449 } 450 451 /** 452 * Gets the Tag type defined by {@link Type Type}. 453 * 454 * @return the Tag type 455 */ 456 public Type getType() { 457 return type; 458 } 459 460 /** 461 * Returns a JavadocTag from the tag text. 462 * 463 * @param text String representing the tag text 464 * @return Returns a JavadocTag type from a String representing the tag 465 * @throws NullPointerException if the text is null 466 * @throws IllegalArgumentException if the text is not a valid tag 467 */ 468 public static JavadocTagInfo fromText(final String text) { 469 if (text == null) { 470 throw new IllegalArgumentException("the text is null"); 471 } 472 473 final JavadocTagInfo tag = TEXT_TO_TAG.get(text); 474 475 if (tag == null) { 476 throw new IllegalArgumentException("the text [" + text 477 + "] is not a valid Javadoc tag text"); 478 } 479 480 return tag; 481 } 482 483 /** 484 * Returns a JavadocTag from the tag name. 485 * 486 * @param name String name of the tag 487 * @return Returns a JavadocTag type from a String representing the tag 488 * @throws NullPointerException if the text is null 489 * @throws IllegalArgumentException if the text is not a valid tag. The name 490 * can be checked using {@link JavadocTagInfo#isValidName(String)} 491 */ 492 public static JavadocTagInfo fromName(final String name) { 493 if (name == null) { 494 throw new IllegalArgumentException("the name is null"); 495 } 496 497 final JavadocTagInfo tag = NAME_TO_TAG.get(name); 498 499 if (tag == null) { 500 throw new IllegalArgumentException("the name [" + name 501 + "] is not a valid Javadoc tag name"); 502 } 503 504 return tag; 505 } 506 507 /** 508 * Returns whether the provided name is for a valid tag. 509 * 510 * @param name the tag name to check. 511 * @return whether the provided name is for a valid tag. 512 */ 513 public static boolean isValidName(final String name) { 514 return NAME_TO_TAG.containsKey(name); 515 } 516 517 @Override 518 public String toString() { 519 return "text [" + text + "] name [" + name 520 + "] type [" + type + "]"; 521 } 522 523 /** 524 * The Javadoc Type. 525 * 526 * <p>For example a {@code @param} tag is a block tag while a 527 * {@code {@link}} tag is a inline tag. 528 * 529 */ 530 public enum Type { 531 532 /** Block type. **/ 533 BLOCK, 534 535 /** Inline type. **/ 536 INLINE 537 538 } 539 540}