| // 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 | |
| /// portion 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]. | |
| new(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 portion of text being copied. | |
| final int startOffset; | |
| /// The offset of the end of the portion 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 | |
| /// portion. | |
| new(this.startOffset, this.endOffset); | |
| @override | |
| void visitNamedType(NamedType node) { | |
| if (node.offset <= endOffset && node.end >= startOffset) { | |
| var importPrefix = node.importPrefix; | |
| var prefix = importPrefix?.element?.name ?? ''; | |
| _addElement(prefix, node.element); | |
| } | |
| super.visitNamedType(node); | |
| } | |
| @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; | |
| 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); | |
| } | |
| } | |
| _addElement(prefix, nodeElement); | |
| } | |
| } | |
| void _addElement(String prefix, Element? element) { | |
| if (element == null) { | |
| return; | |
| } | |
| if (element is PrefixElement) { | |
| return; | |
| } | |
| if (element.enclosingElement is! LibraryElement) { | |
| return; | |
| } | |
| var path = element.library?.firstFragment.source.fullName; | |
| if (path == null) { | |
| return; | |
| } | |
| var key = '$prefix;$path'; | |
| var elements = importedElements.putIfAbsent( | |
| key, | |
| () => ImportedElements(path, prefix, <String>[]), | |
| ); | |
| var elementNames = elements.elements; | |
| var elementName = element.name; | |
| if (elementName != null && !elementNames.contains(elementName)) { | |
| elementNames.add(elementName); | |
| } | |
| } | |
| String _getPrefixFrom(SimpleIdentifier identifier) { | |
| if (identifier.offset <= endOffset && identifier.end >= startOffset) { | |
| var prefixElement = identifier.element; | |
| if (prefixElement is PrefixElement) { | |
| return prefixElement.name ?? ''; | |
| } | |
| } | |
| return ''; | |
| } | |
| static bool _isConstructorDeclarationReturnType(SimpleIdentifier node) { | |
| var parent = node.parent; | |
| return parent is ConstructorDeclaration && parent.typeName == node; | |
| } | |
| } |