Coverage Report - com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
UnusedImportsCheck
100%
85/85
100%
34/34
0
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 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.puppycrawl.tools.checkstyle.checks.imports;
 21  
 
 22  
 import java.util.ArrayList;
 23  
 import java.util.HashSet;
 24  
 import java.util.List;
 25  
 import java.util.Set;
 26  
 import java.util.regex.Matcher;
 27  
 import java.util.regex.Pattern;
 28  
 
 29  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 30  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 31  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 32  
 import com.puppycrawl.tools.checkstyle.api.FileContents;
 33  
 import com.puppycrawl.tools.checkstyle.api.FullIdent;
 34  
 import com.puppycrawl.tools.checkstyle.api.TextBlock;
 35  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 36  
 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
 37  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 38  
 import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
 39  
 
 40  
 /**
 41  
  * <p>
 42  
  * Checks for unused import statements.
 43  
  * </p>
 44  
  *  <p>
 45  
  * An example of how to configure the check is:
 46  
  * </p>
 47  
  * <pre>
 48  
  * &lt;module name="UnusedImports"/&gt;
 49  
  * </pre>
 50  
  * Compatible with Java 1.5 source.
 51  
  *
 52  
  * @author Oliver Burn
 53  
  */
 54  
 @FileStatefulCheck
 55  21
 public class UnusedImportsCheck extends AbstractCheck {
 56  
 
 57  
     /**
 58  
      * A key is pointing to the warning message text in "messages.properties"
 59  
      * file.
 60  
      */
 61  
     public static final String MSG_KEY = "import.unused";
 62  
 
 63  
     /** Regex to match class names. */
 64  1
     private static final Pattern CLASS_NAME = CommonUtils.createPattern(
 65  
            "((:?[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)");
 66  
     /** Regex to match the first class name. */
 67  1
     private static final Pattern FIRST_CLASS_NAME = CommonUtils.createPattern(
 68  
            "^" + CLASS_NAME);
 69  
     /** Regex to match argument names. */
 70  2
     private static final Pattern ARGUMENT_NAME = CommonUtils.createPattern(
 71  1
            "[(,]\\s*" + CLASS_NAME.pattern());
 72  
 
 73  
     /** Regexp pattern to match java.lang package. */
 74  2
     private static final Pattern JAVA_LANG_PACKAGE_PATTERN =
 75  1
         CommonUtils.createPattern("^java\\.lang\\.[a-zA-Z]+$");
 76  
 
 77  
     /** Suffix for the star import. */
 78  
     private static final String STAR_IMPORT_SUFFIX = ".*";
 79  
 
 80  
     /** Set of the imports. */
 81  21
     private final Set<FullIdent> imports = new HashSet<>();
 82  
 
 83  
     /** Set of references - possibly to imports or other things. */
 84  21
     private final Set<String> referenced = new HashSet<>();
 85  
 
 86  
     /** Flag to indicate when time to start collecting references. */
 87  
     private boolean collect;
 88  
     /** Flag whether to process Javadoc comments. */
 89  21
     private boolean processJavadoc = true;
 90  
 
 91  
     /**
 92  
      * Sets whether to process JavaDoc or not.
 93  
      *
 94  
      * @param value Flag for processing JavaDoc.
 95  
      */
 96  
     public void setProcessJavadoc(boolean value) {
 97  1
         processJavadoc = value;
 98  1
     }
 99  
 
 100  
     @Override
 101  
     public void beginTree(DetailAST rootAST) {
 102  15
         collect = false;
 103  15
         imports.clear();
 104  15
         referenced.clear();
 105  15
     }
 106  
 
 107  
     @Override
 108  
     public void finishTree(DetailAST rootAST) {
 109  
         // loop over all the imports to see if referenced.
 110  15
         imports.stream()
 111  106
             .filter(imprt -> isUnusedImport(imprt.getText()))
 112  119
             .forEach(imprt -> log(imprt.getLineNo(),
 113  52
                 imprt.getColumnNo(),
 114  52
                 MSG_KEY, imprt.getText()));
 115  15
     }
 116  
 
 117  
     @Override
 118  
     public int[] getDefaultTokens() {
 119  32
         return getRequiredTokens();
 120  
     }
 121  
 
 122  
     @Override
 123  
     public int[] getRequiredTokens() {
 124  70
         return new int[] {
 125  
             TokenTypes.IDENT,
 126  
             TokenTypes.IMPORT,
 127  
             TokenTypes.STATIC_IMPORT,
 128  
             // Definitions that may contain Javadoc...
 129  
             TokenTypes.PACKAGE_DEF,
 130  
             TokenTypes.ANNOTATION_DEF,
 131  
             TokenTypes.ANNOTATION_FIELD_DEF,
 132  
             TokenTypes.ENUM_DEF,
 133  
             TokenTypes.ENUM_CONSTANT_DEF,
 134  
             TokenTypes.CLASS_DEF,
 135  
             TokenTypes.INTERFACE_DEF,
 136  
             TokenTypes.METHOD_DEF,
 137  
             TokenTypes.CTOR_DEF,
 138  
             TokenTypes.VARIABLE_DEF,
 139  
         };
 140  
     }
 141  
 
 142  
     @Override
 143  
     public int[] getAcceptableTokens() {
 144  5
         return getRequiredTokens();
 145  
     }
 146  
 
 147  
     @Override
 148  
     public void visitToken(DetailAST ast) {
 149  819
         if (ast.getType() == TokenTypes.IDENT) {
 150  634
             if (collect) {
 151  618
                 processIdent(ast);
 152  
             }
 153  
         }
 154  185
         else if (ast.getType() == TokenTypes.IMPORT) {
 155  95
             processImport(ast);
 156  
         }
 157  90
         else if (ast.getType() == TokenTypes.STATIC_IMPORT) {
 158  7
             processStaticImport(ast);
 159  
         }
 160  
         else {
 161  83
             collect = true;
 162  83
             if (processJavadoc) {
 163  66
                 collectReferencesFromJavadoc(ast);
 164  
             }
 165  
         }
 166  819
     }
 167  
 
 168  
     /**
 169  
      * Checks whether an import is unused.
 170  
      * @param imprt an import.
 171  
      * @return true if an import is unused.
 172  
      */
 173  
     private boolean isUnusedImport(String imprt) {
 174  91
         final Matcher javaLangPackageMatcher = JAVA_LANG_PACKAGE_PATTERN.matcher(imprt);
 175  182
         return !referenced.contains(CommonUtils.baseClassName(imprt))
 176  50
             || javaLangPackageMatcher.matches();
 177  
     }
 178  
 
 179  
     /**
 180  
      * Collects references made by IDENT.
 181  
      * @param ast the IDENT node to process
 182  
      */
 183  
     private void processIdent(DetailAST ast) {
 184  618
         final DetailAST parent = ast.getParent();
 185  618
         final int parentType = parent.getType();
 186  618
         if (parentType != TokenTypes.DOT
 187  
             && parentType != TokenTypes.METHOD_DEF
 188  
             || parentType == TokenTypes.DOT
 189  490
                 && ast.getNextSibling() != null) {
 190  243
             referenced.add(ast.getText());
 191  
         }
 192  618
     }
 193  
 
 194  
     /**
 195  
      * Collects the details of imports.
 196  
      * @param ast node containing the import details
 197  
      */
 198  
     private void processImport(DetailAST ast) {
 199  95
         final FullIdent name = FullIdent.createFullIdentBelow(ast);
 200  95
         if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
 201  86
             imports.add(name);
 202  
         }
 203  95
     }
 204  
 
 205  
     /**
 206  
      * Collects the details of static imports.
 207  
      * @param ast node containing the static import details
 208  
      */
 209  
     private void processStaticImport(DetailAST ast) {
 210  7
         final FullIdent name =
 211  7
             FullIdent.createFullIdent(
 212  7
                 ast.getFirstChild().getNextSibling());
 213  7
         if (!name.getText().endsWith(STAR_IMPORT_SUFFIX)) {
 214  5
             imports.add(name);
 215  
         }
 216  7
     }
 217  
 
 218  
     /**
 219  
      * Collects references made in Javadoc comments.
 220  
      * @param ast node to inspect for Javadoc
 221  
      */
 222  
     private void collectReferencesFromJavadoc(DetailAST ast) {
 223  66
         final FileContents contents = getFileContents();
 224  66
         final int lineNo = ast.getLineNo();
 225  66
         final TextBlock textBlock = contents.getJavadocBefore(lineNo);
 226  66
         if (textBlock != null) {
 227  18
             referenced.addAll(collectReferencesFromJavadoc(textBlock));
 228  
         }
 229  66
     }
 230  
 
 231  
     /**
 232  
      * Process a javadoc {@link TextBlock} and return the set of classes
 233  
      * referenced within.
 234  
      * @param textBlock The javadoc block to parse
 235  
      * @return a set of classes referenced in the javadoc block
 236  
      */
 237  
     private static Set<String> collectReferencesFromJavadoc(TextBlock textBlock) {
 238  18
         final List<JavadocTag> tags = new ArrayList<>();
 239  
         // gather all the inline tags, like @link
 240  
         // INLINE tags inside BLOCKs get hidden when using ALL
 241  18
         tags.addAll(getValidTags(textBlock, JavadocUtils.JavadocTagType.INLINE));
 242  
         // gather all the block-level tags, like @throws and @see
 243  18
         tags.addAll(getValidTags(textBlock, JavadocUtils.JavadocTagType.BLOCK));
 244  
 
 245  18
         final Set<String> references = new HashSet<>();
 246  
 
 247  18
         tags.stream()
 248  18
             .filter(JavadocTag::canReferenceImports)
 249  35
             .forEach(tag -> references.addAll(processJavadocTag(tag)));
 250  18
         return references;
 251  
     }
 252  
 
 253  
     /**
 254  
      * Returns the list of valid tags found in a javadoc {@link TextBlock}.
 255  
      * @param cmt The javadoc block to parse
 256  
      * @param tagType The type of tags we're interested in
 257  
      * @return the list of tags
 258  
      */
 259  
     private static List<JavadocTag> getValidTags(TextBlock cmt,
 260  
             JavadocUtils.JavadocTagType tagType) {
 261  36
         return JavadocUtils.getJavadocTags(cmt, tagType).getValidTags();
 262  
     }
 263  
 
 264  
     /**
 265  
      * Returns a list of references found in a javadoc {@link JavadocTag}.
 266  
      * @param tag The javadoc tag to parse
 267  
      * @return A list of references found in this tag
 268  
      */
 269  
     private static Set<String> processJavadocTag(JavadocTag tag) {
 270  17
         final Set<String> references = new HashSet<>();
 271  17
         final String identifier = tag.getFirstArg().trim();
 272  51
         for (Pattern pattern : new Pattern[]
 273  
         {FIRST_CLASS_NAME, ARGUMENT_NAME}) {
 274  34
             references.addAll(matchPattern(identifier, pattern));
 275  
         }
 276  17
         return references;
 277  
     }
 278  
 
 279  
     /**
 280  
      * Extracts a list of texts matching a {@link Pattern} from a
 281  
      * {@link String}.
 282  
      * @param identifier The String to match the pattern against
 283  
      * @param pattern The Pattern used to extract the texts
 284  
      * @return A list of texts which matched the pattern
 285  
      */
 286  
     private static Set<String> matchPattern(String identifier, Pattern pattern) {
 287  34
         final Set<String> references = new HashSet<>();
 288  34
         final Matcher matcher = pattern.matcher(identifier);
 289  57
         while (matcher.find()) {
 290  23
             references.add(topLevelType(matcher.group(1)));
 291  
         }
 292  34
         return references;
 293  
     }
 294  
 
 295  
     /**
 296  
      * If the given type string contains "." (e.g. "Map.Entry"), returns the
 297  
      * top level type (e.g. "Map"), as that is what must be imported for the
 298  
      * type to resolve. Otherwise, returns the type as-is.
 299  
      * @param type A possibly qualified type name
 300  
      * @return The simple name of the top level type
 301  
      */
 302  
     private static String topLevelType(String type) {
 303  
         final String topLevelType;
 304  23
         final int dotIndex = type.indexOf('.');
 305  23
         if (dotIndex == -1) {
 306  22
             topLevelType = type;
 307  
         }
 308  
         else {
 309  1
             topLevelType = type.substring(0, dotIndex);
 310  
         }
 311  23
         return topLevelType;
 312  
     }
 313  
 }