001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import org.apache.commons.lang3.ArrayUtils; 026 027import com.puppycrawl.tools.checkstyle.api.Check; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.FileContents; 030import com.puppycrawl.tools.checkstyle.api.SeverityLevel; 031import com.puppycrawl.tools.checkstyle.api.TextBlock; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 034 035/** 036 * <p> 037 * Outputs a JavaDoc tag as information. Can be used e.g. with the stylesheets 038 * that sort the report by author name. 039 * To define the format for a tag, set property tagFormat to a 040 * regular expression. 041 * This check uses two different severity levels. The normal one is used for 042 * reporting when the tag is missing. The additional one (tagSeverity) is used 043 * for the level of reporting when the tag exists. The default value for 044 * tagSeverity is info. 045 * </p> 046 * <p> An example of how to configure the check for printing author name is: 047 *</p> 048 * <pre> 049 * <module name="WriteTag"> 050 * <property name="tag" value="@author"/> 051 * <property name="tagFormat" value="\S"/> 052 * </module> 053 * </pre> 054 * <p> An example of how to configure the check to print warnings if an 055 * "@incomplete" tag is found, and not print anything if it is not found: 056 *</p> 057 * <pre> 058 * <module name="WriteTag"> 059 * <property name="tag" value="@incomplete"/> 060 * <property name="tagFormat" value="\S"/> 061 * <property name="severity" value="ignore"/> 062 * <property name="tagSeverity" value="warning"/> 063 * </module> 064 * </pre> 065 * 066 * @author Daniel Grenner 067 */ 068public class WriteTagCheck 069 extends Check { 070 071 /** 072 * A key is pointing to the warning message text in "messages.properties" 073 * file. 074 */ 075 public static final String MISSING_TAG = "type.missingTag"; 076 077 /** 078 * A key is pointing to the warning message text in "messages.properties" 079 * file. 080 */ 081 public static final String WRITE_TAG = "javadoc.writeTag"; 082 083 /** 084 * A key is pointing to the warning message text in "messages.properties" 085 * file. 086 */ 087 public static final String TAG_FORMAT = "type.tagFormat"; 088 089 /** Compiled regexp to match tag. **/ 090 private Pattern tagRegExp; 091 /** Compiled regexp to match tag content. **/ 092 private Pattern tagFormatRegExp; 093 094 /** Regexp to match tag. */ 095 private String tag; 096 /** Regexp to match tag content. */ 097 private String tagFormat; 098 /** The severity level of found tag reports. */ 099 private SeverityLevel tagSeverityLevel = SeverityLevel.INFO; 100 101 /** 102 * Sets the tag to check. 103 * @param tag tag to check 104 */ 105 public void setTag(String tag) { 106 this.tag = tag; 107 tagRegExp = CommonUtils.createPattern(tag + "\\s*(.*$)"); 108 } 109 110 /** 111 * Set the tag format. 112 * @param format a {@code String} value 113 */ 114 public void setTagFormat(String format) { 115 tagFormat = format; 116 tagFormatRegExp = CommonUtils.createPattern(format); 117 } 118 119 /** 120 * Sets the tag severity level. The string should be one of the names 121 * defined in the {@code SeverityLevel} class. 122 * 123 * @param severity The new severity level 124 * @see SeverityLevel 125 */ 126 public final void setTagSeverity(String severity) { 127 tagSeverityLevel = SeverityLevel.getInstance(severity); 128 } 129 130 @Override 131 public int[] getDefaultTokens() { 132 return new int[] {TokenTypes.INTERFACE_DEF, 133 TokenTypes.CLASS_DEF, 134 TokenTypes.ENUM_DEF, 135 TokenTypes.ANNOTATION_DEF, 136 }; 137 } 138 139 @Override 140 public int[] getAcceptableTokens() { 141 return new int[] {TokenTypes.INTERFACE_DEF, 142 TokenTypes.CLASS_DEF, 143 TokenTypes.ENUM_DEF, 144 TokenTypes.ANNOTATION_DEF, 145 TokenTypes.METHOD_DEF, 146 TokenTypes.CTOR_DEF, 147 TokenTypes.ENUM_CONSTANT_DEF, 148 TokenTypes.ANNOTATION_FIELD_DEF, 149 }; 150 } 151 152 @Override 153 public int[] getRequiredTokens() { 154 return ArrayUtils.EMPTY_INT_ARRAY; 155 } 156 157 @Override 158 public void visitToken(DetailAST ast) { 159 final FileContents contents = getFileContents(); 160 final int lineNo = ast.getLineNo(); 161 final TextBlock cmt = 162 contents.getJavadocBefore(lineNo); 163 if (cmt == null) { 164 log(lineNo, MISSING_TAG, tag); 165 } 166 else { 167 checkTag(lineNo, cmt.getText()); 168 } 169 } 170 171 /** 172 * Verifies that a type definition has a required tag. 173 * @param lineNo the line number for the type definition. 174 * @param comment the Javadoc comment for the type definition. 175 */ 176 private void checkTag(int lineNo, String... comment) { 177 if (tagRegExp == null) { 178 return; 179 } 180 181 int tagCount = 0; 182 for (int i = 0; i < comment.length; i++) { 183 final String commentValue = comment[i]; 184 final Matcher matcher = tagRegExp.matcher(commentValue); 185 if (matcher.find()) { 186 tagCount += 1; 187 final int contentStart = matcher.start(1); 188 final String content = commentValue.substring(contentStart); 189 if (tagFormatRegExp == null || tagFormatRegExp.matcher(content).find()) { 190 logTag(lineNo + i - comment.length, tag, content); 191 } 192 else { 193 log(lineNo + i - comment.length, TAG_FORMAT, tag, tagFormat); 194 } 195 } 196 } 197 if (tagCount == 0) { 198 log(lineNo, MISSING_TAG, tag); 199 } 200 201 } 202 203 /** 204 * Log a message. 205 * 206 * @param line the line number where the error was found 207 * @param tagName the javadoc tag to be logged 208 * @param tagValue the contents of the tag 209 * 210 * @see java.text.MessageFormat 211 */ 212 protected final void logTag(int line, String tagName, String tagValue) { 213 final String originalSeverity = getSeverity(); 214 setSeverity(tagSeverityLevel.getName()); 215 216 log(line, WRITE_TAG, tagName, tagValue); 217 218 setSeverity(originalSeverity); 219 } 220}