| // 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 Element requestedElement, |
| }) { |
| var targetLibrary = targetResolvedLibrary.element; |
| |
| var requestedLibraryUri = requestedLibrary.source.uri; |
| var requestedElementUri = requestedElement.librarySource.uri; |
| var requestedName = requestedElement.displayName; |
| |
| // 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; |
| _removeEntriesForDynamicAndNever(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 types `dynamic` and `Never` are part of 'dart:core', but have no |
| /// library. |
| void _removeEntriesForDynamicAndNever(Map<String, Element> requestedElements) { |
| requestedElements.removeWhere((_, element) { |
| if (element.librarySource == null) { |
| assert( |
| element.displayName == 'dynamic' || element.displayName == 'Never'); |
| 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); |
| } |
| } |
| } |