View Javadoc
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   * <p>
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 ( &#42; )
38   * of opening Javadoc tag. The alignment of closing Javadoc tag ( &#42;/ ) 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   * </p>
44   * <p>
45   * If you're using tabs then specify the the tab width in the
46   * <a href="https://checkstyle.org/config.html#tabWidth">tabWidth</a> property.
47   * </p>
48   * <ul>
49   * <li>
50   * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations if the
51   * Javadoc being examined by this check violates the tight html rules defined at
52   * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>.
53   * Type is {@code boolean}.
54   * Default value is {@code false}.
55   * </li>
56   * </ul>
57   * <p>
58   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
59   * </p>
60   * <p>
61   * Violation Message Keys:
62   * </p>
63   * <ul>
64   * <li>
65   * {@code javadoc.asterisk.indentation}
66   * </li>
67   * <li>
68   * {@code javadoc.missed.html.close}
69   * </li>
70   * <li>
71   * {@code javadoc.parse.rule.error}
72   * </li>
73   * <li>
74   * {@code javadoc.unclosedHtml}
75   * </li>
76   * <li>
77   * {@code javadoc.wrong.singleton.html.tag}
78   * </li>
79   * </ul>
80   *
81   * @since 10.18.0
82   */
83  @GlobalStatefulCheck
84  public class JavadocLeadingAsteriskAlignCheck extends AbstractJavadocCheck {
85  
86      /**
87       * A key is pointing to the warning message text in "messages.properties"
88       * file.
89       */
90      public static final String MSG_KEY = "javadoc.asterisk.indentation";
91  
92      /** Specifies the line number of starting block of the javadoc comment. */
93      private int javadocStartLineNumber;
94  
95      /** Specifies the column number of starting block of the javadoc comment with tabs expanded. */
96      private int expectedColumnNumberTabsExpanded;
97  
98      /**
99       * Specifies the column number of the leading asterisk
100      * without tabs expanded.
101      */
102     private int expectedColumnNumberWithoutExpandedTabs;
103 
104     /** Specifies the lines of the file being processed. */
105     private String[] fileLines;
106 
107     @Override
108     public int[] getDefaultJavadocTokens() {
109         return new int[] {
110             JavadocTokenTypes.LEADING_ASTERISK,
111         };
112     }
113 
114     @Override
115     public int[] getRequiredJavadocTokens() {
116         return getAcceptableJavadocTokens();
117     }
118 
119     @Override
120     public void beginJavadocTree(DetailNode rootAst) {
121         // this method processes and sets information of starting javadoc tag.
122         fileLines = getLines();
123         final String startLine = fileLines[rootAst.getLineNumber() - 1];
124         javadocStartLineNumber = rootAst.getLineNumber();
125         expectedColumnNumberTabsExpanded = CommonUtil.lengthExpandedTabs(
126             startLine, rootAst.getColumnNumber() - 1, getTabWidth());
127     }
128 
129     @Override
130     public void visitJavadocToken(DetailNode ast) {
131         // this method checks the alignment of leading asterisks.
132         final boolean isJavadocStartingLine = ast.getLineNumber() == javadocStartLineNumber;
133 
134         if (!isJavadocStartingLine) {
135             final Optional<Integer> leadingAsteriskColumnNumber =
136                                         getAsteriskColumnNumber(ast.getText());
137 
138             leadingAsteriskColumnNumber
139                     .map(columnNumber -> expandedTabs(ast.getText(), columnNumber))
140                     .filter(columnNumber -> {
141                         return !hasValidAlignment(expectedColumnNumberTabsExpanded, columnNumber);
142                     })
143                     .ifPresent(columnNumber -> {
144                         logViolation(ast.getLineNumber(),
145                                 columnNumber,
146                                 expectedColumnNumberTabsExpanded);
147                     });
148         }
149     }
150 
151     @Override
152     public void finishJavadocTree(DetailNode rootAst) {
153         // this method checks the alignment of closing javadoc tag.
154         final DetailAST javadocEndToken = getBlockCommentAst().getLastChild();
155         final String lastLine = fileLines[javadocEndToken.getLineNo() - 1];
156         final Optional<Integer> endingBlockColumnNumber = getAsteriskColumnNumber(lastLine);
157 
158         endingBlockColumnNumber
159                 .map(columnNumber -> expandedTabs(lastLine, columnNumber))
160                 .filter(columnNumber -> {
161                     return !hasValidAlignment(expectedColumnNumberTabsExpanded, columnNumber);
162                 })
163                 .ifPresent(columnNumber -> {
164                     logViolation(javadocEndToken.getLineNo(),
165                             columnNumber,
166                             expectedColumnNumberTabsExpanded);
167                 });
168     }
169 
170     /**
171      * Processes and returns the column number of
172      * leading asterisk with tabs expanded.
173      * Also sets 'expectedColumnNumberWithoutExpandedTabs' if the leading asterisk is present.
174      *
175      * @param line javadoc comment line
176      * @param columnNumber column number of leading asterisk
177      * @return column number of leading asterisk with tabs expanded
178      */
179     private int expandedTabs(String line, int columnNumber) {
180         expectedColumnNumberWithoutExpandedTabs = columnNumber - 1;
181         return CommonUtil.lengthExpandedTabs(
182                     line, columnNumber, getTabWidth());
183     }
184 
185     /**
186      * Processes and returns an OptionalInt containing
187      * the column number of leading asterisk without tabs expanded.
188      *
189      * @param line javadoc comment line
190      * @return asterisk's column number
191      */
192     private static Optional<Integer> getAsteriskColumnNumber(String line) {
193         final Pattern pattern = Pattern.compile("^(\\s*)\\*");
194         final Matcher matcher = pattern.matcher(line);
195 
196         // We may not always have a leading asterisk because a javadoc line can start with
197         // a non-whitespace character or the javadoc line can be empty.
198         // In such cases, there is no leading asterisk and Optional will be empty.
199         return Optional.of(matcher)
200                 .filter(Matcher::find)
201                 .map(matcherInstance -> matcherInstance.group(1))
202                 .map(groupLength -> groupLength.length() + 1);
203     }
204 
205     /**
206      * Checks alignment of asterisks and logs violations.
207      *
208      * @param lineNumber line number of current comment line
209      * @param asteriskColNumber column number of leading asterisk
210      * @param expectedColNumber column number of javadoc starting token
211      */
212     private void logViolation(int lineNumber,
213                               int asteriskColNumber,
214                               int expectedColNumber) {
215 
216         log(lineNumber,
217             expectedColumnNumberWithoutExpandedTabs,
218             MSG_KEY,
219             asteriskColNumber,
220             expectedColNumber);
221     }
222 
223     /**
224      * Checks the column difference between
225      * expected column number and leading asterisk column number.
226      *
227      * @param expectedColNumber column number of javadoc starting token
228      * @param asteriskColNumber column number of leading asterisk
229      * @return true if the asterisk is aligned properly, false otherwise
230      */
231     private static boolean hasValidAlignment(int expectedColNumber,
232                                              int asteriskColNumber) {
233         return expectedColNumber - asteriskColNumber == 0;
234     }
235 }