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 }