Package com.google.dart.engine.internal.hint

Source Code of com.google.dart.engine.internal.hint.ImportsVerifier

/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.internal.hint;

import com.google.dart.engine.ast.Annotation;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.Directive;
import com.google.dart.engine.ast.ExportDirective;
import com.google.dart.engine.ast.ImportDirective;
import com.google.dart.engine.ast.LibraryDirective;
import com.google.dart.engine.ast.NodeList;
import com.google.dart.engine.ast.PrefixedIdentifier;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.visitor.RecursiveAstVisitor;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ImportElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.MultiplyDefinedElement;
import com.google.dart.engine.element.PrefixElement;
import com.google.dart.engine.error.HintCode;
import com.google.dart.engine.internal.error.ErrorReporter;
import com.google.dart.engine.internal.resolver.ElementResolver;
import com.google.dart.engine.internal.scope.Namespace;
import com.google.dart.engine.internal.scope.NamespaceBuilder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

/**
* Instances of the class {@code ImportsVerifier} visit all of the referenced libraries in the
* source code verifying that all of the imports are used, otherwise a
* {@link HintCode#UNUSED_IMPORT} is generated with
* {@link #generateUnusedImportHints(ErrorReporter)}.
* <p>
* While this class does not yet have support for an "Organize Imports" action, this logic built up
* in this class could be used for such an action in the future.
*
* @coverage dart.engine.resolver
*/
public class ImportsVerifier extends RecursiveAstVisitor<Void> {
  /**
   * This is set to {@code true} if the current compilation unit which is being visited is the
   * defining compilation unit for the library, its value can be set with
   * {@link #setInDefiningCompilationUnit(boolean)}.
   */
  private boolean inDefiningCompilationUnit = false;

  /**
   * The current library.
   */
  private final LibraryElement currentLibrary;

  /**
   * A list of {@link ImportDirective}s that the current library imports, as identifiers are visited
   * by this visitor and an import has been identified as being used by the library, the
   * {@link ImportDirective} is removed from this list. After all the sources in the library have
   * been evaluated, this list represents the set of unused imports.
   *
   * @see ImportsVerifier#generateUnusedImportErrors(ErrorReporter)
   */
  private final ArrayList<ImportDirective> unusedImports;

  /**
   * After the list of {@link #unusedImports} has been computed, this list is a proper subset of the
   * unused imports that are listed more than once.
   */
  private final ArrayList<ImportDirective> duplicateImports;

  /**
   * This is a map between the set of {@link LibraryElement}s that the current library imports, and
   * a list of {@link ImportDirective}s that imports the library. In cases where the current library
   * imports a library with a single directive (such as {@code import lib1.dart;}), the library
   * element will map to a list of one {@link ImportDirective}, which will then be removed from the
   * {@link #unusedImports} list. In cases where the current library imports a library with multiple
   * directives (such as {@code import lib1.dart; import lib1.dart show C;}), the
   * {@link LibraryElement} will be mapped to a list of the import directives, and the namespace
   * will need to be used to compute the correct {@link ImportDirective} being used, see
   * {@link #namespaceMap}.
   */
  private final HashMap<LibraryElement, ArrayList<ImportDirective>> libraryMap;

  /**
   * In cases where there is more than one import directive per library element, this mapping is
   * used to determine which of the multiple import directives are used by generating a
   * {@link Namespace} for each of the imports to do lookups in the same way that they are done from
   * the {@link ElementResolver}.
   */
  private final HashMap<ImportDirective, Namespace> namespaceMap;

  /**
   * This is a map between prefix elements and the import directives from which they are derived. In
   * cases where a type is referenced via a prefix element, the import directive can be marked as
   * used (removed from the unusedImports) by looking at the resolved {@code lib} in {@code lib.X},
   * instead of looking at which library the {@code lib.X} resolves.
   * <p>
   * TODO (jwren) Since multiple {@link ImportDirective}s can share the same {@link PrefixElement},
   * it is possible to have an unreported unused import in situations where two imports use the same
   * prefix and at least one import directive is used.
   */
  private final HashMap<PrefixElement, ArrayList<ImportDirective>> prefixElementMap;

