1 /* 2 * checkstyle: Checks Java source code for adherence to a set of rules. 3 * Copyright (C) 2001-2016 the original author or authors. 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public 7 * License as published by the Free Software Foundation; either 8 * version 2.1 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public 16 * License along with this library; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 */ 19 20 package com.android.support.checkstyle; 21 22 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 23 import com.puppycrawl.tools.checkstyle.api.DetailAST; 24 import com.puppycrawl.tools.checkstyle.api.TextBlock; 25 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 26 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility; 27 import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 28 29 import java.util.regex.Pattern; 30 31 /** 32 * This class is used to verify that both an annotation and javadoc tag are 33 * present when either one is present. 34 * <p> 35 * Typically, both ways of flagging APIs serve their own purposes. Annotations 36 * are used for compilers and development tools, while javadoc tags are used 37 * for documentation. 38 * <p> 39 * In some cases, the presence of an annotations implies the presence of a 40 * javadoc tag (or vice versa). For example, in the case of the 41 * {@literal @}Deprecated annotation, the {@literal @}deprecated tag should 42 * also be present. In the case of the {@literal @}RestrictTo tag, the 43 * {@literal @}hide tag should also be present. 44 * <p> 45 * To configure this check, do the following: 46 * <pre> 47 * <module name="MismatchedAnnotationCheck"> 48 * <property name="tag" value="hide" /> 49 * <property name="annotation" value="android.support.annotation.RestrictTo" /> 50 * <property name="messageKey" value="annotation.missing.hide" /> 51 * <message key="annotation.missing.hide" 52 * value="Must include both {@literal @}RestrictTo annotation 53 * and {@literal @}hide Javadoc tag." /> 54 * </module> 55 * </pre> 56 */ 57 @SuppressWarnings("unused") 58 public final class MismatchedAnnotationCheck extends AbstractCheck { 59 60 /** Key for the warning message text by check properties. */ 61 private static final String MSG_KEY_JAVADOC_DUPLICATE_TAG = "javadoc.duplicateTag"; 62 63 /** Key for the warning message text by check properties. */ 64 private static final String MSG_KEY_JAVADOC_MISSING = "javadoc.missing"; 65 66 /** Javadoc tag. */ 67 private String mTag; 68 69 /** Pattern for matching javadoc tag. */ 70 private Pattern mMatchTag; 71 72 /** Simple annotation name. */ 73 private String mAnnotationSimpleName; 74 75 /** Fully-qualified annotation name. */ 76 private String mAnnotation; 77 78 /** Key for the warning message text specified by check properties. */ 79 private String mMessageKey; 80 81 @Override getDefaultTokens()82 public int[] getDefaultTokens() { 83 return getAcceptableTokens(); 84 } 85 86 /** 87 * Sets javadoc tag. 88 * 89 * @param tag javadoc tag to check 90 */ 91 @SuppressWarnings("unused") setTag(String tag)92 public void setTag(String tag) { 93 mTag = tag; 94 95 // Tag may either have a description or be on a line by itself. 96 mMatchTag = CommonUtils.createPattern("@" + tag + "(?:\\s|$)"); 97 } 98 99 /** 100 * Sets annotation tag. 101 * 102 * @param annotation annotation to check 103 */ 104 @SuppressWarnings("unused") setAnnotation(String annotation)105 public void setAnnotation(String annotation) { 106 mAnnotation = annotation; 107 108 // Extract the simple class name. 109 final int lastDollar = annotation.lastIndexOf('$'); 110 final int lastSep = lastDollar >= 0 ? lastDollar : annotation.lastIndexOf('.'); 111 mAnnotationSimpleName = annotation.substring(lastSep + 1); 112 } 113 114 115 /** 116 * Sets annotation tag. 117 * 118 * @param messageKey key to use for failed check message 119 */ 120 @SuppressWarnings("unused") setMessageKey(String messageKey)121 public void setMessageKey(String messageKey) { 122 mMessageKey = messageKey; 123 } 124 125 @Override getAcceptableTokens()126 public int[] getAcceptableTokens() { 127 return new int[] { 128 TokenTypes.INTERFACE_DEF, 129 TokenTypes.CLASS_DEF, 130 TokenTypes.ANNOTATION_DEF, 131 TokenTypes.ENUM_DEF, 132 TokenTypes.METHOD_DEF, 133 TokenTypes.CTOR_DEF, 134 TokenTypes.VARIABLE_DEF, 135 TokenTypes.ENUM_CONSTANT_DEF, 136 TokenTypes.ANNOTATION_FIELD_DEF, 137 }; 138 } 139 140 @Override getRequiredTokens()141 public int[] getRequiredTokens() { 142 return getAcceptableTokens(); 143 } 144 145 @Override visitToken(final DetailAST ast)146 public void visitToken(final DetailAST ast) { 147 final boolean containsAnnotation = 148 AnnotationUtility.containsAnnotation(ast, mAnnotationSimpleName) 149 || AnnotationUtility.containsAnnotation(ast, mAnnotation); 150 final boolean containsJavadocTag = containsJavadocTag(ast); 151 if (containsAnnotation ^ containsJavadocTag) { 152 log(ast.getLineNo(), mMessageKey); 153 } 154 } 155 156 /** 157 * Checks to see if the text block contains the tag. 158 * 159 * @param ast the AST being visited 160 * @return true if contains the tag 161 */ containsJavadocTag(final DetailAST ast)162 private boolean containsJavadocTag(final DetailAST ast) { 163 final TextBlock javadoc = getFileContents().getJavadocBefore(ast.getLineNo()); 164 if (javadoc == null) { 165 return false; 166 } 167 168 int currentLine = javadoc.getStartLineNo(); 169 boolean found = false; 170 171 final String[] lines = javadoc.getText(); 172 for (String line : lines) { 173 if (mMatchTag.matcher(line).find()) { 174 if (found) { 175 log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, mTag); 176 } 177 found = true; 178 } 179 currentLine++; 180 } 181 182 return found; 183 } 184 } 185