View Javadoc
1   /*
2    * Copyright (c) 2011-2025 Yegor Bugayenko
3    *
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions
8    * are met: 1) Redistributions of source code must retain the above
9    * copyright notice, this list of conditions and the following
10   * disclaimer. 2) Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following
12   * disclaimer in the documentation and/or other materials provided
13   * with the distribution. 3) Neither the name of the Qulice.com nor
14   * the names of its contributors may be used to endorse or promote
15   * products derived from this software without specific prior written
16   * permission.
17   *
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
20   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29   * OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  package com.qulice.checkstyle;
32  
33  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
34  import com.puppycrawl.tools.checkstyle.api.DetailAST;
35  import com.puppycrawl.tools.checkstyle.api.FileContents;
36  import com.puppycrawl.tools.checkstyle.api.TextBlock;
37  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
38  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
39  import com.qulice.checkstyle.parameters.Arguments;
40  import com.qulice.checkstyle.parameters.TypeParameters;
41  import java.util.ArrayList;
42  import java.util.LinkedList;
43  import java.util.List;
44  import java.util.function.Consumer;
45  import java.util.regex.Matcher;
46  import java.util.regex.Pattern;
47  
48  /**
49   * Checks method parameters order to comply with what is defined in method
50   * javadoc.
51   *
52   * @since 0.18.10
53   */
54  @SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops", "PMD.LongVariable"})
55  public final class JavadocParameterOrderCheck extends AbstractCheck {
56  
57      /**
58       * Compiled regexp to match Javadoc tags that take an argument.
59       */
60      private static final Pattern MATCH_JAVADOC_ARG = Pattern.compile(
61          "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(param)\\s+(\\S+)\\s+\\S*"
62      );
63  
64      /**
65       * Compiled regexp to match first part of multilineJavadoc tags.
66       */
67      private static final Pattern MATCH_JAVADOC_ARG_MULTILINE_START =
68          Pattern.compile(
69              "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(param)\\s+(\\S+)\\s*$"
70          );
71  
72      /**
73       * Compiled regexp to look for a continuation of the comment.
74       */
75      private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
76          Pattern.compile("(\\*/|@|[^\\s\\*])");
77  
78      /**
79       * Multiline finished at end of comment.
80       */
81      private static final String END_JAVADOC = "*/";
82  
83      /**
84       * Multiline finished at next Javadoc.
85       */
86      private static final String NEXT_TAG = "@";
87  
88      @Override
89      public int[] getDefaultTokens() {
90          return new int[] {
91              TokenTypes.INTERFACE_DEF,
92              TokenTypes.CLASS_DEF,
93              TokenTypes.CTOR_DEF,
94              TokenTypes.METHOD_DEF,
95          };
96      }
97  
98      @Override
99      public int[] getAcceptableTokens() {
100         return this.getDefaultTokens();
101     }
102 
103     @Override
104     public int[] getRequiredTokens() {
105         return this.getDefaultTokens();
106     }
107 
108     @Override
109     @SuppressWarnings("deprecation")
110     public void visitToken(final DetailAST ast) {
111         final FileContents contents = this.getFileContents();
112         final TextBlock doc = contents.getJavadocBefore(ast.getLineNo());
113         if (doc != null) {
114             this.checkParameters(ast, doc);
115         }
116     }
117 
118     /**
119      * Returns the param tags in a javadoc comment.
120      *
121      * @param comment The Javadoc comment
122      * @return The param tags found
123      */
124     private static List<JavadocTag> getMethodTags(final TextBlock comment) {
125         final String[] lines = comment.getText();
126         final List<JavadocTag> tags = new LinkedList<>();
127         int current = comment.getStartLineNo() - 1;
128         final int start = comment.getStartColNo();
129         for (int line = 0; line < lines.length; line = line + 1) {
130             current = current + 1;
131             final Matcher docmatcher =
132                 MATCH_JAVADOC_ARG.matcher(lines[line]);
133             final Matcher multiline =
134                 MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[line]);
135             if (docmatcher.find()) {
136                 final int col = calculateTagColumn(
137                     docmatcher, line, start
138                 );
139                 tags.add(
140                     new JavadocTag(
141                         current,
142                         col,
143                         docmatcher.group(1),
144                         docmatcher.group(2)
145                     )
146                 );
147             } else if (multiline.find()) {
148                 final int col =
149                     calculateTagColumn(
150                         multiline,
151                         line,
152                         start
153                     );
154                 tags.addAll(
155                     getMultilineArgTags(
156                         multiline,
157                         col,
158                         lines,
159                         line,
160                         current
161                     )
162                 );
163             }
164         }
165         return tags;
166     }
167 
168     /**
169      * Calculates column number using Javadoc tag matcher.
170      * @param matcher Found javadoc tag matcher
171      * @param line Line number of Javadoc tag in comment
172      * @param start Column number of Javadoc comment beginning
173      * @return Column number
174      */
175     private static int calculateTagColumn(
176         final Matcher matcher, final int line, final int start
177     ) {
178         int col = matcher.start(1) - 1;
179         if (line == 0) {
180             col += start;
181         }
182         return col;
183     }
184 
185     /**
186      * Gets multiline Javadoc tags with arguments.
187      * @param matcher Javadoc tag Matcher
188      * @param column Column number of Javadoc tag
189      * @param lines Comment text lines
190      * @param index Line number that contains the javadoc tag
191      * @param line Javadoc tag line number in file
192      * @return Javadoc tags with arguments
193      * @checkstyle ParameterNumberCheck (30 lines)
194      */
195     private static List<JavadocTag> getMultilineArgTags(
196         final Matcher matcher, final int column, final String[] lines,
197         final int index, final int line) {
198         final List<JavadocTag> tags = new ArrayList<>(0);
199         final String paramone = matcher.group(1);
200         final String paramtwo = matcher.group(2);
201         int remindex = index + 1;
202         while (remindex < lines.length) {
203             final Matcher multiline =
204                 MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remindex]);
205             if (multiline.find()) {
206                 remindex = lines.length;
207                 final String lfin = multiline.group(1);
208                 if (!JavadocParameterOrderCheck.NEXT_TAG.equals(lfin)
209                     && !JavadocParameterOrderCheck.END_JAVADOC.equals(lfin)) {
210                     tags.add(new JavadocTag(line, column, paramone, paramtwo));
211                 }
212             }
213             remindex = remindex + 1;
214         }
215         return tags;
216     }
217 
218     /**
219      * Checks method parameters order to comply with what is defined in method
220      * javadoc.
221      * @param ast The method node.
222      * @param doc Javadoc text block.
223      */
224     private void checkParameters(final DetailAST ast, final TextBlock doc) {
225         final List<JavadocTag> tags = getMethodTags(doc);
226         final Arguments args = new Arguments(ast);
227         final TypeParameters types = new TypeParameters(ast);
228         final int count = args.count() + types.count();
229         if (tags.size() == count) {
230             final Consumer<JavadocTag> logger = tag -> this.log(
231                 tag.getLineNo(),
232                 "Javadoc parameter order different than method signature"
233             );
234             args.checkOrder(tags, logger);
235             types.checkOrder(tags, logger);
236         } else {
237             this.log(
238                 ast.getLineNo(),
239                 "Number of javadoc parameters different than method signature"
240             );
241         }
242     }
243 }