View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 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   * @since 0.18.10
26   */
27  @SuppressWarnings("PMD.LongVariable")
28  public final class JavadocParameterOrderCheck extends AbstractCheck {
29  
30      /**
31       * Compiled regexp to match Javadoc tags that take an argument.
32       */
33      private static final Pattern MATCH_JAVADOC_ARG = Pattern.compile(
34          "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(param)\\s+(\\S+)\\s+\\S*"
35      );
36  
37      /**
38       * Compiled regexp to match first part of multilineJavadoc tags.
39       */
40      private static final Pattern MATCH_JAVADOC_ARG_MULTILINE_START =
41          Pattern.compile(
42              "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(param)\\s+(\\S+)\\s*$"
43          );
44  
45      /**
46       * Compiled regexp to look for a continuation of the comment.
47       */
48      private static final Pattern MATCH_JAVADOC_MULTILINE_CONT =
49          Pattern.compile("(\\*/|@|[^\\s\\*])");
50  
51      /**
52       * Multiline finished at end of comment.
53       */
54      private static final String END_JAVADOC = "*/";
55  
56      /**
57       * Multiline finished at next Javadoc.
58       */
59      private static final String NEXT_TAG = "@";
60  
61      @Override
62      public int[] getDefaultTokens() {
63          return new int[] {
64              TokenTypes.INTERFACE_DEF,
65              TokenTypes.CLASS_DEF,
66              TokenTypes.CTOR_DEF,
67              TokenTypes.METHOD_DEF,
68          };
69      }
70  
71      @Override
72      public int[] getAcceptableTokens() {
73          return this.getDefaultTokens();
74      }
75  
76      @Override
77      public int[] getRequiredTokens() {
78          return this.getDefaultTokens();
79      }
80  
81      @Override
82      @SuppressWarnings("deprecation")
83      public void visitToken(final DetailAST ast) {
84          final FileContents contents = this.getFileContents();
85          final TextBlock doc = contents.getJavadocBefore(ast.getLineNo());
86          if (doc != null) {
87              this.checkParameters(ast, doc);
88          }
89      }
90  
91      /**
92       * Returns the param tags in a javadoc comment.
93       * @param comment The Javadoc comment
94       * @return The param tags found
95       */
96      private static List<JavadocTag> getMethodTags(final TextBlock comment) {
97          final String[] lines = comment.getText();
98          final List<JavadocTag> tags = new LinkedList<>();
99          int current = comment.getStartLineNo() - 1;
100         final int start = comment.getStartColNo();
101         for (int line = 0; line < lines.length; line = line + 1) {
102             current = current + 1;
103             final Matcher docmatcher =
104                 JavadocParameterOrderCheck.MATCH_JAVADOC_ARG.matcher(lines[line]);
105             final Matcher multiline =
106                 JavadocParameterOrderCheck.MATCH_JAVADOC_ARG_MULTILINE_START
107                     .matcher(lines[line]);
108             if (docmatcher.find()) {
109                 final int col = calculateTagColumn(
110                     docmatcher, line, start
111                 );
112                 tags.add(
113                     new JavadocTag(
114                         current,
115                         col,
116                         docmatcher.group(1),
117                         docmatcher.group(2)
118                     )
119                 );
120             } else if (multiline.find()) {
121                 final int col =
122                     calculateTagColumn(
123                         multiline,
124                         line,
125                         start
126                     );
127                 tags.addAll(
128                     getMultilineArgTags(
129                         multiline,
130                         col,
131                         lines,
132                         line,
133                         current
134                     )
135                 );
136             }
137         }
138         return tags;
139     }
140 
141     /**
142      * Calculates column number using Javadoc tag matcher.
143      * @param matcher Found javadoc tag matcher
144      * @param line Line number of Javadoc tag in comment
145      * @param start Column number of Javadoc comment beginning
146      * @return Column number
147      */
148     private static int calculateTagColumn(
149         final Matcher matcher, final int line, final int start
150     ) {
151         int col = matcher.start(1) - 1;
152         if (line == 0) {
153             col += start;
154         }
155         return col;
156     }
157 
158     /**
159      * Gets multiline Javadoc tags with arguments.
160      * @param matcher Javadoc tag Matcher
161      * @param column Column number of Javadoc tag
162      * @param lines Comment text lines
163      * @param index Line number that contains the javadoc tag
164      * @param line Javadoc tag line number in file
165      * @return Javadoc tags with arguments
166      * @checkstyle ParameterNumberCheck (30 lines)
167      */
168     private static List<JavadocTag> getMultilineArgTags(
169         final Matcher matcher, final int column, final String[] lines,
170         final int index, final int line) {
171         final List<JavadocTag> tags = new ArrayList<>(0);
172         final String paramone = matcher.group(1);
173         final String paramtwo = matcher.group(2);
174         int remindex = index + 1;
175         while (remindex < lines.length) {
176             final Matcher multiline =
177                 JavadocParameterOrderCheck.MATCH_JAVADOC_MULTILINE_CONT
178                     .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 }