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