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