001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.Locale; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 029import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 030 031/** 032 * <p> 033 * Checks that the Javadoc content begins from the same position 034 * for all Javadoc comments in the project. Any leading asterisks and spaces 035 * are not counted as the beginning of the content and are therefore ignored. 036 * </p> 037 * <p> 038 * It is possible to enforce two different styles: 039 * </p> 040 * <ul> 041 * <li> 042 * {@code first_line} - Javadoc content starts from the first line: 043 * <pre> 044 * /** Summary text. 045 * * More details. 046 * */ 047 * public void method(); 048 * </pre> 049 * </li> 050 * <li> 051 * {@code second_line} - Javadoc content starts from the second line: 052 * <pre> 053 * /** 054 * * Summary text. 055 * * More details. 056 * */ 057 * public void method(); 058 * </pre> 059 * </li> 060 * </ul> 061 * <p> 062 * This check does not validate the Javadoc summary itself nor its presence. 063 * The check will not report any violations for missing or malformed javadoc summary. 064 * To validate the Javadoc summary use 065 * <a href="https://checkstyle.org/config_javadoc.html#SummaryJavadoc">SummaryJavadoc</a> check. 066 * </p> 067 * <p> 068 * The <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html"> 069 * Documentation Comment Specification</a> permits leading asterisks on the first line. 070 * For these Javadoc comments: 071 * </p> 072 * <pre> 073 * /*** 074 * * Some text. 075 * */ 076 * /************ 077 * * Some text. 078 * */ 079 * /** ** 080 * * Some text. 081 * */ 082 * </pre> 083 * <p> 084 * The documentation generated will be just "Some text." without any asterisks. 085 * Since these asterisks will not appear in the generated documentation, 086 * they should not be considered as the beginning of the Javadoc content. 087 * In such cases, the check assumes that the Javadoc content begins on the second line. 088 * </p> 089 * <ul> 090 * <li> 091 * Property {@code location} - Specify the policy on placement of the Javadoc content. 092 * Type is {@code com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocContentLocationOption}. 093 * Default value is {@code second_line}. 094 * </li> 095 * </ul> 096 * <p> 097 * By default Check validate that the Javadoc content starts from the second line: 098 * </p> 099 * <pre> 100 * <module name="JavadocContentLocationCheck"/> 101 * </pre> 102 * <p> 103 * This setting produces a violation for each multi-line comment starting 104 * on the same line as the initial asterisks: 105 * </p> 106 * <pre> 107 * /** This comment causes a violation because it starts from the first line 108 * * and spans several lines. 109 * */ 110 * /** 111 * * This comment is OK because it starts from the second line. 112 * */ 113 * /** This comment is OK because it is on the single line. */ 114 * </pre> 115 * <p> 116 * To ensure that Javadoc content starts from the first line: 117 * </p> 118 * <pre> 119 * <module name="JavadocContentLocationCheck"> 120 * <property name="location" value="first_line"/> 121 * </module> 122 * </pre> 123 * <p> 124 * This setting produces a violation for each comment not 125 * starting on the same line as the initial asterisks: 126 * </p> 127 * <pre> 128 * /** This comment is OK because it starts on the first line. 129 * * There may be additional text. 130 * */ 131 * /** 132 * * This comment causes a violation because it starts on the second line. 133 * */ 134 * /** This single-line comment also is OK. */ 135 * </pre> 136 * <p> 137 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 138 * </p> 139 * <p> 140 * Violation Message Keys: 141 * </p> 142 * <ul> 143 * <li> 144 * {@code javadoc.content.first.line} 145 * </li> 146 * <li> 147 * {@code javadoc.content.second.line} 148 * </li> 149 * </ul> 150 * 151 * @since 8.27 152 */ 153@StatelessCheck 154public class JavadocContentLocationCheck extends AbstractCheck { 155 156 /** 157 * A key is pointing to the warning message text in "messages.properties" file. 158 */ 159 public static final String MSG_JAVADOC_CONTENT_FIRST_LINE = "javadoc.content.first.line"; 160 161 /** 162 * A key is pointing to the warning message text in "messages.properties" file. 163 */ 164 public static final String MSG_JAVADOC_CONTENT_SECOND_LINE = "javadoc.content.second.line"; 165 166 /** 167 * Specify the policy on placement of the Javadoc content. 168 */ 169 private JavadocContentLocationOption location = JavadocContentLocationOption.SECOND_LINE; 170 171 @Override 172 public int[] getRequiredTokens() { 173 return new int[] { 174 TokenTypes.BLOCK_COMMENT_BEGIN, 175 }; 176 } 177 178 @Override 179 public int[] getAcceptableTokens() { 180 return getRequiredTokens(); 181 } 182 183 @Override 184 public int[] getDefaultTokens() { 185 return getRequiredTokens(); 186 } 187 188 @Override 189 public boolean isCommentNodesRequired() { 190 return true; 191 } 192 193 /** 194 * Setter to specify the policy on placement of the Javadoc content. 195 * 196 * @param value string to decode location from 197 * @throws IllegalArgumentException if unable to decode 198 */ 199 public void setLocation(String value) { 200 location = JavadocContentLocationOption.valueOf(value.trim().toUpperCase(Locale.ENGLISH)); 201 } 202 203 @Override 204 public void visitToken(DetailAST ast) { 205 if (isMultilineComment(ast) && JavadocUtil.isJavadocComment(ast)) { 206 final String commentContent = JavadocUtil.getJavadocCommentContent(ast); 207 final int indexOfFirstNonBlankLine = findIndexOfFirstNonBlankLine(commentContent); 208 if (indexOfFirstNonBlankLine >= 0) { 209 if (location == JavadocContentLocationOption.FIRST_LINE) { 210 if (indexOfFirstNonBlankLine != 0) { 211 log(ast, MSG_JAVADOC_CONTENT_FIRST_LINE); 212 } 213 } 214 else if (indexOfFirstNonBlankLine != 1) { 215 log(ast, MSG_JAVADOC_CONTENT_SECOND_LINE); 216 } 217 } 218 } 219 } 220 221 /** 222 * Checks if a DetailAST of type {@code TokenTypes.BLOCK_COMMENT_BEGIN} span 223 * more than one line. The node always has at least one child of the type 224 * {@code TokenTypes.BLOCK_COMMENT_END}. 225 * 226 * @param node node to check 227 * @return {@code true} for multi-line comment nodes 228 */ 229 private static boolean isMultilineComment(DetailAST node) { 230 return !TokenUtil.areOnSameLine(node, node.getLastChild()); 231 } 232 233 /** 234 * Returns the index of the first non-blank line. 235 * All lines consists only of asterisks and whitespaces are treated as blank. 236 * 237 * @param commentContent Javadoc content to process 238 * @return the index of the first non-blank line or {@code -1} if all lines are blank 239 */ 240 private static int findIndexOfFirstNonBlankLine(String commentContent) { 241 int lineNo = 0; 242 boolean noContent = true; 243 for (int i = 0; i < commentContent.length(); ++i) { 244 final char character = commentContent.charAt(i); 245 if (character == '\n') { 246 ++lineNo; 247 } 248 else if (character != '*' && !Character.isWhitespace(character)) { 249 noContent = false; 250 break; 251 } 252 } 253 if (noContent) { 254 lineNo = -1; 255 } 256 return lineNo; 257 } 258 259}