  /**
   * Create a new instance of the {@link ImportsVerifier}.
   *
   * @param errorReporter the error reporter
   */
  public ImportsVerifier(LibraryElement library) {
    this.currentLibrary = library;
    this.unusedImports = new ArrayList<ImportDirective>();
    this.duplicateImports = new ArrayList<ImportDirective>();
    this.libraryMap = new HashMap<LibraryElement, ArrayList<ImportDirective>>();
    this.namespaceMap = new HashMap<ImportDirective, Namespace>();
    this.prefixElementMap = new HashMap<PrefixElement, ArrayList<ImportDirective>>();
  }

  /**
   * Any time after the defining compilation unit has been visited by this visitor, this method can
   * be called to report an {@link HintCode#DUPLICATE_IMPORT} hint for each of the import directives
   * in the {@link #duplicateImports} list.
   *
   * @param errorReporter the error reporter to report the set of {@link HintCode#DUPLICATE_IMPORT}
   *          hints to
   */
  public void generateDuplicateImportHints(ErrorReporter errorReporter) {
    for (ImportDirective duplicateImport : duplicateImports) {
      errorReporter.reportErrorForNode(HintCode.DUPLICATE_IMPORT, duplicateImport.getUri());
    }
  }

  /**
   * After all of the compilation units have been visited by this visitor, this method can be called
   * to report an {@link HintCode#UNUSED_IMPORT} hint for each of the import directives in the
   * {@link #unusedImports} list.
   *
   * @param errorReporter the error reporter to report the set of {@link HintCode#UNUSED_IMPORT}
   *          hints to
   */
  public void generateUnusedImportHints(ErrorReporter errorReporter) {
    for (ImportDirective unusedImport : unusedImports) {
      // Check that the import isn't dart:core
      ImportElement importElement = unusedImport.getElement();
      if (importElement != null) {
        LibraryElement libraryElement = importElement.getImportedLibrary();
        if (libraryElement != null && libraryElement.isDartCore()) {
          continue;
        }
      }
      errorReporter.reportErrorForNode(HintCode.UNUSED_IMPORT, unusedImport.getUri());
    }
  }

  /*
   * Should we mark imports which are only used by comments as unused?  While the VM and dart2js
   * don't care, the analyzer (and thus the hyperlink in the Editor) and the link in dartdoc is
   * lost- thus we have made the decision for the time being to not mark them as unused.
   */
//  @Override
//  public Void visitComment(Comment node) {
//    return null;
//  }

