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 * Default value is {@code second_line}. 093 * </li> 094 * </ul> 095 * <p> 096 * By default Check validate that the Javadoc content starts from the second line: 097 * </p> 098 * <pre> 099 * <module name="JavadocContentLocationCheck"/> 100 * </pre> 101 * <p> 102 * This setting produces a violation for each multi-line comment starting 103 * on the same line as the initial asterisks: 104 * </p> 105 * <pre> 106 * /** This comment causes a violation because it starts from the first line 107 * * and spans several lines. 108 * */ 109 * /** 110 * * This comment is OK because it starts from the second line. 111 * */ 112 * /** This comment is OK because it is on the single line. */ 113 * </pre> 114 * <p> 115 * To ensure that Javadoc content starts from the first line: 116 * </p> 117 * <pre> 118 * <module name="JavadocContentLocationCheck"> 119 * <property name="location" value="first_line"/> 120 * </module> 121 * </pre> 122 * <p> 123 * This setting produces a violation for each comment not 124 * starting on the same line as the initial asterisks: 125 * </p> 126 * <pre> 127 * /** This comment is OK because it starts on the first line. 128 * * There may be additional text. 129 * */ 130 * /** 131 * * This comment causes a violation because it starts on the second line. 132 * */ 133 * /** This single-line comment also is OK. */ 134 * </pre> 135 * 136 * @since 8.27 137 */ 138@StatelessCheck 139public class JavadocContentLocationCheck extends AbstractCheck { 140 141 /** 142 * A key is pointing to the warning message text in "messages.properties" file. 143 */ 144 public static final String MSG_JAVADOC_CONTENT_FIRST_LINE = "javadoc.content.first.line"; 145 146 /** 147 * A key is pointing to the warning message text in "messages.properties" file. 148 */ 149 public static final String MSG_JAVADOC_CONTENT_SECOND_LINE = "javadoc.content.second.line"; 150 151 /** 152 * Specify the policy on placement of the Javadoc content. 153 */ 154 private JavadocContentLocationOption location = JavadocContentLocationOption.SECOND_LINE; 155 156 @Override 157 public int[] getRequiredTokens() { 158 return new int[] { 159 TokenTypes.BLOCK_COMMENT_BEGIN, 160 }; 161 } 162 163 @Override 164 public int[] getAcceptableTokens() { 165 return getRequiredTokens(); 166 } 167 168 @Override 169 public int[] getDefaultTokens() { 170 return getRequiredTokens(); 171 } 172 173 @Override 174 public boolean isCommentNodesRequired() { 175 return true; 176 } 177 178 /** 179 * Setter to specify the policy on placement of the Javadoc content. 180 * 181 * @param value string to decode location from 182 * @throws IllegalArgumentException if unable to decode 183 */ 184 public void setLocation(String value) { 185 location = JavadocContentLocationOption.valueOf(value.trim().toUpperCase(Locale.ENGLISH)); 186 } 187 188 @Override 189 public void visitToken(DetailAST ast) { 190 if (isMultilineComment(ast) && JavadocUtil.isJavadocComment(ast)) { 191 final String commentContent = JavadocUtil.getJavadocCommentContent(ast); 192 final int indexOfFirstNonBlankLine = findIndexOfFirstNonBlankLine(commentContent); 193 if (indexOfFirstNonBlankLine >= 0) { 194 if (location == JavadocContentLocationOption.FIRST_LINE) { 195 if (indexOfFirstNonBlankLine != 0) { 196 log(ast, MSG_JAVADOC_CONTENT_FIRST_LINE); 197 } 198 } 199 else if (indexOfFirstNonBlankLine != 1) { 200 log(ast, MSG_JAVADOC_CONTENT_SECOND_LINE); 201 } 202 } 203 } 204 } 205 206 /** 207 * Checks if a DetailAST of type {@code TokenTypes.BLOCK_COMMENT_BEGIN} span 208 * more than one line. The node always has at least one child of the type 209 * {@code TokenTypes.BLOCK_COMMENT_END}. 210 * 211 * @param node node to check 212 * @return {@code true} for multi-line comment nodes 213 */ 214 private static boolean isMultilineComment(DetailAST node) { 215 return !TokenUtil.areOnSameLine(node, node.getLastChild()); 216 } 217 218 /** 219 * Returns the index of the first non-blank line. 220 * All lines consists only of asterisks and whitespaces are treated as blank. 221 * 222 * @param commentContent Javadoc content to process 223 * @return the index of the first non-blank line or {@code -1} if all lines are blank 224 */ 225 private static int findIndexOfFirstNonBlankLine(String commentContent) { 226 int lineNo = 0; 227 boolean noContent = true; 228 for (int i = 0; i < commentContent.length(); ++i) { 229 final char character = commentContent.charAt(i); 230 if (character == '\n') { 231 ++lineNo; 232 } 233 else if (character != '*' && !Character.isWhitespace(character)) { 234 noContent = false; 235 break; 236 } 237 } 238 if (noContent) { 239 lineNo = -1; 240 } 241 return lineNo; 242 } 243 244}