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