All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.checks.UselessImportCheck Maven / Gradle / Ivy

/*
 * SonarQube Java
 * Copyright (C) 2012 SonarSource
 * [email protected]
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package org.sonar.java.checks;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.sonar.sslr.api.AstAndTokenVisitor;
import com.sonar.sslr.api.AstNode;
import com.sonar.sslr.api.Token;
import com.sonar.sslr.api.Trivia;
import com.sonar.sslr.squid.checks.SquidCheck;
import org.sonar.check.BelongsToProfile;
import org.sonar.check.Priority;
import org.sonar.check.Rule;
import org.sonar.java.ast.api.JavaKeyword;
import org.sonar.java.ast.api.JavaPunctuator;
import org.sonar.java.ast.api.JavaTokenType;
import org.sonar.java.ast.parser.JavaGrammar;
import org.sonar.sslr.parser.LexerlessGrammar;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

@Rule(
  key = "UselessImportCheck",
  priority = Priority.MINOR)
@BelongsToProfile(title = "Sonar way", priority = Priority.MINOR)
public class UselessImportCheck extends SquidCheck implements AstAndTokenVisitor {

  private final Map lineByImportReference = Maps.newHashMap();
  private final Set pendingImports = Sets.newHashSet();
  private final Set pendingReferences = Sets.newHashSet();

  private String currentPackage;

  @Override
  public void init() {
    subscribeTo(JavaGrammar.PACKAGE_DECLARATION);
    subscribeTo(JavaGrammar.IMPORT_DECLARATION);
    subscribeTo(JavaGrammar.CLASS_TYPE);
    subscribeTo(JavaGrammar.CREATED_NAME);
    subscribeTo(JavaGrammar.ANNOTATION);
    subscribeTo(JavaKeyword.THROWS);
    subscribeTo(JavaGrammar.QUALIFIED_IDENTIFIER);
  }

  @Override
  public void visitFile(AstNode astNode) {
    pendingReferences.clear();
    lineByImportReference.clear();
    pendingImports.clear();

    currentPackage = "";
  }

  @Override
  public void visitNode(AstNode node) {
    if (node.is(JavaGrammar.PACKAGE_DECLARATION)) {
      currentPackage = mergeIdentifiers(node.getFirstChild(JavaGrammar.QUALIFIED_IDENTIFIER));
    } else if (node.is(JavaGrammar.IMPORT_DECLARATION)) {
      if (!isStaticImport(node)) {
        String reference = mergeIdentifiers(node.getFirstChild(JavaGrammar.QUALIFIED_IDENTIFIER));

        if ("java.lang".equals(reference)) {
          getContext().createLineViolation(this, "Remove this unnecessary import: java.lang classes are always implicitly imported.", node);
        } else if (isImportFromSamePackage(reference)) {
          getContext().createLineViolation(this, "Remove this unnecessary import: same package classes are always implicitly imported.", node);
        } else if (!isImportOnDemand(node)) {
          if (isJavaLangImport(reference)) {
            getContext().createLineViolation(this, "Remove this unnecessary import: java.lang classes are always implicitly imported.", node);
          } else if (isDuplicatedImport(reference)) {
            getContext().createLineViolation(this, "Remove this duplicated import.", node);
          } else {
            lineByImportReference.put(reference, node.getTokenLine());
            pendingImports.add(reference);
          }
        }
      }
    } else if (!node.getParent().is(JavaGrammar.IMPORT_DECLARATION)) {
      pendingReferences.addAll(getReferences(node));
    }
  }

  @Override
  public void leaveFile(AstNode node) {
    for (String reference : pendingReferences) {
      updatePendingImports(reference);
    }

    for (String pendingImport : pendingImports) {
      getContext().createLineViolation(this, "Remove this unused import '" + pendingImport + "'.", lineByImportReference.get(pendingImport));
    }
  }

  private static boolean isJavaLangImport(String reference) {
    return reference.startsWith("java.lang.") && reference.indexOf('.', "java.lang.".length()) == -1;
  }

  private boolean isImportFromSamePackage(String reference) {
    return !currentPackage.isEmpty() &&
      reference.startsWith(currentPackage) &&
      reference.lastIndexOf('.') < currentPackage.length();
  }

  private boolean isDuplicatedImport(String reference) {
    return pendingImports.contains(reference);
  }

  private void updatePendingImports(String reference) {
    if (!isFullyQualified(reference)) {
      Iterator it = pendingImports.iterator();
      while (it.hasNext()) {
        String pendingImport = it.next();
        if (pendingImport.endsWith(reference)) {
          it.remove();
        }
      }
    } else {
      Iterator it = pendingImports.iterator();
      while (it.hasNext()) {
        String pendingImport = it.next();
        if (pendingImport.endsWith(extractFirstClassName(reference))) {
          it.remove();
        }
      }
    }
  }

  private static boolean isFullyQualified(String reference) {
    return reference.indexOf('.') != -1;
  }

  private static Collection getReferences(AstNode node) {
    if (node.is(JavaKeyword.THROWS)) {
      ImmutableList.Builder builder = ImmutableList.builder();

      for (AstNode qualifiedIdentifier : node.getNextSibling().getChildren(JavaGrammar.QUALIFIED_IDENTIFIER)) {
        builder.add(mergeIdentifiers(qualifiedIdentifier));
      }

      return builder.build();
    } else {
      AstNode actualNode;
      if (node.is(JavaGrammar.ANNOTATION)) {
        actualNode = node.getFirstChild(JavaGrammar.QUALIFIED_IDENTIFIER);
      } else {
        actualNode = node;
      }

      return Collections.singleton(mergeIdentifiers(actualNode));
    }
  }

  private static String mergeIdentifiers(AstNode node) {
    StringBuilder sb = new StringBuilder();
    for (AstNode child : node.getChildren(JavaTokenType.IDENTIFIER)) {
      sb.append(child.getTokenOriginalValue());
      sb.append('.');
    }
    sb.deleteCharAt(sb.length() - 1);

    return sb.toString();
  }

  private static boolean isStaticImport(AstNode node) {
    return node.hasDirectChildren(JavaKeyword.STATIC);
  }

  private static boolean isImportOnDemand(AstNode node) {
    return node.hasDirectChildren(JavaPunctuator.STAR);
  }

  @Override
  public void visitToken(Token token) {
    if (token.hasTrivia()) {
      for (Trivia trivia : token.getTrivia()) {
        updatePendingImportsForComments(trivia.getToken().getOriginalValue());
      }
    }
  }

  private void updatePendingImportsForComments(String comment) {
    Iterator it = pendingImports.iterator();
    while (it.hasNext()) {
      String pendingImport = it.next();

      if (comment.contains(extractLastClassName(pendingImport))) {
        it.remove();
      }
    }
  }

  private static String extractFirstClassName(String reference) {
    int firstIndexOfDot = reference.indexOf('.');
    return firstIndexOfDot == -1 ? reference : reference.substring(0, firstIndexOfDot);
  }

  private static String extractLastClassName(String reference) {
    int lastIndexOfDot = reference.lastIndexOf('.');
    return lastIndexOfDot == -1 ? reference : reference.substring(lastIndexOfDot + 1);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy