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 }