001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import javax.annotation.Nullable; 026 027import com.puppycrawl.tools.checkstyle.StatelessCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.SeverityLevel; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 033import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 034 035/** 036 * <div> 037 * Requires user defined Javadoc tag to be present in Javadoc comment with defined format. 038 * To define the format for a tag, set property tagFormat to a regular expression. 039 * Property tagSeverity is used for severity of events when the tag exists. 040 * No violation reported in case there is no javadoc. 041 * </div> 042 * 043 * @since 4.2 044 */ 045@StatelessCheck 046public class WriteTagCheck 047 extends AbstractCheck { 048 049 /** 050 * A key is pointing to the warning message text in "messages.properties" 051 * file. 052 */ 053 public static final String MSG_MISSING_TAG = "type.missingTag"; 054 055 /** 056 * A key is pointing to the warning message text in "messages.properties" 057 * file. 058 */ 059 public static final String MSG_WRITE_TAG = "javadoc.writeTag"; 060 061 /** 062 * A key is pointing to the warning message text in "messages.properties" 063 * file. 064 */ 065 public static final String MSG_TAG_FORMAT = "type.tagFormat"; 066 067 /** Line split pattern. */ 068 private static final Pattern LINE_SPLIT_PATTERN = Pattern.compile("\\R"); 069 070 /** Compiled regexp to match tag. */ 071 private Pattern tagRegExp; 072 /** Specify the regexp to match tag content. */ 073 private Pattern tagFormat; 074 075 /** Specify the name of tag. */ 076 private String tag; 077 /** Specify the severity level when tag is found and printed. */ 078 private SeverityLevel tagSeverity = SeverityLevel.INFO; 079 080 /** 081 * Setter to specify the name of tag. 082 * 083 * @param tag tag to check 084 * @since 4.2 085 */ 086 public void setTag(String tag) { 087 this.tag = tag; 088 tagRegExp = CommonUtil.createPattern(tag + "\\s*(.*$)"); 089 } 090 091 /** 092 * Setter to specify the regexp to match tag content. 093 * 094 * @param pattern a {@code String} value 095 * @since 4.2 096 */ 097 public void setTagFormat(Pattern pattern) { 098 tagFormat = pattern; 099 } 100 101 /** 102 * Setter to specify the severity level when tag is found and printed. 103 * 104 * @param severity The new severity level 105 * @see SeverityLevel 106 * @since 4.2 107 */ 108 public final void setTagSeverity(SeverityLevel severity) { 109 tagSeverity = severity; 110 } 111 112 @Override 113 public int[] getDefaultTokens() { 114 return new int[] { 115 TokenTypes.INTERFACE_DEF, 116 TokenTypes.CLASS_DEF, 117 TokenTypes.ENUM_DEF, 118 TokenTypes.ANNOTATION_DEF, 119 TokenTypes.RECORD_DEF, 120 }; 121 } 122 123 @Override 124 public int[] getAcceptableTokens() { 125 return new int[] { 126 TokenTypes.INTERFACE_DEF, 127 TokenTypes.CLASS_DEF, 128 TokenTypes.ENUM_DEF, 129 TokenTypes.ANNOTATION_DEF, 130 TokenTypes.METHOD_DEF, 131 TokenTypes.CTOR_DEF, 132 TokenTypes.ENUM_CONSTANT_DEF, 133 TokenTypes.ANNOTATION_FIELD_DEF, 134 TokenTypes.RECORD_DEF, 135 TokenTypes.COMPACT_CTOR_DEF, 136 }; 137 } 138 139 @Override 140 public boolean isCommentNodesRequired() { 141 return true; 142 } 143 144 @Override 145 public int[] getRequiredTokens() { 146 return CommonUtil.EMPTY_INT_ARRAY; 147 } 148 149 @Override 150 public void visitToken(DetailAST ast) { 151 final DetailAST javadoc = getJavadoc(ast); 152 153 if (javadoc != null) { 154 final String[] cmtLines = LINE_SPLIT_PATTERN 155 .split(JavadocUtil.getJavadocCommentContent(javadoc)); 156 157 checkTag(javadoc.getLineNo(), 158 javadoc.getLineNo() + countCommentLines(javadoc), 159 cmtLines); 160 } 161 } 162 163 /** 164 * Retrieves the Javadoc comment associated with a given AST node. 165 * 166 * @param ast the AST node (e.g., class, method, constructor) to search above. 167 * @return the {@code DetailAST} representing the Javadoc comment if found and 168 * valid; {@code null} otherwise. 169 */ 170 @Nullable 171 private static DetailAST getJavadoc(DetailAST ast) { 172 // Prefer Javadoc directly above the node 173 DetailAST cmt = ast.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN); 174 if (cmt == null) { 175 // Check MODIFIERS and TYPE block for comments 176 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 177 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 178 179 cmt = modifiers.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN); 180 if (cmt == null && type != null) { 181 cmt = type.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN); 182 } 183 } 184 185 final DetailAST javadoc; 186 if (cmt != null && JavadocUtil.isJavadocComment(cmt)) { 187 javadoc = cmt; 188 } 189 else { 190 javadoc = null; 191 } 192 193 return javadoc; 194 } 195 196 /** 197 * Counts the number of lines in a block comment. 198 * 199 * @param blockComment the AST node representing the block comment. 200 * @return the number of lines in the comment. 201 */ 202 private static int countCommentLines(DetailAST blockComment) { 203 final String content = JavadocUtil.getBlockCommentContent(blockComment); 204 return LINE_SPLIT_PATTERN.split(content).length; 205 } 206 207 /** 208 * Validates the Javadoc comment against the configured requirements. 209 * 210 * @param astLineNo the line number of the type definition. 211 * @param javadocLineNo the starting line number of the Javadoc comment block. 212 * @param comment the lines of the Javadoc comment block. 213 */ 214 private void checkTag(int astLineNo, int javadocLineNo, String... comment) { 215 if (tagRegExp != null) { 216 boolean hasTag = false; 217 for (int i = 0; i < comment.length; i++) { 218 final String commentValue = comment[i]; 219 final Matcher matcher = tagRegExp.matcher(commentValue); 220 if (matcher.find()) { 221 hasTag = true; 222 final int contentStart = matcher.start(1); 223 final String content = commentValue.substring(contentStart); 224 if (tagFormat == null || tagFormat.matcher(content).find()) { 225 logTag(astLineNo + i, tag, content); 226 } 227 else { 228 log(astLineNo + i, MSG_TAG_FORMAT, tag, tagFormat.pattern()); 229 } 230 } 231 } 232 if (!hasTag) { 233 log(javadocLineNo, MSG_MISSING_TAG, tag); 234 } 235 } 236 } 237 238 /** 239 * Log a message. 240 * 241 * @param line the line number where the violation was found 242 * @param tagName the javadoc tag to be logged 243 * @param tagValue the contents of the tag 244 * 245 * @see java.text.MessageFormat 246 */ 247 private void logTag(int line, String tagName, String tagValue) { 248 final String originalSeverity = getSeverity(); 249 setSeverity(tagSeverity.getName()); 250 251 log(line, MSG_WRITE_TAG, tagName, tagValue); 252 253 setSeverity(originalSeverity); 254 } 255}