001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.Optional; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import com.puppycrawl.tools.checkstyle.GlobalStatefulCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.DetailNode; 029import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 031 032/** 033 * <div> 034 * Checks the alignment of 035 * <a href="https://docs.oracle.com/en/java/javase/14/docs/specs/javadoc/doc-comment-spec.html#leading-asterisks"> 036 * leading asterisks</a> in a Javadoc comment. The Check ensures that leading asterisks 037 * are aligned vertically under the first asterisk ( * ) 038 * of opening Javadoc tag. The alignment of closing Javadoc tag ( */ ) is also checked. 039 * If a closing Javadoc tag contains non-whitespace character before it 040 * then it's alignment will be ignored. 041 * If the ending javadoc line contains a leading asterisk, then that leading asterisk's alignment 042 * will be considered, the closing Javadoc tag will be ignored. 043 * </div> 044 * 045 * <p> 046 * If you're using tabs then specify the the tab width in the 047 * <a href="https://checkstyle.org/config.html#tabWidth">tabWidth</a> property. 048 * </p> 049 * 050 * @since 10.18.0 051 */ 052@GlobalStatefulCheck 053public class JavadocLeadingAsteriskAlignCheck extends AbstractJavadocCheck { 054 055 /** 056 * A key is pointing to the warning message text in "messages.properties" 057 * file. 058 */ 059 public static final String MSG_KEY = "javadoc.asterisk.indentation"; 060 061 /** Specifies the line number of starting block of the javadoc comment. */ 062 private int javadocStartLineNumber; 063 064 /** Specifies the column number of starting block of the javadoc comment with tabs expanded. */ 065 private int expectedColumnNumberTabsExpanded; 066 067 /** Specifies the lines of the file being processed. */ 068 private String[] fileLines; 069 070 /** 071 * Creates a new {@code JavadocLeadingAsteriskAlignCheck} instance. 072 */ 073 public JavadocLeadingAsteriskAlignCheck() { 074 // no code by default 075 } 076 077 @Override 078 public int[] getDefaultJavadocTokens() { 079 return new int[] { 080 JavadocCommentsTokenTypes.LEADING_ASTERISK, 081 }; 082 } 083 084 @Override 085 public int[] getRequiredJavadocTokens() { 086 return getAcceptableJavadocTokens(); 087 } 088 089 @Override 090 public void beginJavadocTree(DetailNode rootAst) { 091 // this method processes and sets information of starting javadoc tag. 092 fileLines = getLines(); 093 final String startLine = fileLines[rootAst.getLineNumber() - 1]; 094 javadocStartLineNumber = rootAst.getLineNumber(); 095 expectedColumnNumberTabsExpanded = CommonUtil.lengthExpandedTabs( 096 startLine, rootAst.getColumnNumber() - 1, getTabWidth()); 097 } 098 099 @Override 100 public void visitJavadocToken(DetailNode ast) { 101 // this method checks the alignment of leading asterisks. 102 final boolean isJavadocStartingLine = ast.getLineNumber() == javadocStartLineNumber; 103 104 if (!isJavadocStartingLine) { 105 final Optional<Integer> leadingAsteriskColumnNumber = 106 getAsteriskColumnNumber(ast.getText()); 107 108 leadingAsteriskColumnNumber.ifPresent(columnNumber -> { 109 final int columnNumberTabsExpanded = CommonUtil.lengthExpandedTabs( 110 ast.getText(), columnNumber, getTabWidth()); 111 112 if (!hasValidAlignment( 113 expectedColumnNumberTabsExpanded, columnNumberTabsExpanded)) { 114 log(ast.getLineNumber(), columnNumber - 1, MSG_KEY, 115 columnNumberTabsExpanded, expectedColumnNumberTabsExpanded); 116 } 117 }); 118 } 119 } 120 121 @Override 122 public void finishJavadocTree(DetailNode rootAst) { 123 // this method checks the alignment of closing javadoc tag. 124 final DetailAST javadocEndToken = getBlockCommentAst().getLastChild(); 125 final String lastLine = fileLines[javadocEndToken.getLineNo() - 1]; 126 final Optional<Integer> endingBlockColumnNumber = getAsteriskColumnNumber(lastLine); 127 128 endingBlockColumnNumber 129 .filter(columnNumber -> columnNumber - 1 == javadocEndToken.getColumnNo()) 130 .ifPresent(columnNumber -> { 131 final int columnNumberTabsExpanded = CommonUtil.lengthExpandedTabs( 132 lastLine, columnNumber, getTabWidth()); 133 134 if (!hasValidAlignment( 135 expectedColumnNumberTabsExpanded, columnNumberTabsExpanded)) { 136 log(javadocEndToken, MSG_KEY, 137 columnNumberTabsExpanded, expectedColumnNumberTabsExpanded); 138 } 139 }); 140 } 141 142 /** 143 * Processes and returns an OptionalInt containing 144 * the column number of leading asterisk without tabs expanded. 145 * 146 * @param line javadoc comment line 147 * @return asterisk's column number 148 */ 149 private static Optional<Integer> getAsteriskColumnNumber(String line) { 150 final Pattern pattern = Pattern.compile("^(\\s*)\\*"); 151 final Matcher matcher = pattern.matcher(line); 152 153 // We may not always have a leading asterisk because a javadoc line can start with 154 // a non-whitespace character or the javadoc line can be empty. 155 // In such cases, there is no leading asterisk and Optional will be empty. 156 return Optional.of(matcher) 157 .filter(Matcher::find) 158 .map(matcherInstance -> matcherInstance.group(1)) 159 .map(groupLength -> groupLength.length() + 1); 160 } 161 162 /** 163 * Checks the column difference between 164 * expected column number and leading asterisk column number. 165 * 166 * @param expectedColNumber column number of javadoc starting token 167 * @param asteriskColNumber column number of leading asterisk 168 * @return true if the asterisk is aligned properly, false otherwise 169 */ 170 private static boolean hasValidAlignment(int expectedColNumber, 171 int asteriskColNumber) { 172 return expectedColNumber - asteriskColNumber == 0; 173 } 174 175}