View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8   import com.puppycrawl.tools.checkstyle.api.DetailAST;
9   import com.puppycrawl.tools.checkstyle.api.FileContents;
10  import com.puppycrawl.tools.checkstyle.api.TextBlock;
11  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
12  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
13  import com.qulice.checkstyle.parameters.Arguments;
14  import com.qulice.checkstyle.parameters.TypeParameters;
15  import java.util.ArrayList;
16  import java.util.LinkedList;
17  import java.util.List;
18  import java.util.function.Consumer;
19  import java.util.regex.Matcher;
20  import java.util.regex.Pattern;
21  
22  /**
23   * Checks method parameters order to comply with what is defined in method
24   * javadoc.
25   *
26   * @since 0.18.10
27   */
28  @SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops", "PMD.LongVariable"})
29  public final class JavadocParameterOrderCheck extends AbstractCheck {
30  
31      /**
32       * Compiled regexp to match Javadoc tags that take an argument.
33       */
34      private static final Pattern MATCH_JAVADOC_ARG = Pattern.compile(
35          "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(param)\\s+(\\S+)\\s+\\S*"
36      );
37  
38      /**
39       * Compiled regexp to match first part of multilineJavadoc tags.
40       */
41      private static final Pattern MATCH_JAVADOC_ARG_MULTILINE_START =
42          Pattern.compile(
43              "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(param)\\s+(\\S+)\\s*$"
44          );
45  
46      /**
47       * Compiled regexp to look for a continuation of the comment.
48       */
49      private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
50          Pattern.compile("(\\*/|@|[^\\s\\*])");
51  
52      /**
53       * Multiline finished at end of comment.
54       */
55      private static final String END_JAVADOC = "*/";
56  
57      /**
58       * Multiline finished at next Javadoc.
59       */
60      private static final String NEXT_TAG = "@";
61  
62      @Override
63      public int[] getDefaultTokens() {
64          return new int[] {
65              TokenTypes.INTERFACE_DEF,
66              TokenTypes.CLASS_DEF,
67              TokenTypes.CTOR_DEF,
68              TokenTypes.METHOD_DEF,
69          };
70      }
71  
72      @Override
73      public int[] getAcceptableTokens() {
74          return this.getDefaultTokens();
75      }
76  
77      @Override
78      public int[] getRequiredTokens() {
79          return this.getDefaultTokens();
80      }
81  
82      @Override
83      @SuppressWarnings("deprecation")
84      public void visitToken(final DetailAST ast) {
85          final FileContents contents = this.getFileContents();
86          final TextBlock doc = contents.getJavadocBefore(ast.getLineNo());
87          if (doc != null) {
88              this.checkParameters(ast, doc);
89          }
90      }
91  
92      /**
93       * Returns the param tags in a javadoc comment.
94       *
95       * @param comment The Javadoc comment
96       * @return The param tags found
97       */
98      private static List<JavadocTag> getMethodTags(final TextBlock comment) {
99          final String[] lines = comment.getText();
100         final List<JavadocTag> tags = new LinkedList<>();
101         int current = comment.getStartLineNo() - 1;
102         final int start = comment.getStartColNo();
103         for (int line = 0; line < lines.length; line = line + 1) {
104             current = current + 1;
105             final Matcher docmatcher =
106                 MATCH_JAVADOC_ARG.matcher(lines[line]);
107             final Matcher multiline =
108                 MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[line]);
109             if (docmatcher.find()) {
110                 final int col = calculateTagColumn(
111                     docmatcher, line, start
112                 );
113                 tags.add(
114                     new JavadocTag(
115                         current,
116                         col,
117                         docmatcher.group(1),
118                         docmatcher.group(2)
119                     )
120                 );
121             } else if (multiline.find()) {
122                 final int col =
123                     calculateTagColumn(
124                         multiline,
125                         line,
126                         start
127                     );
128                 tags.addAll(
129                     getMultilineArgTags(
130                         multiline,
131                         col,
132                         lines,
133                         line,
134                         current
135                     )
136                 );
137             }
138         }
139         return tags;
140     }
141 
142     /**
143      * Calculates column number using Javadoc tag matcher.
144      * @param matcher Found javadoc tag matcher
145      * @param line Line number of Javadoc tag in comment
146      * @param start Column number of Javadoc comment beginning
147      * @return Column number
148      */
149     private static int calculateTagColumn(
150         final Matcher matcher, final int line, final int start
151     ) {
152         int col = matcher.start(1) - 1;
153         if (line == 0) {
154             col += start;
155         }
156         return col;
157     }
158 
159     /**
160      * Gets multiline Javadoc tags with arguments.
161      * @param matcher Javadoc tag Matcher
162      * @param column Column number of Javadoc tag
163      * @param lines Comment text lines
164      * @param index Line number that contains the javadoc tag
165      * @param line Javadoc tag line number in file
166      * @return Javadoc tags with arguments
167      * @checkstyle ParameterNumberCheck (30 lines)
168      */
169     private static List<JavadocTag> getMultilineArgTags(
170         final Matcher matcher, final int column, final String[] lines,
171         final int index, final int line) {
172         final List<JavadocTag> tags = new ArrayList<>(0);
173         final String paramone = matcher.group(1);
174         final String paramtwo = matcher.group(2);
175         int remindex = index + 1;
176         while (remindex < lines.length) {
177             final Matcher multiline =
178                 MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remindex]);
179             if (multiline.find()) {
180                 remindex = lines.length;
181                 final String lfin = multiline.group(1);
182                 if (!JavadocParameterOrderCheck.NEXT_TAG.equals(lfin)
183                     && !JavadocParameterOrderCheck.END_JAVADOC.equals(lfin)) {
184                     tags.add(new JavadocTag(line, column, paramone, paramtwo));
185                 }
186             }
187             remindex = remindex + 1;
188         }
189         return tags;
190     }
191 
192     /**
193      * Checks method parameters order to comply with what is defined in method
194      * javadoc.
195      * @param ast The method node.
196      * @param doc Javadoc text block.
197      */
198     private void checkParameters(final DetailAST ast, final TextBlock doc) {
199         final List<JavadocTag> tags = getMethodTags(doc);
200         final Arguments args = new Arguments(ast);
201         final TypeParameters types = new TypeParameters(ast);
202         final int count = args.count() + types.count();
203         if (tags.size() == count) {
204             final Consumer<JavadocTag> logger = tag -> this.log(
205                 tag.getLineNo(),
206                 "Javadoc parameter order different than method signature"
207             );
208             args.checkOrder(tags, logger);
209             types.checkOrder(tags, logger);
210         } else {
211             this.log(
212                 ast.getLineNo(),
213                 "Number of javadoc parameters different than method signature"
214             );
215         }
216     }
217 }