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 java.util.HashMap;
8   import java.util.Map;
9   import java.util.regex.Matcher;
10  import java.util.regex.Pattern;
11  
12  /**
13   * Check the required JavaDoc tag in the lines.
14   * <p>Correct format is the following (of a class javadoc):
15   *
16   * <pre>
17   * &#47;**
18   *  * This is my new class.
19   *  *
20   *  * &#64;since 0.3
21   *  *&#47;
22   * public final class Foo {
23   *     &#47;**
24   *      * This is my other class.
25   *      *
26   *      *    &#64;since    0.3
27   *      *&#47;
28   *     public final class Boo {
29   *     // ...
30   * </pre>
31   *
32   * @since 0.23.1
33   */
34  final class RequiredJavaDocTag {
35  
36      /**
37       * Tag name.
38       */
39      private final String name;
40  
41      /**
42       * Pattern for searching a tag in a string.
43       */
44      private final Pattern tag;
45  
46      /**
47       * Pattern for checking the contents of a tag in a string.
48       */
49      private final Pattern content;
50  
51      /**
52       * Reference to a method for writing a message to the log.
53       */
54      private final Reporter reporter;
55  
56      /**
57       * Ctor.
58       * @param cname Tag name
59       * @param ptag Pattern for searching a tag in a string
60       * @param patt Pattern for checking the contents of a tag in a string
61       * @param rep Reference to a method for writing a message to the log
62       * @checkstyle ParameterNumberCheck (3 lines)
63       */
64      RequiredJavaDocTag(
65          final String cname,
66          final Pattern ptag,
67          final Pattern patt,
68          final Reporter rep
69      ) {
70          this.name = cname;
71          this.tag = ptag;
72          this.content = patt;
73          this.reporter = rep;
74      }
75  
76      /**
77       * Check if the tag text matches the format from pattern.
78       * @param lines List of all lines
79       * @param start Line number where comment starts
80       * @param end Line number where comment ends
81       */
82      void matchTagFormat(
83          final String[] lines,
84          final int start,
85          final int end
86      ) {
87          final Pattern loose = Pattern.compile(
88              String.format("@%s\\b", this.name)
89          );
90          Integer looseline = null;
91          final Map<Integer, String> found = new HashMap<>(1);
92          for (int pos = start; pos <= end; pos += 1) {
93              final String line = lines[pos];
94              final Matcher matcher = this.tag.matcher(line);
95              if (RequiredJavaDocTag.tagFound(matcher)) {
96                  found.put(pos, matcher.group("cont"));
97                  break;
98              }
99              if (looseline == null && loose.matcher(line).find()) {
100                 looseline = pos;
101             }
102         }
103         if (found.isEmpty()) {
104             this.logMissing(start, looseline);
105         } else {
106             this.logMismatches(found);
107         }
108     }
109 
110     /**
111      * Log either "missing" or "malformed" depending on whether a loose
112      * match was found in the comment.
113      * @param start Line number where the comment starts
114      * @param looseline Line number where a loose match was found, or
115      *  {@code null} if none was found
116      */
117     private void logMissing(final int start, final Integer looseline) {
118         if (looseline == null) {
119             this.reporter.log(
120                 start + 1,
121                 "Missing ''@{0}'' tag in class/interface comment",
122                 this.name
123             );
124         } else {
125             this.reporter.log(
126                 looseline + 1,
127                 "Malformed ''@{0}'' tag, expected '' * @{0} <value>'' format",
128                 this.name
129             );
130         }
131     }
132 
133     /**
134      * Log a violation for every tag whose text does not match the
135      * configured pattern.
136      * @param found Map of line number to tag text
137      */
138     private void logMismatches(final Map<Integer, String> found) {
139         for (final Map.Entry<Integer, String> item : found.entrySet()) {
140             if (!this.content.matcher(item.getValue()).matches()) {
141                 this.reporter.log(
142                     item.getKey() + 1,
143                     "Tag text ''{0}'' does not match the pattern ''{1}''",
144                     item.getValue(),
145                     this.content.toString()
146                 );
147             }
148         }
149     }
150 
151     /**
152      * Finds the tag name and the following sentences.
153      * @param matcher Tag name matcher
154      * @return True if the tag and its clauses are found
155      */
156     private static boolean tagFound(final Matcher matcher) {
157         return matcher.matches()
158             && !RequiredJavaDocTag.empty(matcher.group("name"))
159             && !RequiredJavaDocTag.empty(matcher.group("cont"));
160     }
161 
162     /**
163      * Checks for an empty string.
164      * @param str Line to check
165      * @return True if str is empty
166      */
167     private static boolean empty(final String str) {
168         return str == null || str.chars().allMatch(Character::isWhitespace);
169     }
170 
171     /**
172      * Logger.
173      * @see com.puppycrawl.tools.checkstyle.api.AbstractCheck#log(int, String, Object...)
174      * @since 0.23.1
175      */
176     @FunctionalInterface
177     interface Reporter {
178 
179         /**
180          * Log a message that has no column information.
181          * @param line The line number where the audit event was found
182          * @param msg The message that describes the audit event
183          * @param args The details of the message
184          * @see java.text.MessageFormat
185          */
186         void log(int line, String msg, Object... args);
187     }
188 }