1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.javadoc; 21 22 import java.util.Optional; 23 import java.util.regex.Matcher; 24 import java.util.regex.Pattern; 25 26 import com.puppycrawl.tools.checkstyle.GlobalStatefulCheck; 27 import com.puppycrawl.tools.checkstyle.api.DetailAST; 28 import com.puppycrawl.tools.checkstyle.api.DetailNode; 29 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 30 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 31 32 /** 33 * <div> 34 * Checks the alignment of 35 * <a href="https://docs.oracle.com/en/java/javase/14/docs/specs/javadoc/doc-comment-spec.html#leading-asterisks"> 36 * leading asterisks</a> in a Javadoc comment. The Check ensures that leading asterisks 37 * are aligned vertically under the first asterisk ( * ) 38 * of opening Javadoc tag. The alignment of closing Javadoc tag ( */ ) is also checked. 39 * If a closing Javadoc tag contains non-whitespace character before it 40 * then it's alignment will be ignored. 41 * If the ending javadoc line contains a leading asterisk, then that leading asterisk's alignment 42 * will be considered, the closing Javadoc tag will be ignored. 43 * </div> 44 * 45 * <p> 46 * If you're using tabs then specify the the tab width in the 47 * <a href="https://checkstyle.org/config.html#tabWidth">tabWidth</a> property. 48 * </p> 49 * <ul> 50 * <li> 51 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations if the 52 * Javadoc being examined by this check violates the tight html rules defined at 53 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>. 54 * Type is {@code boolean}. 55 * Default value is {@code false}. 56 * </li> 57 * </ul> 58 * 59 * <p> 60 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 61 * </p> 62 * 63 * <p> 64 * Violation Message Keys: 65 * </p> 66 * <ul> 67 * <li> 68 * {@code javadoc.asterisk.indentation} 69 * </li> 70 * <li> 71 * {@code javadoc.missed.html.close} 72 * </li> 73 * <li> 74 * {@code javadoc.parse.rule.error} 75 * </li> 76 * <li> 77 * {@code javadoc.unclosedHtml} 78 * </li> 79 * <li> 80 * {@code javadoc.wrong.singleton.html.tag} 81 * </li> 82 * </ul> 83 * 84 * @since 10.18.0 85 */ 86 @GlobalStatefulCheck 87 public class JavadocLeadingAsteriskAlignCheck extends AbstractJavadocCheck { 88 89 /** 90 * A key is pointing to the warning message text in "messages.properties" 91 * file. 92 */ 93 public static final String MSG_KEY = "javadoc.asterisk.indentation"; 94 95 /** Specifies the line number of starting block of the javadoc comment. */ 96 private int javadocStartLineNumber; 97 98 /** Specifies the column number of starting block of the javadoc comment with tabs expanded. */ 99 private int expectedColumnNumberTabsExpanded; 100 101 /** 102 * Specifies the column number of the leading asterisk 103 * without tabs expanded. 104 */ 105 private int expectedColumnNumberWithoutExpandedTabs; 106 107 /** Specifies the lines of the file being processed. */ 108 private String[] fileLines; 109 110 @Override 111 public int[] getDefaultJavadocTokens() { 112 return new int[] { 113 JavadocTokenTypes.LEADING_ASTERISK, 114 }; 115 } 116 117 @Override 118 public int[] getRequiredJavadocTokens() { 119 return getAcceptableJavadocTokens(); 120 } 121 122 @Override 123 public void beginJavadocTree(DetailNode rootAst) { 124 // this method processes and sets information of starting javadoc tag. 125 fileLines = getLines(); 126 final String startLine = fileLines[rootAst.getLineNumber() - 1]; 127 javadocStartLineNumber = rootAst.getLineNumber(); 128 expectedColumnNumberTabsExpanded = CommonUtil.lengthExpandedTabs( 129 startLine, rootAst.getColumnNumber() - 1, getTabWidth()); 130 } 131 132 @Override 133 public void visitJavadocToken(DetailNode ast) { 134 // this method checks the alignment of leading asterisks. 135 final boolean isJavadocStartingLine = ast.getLineNumber() == javadocStartLineNumber; 136 137 if (!isJavadocStartingLine) { 138 final Optional<Integer> leadingAsteriskColumnNumber = 139 getAsteriskColumnNumber(ast.getText()); 140 141 leadingAsteriskColumnNumber 142 .map(columnNumber -> expandedTabs(ast.getText(), columnNumber)) 143 .filter(columnNumber -> { 144 return !hasValidAlignment(expectedColumnNumberTabsExpanded, columnNumber); 145 }) 146 .ifPresent(columnNumber -> { 147 logViolation(ast.getLineNumber(), 148 columnNumber, 149 expectedColumnNumberTabsExpanded); 150 }); 151 } 152 } 153 154 @Override 155 public void finishJavadocTree(DetailNode rootAst) { 156 // this method checks the alignment of closing javadoc tag. 157 final DetailAST javadocEndToken = getBlockCommentAst().getLastChild(); 158 final String lastLine = fileLines[javadocEndToken.getLineNo() - 1]; 159 final Optional<Integer> endingBlockColumnNumber = getAsteriskColumnNumber(lastLine); 160 161 endingBlockColumnNumber 162 .map(columnNumber -> expandedTabs(lastLine, columnNumber)) 163 .filter(columnNumber -> { 164 return !hasValidAlignment(expectedColumnNumberTabsExpanded, columnNumber); 165 }) 166 .ifPresent(columnNumber -> { 167 logViolation(javadocEndToken.getLineNo(), 168 columnNumber, 169 expectedColumnNumberTabsExpanded); 170 }); 171 } 172 173 /** 174 * Processes and returns the column number of 175 * leading asterisk with tabs expanded. 176 * Also sets 'expectedColumnNumberWithoutExpandedTabs' if the leading asterisk is present. 177 * 178 * @param line javadoc comment line 179 * @param columnNumber column number of leading asterisk 180 * @return column number of leading asterisk with tabs expanded 181 */ 182 private int expandedTabs(String line, int columnNumber) { 183 expectedColumnNumberWithoutExpandedTabs = columnNumber - 1; 184 return CommonUtil.lengthExpandedTabs( 185 line, columnNumber, getTabWidth()); 186 } 187 188 /** 189 * Processes and returns an OptionalInt containing 190 * the column number of leading asterisk without tabs expanded. 191 * 192 * @param line javadoc comment line 193 * @return asterisk's column number 194 */ 195 private static Optional<Integer> getAsteriskColumnNumber(String line) { 196 final Pattern pattern = Pattern.compile("^(\\s*)\\*"); 197 final Matcher matcher = pattern.matcher(line); 198 199 // We may not always have a leading asterisk because a javadoc line can start with 200 // a non-whitespace character or the javadoc line can be empty. 201 // In such cases, there is no leading asterisk and Optional will be empty. 202 return Optional.of(matcher) 203 .filter(Matcher::find) 204 .map(matcherInstance -> matcherInstance.group(1)) 205 .map(groupLength -> groupLength.length() + 1); 206 } 207 208 /** 209 * Checks alignment of asterisks and logs violations. 210 * 211 * @param lineNumber line number of current comment line 212 * @param asteriskColNumber column number of leading asterisk 213 * @param expectedColNumber column number of javadoc starting token 214 */ 215 private void logViolation(int lineNumber, 216 int asteriskColNumber, 217 int expectedColNumber) { 218 219 log(lineNumber, 220 expectedColumnNumberWithoutExpandedTabs, 221 MSG_KEY, 222 asteriskColNumber, 223 expectedColNumber); 224 } 225 226 /** 227 * Checks the column difference between 228 * expected column number and leading asterisk column number. 229 * 230 * @param expectedColNumber column number of javadoc starting token 231 * @param asteriskColNumber column number of leading asterisk 232 * @return true if the asterisk is aligned properly, false otherwise 233 */ 234 private static boolean hasValidAlignment(int expectedColNumber, 235 int asteriskColNumber) { 236 return expectedColNumber - asteriskColNumber == 0; 237 } 238 }