001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.ArrayList; 023import java.util.Arrays; 024import java.util.List; 025import java.util.stream.Collectors; 026 027import com.puppycrawl.tools.checkstyle.StatelessCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.DetailNode; 030import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 032import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 033 034/** 035 * <p> 036 * Checks that a Javadoc block can fit in a single line and doesn't contain block tags. 037 * Javadoc comment that contains at least one block tag should be formatted in a few lines. 038 * </p> 039 * <ul> 040 * <li> 041 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations 042 * if the Javadoc being examined by this check violates the tight html rules defined at 043 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>. 044 * Type is {@code boolean}. 045 * Default value is {@code false}. 046 * </li> 047 * <li> 048 * Property {@code ignoredTags} - Specify 049 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF"> 050 * block tags</a> which are ignored by the check. 051 * Type is {@code java.lang.String[]}. 052 * Default value is {@code ""}. 053 * </li> 054 * <li> 055 * Property {@code ignoreInlineTags} - Control whether 056 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF"> 057 * inline tags</a> must be ignored. 058 * Type is {@code boolean}. 059 * Default value is {@code true}. 060 * </li> 061 * </ul> 062 * <p> 063 * To configure the check: 064 * </p> 065 * <pre> 066 * <module name="SingleLineJavadoc"/> 067 * </pre> 068 * <p>Example:</p> 069 * <pre> 070 * /** @see Math */ // violation, javadoc should be multiline 071 * public int foo() { 072 * return 42; 073 * } 074 * 075 * /** 076 * * @return 42 077 * */ // ok 078 * public int bar() { 079 * return 42; 080 * } 081 * 082 * /** {@link #equals(Object)} */ // ok 083 * public int baz() { 084 * return 42; 085 * } 086 * 087 * /** 088 * * <p>the answer to the ultimate question 089 * */ // ok 090 * public int magic() { 091 * return 42; 092 * } 093 * 094 * /** 095 * * <p>the answer to the ultimate question</p> 096 * */ // ok 097 * public int foobar() { 098 * return 42; 099 * } 100 * </pre> 101 * <p> 102 * To configure the check with a list of ignored block tags: 103 * </p> 104 * <pre> 105 * <module name="SingleLineJavadoc"> 106 * <property name="ignoredTags" value="@inheritDoc, @see"/> 107 * </module> 108 * </pre> 109 * <p>Example:</p> 110 * <pre> 111 * /** @see Math */ // ok 112 * public int foo() { 113 * return 42; 114 * } 115 * 116 * /** 117 * * @return 42 118 * */ // ok 119 * public int bar() { 120 * return 42; 121 * } 122 * 123 * /** {@link #equals(Object)} */ // ok 124 * public int baz() { 125 * return 42; 126 * } 127 * 128 * /** 129 * * <p>the answer to the ultimate question 130 * */ // ok 131 * public int magic() { 132 * return 42; 133 * } 134 * 135 * /** 136 * * <p>the answer to the ultimate question</p> 137 * */ // ok 138 * public int foobar() { 139 * return 42; 140 * } 141 * </pre> 142 * <p> 143 * To configure the check to not ignore inline tags: 144 * </p> 145 * <pre> 146 * <module name="SingleLineJavadoc"> 147 * <property name="ignoreInlineTags" value="false"/> 148 * </module> 149 * </pre> 150 * <p>Example:</p> 151 * <pre> 152 * /** @see Math */ // violation, javadoc should be multiline 153 * public int foo() { 154 * return 42; 155 * } 156 * 157 * /** 158 * * @return 42 159 * */ // ok 160 * public int bar() { 161 * return 42; 162 * } 163 * 164 * /** {@link #equals(Object)} */ // violation, javadoc should be multiline 165 * public int baz() { 166 * return 42; 167 * } 168 * 169 * /** 170 * * <p>the answer to the ultimate question 171 * */ // ok 172 * public int magic() { 173 * return 42; 174 * } 175 * 176 * /** 177 * * <p>the answer to the ultimate question</p> 178 * */ // ok 179 * public int foobar() { 180 * return 42; 181 * } 182 * </pre> 183 * <p> 184 * To configure the check to report violations for Tight-HTML Rules: 185 * </p> 186 * <pre> 187 * <module name="SingleLineJavadoc"> 188 * <property name="violateExecutionOnNonTightHtml" value="true"/> 189 * </module> 190 * </pre> 191 * <p>Example:</p> 192 * <pre> 193 * /** @see Math */ // violation, javadoc should be multiline 194 * public int foo() { 195 * return 42; 196 * } 197 * 198 * /** 199 * * @return 42 200 * */ // ok 201 * public int bar() { 202 * return 42; 203 * } 204 * 205 * /** {@link #equals(Object)} */ // ok 206 * public int baz() { 207 * return 42; 208 * } 209 * 210 * /** 211 * * <p>the answer to the ultimate question 212 * */ // violation, unclosed HTML tag found: p 213 * public int magic() { 214 * return 42; 215 * } 216 * 217 * /** 218 * * <p>the answer to the ultimate question</p> 219 * */ // ok 220 * public int foobar() { 221 * return 42; 222 * } 223 * </pre> 224 * <p> 225 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 226 * </p> 227 * <p> 228 * Violation Message Keys: 229 * </p> 230 * <ul> 231 * <li> 232 * {@code javadoc.missed.html.close} 233 * </li> 234 * <li> 235 * {@code javadoc.parse.rule.error} 236 * </li> 237 * <li> 238 * {@code javadoc.wrong.singleton.html.tag} 239 * </li> 240 * <li> 241 * {@code singleline.javadoc} 242 * </li> 243 * </ul> 244 * 245 * @since 6.0 246 */ 247@StatelessCheck 248public class SingleLineJavadocCheck extends AbstractJavadocCheck { 249 250 /** 251 * A key is pointing to the warning message text in "messages.properties" 252 * file. 253 */ 254 public static final String MSG_KEY = "singleline.javadoc"; 255 256 /** 257 * Specify 258 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF"> 259 * block tags</a> which are ignored by the check. 260 */ 261 private List<String> ignoredTags = new ArrayList<>(); 262 263 /** 264 * Control whether 265 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF"> 266 * inline tags</a> must be ignored. 267 */ 268 private boolean ignoreInlineTags = true; 269 270 /** 271 * Setter to specify 272 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF"> 273 * block tags</a> which are ignored by the check. 274 * 275 * @param tags to be ignored by check. 276 */ 277 public void setIgnoredTags(String... tags) { 278 ignoredTags = Arrays.stream(tags).collect(Collectors.toList()); 279 } 280 281 /** 282 * Setter to control whether 283 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF"> 284 * inline tags</a> must be ignored. 285 * 286 * @param ignoreInlineTags whether inline tags must be ignored. 287 */ 288 public void setIgnoreInlineTags(boolean ignoreInlineTags) { 289 this.ignoreInlineTags = ignoreInlineTags; 290 } 291 292 @Override 293 public int[] getDefaultJavadocTokens() { 294 return new int[] { 295 JavadocTokenTypes.JAVADOC, 296 }; 297 } 298 299 @Override 300 public int[] getRequiredJavadocTokens() { 301 return getAcceptableJavadocTokens(); 302 } 303 304 @Override 305 public void visitJavadocToken(DetailNode ast) { 306 if (isSingleLineJavadoc(getBlockCommentAst()) 307 && (hasJavadocTags(ast) || !ignoreInlineTags && hasJavadocInlineTags(ast))) { 308 log(ast.getLineNumber(), MSG_KEY); 309 } 310 } 311 312 /** 313 * Checks if comment is single line comment. 314 * 315 * @param blockCommentStart the AST tree in which a block comment starts 316 * @return true, if comment is single line comment. 317 */ 318 private static boolean isSingleLineJavadoc(DetailAST blockCommentStart) { 319 final DetailAST blockCommentEnd = blockCommentStart.getLastChild(); 320 return TokenUtil.areOnSameLine(blockCommentStart, blockCommentEnd); 321 } 322 323 /** 324 * Checks if comment has javadoc tags which are not ignored. Also works 325 * on custom tags. As block tags can be interpreted only at the beginning of a line, 326 * only the first instance is checked. 327 * 328 * @param javadocRoot javadoc root node. 329 * @return true, if comment has javadoc tags which are not ignored. 330 * @see <a href= 331 * "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#blockandinlinetags"> 332 * Block and inline tags</a> 333 */ 334 private boolean hasJavadocTags(DetailNode javadocRoot) { 335 final DetailNode javadocTagSection = 336 JavadocUtil.findFirstToken(javadocRoot, JavadocTokenTypes.JAVADOC_TAG); 337 return javadocTagSection != null && !isTagIgnored(javadocTagSection); 338 } 339 340 /** 341 * Checks if comment has in-line tags which are not ignored. 342 * 343 * @param javadocRoot javadoc root node. 344 * @return true, if comment has in-line tags which are not ignored. 345 * @see <a href= 346 * "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadoctags"> 347 * JavadocTags</a> 348 */ 349 private boolean hasJavadocInlineTags(DetailNode javadocRoot) { 350 DetailNode javadocTagSection = 351 JavadocUtil.findFirstToken(javadocRoot, JavadocTokenTypes.JAVADOC_INLINE_TAG); 352 boolean foundTag = false; 353 while (javadocTagSection != null) { 354 if (!isTagIgnored(javadocTagSection)) { 355 foundTag = true; 356 break; 357 } 358 javadocTagSection = JavadocUtil.getNextSibling( 359 javadocTagSection, JavadocTokenTypes.JAVADOC_INLINE_TAG); 360 } 361 return foundTag; 362 } 363 364 /** 365 * Checks if list of ignored tags contains javadocTagSection's javadoc tag. 366 * 367 * @param javadocTagSection to check javadoc tag in. 368 * @return true, if ignoredTags contains javadocTagSection's javadoc tag. 369 */ 370 private boolean isTagIgnored(DetailNode javadocTagSection) { 371 return ignoredTags.contains(JavadocUtil.getTagName(javadocTagSection)); 372 } 373 374}