  @Override
  public Void visitCompilationUnit(CompilationUnit node) {
    if (inDefiningCompilationUnit) {
      NodeList<Directive> directives = node.getDirectives();
      for (Directive directive : directives) {
        if (directive instanceof ImportDirective) {
          ImportDirective importDirective = (ImportDirective) directive;
          LibraryElement libraryElement = importDirective.getUriElement();
          if (libraryElement != null) {
            unusedImports.add(importDirective);
            //
            // Initialize prefixElementMap
            //
            if (importDirective.getAsToken() != null) {
              SimpleIdentifier prefixIdentifier = importDirective.getPrefix();
              if (prefixIdentifier != null) {
                Element element = prefixIdentifier.getStaticElement();
                if (element instanceof PrefixElement) {
                  PrefixElement prefixElementKey = (PrefixElement) element;
                  ArrayList<ImportDirective> list = prefixElementMap.get(prefixElementKey);
                  if (list == null) {
                    list = new ArrayList<ImportDirective>(1);
                    prefixElementMap.put(prefixElementKey, list);
                  }
                  list.add(importDirective);
                }
                // TODO (jwren) Can the element ever not be a PrefixElement?
              }
            }
            //
            // Initialize libraryMap: libraryElement -> importDirective
            //
            putIntoLibraryMap(libraryElement, importDirective);
            //
            // For this new addition to the libraryMap, also recursively add any exports from the
            // libraryElement
            //
            addAdditionalLibrariesForExports(
                libraryElement,
                importDirective,
                new ArrayList<LibraryElement>());
          }
        }
      }
    }
    // If there are no imports in this library, don't visit the identifiers in the library- there
    // can be no unused imports.
    if (unusedImports.isEmpty()) {
      return null;
    }
    if (unusedImports.size() > 1) {
      // order the list of unusedImports to find duplicates in faster than O(n^2) time
      ImportDirective[] importDirectiveArray = unusedImports.toArray(new ImportDirective[unusedImports.size()]);
      Arrays.sort(importDirectiveArray, ImportDirective.COMPARATOR);
      ImportDirective currentDirective = importDirectiveArray[0];
      for (int i = 1; i < importDirectiveArray.length; i++) {
        ImportDirective nextDirective = importDirectiveArray[i];
        if (ImportDirective.COMPARATOR.compare(currentDirective, nextDirective) == 0) {
          // Add either the currentDirective or nextDirective depending on which comes second, this
          // guarantees that the first of the duplicates won't be highlighted.
          if (currentDirective.getOffset() < nextDirective.getOffset()) {
            duplicateImports.add(nextDirective);
          } else {
            duplicateImports.add(currentDirective);
          }
        }
        currentDirective = nextDirective;
      }
    }
    return super.visitCompilationUnit(node);
  }

  @Override
  public Void visitExportDirective(ExportDirective node) {
    visitMetadata(node.getMetadata());
    return null;
  }

  @Override
  public Void visitImportDirective(ImportDirective node) {
    visitMetadata(node.getMetadata());
    return null;
  }

  @Override
  public Void visitLibraryDirective(LibraryDirective node) {
    visitMetadata(node.getMetadata());
    return null;
  }

  @Override
  public Void visitPrefixedIdentifier(PrefixedIdentifier node) {
    if (unusedImports.isEmpty()) {
      return null;
    }
    // If the prefixed identifier references some A.B, where A is a library prefix, then we can
    // lookup the associated ImportDirective in prefixElementMap and remove it from the
    // unusedImports list.
    SimpleIdentifier prefixIdentifier = node.getPrefix();
    Element element = prefixIdentifier.getStaticElement();
    if (element instanceof PrefixElement) {
      ArrayList<ImportDirective> importDirectives = prefixElementMap.get(element);
      if (importDirectives != null) {
        for (ImportDirective importDirective : importDirectives) {
          unusedImports.remove(importDirective);
        }
      }
      return null;
    }
    // Otherwise, pass the prefixed identifier element and name onto visitIdentifier.
    return visitIdentifier(element, prefixIdentifier.getName());
  }

  @Override
  public Void visitSimpleIdentifier(SimpleIdentifier node) {
    if (unusedImports.isEmpty()) {
      return null;
    }
    return visitIdentifier(node.getStaticElement(), node.getName());
  }

  void setInDefiningCompilationUnit(boolean inDefiningCompilationUnit) {
    this.inDefiningCompilationUnit = inDefiningCompilationUnit;
  }

  /**
   * Recursively add any exported library elements into the {@link #libraryMap}.
   */
  private void addAdditionalLibrariesForExports(LibraryElement library,
      ImportDirective importDirective, ArrayList<LibraryElement> exportPath) {
    if (exportPath.contains(library)) {
      return;
    }
    exportPath.add(library);
    for (LibraryElement exportedLibraryElt : library.getExportedLibraries()) {
      putIntoLibraryMap(exportedLibraryElt, importDirective);
      addAdditionalLibrariesForExports(exportedLibraryElt, importDirective, exportPath);
    }
  }

