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 java.util.HashMap; 34 import java.util.Map; 35 import java.util.regex.Matcher; 36 import java.util.regex.Pattern; 37 38 /** 39 * Check the required JavaDoc tag in the lines. 40 * <p>Correct format is the following (of a class javadoc): 41 * 42 * <pre> 43 * /** 44 * * This is my new class. 45 * * 46 * * @since 0.3 47 * */ 48 * public final class Foo { 49 * /** 50 * * This is my other class. 51 * * 52 * * @since 0.3 53 * */ 54 * public final class Boo { 55 * // ... 56 * </pre> 57 * 58 * <p>"$Id$" will be replaced by a full text automatically 59 * by Subversion as explained in their documentation (see link below). 60 * 61 * @see <a href="http://svnbook.red-bean.com/en/1.4/svn.advanced.props.special.keywords.html">Keywords substitution in Subversion</a> 62 63 * @since 0.23.1 64 */ 65 final class RequiredJavaDocTag { 66 /** 67 * Tag name. 68 */ 69 private final String name; 70 71 /** 72 * Pattern for searching a tag in a string. 73 */ 74 private final Pattern tag; 75 76 /** 77 * Pattern for checking the contents of a tag in a string. 78 */ 79 private final Pattern content; 80 81 /** 82 * Reference to a method for writing a message to the log. 83 */ 84 private final Reporter reporter; 85 86 /** 87 * Ctor. 88 * @param name Tag name. 89 * @param patt Pattern for checking the contents of a tag in a string. 90 * @param rep Reference to a method for writing a message to the log. 91 */ 92 RequiredJavaDocTag( 93 final String name, 94 final Pattern patt, 95 final Reporter rep 96 ) { 97 this( 98 name, 99 Pattern.compile( 100 String.format( 101 "(?<name>^ +\\* +@%s)( +)(?<cont>.*)", 102 name 103 ) 104 ), 105 patt, 106 rep 107 ); 108 } 109 110 /** 111 * Ctor. 112 * @param cname Tag name. 113 * @param ptag Pattern for searching a tag in a string. 114 * @param patt Pattern for checking the contents of a tag in a string. 115 * @param rep Reference to a method for writing a message to the log. 116 * @checkstyle ParameterNumberCheck (3 lines) 117 */ 118 RequiredJavaDocTag( 119 final String cname, 120 final Pattern ptag, 121 final Pattern patt, 122 final Reporter rep 123 ) { 124 this.name = cname; 125 this.tag = ptag; 126 this.content = patt; 127 this.reporter = rep; 128 } 129 130 /** 131 * Check if the tag text matches the format from pattern. 132 * @param lines List of all lines. 133 * @param start Line number where comment starts. 134 * @param end Line number where comment ends. 135 */ 136 public void matchTagFormat( 137 final String[] lines, 138 final int start, 139 final int end 140 ) { 141 final Map<Integer, String> found = new HashMap<>(1); 142 for (int pos = start; pos <= end; pos += 1) { 143 final String line = lines[pos]; 144 final Matcher matcher = this.tag.matcher(line); 145 if (RequiredJavaDocTag.tagFound(matcher)) { 146 found.put(pos, matcher.group("cont")); 147 break; 148 } 149 } 150 if (found.isEmpty()) { 151 this.reporter.log( 152 start + 1, 153 "Missing ''@{0}'' tag in class/interface comment", 154 this.name 155 ); 156 } else { 157 for (final Map.Entry<Integer, String> item : found.entrySet()) { 158 if (!this.content.matcher(item.getValue()).matches()) { 159 this.reporter.log( 160 item.getKey() + 1, 161 "Tag text ''{0}'' does not match the pattern ''{1}''", 162 item.getValue(), 163 this.content.toString() 164 ); 165 } 166 } 167 } 168 } 169 170 /** 171 * Finds the tag name and the following sentences. 172 * @param matcher Tag name matcher. 173 * @return True if the tag and its clauses are found. 174 */ 175 private static boolean tagFound(final Matcher matcher) { 176 return matcher.matches() 177 && !RequiredJavaDocTag.empty(matcher.group("name")) 178 && !RequiredJavaDocTag.empty(matcher.group("cont")); 179 } 180 181 /** 182 * Checks for an empty string. 183 * @param str Line to check. 184 * @return True if str is empty. 185 */ 186 private static boolean empty(final String str) { 187 return str == null || str.isBlank(); 188 } 189 190 /** 191 * Logger. 192 * @see com.puppycrawl.tools.checkstyle.api.AbstractCheck#log(int, String, Object...) 193 * @since 0.23.1 194 */ 195 interface Reporter { 196 /** 197 * Log a message that has no column information. 198 * 199 * @param line The line number where the audit event was found. 200 * @param msg The message that describes the audit event. 201 * @param args The details of the message. 202 * @see java.text.MessageFormat 203 */ 204 void log(int line, String msg, Object... args); 205 } 206 }