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   * <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 ( &#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   * </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 }