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.ArrayList;
23  import java.util.List;
24  import java.util.Set;
25  
26  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.DetailNode;
29  import com.puppycrawl.tools.checkstyle.api.Scope;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
32  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
34  import com.puppycrawl.tools.checkstyle.utils.NullUtil;
35  import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
36  
37  /**
38   * <div>
39   * Checks for missing Javadoc comments for class, enum, interface, and annotation interface
40   * definitions. The scope to verify is specified using the {@code Scope} class and defaults
41   * to {@code Scope.PUBLIC}. To verify another scope, set property scope to one of the
42   * {@code Scope} constants.
43   * </div>
44   *
45   * @since 8.20
46   */
47  @FileStatefulCheck
48  public final class MissingJavadocTypeCheck extends AbstractJavadocCheck {
49  
50      /**
51       * A key is pointing to the warning message text in "messages.properties"
52       * file.
53       */
54      public static final String MSG_JAVADOC_MISSING = "javadoc.missing.named";
55  
56      /**
57       * Stores all Javadoc comment nodes collected during the tree traversal.
58       * Used to match a Javadoc comment to a type declaration.
59       */
60      private final List<DetailAST> javadocComments = new ArrayList<>();
61  
62      /** Specify the visibility scope where Javadoc comments are checked. */
63      private Scope scope = Scope.PUBLIC;
64  
65      /** Specify the visibility scope where Javadoc comments are not checked. */
66      private Scope excludeScope;
67  
68      /**
69       * Specify annotations that allow missed documentation.
70       * If annotation is present in target sources in multiple forms of qualified
71       * name, all forms should be listed in this property.
72       */
73      private Set<String> skipAnnotations = Set.of("Generated");
74  
75      /**
76       * Creates a new {@code MissingJavadocTypeCheck} instance.
77       */
78      public MissingJavadocTypeCheck() {
79          // no code by default
80      }
81  
82      /**
83       * Setter to specify the visibility scope where Javadoc comments are checked.
84       *
85       * @param scope a scope.
86       * @since 8.20
87       */
88      public void setScope(Scope scope) {
89          this.scope = scope;
90      }
91  
92      /**
93       * Setter to specify the visibility scope where Javadoc comments are not checked.
94       *
95       * @param excludeScope a scope.
96       * @since 8.20
97       */
98      public void setExcludeScope(Scope excludeScope) {
99          this.excludeScope = excludeScope;
100     }
101 
102     /**
103      * Setter to specify annotations that allow missed documentation.
104      * If annotation is present in target sources in multiple forms of qualified
105      * name, all forms should be listed in this property.
106      *
107      * @param userAnnotations user's value.
108      * @since 8.20
109      */
110     public void setSkipAnnotations(String... userAnnotations) {
111         skipAnnotations = Set.of(userAnnotations);
112     }
113 
114     @Override
115     public int[] getDefaultJavadocTokens() {
116         return CommonUtil.EMPTY_INT_ARRAY;
117     }
118 
119     @Override
120     public void visitJavadocToken(DetailNode node) {
121         // no-op
122     }
123 
124     @Override
125     public void beginTree(DetailAST node) {
126         javadocComments.clear();
127         collectCommentNodes(node);
128     }
129 
130     /**
131      * Collects all Javadoc comment nodes in the AST tree and stores them
132      * in {@code javadocComments}. These comments are later used to determine
133      * whether a type declaration has an associated Javadoc comment.
134      *
135      * @param ast the root AST node from which comment nodes are collected
136      */
137     private void collectCommentNodes(DetailAST ast) {
138         DetailAST current = ast;
139         while (current != null) {
140             if (current.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
141                     && JavadocUtil.isJavadocComment(current)) {
142                 javadocComments.add(current);
143             }
144             if (current.getFirstChild() != null) {
145                 current = current.getFirstChild();
146             }
147             else {
148                 DetailAST parent = current;
149                 while (parent != null && current.getNextSibling() == null) {
150                     current = parent;
151                     parent = parent.getParent();
152                 }
153                 current = current.getNextSibling();
154             }
155         }
156     }
157 
158     @Override
159     public int[] getDefaultTokens() {
160         return getAcceptableTokens();
161     }
162 
163     @Override
164     public int[] getAcceptableTokens() {
165         return new int[] {
166             TokenTypes.INTERFACE_DEF,
167             TokenTypes.CLASS_DEF,
168             TokenTypes.ENUM_DEF,
169             TokenTypes.ANNOTATION_DEF,
170             TokenTypes.RECORD_DEF,
171         };
172     }
173 
174     @Override
175     public int[] getRequiredTokens() {
176         return CommonUtil.EMPTY_INT_ARRAY;
177     }
178 
179     @Override
180     public void visitToken(DetailAST ast) {
181         if (shouldCheck(ast) && !hasJavadoc(ast)) {
182             final String name = NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT))
183                 .getText();
184             log(ast, MSG_JAVADOC_MISSING, name);
185         }
186     }
187 
188     /**
189      * Determines whether the specified type AST node has a valid Javadoc
190      * comment immediately preceding it, with no intervening executable code.
191      *
192      * @param ast the AST node representing the type definition
193      * @return {@code true} if a valid Javadoc comment exists before the type;
194      *         {@code false} otherwise
195      */
196     private boolean hasJavadoc(DetailAST ast) {
197         DetailAST best = null;
198 
199         for (DetailAST comment : javadocComments) {
200             final int endLine = comment.getLineNo();
201             if (endLine <= ast.getLineNo()) {
202                 best = comment;
203             }
204         }
205         return best != null && noInterveningCode(best, ast);
206     }
207 
208     /**
209      * Checks whether there is any executable code between the Javadoc comment
210      * and the type declaration by walking AST siblings between them.
211      *
212      * @param javadoc the AST node representing the Javadoc comment
213      * @param type    the AST node representing the type declaration
214      * @return {@code true} if no executable code exists between them;
215      *         {@code false} otherwise
216      */
217     private static boolean noInterveningCode(DetailAST javadoc, DetailAST type) {
218         DetailAST detailAST = javadoc;
219         final int typeStartLine = type.getLineNo();
220         boolean hasOnlyJavadoc = true;
221         while (detailAST != null) {
222             final int siblingLine = detailAST.getLineNo();
223 
224             if (siblingLine < typeStartLine) {
225                 final int tokenType = detailAST.getType();
226                 if (!isAllowedBetweenJavadocAndType(tokenType)) {
227                     hasOnlyJavadoc = false;
228                     break;
229                 }
230             }
231             detailAST = detailAST.getNextSibling();
232         }
233         return hasOnlyJavadoc;
234     }
235 
236     /**
237      * Returns whether the given token type is permitted to appear between
238      * a Javadoc comment and a type declaration.
239      *
240      * @param tokenType the token type to check
241      * @return {@code true} if the token is allowed between Javadoc and a type;
242      *         {@code false} otherwise
243      */
244     private static boolean isAllowedBetweenJavadocAndType(int tokenType) {
245         return tokenType == TokenTypes.BLOCK_COMMENT_BEGIN
246                 || tokenType == TokenTypes.SINGLE_LINE_COMMENT;
247 
248     }
249 
250     /**
251      * Whether we should check this node.
252      *
253      * @param ast a given node.
254      * @return whether we should check a given node.
255      */
256     private boolean shouldCheck(final DetailAST ast) {
257         return ScopeUtil.getSurroundingScope(ast)
258             .map(surroundingScope -> {
259                 return surroundingScope.isIn(scope)
260                     && (excludeScope == null || !surroundingScope.isIn(excludeScope))
261                     && !AnnotationUtil.containsAnnotation(ast, skipAnnotations);
262             })
263             .orElse(Boolean.FALSE);
264     }
265 
266 }