001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 offset} - Specify how many spaces to use for new indentation level. 042 * Type is {@code int}. 043 * Default value is {@code 4}. 044 * </li> 045 * <li> 046 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations 047 * if the Javadoc being examined by this check violates the tight html rules defined at 048 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>. 049 * Type is {@code boolean}. 050 * Default value is {@code false}. 051 * </li> 052 * </ul> 053 * <p> 054 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 055 * </p> 056 * <p> 057 * Violation Message Keys: 058 * </p> 059 * <ul> 060 * <li> 061 * {@code javadoc.missed.html.close} 062 * </li> 063 * <li> 064 * {@code javadoc.parse.rule.error} 065 * </li> 066 * <li> 067 * {@code javadoc.unclosedHtml} 068 * </li> 069 * <li> 070 * {@code javadoc.wrong.singleton.html.tag} 071 * </li> 072 * <li> 073 * {@code tag.continuation.indent} 074 * </li> 075 * </ul> 076 * 077 * @since 6.0 078 * 079 */ 080@StatelessCheck 081public class JavadocTagContinuationIndentationCheck extends AbstractJavadocCheck { 082 083 /** 084 * A key is pointing to the warning message text in "messages.properties" 085 * file. 086 */ 087 public static final String MSG_KEY = "tag.continuation.indent"; 088 089 /** Default tag continuation indentation. */ 090 private static final int DEFAULT_INDENTATION = 4; 091 092 /** 093 * Specify how many spaces to use for new indentation level. 094 */ 095 private int offset = DEFAULT_INDENTATION; 096 097 /** 098 * Setter to specify how many spaces to use for new indentation level. 099 * 100 * @param offset custom value. 101 * @since 6.0 102 */ 103 public void setOffset(int offset) { 104 this.offset = offset; 105 } 106 107 @Override 108 public int[] getDefaultJavadocTokens() { 109 return new int[] {JavadocTokenTypes.DESCRIPTION }; 110 } 111 112 @Override 113 public int[] getRequiredJavadocTokens() { 114 return getAcceptableJavadocTokens(); 115 } 116 117 @Override 118 public void visitJavadocToken(DetailNode ast) { 119 if (!isInlineDescription(ast)) { 120 final List<DetailNode> textNodes = getAllNewlineNodes(ast); 121 for (DetailNode newlineNode : textNodes) { 122 final DetailNode textNode = JavadocUtil.getNextSibling(newlineNode); 123 if (textNode.getType() == JavadocTokenTypes.TEXT && isViolation(textNode)) { 124 log(textNode.getLineNumber(), MSG_KEY, offset); 125 } 126 } 127 } 128 } 129 130 /** 131 * Checks if a text node meets the criteria for a violation. 132 * If the text is shorter than {@code offset} characters, then a violation is 133 * detected if the text is not blank or the next node is not a newline. 134 * If the text is longer than {@code offset} characters, then a violation is 135 * detected if any of the first {@code offset} characters are not blank. 136 * 137 * @param textNode the node to check. 138 * @return true if the node has a violation. 139 */ 140 private boolean isViolation(DetailNode textNode) { 141 boolean result = false; 142 final String text = textNode.getText(); 143 if (text.length() <= offset) { 144 if (CommonUtil.isBlank(text)) { 145 final DetailNode nextNode = JavadocUtil.getNextSibling(textNode); 146 if (nextNode != null && nextNode.getType() != JavadocTokenTypes.NEWLINE) { 147 // text is blank but line hasn't ended yet 148 result = true; 149 } 150 } 151 else { 152 // text is not blank 153 result = true; 154 } 155 } 156 else if (!CommonUtil.isBlank(text.substring(1, offset + 1))) { 157 // first offset number of characters are not blank 158 result = true; 159 } 160 return result; 161 } 162 163 /** 164 * Finds and collects all NEWLINE nodes inside DESCRIPTION node. 165 * 166 * @param descriptionNode DESCRIPTION node. 167 * @return List with NEWLINE nodes. 168 */ 169 private static List<DetailNode> getAllNewlineNodes(DetailNode descriptionNode) { 170 final List<DetailNode> textNodes = new ArrayList<>(); 171 DetailNode node = JavadocUtil.getFirstChild(descriptionNode); 172 while (JavadocUtil.getNextSibling(node) != null) { 173 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) { 174 final DetailNode descriptionNodeChild = JavadocUtil.getFirstChild(node); 175 textNodes.addAll(getAllNewlineNodes(descriptionNodeChild)); 176 } 177 if (node.getType() == JavadocTokenTypes.LEADING_ASTERISK) { 178 textNodes.add(node); 179 } 180 node = JavadocUtil.getNextSibling(node); 181 } 182 return textNodes; 183 } 184 185 /** 186 * Checks, if description node is a description of in-line tag. 187 * 188 * @param description DESCRIPTION node. 189 * @return true, if description node is a description of in-line tag. 190 */ 191 private static boolean isInlineDescription(DetailNode description) { 192 boolean isInline = false; 193 DetailNode currentNode = description; 194 while (currentNode != null) { 195 if (currentNode.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) { 196 isInline = true; 197 break; 198 } 199 currentNode = currentNode.getParent(); 200 } 201 return isInline; 202 } 203 204}