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.List; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailNode; 027import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 029import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 030 031/** 032 * <p> 033 * Checks the indentation of the continuation lines in block tags. That is whether the continued 034 * description of at clauses should be indented or not. If the text is not properly indented it 035 * throws a violation. A continuation line is when the description starts/spans past the line with 036 * the tag. Default indentation required is at least 4, but this can be changed with the help of 037 * properties below. 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 offset} - Specify how many spaces to use for new indentation level. 049 * Type is {@code int}. 050 * Default value is {@code 4}. 051 * </li> 052 * </ul> 053 * <p> 054 * To configure the default check: 055 * </p> 056 * <pre> 057 * <module name="JavadocTagContinuationIndentation"/> 058 * </pre> 059 * <p> 060 * Example: 061 * </p> 062 * <pre> 063 * /** 064 * * @tag comment 065 * * Indentation spacing is 1. Line with violation 066 * * Indentation spacing is 2. Line with violation 067 * * Indentation spacing is 4. OK 068 * */ 069 * public class Test { 070 * } 071 * </pre> 072 * <p> 073 * To configure the check with two spaces indentation: 074 * </p> 075 * <pre> 076 * <module name="JavadocTagContinuationIndentation"> 077 * <property name="offset" value="2"/> 078 * </module> 079 * </pre> 080 * <p> 081 * Example: 082 * </p> 083 * <pre> 084 * /** 085 * * @tag comment 086 * * Indentation spacing is 0. Line with violation 087 * * Indentation spacing is 2. OK 088 * * Indentation spacing is 1. Line with violation 089 * */ 090 * public class Test { 091 * } 092 * </pre> 093 * <p> 094 * To configure the check to show violations for Tight-HTML Rules: 095 * </p> 096 * <pre> 097 * <module name="JavadocTagContinuationIndentation"> 098 * <property name="violateExecutionOnNonTightHtml" value="true"/> 099 * </module> 100 * </pre> 101 * <p> 102 * Example: 103 * </p> 104 * <pre> 105 * /** 106 * * <p> 'p' tag is unclosed. Line with violation, this html tag needs closing tag. 107 * * <p> 'p' tag is closed</p>. OK 108 * */ 109 * public class Test { 110 * } 111 * </pre> 112 * <p> 113 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 114 * </p> 115 * <p> 116 * Violation Message Keys: 117 * </p> 118 * <ul> 119 * <li> 120 * {@code javadoc.missed.html.close} 121 * </li> 122 * <li> 123 * {@code javadoc.parse.rule.error} 124 * </li> 125 * <li> 126 * {@code javadoc.wrong.singleton.html.tag} 127 * </li> 128 * <li> 129 * {@code tag.continuation.indent} 130 * </li> 131 * </ul> 132 * 133 * @since 6.0 134 * 135 */ 136@StatelessCheck 137public class JavadocTagContinuationIndentationCheck extends AbstractJavadocCheck { 138 139 /** 140 * A key is pointing to the warning message text in "messages.properties" 141 * file. 142 */ 143 public static final String MSG_KEY = "tag.continuation.indent"; 144 145 /** Default tag continuation indentation. */ 146 private static final int DEFAULT_INDENTATION = 4; 147 148 /** 149 * Specify how many spaces to use for new indentation level. 150 */ 151 private int offset = DEFAULT_INDENTATION; 152 153 /** 154 * Setter to specify how many spaces to use for new indentation level. 155 * 156 * @param offset custom value. 157 */ 158 public void setOffset(int offset) { 159 this.offset = offset; 160 } 161 162 @Override 163 public int[] getDefaultJavadocTokens() { 164 return new int[] {JavadocTokenTypes.DESCRIPTION }; 165 } 166 167 @Override 168 public int[] getRequiredJavadocTokens() { 169 return getAcceptableJavadocTokens(); 170 } 171 172 @Override 173 public void visitJavadocToken(DetailNode ast) { 174 if (!isInlineDescription(ast)) { 175 final List<DetailNode> textNodes = getAllNewlineNodes(ast); 176 for (DetailNode newlineNode : textNodes) { 177 final DetailNode textNode = JavadocUtil.getNextSibling(newlineNode); 178 if (textNode.getType() == JavadocTokenTypes.TEXT && isViolation(textNode)) { 179 log(textNode.getLineNumber(), MSG_KEY, offset); 180 } 181 } 182 } 183 } 184 185 /** 186 * Checks if a text node meets the criteria for a violation. 187 * If the text is shorter than {@code offset} characters, then a violation is 188 * detected if the text is not blank or the next node is not a newline. 189 * If the text is longer than {@code offset} characters, then a violation is 190 * detected if any of the first {@code offset} characters are not blank. 191 * 192 * @param textNode the node to check. 193 * @return true if the node has a violation. 194 */ 195 private boolean isViolation(DetailNode textNode) { 196 boolean result = false; 197 final String text = textNode.getText(); 198 if (text.length() <= offset) { 199 if (CommonUtil.isBlank(text)) { 200 final DetailNode nextNode = JavadocUtil.getNextSibling(textNode); 201 if (nextNode != null && nextNode.getType() != JavadocTokenTypes.NEWLINE) { 202 // text is blank but line hasn't ended yet 203 result = true; 204 } 205 } 206 else { 207 // text is not blank 208 result = true; 209 } 210 } 211 else if (!CommonUtil.isBlank(text.substring(1, offset + 1))) { 212 // first offset number of characters are not blank 213 result = true; 214 } 215 return result; 216 } 217 218 /** 219 * Finds and collects all NEWLINE nodes inside DESCRIPTION node. 220 * 221 * @param descriptionNode DESCRIPTION node. 222 * @return List with NEWLINE nodes. 223 */ 224 private static List<DetailNode> getAllNewlineNodes(DetailNode descriptionNode) { 225 final List<DetailNode> textNodes = new ArrayList<>(); 226 DetailNode node = JavadocUtil.getFirstChild(descriptionNode); 227 while (JavadocUtil.getNextSibling(node) != null) { 228 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) { 229 final DetailNode descriptionNodeChild = JavadocUtil.getFirstChild(node); 230 textNodes.addAll(getAllNewlineNodes(descriptionNodeChild)); 231 } 232 if (node.getType() == JavadocTokenTypes.LEADING_ASTERISK) { 233 textNodes.add(node); 234 } 235 node = JavadocUtil.getNextSibling(node); 236 } 237 return textNodes; 238 } 239 240 /** 241 * Checks, if description node is a description of in-line tag. 242 * 243 * @param description DESCRIPTION node. 244 * @return true, if description node is a description of in-line tag. 245 */ 246 private static boolean isInlineDescription(DetailNode description) { 247 boolean isInline = false; 248 DetailNode inlineTag = description.getParent(); 249 while (inlineTag != null) { 250 if (inlineTag.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) { 251 isInline = true; 252 break; 253 } 254 inlineTag = inlineTag.getParent(); 255 } 256 return isInline; 257 } 258 259}