blob: 85352f07c0ee0076a561fac8f8b0c2cfb432b5d7 [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';
import 'package:analyzer/src/dart/ast/extensions.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(offset, offset + length);
unit.accept(visitor);
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 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.startOffset, this.endOffset);
@override
void visitNode(AstNode node) {
if (node.offset <= endOffset && node.end >= startOffset) {
node.visitChildren(this);
}
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (!node.inDeclarationContext() &&
node.offset <= endOffset &&
node.end >= startOffset &&
!_isConstructorDeclarationReturnType(node)) {
var nodeElement = node.writeOrReadElement;
if (nodeElement != null &&
nodeElement.enclosingElement is CompilationUnitElement) {
var nodeLibrary = nodeElement.library;
var path = nodeLibrary?.definingCompilationUnit.source.fullName;
if (path == null) {
return;
}
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) {
var target = parent.target;
if (target is SimpleIdentifier) {
prefix = _getPrefixFrom(target);
}
}
var key = '$prefix;$path';
var elements = importedElements.putIfAbsent(
key, () => ImportedElements(path, prefix, <String>[]));
var elementNames = elements.elements;
var elementName = nodeElement.name;
if (elementName != null && !elementNames.contains(elementName)) {
elementNames.add(elementName);
}
}
}
}
String _getPrefixFrom(SimpleIdentifier identifier) {
if (identifier.offset <= endOffset && identifier.end >= startOffset) {
var prefixElement = identifier.staticElement;
if (prefixElement is PrefixElement) {
return prefixElement.name;
}
}
return '';
}
static bool _isConstructorDeclarationReturnType(SimpleIdentifier node) {
var parent = node.parent;
return parent is ConstructorDeclaration && parent.returnType == node;
}
}