blob: 086f7c6ee6e473765c7e97d8e4187c62435530a8 [file] [log] [blame]
// Copyright (c) 2019, 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:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:analyzer_plugin/src/utilities/change_builder/dart/syntactic_scope.dart';
import 'package:meta/meta.dart';
ImportLibraryRequest importLibraryElementImpl({
@required ResolvedLibraryResult targetResolvedLibrary,
@required String targetPath,
@required int targetOffset,
@required LibraryElement requestedLibrary,
@required String requestedName,
}) {
var targetLibrary = targetResolvedLibrary.element;
var requestedLibraryUri = requestedLibrary.source.uri;
var requestedElement = requestedLibrary.exportNamespace.get(requestedName);
var requestedElementUri = requestedElement.librarySource.uri;
// If the element is defined in this library, then no prefix needed.
if (targetLibrary == requestedElement.library) {
return ImportLibraryRequest(null, null);
}
var requestedElements = requestedLibrary.exportNamespace.definedNames;
_removeEntryForDynamic(requestedElements);
// Find URIs of all libraries that import the requested name into the target.
var unprefixedNameUriSet = Set<Uri>();
for (var import in targetLibrary.imports) {
var definedNames = import.namespace.definedNames;
if (import.prefix == null) {
var element = definedNames[requestedName];
if (element != null) {
unprefixedNameUriSet.add(element.librarySource.uri);
}
}
}
// Find names that will be shadowed by a new unprefixed import.
var collector = NotSyntacticScopeReferencedNamesCollector(
targetResolvedLibrary.element,
requestedElements.keys.toSet(),
);
for (var resolvedUnit in targetResolvedLibrary.units) {
resolvedUnit.unit.accept(collector);
}
// Find names that will shadow unprefixed references.
var scopeNames = Set<String>();
targetLibrary.accept(_TopLevelNamesCollector(scopeNames));
for (var resolvedUnit in targetResolvedLibrary.units) {
if (resolvedUnit.path == targetPath) {
resolvedUnit.unit.accept(
SyntacticScopeNamesCollector(scopeNames, targetOffset),
);
}
}
var canUseUnprefixedImport = true;
// If a name is inherited, it will be shadowed by a new unprefixed import.
if (collector.inheritedNames.isNotEmpty) {
canUseUnprefixedImport = false;
}
// If a name is imported, and it is not the same as the one from the
// requested library, then a new unprefixed import will cause ambiguity.
for (var name in collector.importedNames.keys) {
var importedUri = collector.importedNames[name];
var requestedUri = requestedElements[name]?.librarySource?.uri;
if (requestedUri != importedUri) {
canUseUnprefixedImport = false;
break;
}
}
// If syntactic scope at the offset has the requested name, then the name
// from an unprefixed import will be shadowed.
if (scopeNames.contains(requestedName)) {
canUseUnprefixedImport = false;
}
// If the requested name is ambiguous from existing unprefixed imports,
// or is not the same element as the one from the requested library, then
// we cannot use unprefixed import.
if (unprefixedNameUriSet.isNotEmpty) {
if (unprefixedNameUriSet.length > 1 ||
unprefixedNameUriSet.first != requestedElementUri) {
canUseUnprefixedImport = false;
}
}
// Find import prefixes with which the name is ambiguous.
var ambiguousWithImportPrefixes = Set<String>();
for (var import in targetLibrary.imports) {
var definedNames = import.namespace.definedNames;
if (import.prefix != null) {
var prefix = import.prefix.name;
var prefixedName = '$prefix.$requestedName';
var importedElement = definedNames[prefixedName];
if (importedElement != null &&
importedElement.librarySource.uri != requestedElementUri) {
ambiguousWithImportPrefixes.add(prefix);
}
}
}
// Check for existing imports of the requested library.
for (var import in targetLibrary.imports) {
if (import.importedLibrary.source.uri == requestedLibraryUri) {
var importedNames = import.namespace.definedNames;
if (import.prefix == null) {
if (canUseUnprefixedImport &&
importedNames.containsKey(requestedName)) {
return ImportLibraryRequest(null, null);
}
} else {
var prefix = import.prefix.name;
var prefixedName = '$prefix.$requestedName';
if (importedNames.containsKey(prefixedName) &&
!ambiguousWithImportPrefixes.contains(prefix)) {
return ImportLibraryRequest(null, prefix);
}
}
}
}
// If the name cannot be used without import prefix, generate one.
String prefix;
if (!canUseUnprefixedImport) {
prefix = 'prefix';
for (var index = 0;; index++) {
prefix = 'prefix$index';
if (!collector.referencedNames.contains(prefix)) {
break;
}
}
}
return ImportLibraryRequest(requestedLibraryUri, prefix);
}
/// The type `dynamic` is part of 'dart:core', but has no library.
void _removeEntryForDynamic(Map<String, Element> requestedElements) {
requestedElements.removeWhere((_, element) {
if (element.librarySource == null) {
assert(element.displayName == 'dynamic');
return true;
}
return false;
});
}
/// Information about a library to import.
class ImportLibraryRequest {
/// The URI of the library to import, or `null` if the requested library is
/// already imported, with the [prefix], so no new import is required.
final Uri uri;
/// The prefix with which ths requested library is already imported,
/// or should be imported, or `null` is no prefix is necessary.
final String prefix;
ImportLibraryRequest(this.uri, this.prefix);
}
/// Element visitor that collects names of top-level elements.
class _TopLevelNamesCollector extends GeneralizingElementVisitor<void> {
final Set<String> names;
_TopLevelNamesCollector(this.names);
@override
void visitElement(Element element) {
if (element is LibraryElement || element is CompilationUnitElement) {
super.visitElement(element);
} else if (element is ImportElement) {
var prefix = element.prefix?.displayName;
if (prefix != null) {
names.add(prefix);
}
} else if (element.enclosingElement is CompilationUnitElement) {
names.add(element.displayName);
}
}
}