001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.getScopeFromMods(ast 153 .findFirstToken(TokenTypes.MODIFIERS)) != Scope.PRIVATE; 154 } 155 156 }, 157 158 /** 159 * {@code {@link}}. 160 */ 161 LINK("{@link}", "link", Type.INLINE) { 162 163 @Override 164 public boolean isValidOn(final DetailAST ast) { 165 final int astType = ast.getType(); 166 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 167 && !ScopeUtil.isLocalVariableDef(ast); 168 } 169 170 }, 171 172 /** 173 * {@code {@linkplain}}. 174 */ 175 LINKPLAIN("{@linkplain}", "linkplain", Type.INLINE) { 176 177 @Override 178 public boolean isValidOn(final DetailAST ast) { 179 final int astType = ast.getType(); 180 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 181 && !ScopeUtil.isLocalVariableDef(ast); 182 } 183 184 }, 185 186 /** 187 * {@code {@literal}}. 188 */ 189 LITERAL("{@literal}", "literal", Type.INLINE) { 190 191 @Override 192 public boolean isValidOn(final DetailAST ast) { 193 final int astType = ast.getType(); 194 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 195 && !ScopeUtil.isLocalVariableDef(ast); 196 } 197 198 }, 199 200 /** 201 * {@code @param}. 202 */ 203 PARAM("@param", "param", Type.BLOCK) { 204 205 @Override 206 public boolean isValidOn(final DetailAST ast) { 207 final int astType = ast.getType(); 208 return astType == TokenTypes.CLASS_DEF 209 || astType == TokenTypes.INTERFACE_DEF 210 || astType == TokenTypes.METHOD_DEF 211 || astType == TokenTypes.CTOR_DEF; 212 } 213 214 }, 215 216 /** 217 * {@code @return}. 218 */ 219 RETURN("@return", "return", Type.BLOCK) { 220 221 @Override 222 public boolean isValidOn(final DetailAST ast) { 223 final int astType = ast.getType(); 224 final DetailAST returnType = ast.findFirstToken(TokenTypes.TYPE); 225 226 return astType == TokenTypes.METHOD_DEF 227 && returnType.getFirstChild().getType() != TokenTypes.LITERAL_VOID; 228 } 229 230 }, 231 232 /** 233 * {@code @see}. 234 */ 235 SEE("@see", "see", Type.BLOCK) { 236 237 @Override 238 public boolean isValidOn(final DetailAST ast) { 239 final int astType = ast.getType(); 240 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 241 && !ScopeUtil.isLocalVariableDef(ast); 242 } 243 244 }, 245 246 /** 247 * {@code @serial}. 248 */ 249 SERIAL("@serial", "serial", Type.BLOCK) { 250 251 @Override 252 public boolean isValidOn(final DetailAST ast) { 253 final int astType = ast.getType(); 254 255 return astType == TokenTypes.VARIABLE_DEF 256 && !ScopeUtil.isLocalVariableDef(ast); 257 } 258 259 }, 260 261 /** 262 * {@code @serialData}. 263 */ 264 SERIAL_DATA("@serialData", "serialData", Type.BLOCK) { 265 266 @Override 267 public boolean isValidOn(final DetailAST ast) { 268 final int astType = ast.getType(); 269 final DetailAST methodNameAst = ast.findFirstToken(TokenTypes.IDENT); 270 final String methodName = methodNameAst.getText(); 271 272 return astType == TokenTypes.METHOD_DEF 273 && ("writeObject".equals(methodName) 274 || "readObject".equals(methodName) 275 || "writeExternal".equals(methodName) 276 || "readExternal".equals(methodName) 277 || "writeReplace".equals(methodName) 278 || "readResolve".equals(methodName)); 279 } 280 281 }, 282 283 /** 284 * {@code @serialField}. 285 */ 286 SERIAL_FIELD("@serialField", "serialField", Type.BLOCK) { 287 288 @Override 289 public boolean isValidOn(final DetailAST ast) { 290 final int astType = ast.getType(); 291 final DetailAST varType = ast.findFirstToken(TokenTypes.TYPE); 292 293 return astType == TokenTypes.VARIABLE_DEF 294 && varType.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR 295 && "ObjectStreamField".equals(varType.getFirstChild().getText()); 296 } 297 298 }, 299 300 /** 301 * {@code @since}. 302 */ 303 SINCE("@since", "since", Type.BLOCK) { 304 305 @Override 306 public boolean isValidOn(final DetailAST ast) { 307 final int astType = ast.getType(); 308 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 309 && !ScopeUtil.isLocalVariableDef(ast); 310 } 311 312 }, 313 314 /** 315 * {@code @throws}. 316 */ 317 THROWS("@throws", "throws", Type.BLOCK) { 318 319 @Override 320 public boolean isValidOn(final DetailAST ast) { 321 final int astType = ast.getType(); 322 return astType == TokenTypes.METHOD_DEF 323 || astType == TokenTypes.CTOR_DEF; 324 } 325 326 }, 327 328 /** 329 * {@code {@value}}. 330 */ 331 VALUE("{@value}", "value", Type.INLINE) { 332 333 @Override 334 public boolean isValidOn(final DetailAST ast) { 335 final int astType = ast.getType(); 336 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 337 && !ScopeUtil.isLocalVariableDef(ast); 338 } 339 340 }, 341 342 /** 343 * {@code @version}. 344 */ 345 VERSION("@version", "version", Type.BLOCK) { 346 347 @Override 348 public boolean isValidOn(final DetailAST ast) { 349 final int astType = ast.getType(); 350 return astType == TokenTypes.PACKAGE_DEF 351 || TokenUtil.isTypeDeclaration(astType); 352 } 353 354 }; 355 356 /** Default token types for DEPRECATED Javadoc tag.*/ 357 private static final int[] DEF_TOKEN_TYPES_DEPRECATED = { 358 TokenTypes.CTOR_DEF, 359 TokenTypes.METHOD_DEF, 360 TokenTypes.VARIABLE_DEF, 361 TokenTypes.CLASS_DEF, 362 TokenTypes.INTERFACE_DEF, 363 TokenTypes.ENUM_DEF, 364 TokenTypes.ENUM_CONSTANT_DEF, 365 TokenTypes.ANNOTATION_DEF, 366 TokenTypes.ANNOTATION_FIELD_DEF, 367 }; 368 369 /** Default token types.*/ 370 private static final int[] DEF_TOKEN_TYPES = { 371 TokenTypes.CTOR_DEF, 372 TokenTypes.METHOD_DEF, 373 TokenTypes.VARIABLE_DEF, 374 TokenTypes.CLASS_DEF, 375 TokenTypes.INTERFACE_DEF, 376 TokenTypes.PACKAGE_DEF, 377 TokenTypes.ENUM_DEF, 378 TokenTypes.ANNOTATION_DEF, 379 }; 380 381 /** Holds tag text to tag enum mappings. **/ 382 private static final Map<String, JavadocTagInfo> TEXT_TO_TAG; 383 /** Holds tag name to tag enum mappings. **/ 384 private static final Map<String, JavadocTagInfo> NAME_TO_TAG; 385 386 static { 387 TEXT_TO_TAG = Collections.unmodifiableMap(Arrays.stream(values()) 388 .collect(Collectors.toMap(JavadocTagInfo::getText, Function.identity()))); 389 NAME_TO_TAG = Collections.unmodifiableMap(Arrays.stream(values()) 390 .collect(Collectors.toMap(JavadocTagInfo::getName, Function.identity()))); 391 392 // Arrays sorting for binary search 393 Arrays.sort(DEF_TOKEN_TYPES); 394 Arrays.sort(DEF_TOKEN_TYPES_DEPRECATED); 395 } 396 397 /** The tag text. **/ 398 private final String text; 399 /** The tag name. **/ 400 private final String name; 401 /** The tag type. **/ 402 private final Type type; 403 404 /** 405 * Sets the various properties of a Javadoc tag. 406 * 407 * @param text the tag text 408 * @param name the tag name 409 * @param type the type of tag 410 */ 411 JavadocTagInfo(final String text, final String name, 412 final Type type) { 413 this.text = text; 414 this.name = name; 415 this.type = type; 416 } 417 418 /** 419 * Checks if a particular Javadoc tag is valid within a Javadoc block of a 420 * given AST. 421 * 422 * <p> 423 * If passing in a DetailAST representing a non-void METHOD_DEF 424 * {@code true } would be returned. If passing in a DetailAST 425 * representing a CLASS_DEF {@code false } would be returned because 426 * CLASS_DEF's cannot return a value. 427 * </p> 428 * 429 * @param ast the AST representing a type that can be Javadoc'd 430 * @return true if tag is valid. 431 */ 432 public abstract boolean isValidOn(DetailAST ast); 433 434 /** 435 * Gets the tag text. 436 * 437 * @return the tag text 438 */ 439 public String getText() { 440 return text; 441 } 442 443 /** 444 * Gets the tag name. 445 * 446 * @return the tag name 447 */ 448 public String getName() { 449 return name; 450 } 451 452 /** 453 * Gets the Tag type defined by {@link Type Type}. 454 * 455 * @return the Tag type 456 */ 457 public Type getType() { 458 return type; 459 } 460 461 /** 462 * Returns a JavadocTag from the tag text. 463 * 464 * @param text String representing the tag text 465 * @return Returns a JavadocTag type from a String representing the tag 466 * @throws NullPointerException if the text is null 467 * @throws IllegalArgumentException if the text is not a valid tag 468 */ 469 public static JavadocTagInfo fromText(final String text) { 470 if (text == null) { 471 throw new IllegalArgumentException("the text is null"); 472 } 473 474 final JavadocTagInfo tag = TEXT_TO_TAG.get(text); 475 476 if (tag == null) { 477 throw new IllegalArgumentException("the text [" + text 478 + "] is not a valid Javadoc tag text"); 479 } 480 481 return tag; 482 } 483 484 /** 485 * Returns a JavadocTag from the tag name. 486 * 487 * @param name String name of the tag 488 * @return Returns a JavadocTag type from a String representing the tag 489 * @throws NullPointerException if the text is null 490 * @throws IllegalArgumentException if the text is not a valid tag. The name 491 * can be checked using {@link JavadocTagInfo#isValidName(String)} 492 */ 493 public static JavadocTagInfo fromName(final String name) { 494 if (name == null) { 495 throw new IllegalArgumentException("the name is null"); 496 } 497 498 final JavadocTagInfo tag = NAME_TO_TAG.get(name); 499 500 if (tag == null) { 501 throw new IllegalArgumentException("the name [" + name 502 + "] is not a valid Javadoc tag name"); 503 } 504 505 return tag; 506 } 507 508 /** 509 * Returns whether the provided name is for a valid tag. 510 * 511 * @param name the tag name to check. 512 * @return whether the provided name is for a valid tag. 513 */ 514 public static boolean isValidName(final String name) { 515 return NAME_TO_TAG.containsKey(name); 516 } 517 518 @Override 519 public String toString() { 520 return "text [" + text + "] name [" + name 521 + "] type [" + type + "]"; 522 } 523 524 /** 525 * The Javadoc Type. 526 * 527 * <p>For example a {@code @param} tag is a block tag while a 528 * {@code {@link}} tag is a inline tag. 529 * 530 */ 531 public enum Type { 532 533 /** Block type. **/ 534 BLOCK, 535 536 /** Inline type. **/ 537 INLINE 538 539 } 540 541}