  /**
   * Lookup and return the {@link Namespace} from the {@link #namespaceMap}, if the map does not
   * have the computed namespace, compute it and cache it in the map. If the import directive is not
   * resolved or is not resolvable, {@code null} is returned.
   *
   * @param importDirective the import directive used to compute the returned namespace
   * @return the computed or looked up {@link Namespace}
   */
  private Namespace computeNamespace(ImportDirective importDirective) {
    Namespace namespace = namespaceMap.get(importDirective);
    if (namespace == null) {
      // If the namespace isn't in the namespaceMap, then compute and put it in the map
      ImportElement importElement = importDirective.getElement();
      if (importElement != null) {
        NamespaceBuilder builder = new NamespaceBuilder();
        namespace = builder.createImportNamespaceForDirective(importElement);
        namespaceMap.put(importDirective, namespace);
      }
    }
    return namespace;
  }

  /**
   * The {@link #libraryMap} is a mapping between a library elements and a list of import
   * directives, but when adding these mappings into the {@link #libraryMap}, this method can be
   * used to simply add the mapping between the library element an an import directive without
   * needing to check to see if a list needs to be created.
   */
  private void putIntoLibraryMap(LibraryElement libraryElement, ImportDirective importDirective) {
    ArrayList<ImportDirective> importList = libraryMap.get(libraryElement);
    if (importList == null) {
      importList = new ArrayList<ImportDirective>(3);
      libraryMap.put(libraryElement, importList);
    }
    importList.add(importDirective);
  }

  private Void visitIdentifier(Element element, String name) {
    if (element == null) {
      return null;
    }
    // If the element is multiply defined then call this method recursively for each of the
    // conflicting elements.
    if (element instanceof MultiplyDefinedElement) {
      MultiplyDefinedElement multiplyDefinedElement = (MultiplyDefinedElement) element;
      for (Element elt : multiplyDefinedElement.getConflictingElements()) {
        visitIdentifier(elt, name);
      }
      return null;
    } else if (element instanceof PrefixElement) {
      ArrayList<ImportDirective> importDirectives = prefixElementMap.get(element);
      if (importDirectives != null) {
        for (ImportDirective importDirective : importDirectives) {
          unusedImports.remove(importDirective);
        }
      }
      return null;
    } else if (!(element.getEnclosingElement() instanceof CompilationUnitElement)) {
      // Identifiers that aren't a prefix element and whose enclosing element isn't a
      // CompilationUnit are ignored- this covers the case the identifier is a relative-reference,
      // a reference to an identifier not imported by this library.
      return null;
    }
    LibraryElement containingLibrary = element.getLibrary();
    if (containingLibrary == null) {
      return null;
    }

    // If the element is declared in the current library, return.
    if (currentLibrary.equals(containingLibrary)) {
      return null;
    }
    ArrayList<ImportDirective> importsFromSameLibrary = libraryMap.get(containingLibrary);
    if (importsFromSameLibrary == null) {
      return null;
    }
    if (importsFromSameLibrary.size() == 1) {
      // If there is only one import directive for this library, then it must be the directive that
      // this element is imported with, remove it from the unusedImports list.
      ImportDirective usedImportDirective = importsFromSameLibrary.get(0);
      unusedImports.remove(usedImportDirective);
    } else {
      // Otherwise, for each of the imported directives, use the namespaceMap to
      for (ImportDirective importDirective : importsFromSameLibrary) {
        // Get the namespace for this import
        Namespace namespace = computeNamespace(importDirective);
        if (namespace != null && namespace.get(name) != null) {
          unusedImports.remove(importDirective);
        }
      }
    }
    return null;
  }

  /**
   * Given some {@link NodeList} of {@link Annotation}s, ensure that the identifiers are visited by
   * this visitor. Specifically, this covers the cases where AST nodes don't have their identifiers
   * visited by this visitor, but still need their annotations visited.
   *
   * @param annotations the list of annotations to visit
   */
  private void visitMetadata(NodeList<Annotation> annotations) {
    int count = annotations.size();
    for (int i = 0; i < count; i++) {
      annotations.get(i).accept(this);
    }
  }
}
TOP

Related Classes of com.google.dart.engine.internal.hint.ImportsVerifier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.