blob: 8d9ac7bc086dd19590e09c1ebc7bb2218954856c [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
/// An object used to compute the list of elements referenced within a given
/// region of a compilation unit that are imported into the compilation unit's
/// library.
class ImportedElementsComputer {
/// The compilation unit in which the elements are referenced.
final CompilationUnit unit;
/// The offset of the region containing the references to be returned.
final int offset;
/// The length of the region containing the references to be returned.
final int length;
/// Initialize a newly created computer to compute the list of imported
/// elements referenced in the given [unit] within the region with the given
/// [offset] and [length].
ImportedElementsComputer(this.unit, this.offset, this.length);
/// Compute and return the list of imported elements.
List<ImportedElements> compute() {
if (_regionIncludesDirectives()) {
return const <ImportedElements>[];
var visitor =
_Visitor(unit.declaredElement.library, offset, offset + length);
return visitor.importedElements.values.toList();
/// Return `true` if the region being copied includes any directives. This
/// really only needs to check for import and export directives, but excluding
/// other directives is unlikely to hurt the UX.
bool _regionIncludesDirectives() {
var directives = unit.directives;
if (directives.isEmpty) {
return false;
// This might be overly restrictive if there are directives after the first
// declaration, but that should be a rare case given that it's invalid.
return offset < directives.last.end;
/// The visitor used by an [ImportedElementsComputer] to record the names of all
/// imported elements.
class _Visitor extends UnifyingAstVisitor<void> {
/// The element representing the library containing the code being visited.
final LibraryElement containingLibrary;
/// The offset of the start of the region of text being copied.
final int startOffset;
/// The offset of the end of the region of text being copied.
final int endOffset;
/// A table mapping library path and prefix keys to the imported elements from
/// that library.
Map<String, ImportedElements> importedElements = <String, ImportedElements>{};
/// Initialize a newly created visitor to visit nodes within a specified
/// region.
_Visitor(this.containingLibrary, this.startOffset, this.endOffset);
void visitNode(AstNode node) {
if (node.offset <= endOffset && node.end >= startOffset) {
void visitSimpleIdentifier(SimpleIdentifier node) {
if (!node.inDeclarationContext() &&
node.offset <= endOffset &&
node.end >= startOffset &&
!_isConstructorDeclarationReturnType(node)) {
var nodeElement = node.staticElement;
if (nodeElement != null &&
nodeElement.enclosingElement is CompilationUnitElement) {
var nodeLibrary = nodeElement.library;
var path = nodeLibrary.definingCompilationUnit.source.fullName;
var prefix = '';
var parent = node.parent;
if (parent is PrefixedIdentifier && parent.identifier == node) {
prefix = _getPrefixFrom(parent.prefix);
} else if (parent is MethodInvocation &&
parent.methodName == node && is SimpleIdentifier) {
prefix = _getPrefixFrom(;
var key = '$prefix;$path';
var elements = importedElements.putIfAbsent(
key, () => ImportedElements(path, prefix, <String>[]));
var elementNames = elements.elements;
var elementName =;
if (!elementNames.contains(elementName)) {
String _getPrefixFrom(SimpleIdentifier identifier) {
if (identifier.offset <= endOffset && identifier.end >= startOffset) {
var prefixElement = identifier.staticElement;
if (prefixElement is PrefixElement) {
return '';
static bool _isConstructorDeclarationReturnType(SimpleIdentifier node) {
var parent = node.parent;
return parent is ConstructorDeclaration && parent.returnType == node;