001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.imports; 021 022import java.util.HashSet; 023import java.util.List; 024import java.util.Set; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.FileContents; 031import com.puppycrawl.tools.checkstyle.api.FullIdent; 032import com.puppycrawl.tools.checkstyle.api.TextBlock; 033import com.puppycrawl.tools.checkstyle.api.TokenTypes; 034import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag; 035import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 036import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 037 038/** 039 * <p> 040 * Checks for unused import statements. 041 * </p> 042 * <p> 043 * An example of how to configure the check is: 044 * </p> 045 * <pre> 046 * <module name="UnusedImports"/> 047 * </pre> 048 * Compatible with Java 1.5 source. 049 * 050 * @author Oliver Burn 051 */ 052public class UnusedImportsCheck extends AbstractCheck { 053 054 /** 055 * A key is pointing to the warning message text in "messages.properties" 056 * file. 057 */ 058 public static final String MSG_KEY = "import.unused"; 059 060 /** Regex to match class names. */ 061 private static final Pattern CLASS_NAME = CommonUtils.createPattern( 062 "((:?[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)"); 063 /** Regex to match the first class name. */ 064 private static final Pattern FIRST_CLASS_NAME = CommonUtils.createPattern( 065 "^" + CLASS_NAME); 066 /** Regex to match argument names. */ 067 private static final Pattern ARGUMENT_NAME = CommonUtils.createPattern( 068 "[(,]\\s*" + CLASS_NAME.pattern()); 069 070 /** Regexp pattern to match java.lang package. */ 071 private static final Pattern JAVA_LANG_PACKAGE_PATTERN = 072 CommonUtils.createPattern("^java\\.lang\\.[a-zA-Z]+$"); 073 074 /** Suffix for the star import. */ 075 private static final String STAR_IMPORT_SUFFIX = ".*"; 076 077 /** Set of the imports. */ 078 private final Set<FullIdent> imports = new HashSet<>(); 079 080 /** Set of references - possibly to imports or other things. */ 081 private final Set<String> referenced = new HashSet<>(); 082 083 /** Flag to indicate when time to start collecting references. */ 084 private boolean collect; 085 /** Flag whether to process Javadoc comments. */ 086 private boolean processJavadoc = true; 087 088 /** 089 * Sets whether to process JavaDoc or not. 090 * 091 * @param value Flag for processing JavaDoc. 092 */ 093 public void setProcessJavadoc(boolean value) { 094 processJavadoc = value; 095 } 096 097 @Override 098 public void beginTree(DetailAST rootAST) { 099 collect = false; 100 imports.clear(); 101 referenced.clear(); 102 } 103 104 @Override 105 public void finishTree(DetailAST rootAST) { 106 // loop over all the imports to see if referenced. 107 imports.stream() 108 .filter(imprt -> isUnusedImport(imprt.getText())) 109 .forEach(imprt -> log(imprt.getLineNo(), 110 imprt.getColumnNo(), 111 MSG_KEY, imprt.getText())); 112 } 113 114 @Override 115 public int[] getDefaultTokens() { 116 return new int[] { 117 TokenTypes.IDENT, 118 TokenTypes.IMPORT, 119 TokenTypes.STATIC_IMPORT, 120 // Definitions that may contain Javadoc... 121 TokenTypes.PACKAGE_DEF, 122 TokenTypes.ANNOTATION_DEF, 123 TokenTypes.ANNOTATION_FIELD_DEF, 124 TokenTypes.ENUM_DEF, 125 TokenTypes.ENUM_CONSTANT_DEF, 126 TokenTypes.CLASS_DEF, 127 TokenTypes.INTERFACE_DEF, 128 TokenTypes.METHOD_DEF, 129 TokenTypes.CTOR_DEF, 130 TokenTypes.VARIABLE_DEF, 131 }; 132 } 133 134 @Override 135 public int[] getRequiredTokens() { 136 return getDefaultTokens(); 137 } 138 139 @Override 140 public int[] getAcceptableTokens() { 141 return new int[] { 142 TokenTypes.IDENT, 143 TokenTypes.IMPORT, 144 TokenTypes.STATIC_IMPORT, 145 // Definitions that may contain Javadoc... 146 TokenTypes.PACKAGE_DEF, 147 TokenTypes.ANNOTATION_DEF, 148 TokenTypes.ANNOTATION_FIELD_DEF, 149 TokenTypes.ENUM_DEF, 150 TokenTypes.ENUM_CONSTANT_DEF, 151 TokenTypes.CLASS_DEF, 152 TokenTypes.INTERFACE_DEF, 153 TokenTypes.METHOD_DEF, 154 TokenTypes.CTOR_DEF, 155 TokenTypes.VARIABLE_DEF, 156 }; 157 } 158 159 @Override 160 public void visitToken(DetailAST ast) { 161 if (ast.getType() == TokenTypes.IDENT) { 162 if (collect) { 163 processIdent(ast); 164 } 165 } 166 else if (ast.getType() == TokenTypes.IMPORT) { 167 processImport(ast); 168 } 169 else if (ast.getType() == TokenTypes.STATIC_IMPORT) { 170 processStaticImport(ast); 171 } 172 else { 173 collect = true; 174 if (processJavadoc) { 175 collectReferencesFromJavadoc(ast); 176 } 177 } 178 } 179 180 /** 181 * Checks whether an import is unused. 182 * @param imprt an import. 183 * @return true if an import is unused. 184 */ 185 private boolean isUnusedImport(String imprt) { 186 final Matcher javaLangPackageMatcher = JAVA_LANG_PACKAGE_PATTERN.matcher(imprt); 187 return !referenced.contains(CommonUtils.baseClassName(imprt)) 188 || javaLangPackageMatcher.matches(); 189 } 190 191 /** 192 * Collects references made by IDENT. 193 * @param ast the IDENT node to process 194 */ 195 private void processIdent(DetailAST ast) { 196 final DetailAST parent = ast.getParent(); 197 final int parentType = parent.getType(); 198 if (parentType != TokenTypes.DOT 199 && parentType != TokenTypes.METHOD_DEF 200 || parentType == TokenTypes.DOT 201 && ast.getNextSibling() != null) { 202 referenced.add(ast.getText()); 203 } 204 } 205 206 /** 207 * Collects the details of imports. 208 * @param ast node containing the import details 209 */ 210 private void processImport(DetailAST ast) { 211 final FullIdent name = FullIdent.createFullIdentBelow(ast); 212 if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) { 213 imports.add(name); 214 } 215 } 216 217 /** 218 * Collects the details of static imports. 219 * @param ast node containing the static import details 220 */ 221 private void processStaticImport(DetailAST ast) { 222 final FullIdent name = 223 FullIdent.createFullIdent( 224 ast.getFirstChild().getNextSibling()); 225 if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) { 226 imports.add(name); 227 } 228 } 229 230 /** 231 * Collects references made in Javadoc comments. 232 * @param ast node to inspect for Javadoc 233 */ 234 private void collectReferencesFromJavadoc(DetailAST ast) { 235 final FileContents contents = getFileContents(); 236 final int lineNo = ast.getLineNo(); 237 final TextBlock textBlock = contents.getJavadocBefore(lineNo); 238 if (textBlock != null) { 239 referenced.addAll(collectReferencesFromJavadoc(textBlock)); 240 } 241 } 242 243 /** 244 * Process a javadoc {@link TextBlock} and return the set of classes 245 * referenced within. 246 * @param textBlock The javadoc block to parse 247 * @return a set of classes referenced in the javadoc block 248 */ 249 private static Set<String> collectReferencesFromJavadoc(TextBlock textBlock) { 250 final Set<String> references = new HashSet<>(); 251 // process all the @link type tags 252 // INLINE tags inside BLOCKs get hidden when using ALL 253 getValidTags(textBlock, JavadocUtils.JavadocTagType.INLINE).stream() 254 .filter(JavadocTag::canReferenceImports) 255 .forEach(tag -> references.addAll(processJavadocTag(tag))); 256 // process all the @throws type tags 257 getValidTags(textBlock, JavadocUtils.JavadocTagType.BLOCK).stream() 258 .filter(JavadocTag::canReferenceImports) 259 .forEach(tag -> references.addAll(matchPattern(tag.getFirstArg(), FIRST_CLASS_NAME))); 260 return references; 261 } 262 263 /** 264 * Returns the list of valid tags found in a javadoc {@link TextBlock}. 265 * @param cmt The javadoc block to parse 266 * @param tagType The type of tags we're interested in 267 * @return the list of tags 268 */ 269 private static List<JavadocTag> getValidTags(TextBlock cmt, 270 JavadocUtils.JavadocTagType tagType) { 271 return JavadocUtils.getJavadocTags(cmt, tagType).getValidTags(); 272 } 273 274 /** 275 * Returns a list of references found in a javadoc {@link JavadocTag}. 276 * @param tag The javadoc tag to parse 277 * @return A list of references found in this tag 278 */ 279 private static Set<String> processJavadocTag(JavadocTag tag) { 280 final Set<String> references = new HashSet<>(); 281 final String identifier = tag.getFirstArg().trim(); 282 for (Pattern pattern : new Pattern[] 283 {FIRST_CLASS_NAME, ARGUMENT_NAME}) { 284 references.addAll(matchPattern(identifier, pattern)); 285 } 286 return references; 287 } 288 289 /** 290 * Extracts a list of texts matching a {@link Pattern} from a 291 * {@link String}. 292 * @param identifier The String to match the pattern against 293 * @param pattern The Pattern used to extract the texts 294 * @return A list of texts which matched the pattern 295 */ 296 private static Set<String> matchPattern(String identifier, Pattern pattern) { 297 final Set<String> references = new HashSet<>(); 298 final Matcher matcher = pattern.matcher(identifier); 299 while (matcher.find()) { 300 references.add(matcher.group(1)); 301 } 302 return references; 303 } 304}