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.stream.Collectors; 026 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.Scope; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 031 032/** 033 * This enum defines the various Javadoc tags and there properties. 034 * 035 * <p> 036 * This class was modeled after documentation located at 037 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html"> 038 * javadoc</a> 039 * 040 * and 041 * 042 * <a href="https://www.oracle.com/technetwork/java/javase/documentation/index-137868.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 || astType == TokenTypes.CLASS_DEF 079 || astType == TokenTypes.INTERFACE_DEF 080 || astType == TokenTypes.ENUM_DEF 081 || astType == TokenTypes.ANNOTATION_DEF; 082 } 083 084 }, 085 086 /** 087 * {@code {@code}}. 088 */ 089 CODE("{@code}", "code", Type.INLINE) { 090 091 @Override 092 public boolean isValidOn(final DetailAST ast) { 093 final int astType = ast.getType(); 094 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 095 && !ScopeUtil.isLocalVariableDef(ast); 096 } 097 098 }, 099 100 /** 101 * {@code {@docRoot}}. 102 */ 103 DOC_ROOT("{@docRoot}", "docRoot", Type.INLINE) { 104 105 @Override 106 public boolean isValidOn(final DetailAST ast) { 107 final int astType = ast.getType(); 108 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 109 && !ScopeUtil.isLocalVariableDef(ast); 110 } 111 112 }, 113 114 /** 115 * {@code @deprecated}. 116 */ 117 DEPRECATED("@deprecated", "deprecated", Type.BLOCK) { 118 119 @Override 120 public boolean isValidOn(final DetailAST ast) { 121 final int astType = ast.getType(); 122 return Arrays.binarySearch(DEF_TOKEN_TYPES_DEPRECATED, astType) >= 0 123 && !ScopeUtil.isLocalVariableDef(ast); 124 } 125 126 }, 127 128 /** 129 * {@code @exception}. 130 */ 131 EXCEPTION("@exception", "exception", Type.BLOCK) { 132 133 @Override 134 public boolean isValidOn(final DetailAST ast) { 135 final int astType = ast.getType(); 136 return astType == TokenTypes.METHOD_DEF || astType == TokenTypes.CTOR_DEF; 137 } 138 139 }, 140 141 /** 142 * {@code {@inheritDoc}}. 143 */ 144 INHERIT_DOC("{@inheritDoc}", "inheritDoc", Type.INLINE) { 145 146 @Override 147 public boolean isValidOn(final DetailAST ast) { 148 final int astType = ast.getType(); 149 150 return astType == TokenTypes.METHOD_DEF 151 && ast.findFirstToken(TokenTypes.MODIFIERS) 152 .findFirstToken(TokenTypes.LITERAL_STATIC) == null 153 && ScopeUtil.getScopeFromMods(ast 154 .findFirstToken(TokenTypes.MODIFIERS)) != Scope.PRIVATE; 155 } 156 157 }, 158 159 /** 160 * {@code {@link}}. 161 */ 162 LINK("{@link}", "link", Type.INLINE) { 163 164 @Override 165 public boolean isValidOn(final DetailAST ast) { 166 final int astType = ast.getType(); 167 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 168 && !ScopeUtil.isLocalVariableDef(ast); 169 } 170 171 }, 172 173 /** 174 * {@code {@linkplain}}. 175 */ 176 LINKPLAIN("{@linkplain}", "linkplain", Type.INLINE) { 177 178 @Override 179 public boolean isValidOn(final DetailAST ast) { 180 final int astType = ast.getType(); 181 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 182 && !ScopeUtil.isLocalVariableDef(ast); 183 } 184 185 }, 186 187 /** 188 * {@code {@literal}}. 189 */ 190 LITERAL("{@literal}", "literal", Type.INLINE) { 191 192 @Override 193 public boolean isValidOn(final DetailAST ast) { 194 final int astType = ast.getType(); 195 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 196 && !ScopeUtil.isLocalVariableDef(ast); 197 } 198 199 }, 200 201 /** 202 * {@code @param}. 203 */ 204 PARAM("@param", "param", Type.BLOCK) { 205 206 @Override 207 public boolean isValidOn(final DetailAST ast) { 208 final int astType = ast.getType(); 209 return astType == TokenTypes.CLASS_DEF 210 || astType == TokenTypes.INTERFACE_DEF 211 || astType == TokenTypes.METHOD_DEF 212 || astType == TokenTypes.CTOR_DEF; 213 } 214 215 }, 216 217 /** 218 * {@code @return}. 219 */ 220 RETURN("@return", "return", Type.BLOCK) { 221 222 @Override 223 public boolean isValidOn(final DetailAST ast) { 224 final int astType = ast.getType(); 225 final DetailAST returnType = ast.findFirstToken(TokenTypes.TYPE); 226 227 return astType == TokenTypes.METHOD_DEF 228 && returnType.getFirstChild().getType() != TokenTypes.LITERAL_VOID; 229 } 230 231 }, 232 233 /** 234 * {@code @see}. 235 */ 236 SEE("@see", "see", Type.BLOCK) { 237 238 @Override 239 public boolean isValidOn(final DetailAST ast) { 240 final int astType = ast.getType(); 241 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 242 && !ScopeUtil.isLocalVariableDef(ast); 243 } 244 245 }, 246 247 /** 248 * {@code @serial}. 249 */ 250 SERIAL("@serial", "serial", Type.BLOCK) { 251 252 @Override 253 public boolean isValidOn(final DetailAST ast) { 254 final int astType = ast.getType(); 255 256 return astType == TokenTypes.VARIABLE_DEF 257 && !ScopeUtil.isLocalVariableDef(ast); 258 } 259 260 }, 261 262 /** 263 * {@code @serialData}. 264 */ 265 SERIAL_DATA("@serialData", "serialData", Type.BLOCK) { 266 267 @Override 268 public boolean isValidOn(final DetailAST ast) { 269 final int astType = ast.getType(); 270 final DetailAST methodNameAst = ast.findFirstToken(TokenTypes.IDENT); 271 final String methodName = methodNameAst.getText(); 272 273 return astType == TokenTypes.METHOD_DEF 274 && ("writeObject".equals(methodName) 275 || "readObject".equals(methodName) 276 || "writeExternal".equals(methodName) 277 || "readExternal".equals(methodName) 278 || "writeReplace".equals(methodName) 279 || "readResolve".equals(methodName)); 280 } 281 282 }, 283 284 /** 285 * {@code @serialField}. 286 */ 287 SERIAL_FIELD("@serialField", "serialField", Type.BLOCK) { 288 289 @Override 290 public boolean isValidOn(final DetailAST ast) { 291 final int astType = ast.getType(); 292 final DetailAST varType = ast.findFirstToken(TokenTypes.TYPE); 293 294 return astType == TokenTypes.VARIABLE_DEF 295 && varType.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR 296 && "ObjectStreamField".equals(varType.getFirstChild().getText()); 297 } 298 299 }, 300 301 /** 302 * {@code @since}. 303 */ 304 SINCE("@since", "since", Type.BLOCK) { 305 306 @Override 307 public boolean isValidOn(final DetailAST ast) { 308 final int astType = ast.getType(); 309 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 310 && !ScopeUtil.isLocalVariableDef(ast); 311 } 312 313 }, 314 315 /** 316 * {@code @throws}. 317 */ 318 THROWS("@throws", "throws", Type.BLOCK) { 319 320 @Override 321 public boolean isValidOn(final DetailAST ast) { 322 final int astType = ast.getType(); 323 return astType == TokenTypes.METHOD_DEF 324 || astType == TokenTypes.CTOR_DEF; 325 } 326 327 }, 328 329 /** 330 * {@code {@value}}. 331 */ 332 VALUE("{@value}", "value", Type.INLINE) { 333 334 @Override 335 public boolean isValidOn(final DetailAST ast) { 336 final int astType = ast.getType(); 337 return Arrays.binarySearch(DEF_TOKEN_TYPES, astType) >= 0 338 && !ScopeUtil.isLocalVariableDef(ast); 339 } 340 341 }, 342 343 /** 344 * {@code @version}. 345 */ 346 VERSION("@version", "version", Type.BLOCK) { 347 348 @Override 349 public boolean isValidOn(final DetailAST ast) { 350 final int astType = ast.getType(); 351 return astType == TokenTypes.PACKAGE_DEF 352 || astType == TokenTypes.CLASS_DEF 353 || astType == TokenTypes.INTERFACE_DEF 354 || astType == TokenTypes.ENUM_DEF 355 || astType == TokenTypes.ANNOTATION_DEF; 356 } 357 358 }; 359 360 /** Default token types for DEPRECATED Javadoc tag.*/ 361 private static final int[] DEF_TOKEN_TYPES_DEPRECATED = { 362 TokenTypes.CTOR_DEF, 363 TokenTypes.METHOD_DEF, 364 TokenTypes.VARIABLE_DEF, 365 TokenTypes.CLASS_DEF, 366 TokenTypes.INTERFACE_DEF, 367 TokenTypes.ENUM_DEF, 368 TokenTypes.ENUM_CONSTANT_DEF, 369 TokenTypes.ANNOTATION_DEF, 370 TokenTypes.ANNOTATION_FIELD_DEF, 371 }; 372 373 /** Default token types.*/ 374 private static final int[] DEF_TOKEN_TYPES = { 375 TokenTypes.CTOR_DEF, 376 TokenTypes.METHOD_DEF, 377 TokenTypes.VARIABLE_DEF, 378 TokenTypes.CLASS_DEF, 379 TokenTypes.INTERFACE_DEF, 380 TokenTypes.PACKAGE_DEF, 381 TokenTypes.ENUM_DEF, 382 TokenTypes.ANNOTATION_DEF, 383 }; 384 385 /** Holds tag text to tag enum mappings. **/ 386 private static final Map<String, JavadocTagInfo> TEXT_TO_TAG; 387 /** Holds tag name to tag enum mappings. **/ 388 private static final Map<String, JavadocTagInfo> NAME_TO_TAG; 389 390 static { 391 TEXT_TO_TAG = Collections.unmodifiableMap(Arrays.stream(JavadocTagInfo.values()) 392 .collect(Collectors.toMap(JavadocTagInfo::getText, tagText -> tagText))); 393 NAME_TO_TAG = Collections.unmodifiableMap(Arrays.stream(JavadocTagInfo.values()) 394 .collect(Collectors.toMap(JavadocTagInfo::getName, tagName -> tagName))); 395 396 //Arrays sorting for binary search 397 Arrays.sort(DEF_TOKEN_TYPES); 398 Arrays.sort(DEF_TOKEN_TYPES_DEPRECATED); 399 } 400 401 /** The tag text. **/ 402 private final String text; 403 /** The tag name. **/ 404 private final String name; 405 /** The tag type. **/ 406 private final Type type; 407 408 /** 409 * Sets the various properties of a Javadoc tag. 410 * 411 * @param text the tag text 412 * @param name the tag name 413 * @param type the type of tag 414 */ 415 JavadocTagInfo(final String text, final String name, 416 final Type type) { 417 this.text = text; 418 this.name = name; 419 this.type = type; 420 } 421 422 /** 423 * Checks if a particular Javadoc tag is valid within a Javadoc block of a 424 * given AST. 425 * 426 * <p> 427 * If passing in a DetailAST representing a non-void METHOD_DEF 428 * {@code true } would be returned. If passing in a DetailAST 429 * representing a CLASS_DEF {@code false } would be returned because 430 * CLASS_DEF's cannot return a value. 431 * </p> 432 * 433 * @param ast the AST representing a type that can be Javadoc'd 434 * @return true if tag is valid. 435 */ 436 public abstract boolean isValidOn(DetailAST ast); 437 438 /** 439 * Gets the tag text. 440 * @return the tag text 441 */ 442 public String getText() { 443 return text; 444 } 445 446 /** 447 * Gets the tag name. 448 * @return the tag name 449 */ 450 public String getName() { 451 return name; 452 } 453 454 /** 455 * Gets the Tag type defined by {@link Type Type}. 456 * @return the Tag type 457 */ 458 public Type getType() { 459 return type; 460 } 461 462 /** 463 * Returns a JavadocTag from the tag text. 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 * @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 * @param name the tag name to check. 510 * @return whether the provided name is for a valid tag. 511 */ 512 public static boolean isValidName(final String name) { 513 return NAME_TO_TAG.containsKey(name); 514 } 515 516 @Override 517 public String toString() { 518 return "text [" + text + "] name [" + name 519 + "] type [" + type + "]"; 520 } 521 522 /** 523 * The Javadoc Type. 524 * 525 * <p>For example a {@code @param} tag is a block tag while a 526 * {@code {@link}} tag is a inline tag. 527 * 528 */ 529 public enum Type { 530 531 /** Block type. **/ 532 BLOCK, 533 534 /** Inline type. **/ 535 INLINE 536 537 } 538 539}