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.List;
17  import java.util.function.Consumer;
18  import java.util.regex.Matcher;
19  import java.util.regex.Pattern;
20  
21  /**
22   * Checks method parameters order to comply with what is defined in method
23   * javadoc.
24   *
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       * Default constructor.
53       */
54      public JavadocParameterOrderCheck() {
55          // nothing to initialize
56      }
57  
58      @Override
59      public int[] getDefaultTokens() {
60          return new int[] {
61              TokenTypes.INTERFACE_DEF,
62              TokenTypes.CLASS_DEF,
63              TokenTypes.CTOR_DEF,
64              TokenTypes.METHOD_DEF,
65          };
66      }
67  
68      @Override
69      public int[] getAcceptableTokens() {
70          return this.getDefaultTokens();
71      }
72  
73      @Override
74      public int[] getRequiredTokens() {
75          return this.getDefaultTokens();
76      }
77  
78      @Override
79      @SuppressWarnings("deprecation")
80      public void visitToken(final DetailAST ast) {
81          final FileContents contents = this.getFileContents();
82          final TextBlock doc = contents.getJavadocBefore(ast.getLineNo());
83          if (doc != null) {
84              this.checkParameters(ast, doc);
85          }
86      }
87  
88      private static List<JavadocTag> getMethodTags(final TextBlock comment) {
89          final String[] lines = comment.getText();
90          final List<JavadocTag> tags = new ArrayList<>(0);
91          int current = comment.getStartLineNo() - 1;
92          final int start = comment.getStartColNo();
93          for (int line = 0; line < lines.length; line = line + 1) {
94              current = current + 1;
95              final Matcher docmatcher =
96                  JavadocParameterOrderCheck.MATCH_JAVADOC_ARG.matcher(lines[line]);
97              final Matcher multiline =
98                  JavadocParameterOrderCheck.MATCH_JAVADOC_ARG_MULTILINE_START
99                      .matcher(lines[line]);
100             if (docmatcher.find()) {
101                 final int col = calculateTagColumn(
102                     docmatcher, line, start
103                 );
104                 tags.add(
105                     new JavadocTag(
106                         current,
107                         col,
108                         docmatcher.group(1),
109                         docmatcher.group(2)
110                     )
111                 );
112             } else if (multiline.find()) {
113                 final int col =
114                     calculateTagColumn(
115                         multiline,
116                         line,
117                         start
118                     );
119                 tags.addAll(
120                     getMultilineArgTags(
121                         multiline,
122                         col,
123                         lines,
124                         line,
125                         current
126                     )
127                 );
128             }
129         }
130         return tags;
131     }
132 
133     private static int calculateTagColumn(
134         final Matcher matcher, final int line, final int start
135     ) {
136         int col = matcher.start(1) - 1;
137         if (line == 0) {
138             col += start;
139         }
140         return col;
141     }
142 
143     private static List<JavadocTag> getMultilineArgTags(
144         final Matcher matcher, final int column, final String[] lines,
145         final int index, final int line) {
146         final List<JavadocTag> tags = new ArrayList<>(0);
147         final String paramone = matcher.group(1);
148         final String paramtwo = matcher.group(2);
149         int remindex = index + 1;
150         while (remindex < lines.length) {
151             final Matcher multiline =
152                 JavadocParameterOrderCheck.MATCH_JAVADOC_MULTILINE_CONT
153                     .matcher(lines[remindex]);
154             if (multiline.find()) {
155                 remindex = lines.length;
156                 final String lfin = multiline.group(1);
157                 if (!"@".equals(lfin) && !"*/".equals(lfin)) {
158                     tags.add(new JavadocTag(line, column, paramone, paramtwo));
159                 }
160             }
161             remindex = remindex + 1;
162         }
163         return tags;
164     }
165 
166     private void checkParameters(final DetailAST ast, final TextBlock doc) {
167         final List<JavadocTag> tags = getMethodTags(doc);
168         final Arguments args = new Arguments(ast);
169         final TypeParameters types = new TypeParameters(ast);
170         final int count = args.count() + types.count();
171         if (tags.size() == count) {
172             final Consumer<JavadocTag> logger = tag -> this.log(
173                 tag.getLineNo(),
174                 "Javadoc parameter order different than method signature"
175             );
176             args.checkOrder(tags, logger);
177             types.checkOrder(tags, logger);
178         } else {
179             this.log(
180                 ast.getLineNo(),
181                 "Number of javadoc parameters different than method signature"
182             );
183         }
184     }
185 }