View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.javadoc;
21  
22  import java.util.Arrays;
23  import java.util.BitSet;
24  import java.util.Map;
25  import java.util.function.Function;
26  import java.util.stream.Collectors;
27  
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.Scope;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
32  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
33  
34  /**
35   * This enum defines the various Javadoc tags and their properties.
36   *
37   * <p>
38   * This class was modeled after documentation located at
39   * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html">
40   * javadoc</a>
41   * and
42   * <a href="https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html">
43   * how to write</a>.
44   * </p>
45   *
46   * <p>
47   * Some of this documentation was a little incomplete (ex: valid placement of
48   * code, value, and literal tags).
49   * </p>
50   *
51   * <p>
52   * Whenever an inconsistency was found the author's judgment was used.
53   * </p>
54   *
55   * <p>
56   * For now, the number of required/optional tag arguments are not included
57   * because some Javadoc tags have very complex rules for determining this
58   * (ex: {@code {@value}} tag).
59   * </p>
60   *
61   * <p>
62   * Also, the {@link #isValidOn(DetailAST) isValidOn} method does not consider
63   * classes defined in a local code block (method, init block, etc.).
64   * </p>
65   *
66   */
67  @SuppressWarnings("UnrecognisedJavadocTag")
68  public enum JavadocTagInfo {
69  
70      /**
71       * {@code @author}.
72       */
73      AUTHOR("@author", "author", Type.BLOCK) {
74  
75          @Override
76          public boolean isValidOn(final DetailAST ast) {
77              final int astType = ast.getType();
78              return astType == TokenTypes.PACKAGE_DEF
79                  || TokenUtil.isTypeDeclaration(astType);
80          }
81  
82      },
83  
84      /**
85       * {@code {@code}}.
86       */
87      CODE("{@code}", "code", Type.INLINE) {
88  
89          @Override
90          public boolean isValidOn(final DetailAST ast) {
91              final int astType = ast.getType();
92              return DEF_TOKEN_TYPES.get(astType)
93                  && !ScopeUtil.isLocalVariableDef(ast);
94          }
95  
96      },
97  
98      /**
99       * {@code {@docRoot}}.
100      */
101     DOC_ROOT("{@docRoot}", "docRoot", Type.INLINE) {
102 
103         @Override
104         public boolean isValidOn(final DetailAST ast) {
105             final int astType = ast.getType();
106             return DEF_TOKEN_TYPES.get(astType)
107                 && !ScopeUtil.isLocalVariableDef(ast);
108         }
109 
110     },
111 
112     /**
113      * {@code @deprecated}.
114      */
115     DEPRECATED("@deprecated", "deprecated", Type.BLOCK) {
116 
117         @Override
118         public boolean isValidOn(final DetailAST ast) {
119             final int astType = ast.getType();
120             return DEF_TOKEN_TYPES_DEPRECATED.get(astType)
121                 && !ScopeUtil.isLocalVariableDef(ast);
122         }
123 
124     },
125 
126     /**
127      * {@code @exception}.
128      */
129     EXCEPTION("@exception", "exception", Type.BLOCK) {
130 
131         @Override
132         public boolean isValidOn(final DetailAST ast) {
133             final int astType = ast.getType();
134             return astType == TokenTypes.METHOD_DEF || astType == TokenTypes.CTOR_DEF;
135         }
136 
137     },
138 
139     /**
140      * {@code {@inheritDoc}}.
141      */
142     INHERIT_DOC("{@inheritDoc}", "inheritDoc", Type.INLINE) {
143 
144         @Override
145         public boolean isValidOn(final DetailAST ast) {
146             final int astType = ast.getType();
147 
148             return astType == TokenTypes.METHOD_DEF
149                 && ast.findFirstToken(TokenTypes.MODIFIERS)
150                     .findFirstToken(TokenTypes.LITERAL_STATIC) == null
151                 && ScopeUtil.getScope(ast) != Scope.PRIVATE;
152         }
153 
154     },
155 
156     /**
157      * {@code {@link}}.
158      */
159     LINK("{@link}", "link", Type.INLINE) {
160 
161         @Override
162         public boolean isValidOn(final DetailAST ast) {
163             final int astType = ast.getType();
164             return DEF_TOKEN_TYPES.get(astType)
165                 && !ScopeUtil.isLocalVariableDef(ast);
166         }
167 
168     },
169 
170     /**
171      * {@code {@linkplain}}.
172      */
173     LINKPLAIN("{@linkplain}", "linkplain", Type.INLINE) {
174 
175         @Override
176         public boolean isValidOn(final DetailAST ast) {
177             final int astType = ast.getType();
178             return DEF_TOKEN_TYPES.get(astType)
179                 && !ScopeUtil.isLocalVariableDef(ast);
180         }
181 
182     },
183 
184     /**
185      * {@code {@literal}}.
186      */
187     LITERAL("{@literal}", "literal", Type.INLINE) {
188 
189         @Override
190         public boolean isValidOn(final DetailAST ast) {
191             final int astType = ast.getType();
192             return DEF_TOKEN_TYPES.get(astType)
193                 && !ScopeUtil.isLocalVariableDef(ast);
194         }
195 
196     },
197 
198     /**
199      * {@code @param}.
200      */
201     PARAM("@param", "param", Type.BLOCK) {
202 
203         @Override
204         public boolean isValidOn(final DetailAST ast) {
205             final int astType = ast.getType();
206             return astType == TokenTypes.CLASS_DEF
207                 || astType == TokenTypes.INTERFACE_DEF
208                 || astType == TokenTypes.METHOD_DEF
209                 || astType == TokenTypes.CTOR_DEF;
210         }
211 
212     },
213 
214     /**
215      * {@code @return}.
216      */
217     RETURN("@return", "return", Type.BLOCK) {
218 
219         @Override
220         public boolean isValidOn(final DetailAST ast) {
221             final int astType = ast.getType();
222             final DetailAST returnType = ast.findFirstToken(TokenTypes.TYPE);
223 
224             return astType == TokenTypes.METHOD_DEF
225                 && returnType.getFirstChild().getType() != TokenTypes.LITERAL_VOID;
226         }
227 
228     },
229 
230     /**
231      * {@code @see}.
232      */
233     SEE("@see", "see", Type.BLOCK) {
234 
235         @Override
236         public boolean isValidOn(final DetailAST ast) {
237             final int astType = ast.getType();
238             return DEF_TOKEN_TYPES.get(astType)
239                 && !ScopeUtil.isLocalVariableDef(ast);
240         }
241 
242     },
243 
244     /**
245      * {@code @serial}.
246      */
247     SERIAL("@serial", "serial", Type.BLOCK) {
248 
249         @Override
250         public boolean isValidOn(final DetailAST ast) {
251             final int astType = ast.getType();
252 
253             return astType == TokenTypes.VARIABLE_DEF
254                 && !ScopeUtil.isLocalVariableDef(ast);
255         }
256 
257     },
258 
259     /**
260      * {@code @serialData}.
261      */
262     SERIAL_DATA("@serialData", "serialData", Type.BLOCK) {
263 
264         @Override
265         public boolean isValidOn(final DetailAST ast) {
266             final int astType = ast.getType();
267             final DetailAST methodNameAst = ast.findFirstToken(TokenTypes.IDENT);
268             final String methodName = methodNameAst.getText();
269 
270             return astType == TokenTypes.METHOD_DEF
271                 && ("writeObject".equals(methodName)
272                     || "readObject".equals(methodName)
273                     || "writeExternal".equals(methodName)
274                     || "readExternal".equals(methodName)
275                     || "writeReplace".equals(methodName)
276                     || "readResolve".equals(methodName));
277         }
278 
279     },
280 
281     /**
282      * {@code @serialField}.
283      */
284     SERIAL_FIELD("@serialField", "serialField", Type.BLOCK) {
285 
286         @Override
287         public boolean isValidOn(final DetailAST ast) {
288             final int astType = ast.getType();
289             final DetailAST varType = ast.findFirstToken(TokenTypes.TYPE);
290 
291             return astType == TokenTypes.VARIABLE_DEF
292                 && varType.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR
293                 && "ObjectStreamField".equals(varType.getFirstChild().getText());
294         }
295 
296     },
297 
298     /**
299      * {@code @since}.
300      */
301     SINCE("@since", "since", Type.BLOCK) {
302 
303         @Override
304         public boolean isValidOn(final DetailAST ast) {
305             final int astType = ast.getType();
306             return DEF_TOKEN_TYPES.get(astType)
307                 && !ScopeUtil.isLocalVariableDef(ast);
308         }
309 
310     },
311 
312     /**
313      * {@code @throws}.
314      */
315     THROWS("@throws", "throws", Type.BLOCK) {
316 
317         @Override
318         public boolean isValidOn(final DetailAST ast) {
319             final int astType = ast.getType();
320             return astType == TokenTypes.METHOD_DEF
321                 || astType == TokenTypes.CTOR_DEF;
322         }
323 
324     },
325 
326     /**
327      * {@code {@value}}.
328      */
329     VALUE("{@value}", "value", Type.INLINE) {
330 
331         @Override
332         public boolean isValidOn(final DetailAST ast) {
333             final int astType = ast.getType();
334             return DEF_TOKEN_TYPES.get(astType)
335                 && !ScopeUtil.isLocalVariableDef(ast);
336         }
337 
338     },
339 
340     /**
341      * {@code @version}.
342      */
343     VERSION("@version", "version", Type.BLOCK) {
344 
345         @Override
346         public boolean isValidOn(final DetailAST ast) {
347             final int astType = ast.getType();
348             return astType == TokenTypes.PACKAGE_DEF
349                 || TokenUtil.isTypeDeclaration(astType);
350         }
351 
352     };
353 
354     /** Default token types for DEPRECATED Javadoc tag.*/
355     private static final BitSet DEF_TOKEN_TYPES_DEPRECATED = TokenUtil.asBitSet(
356         TokenTypes.CTOR_DEF,
357         TokenTypes.METHOD_DEF,
358         TokenTypes.VARIABLE_DEF,
359         TokenTypes.CLASS_DEF,
360         TokenTypes.INTERFACE_DEF,
361         TokenTypes.ENUM_DEF,
362         TokenTypes.ENUM_CONSTANT_DEF,
363         TokenTypes.ANNOTATION_DEF,
364         TokenTypes.ANNOTATION_FIELD_DEF
365     );
366 
367     /** Default token types.*/
368     private static final BitSet DEF_TOKEN_TYPES = TokenUtil.asBitSet(
369         TokenTypes.CTOR_DEF,
370         TokenTypes.METHOD_DEF,
371         TokenTypes.VARIABLE_DEF,
372         TokenTypes.CLASS_DEF,
373         TokenTypes.INTERFACE_DEF,
374         TokenTypes.PACKAGE_DEF,
375         TokenTypes.ENUM_DEF,
376         TokenTypes.ANNOTATION_DEF
377     );
378 
379     /** Holds tag text to tag enum mappings. **/
380     private static final Map<String, JavadocTagInfo> TEXT_TO_TAG;
381     /** Holds tag name to tag enum mappings. **/
382     private static final Map<String, JavadocTagInfo> NAME_TO_TAG;
383 
384     static {
385         final JavadocTagInfo[] values = values();
386         TEXT_TO_TAG = Arrays.stream(values)
387             .collect(Collectors.toUnmodifiableMap(JavadocTagInfo::getText, Function.identity()));
388         NAME_TO_TAG = Arrays.stream(values)
389             .collect(Collectors.toUnmodifiableMap(JavadocTagInfo::getName, Function.identity()));
390     }
391 
392     /** The tag text. **/
393     private final String text;
394     /** The tag name. **/
395     private final String name;
396     /** The tag type. **/
397     private final Type type;
398 
399     /**
400      * Sets the various properties of a Javadoc tag.
401      *
402      * @param text the tag text
403      * @param name the tag name
404      * @param type the type of tag
405      */
406     JavadocTagInfo(final String text, final String name,
407         final Type type) {
408         this.text = text;
409         this.name = name;
410         this.type = type;
411     }
412 
413     /**
414      * Checks if a particular Javadoc tag is valid within a Javadoc block of a
415      * given AST.
416      *
417      * <p>
418      * If passing in a DetailAST representing a non-void METHOD_DEF
419      * {@code true } would be returned. If passing in a DetailAST
420      * representing a CLASS_DEF {@code false } would be returned because
421      * CLASS_DEF's cannot return a value.
422      * </p>
423      *
424      * @param ast the AST representing a type that can be Javadoc'd
425      * @return true if tag is valid.
426      */
427     public abstract boolean isValidOn(DetailAST ast);
428 
429     /**
430      * Gets the tag text.
431      *
432      * @return the tag text
433      */
434     public String getText() {
435         return text;
436     }
437 
438     /**
439      * Gets the tag name.
440      *
441      * @return the tag name
442      */
443     public String getName() {
444         return name;
445     }
446 
447     /**
448      * Gets the Tag type defined by {@link Type Type}.
449      *
450      * @return the Tag type
451      */
452     public Type getType() {
453         return type;
454     }
455 
456     /**
457      * Returns a JavadocTag from the tag text.
458      *
459      * @param text String representing the tag text
460      * @return Returns a JavadocTag type from a String representing the tag
461      * @throws NullPointerException if the text is null
462      * @throws IllegalArgumentException if the text is not a valid tag
463      */
464     public static JavadocTagInfo fromText(final String text) {
465         if (text == null) {
466             throw new IllegalArgumentException("the text is null");
467         }
468 
469         final JavadocTagInfo tag = TEXT_TO_TAG.get(text);
470 
471         if (tag == null) {
472             throw new IllegalArgumentException("the text [" + text
473                 + "] is not a valid Javadoc tag text");
474         }
475 
476         return tag;
477     }
478 
479     /**
480      * Returns a JavadocTag from the tag name.
481      *
482      * @param name String name of the tag
483      * @return Returns a JavadocTag type from a String representing the tag
484      * @throws NullPointerException if the text is null
485      * @throws IllegalArgumentException if the text is not a valid tag. The name
486      *     can be checked using {@link JavadocTagInfo#isValidName(String)}
487      */
488     public static JavadocTagInfo fromName(final String name) {
489         if (name == null) {
490             throw new IllegalArgumentException("the name is null");
491         }
492 
493         final JavadocTagInfo tag = NAME_TO_TAG.get(name);
494 
495         if (tag == null) {
496             throw new IllegalArgumentException("the name [" + name
497                 + "] is not a valid Javadoc tag name");
498         }
499 
500         return tag;
501     }
502 
503     /**
504      * Returns whether the provided name is for a valid tag.
505      *
506      * @param name the tag name to check.
507      * @return whether the provided name is for a valid tag.
508      */
509     public static boolean isValidName(final String name) {
510         return NAME_TO_TAG.containsKey(name);
511     }
512 
513     @Override
514     public String toString() {
515         return "text [" + text + "] name [" + name
516             + "] type [" + type + "]";
517     }
518 
519     /**
520      * The Javadoc Type.
521      *
522      * <p>For example a {@code @param} tag is a block tag while a
523      * {@code {@link}} tag is an inline tag.
524      *
525      */
526     public enum Type {
527 
528         /** Block type. **/
529         BLOCK,
530 
531         /** Inline type. **/
532         INLINE
533 
534     }
535 
536 }