Index: pkg/analyzer/lib/src/dart/analysis/search.dart |
diff --git a/pkg/analyzer/lib/src/dart/analysis/search.dart b/pkg/analyzer/lib/src/dart/analysis/search.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e28a5b6dd5ba76d02ecf69aa933232c975b15938 |
--- /dev/null |
+++ b/pkg/analyzer/lib/src/dart/analysis/search.dart |
@@ -0,0 +1,207 @@ |
+// Copyright (c) 2016, 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 'dart:async'; |
+ |
+import 'package:analyzer/dart/ast/ast.dart'; |
+import 'package:analyzer/dart/ast/visitor.dart'; |
+import 'package:analyzer/dart/element/element.dart'; |
+import 'package:analyzer/dart/element/visitor.dart'; |
+import 'package:analyzer/src/dart/analysis/driver.dart'; |
+import 'package:analyzer/src/dart/ast/utilities.dart'; |
+import 'package:analyzer/src/dart/element/element.dart'; |
+ |
+/** |
+ * Search support for an [AnalysisDriver]. |
+ */ |
+class Search { |
+ final AnalysisDriver _driver; |
+ |
+ Search(this._driver); |
+ |
+ /** |
+ * Returns references to the element at the given [offset] in the file with |
+ * the given [path]. |
+ */ |
+ Future<List<SearchResult>> references(String path, int offset) async { |
+ // Search only in added files. |
+ if (!_driver.addedFiles.contains(path)) { |
+ return const <SearchResult>[]; |
+ } |
+ |
+ AnalysisResult analysisResult = await _driver.getResult(path); |
+ CompilationUnit unit = analysisResult.unit; |
+ |
+ // Prepare the node. |
+ AstNode node = new NodeLocator(offset).searchWithin(unit); |
+ if (node == null) { |
+ return const <SearchResult>[]; |
+ } |
+ |
+ // Prepare the element. |
+ Element element = ElementLocator.locate(node); |
+ if (element == null) { |
+ return const <SearchResult>[]; |
+ } |
+ |
+ ElementKind kind = element.kind; |
+ if (kind == ElementKind.LABEL || kind == ElementKind.LOCAL_VARIABLE) { |
+ Block block = node.getAncestor((n) => n is Block); |
+ return _searchReferences_Local(element, unit.element, block); |
+ } |
+ // TODO(scheglov) support other kinds |
+ return []; |
+ } |
+ |
+ Future<List<SearchResult>> _searchReferences_Local( |
+ Element element, |
+ CompilationUnitElement enclosingUnitElement, |
+ AstNode enclosingNode) async { |
+ _LocalReferencesVisitor visitor = |
+ new _LocalReferencesVisitor(element, enclosingUnitElement); |
+ enclosingNode?.accept(visitor); |
+ return visitor.matches; |
+ } |
+} |
+ |
+/** |
+ * A single search result. |
+ */ |
+class SearchResult { |
+ /** |
+ * The element that is used at this result. |
+ */ |
+ final Element element; |
+ |
+ /** |
+ * The deep most element that contains this result. |
+ */ |
+ final Element enclosingElement; |
+ |
+ /** |
+ * The kind of the [element] usage. |
+ */ |
+ final SearchResultKind kind; |
+ |
+ /** |
+ * The offset relative to the beginning of the containing file. |
+ */ |
+ final int offset; |
+ |
+ /** |
+ * The length of the usage in the containing file context. |
+ */ |
+ final int length; |
+ |
+ /** |
+ * Is `true` if a field or a method is using with a qualifier. |
+ */ |
+ final bool isResolved; |
+ |
+ /** |
+ * Is `true` if the result is a resolved reference to [element]. |
+ */ |
+ final bool isQualified; |
+ |
+ SearchResult._(this.element, this.enclosingElement, this.kind, this.offset, |
+ this.length, this.isResolved, this.isQualified); |
+ |
+ @override |
+ String toString() { |
+ StringBuffer buffer = new StringBuffer(); |
+ buffer.write("SearchResult(kind="); |
+ buffer.write(kind); |
+ buffer.write(", offset="); |
+ buffer.write(offset); |
+ buffer.write(", length="); |
+ buffer.write(length); |
+ buffer.write(", isResolved="); |
+ buffer.write(isResolved); |
+ buffer.write(", isQualified="); |
+ buffer.write(isQualified); |
+ buffer.write(", enclosingElement="); |
+ buffer.write(enclosingElement); |
+ buffer.write(")"); |
+ return buffer.toString(); |
+ } |
+} |
+ |
+/** |
+ * The kind of reference in a [SearchResult]. |
+ */ |
+enum SearchResultKind { READ, READ_WRITE, WRITE, INVOCATION, REFERENCE } |
+ |
+/** |
+ * A visitor that finds the deep-most [Element] that contains the [offset]. |
+ */ |
+class _ContainingElementFinder extends GeneralizingElementVisitor { |
+ final int offset; |
+ Element containingElement; |
+ |
+ _ContainingElementFinder(this.offset); |
+ |
+ visitElement(Element element) { |
+ if (element is ElementImpl) { |
+ if (element.codeOffset != null && |
+ element.codeOffset <= offset && |
+ offset <= element.codeOffset + element.codeLength) { |
+ containingElement = element; |
+ super.visitElement(element); |
+ } |
+ } |
+ } |
+} |
+ |
+/** |
+ * Visitor that adds [SearchResult]s for local elements of a block, method, |
+ * class or a library - labels, local functions, local variables and parameters, |
+ * type parameters, import prefixes. |
+ */ |
+class _LocalReferencesVisitor extends RecursiveAstVisitor { |
+ final List<SearchResult> matches = <SearchResult>[]; |
+ |
+ final Element element; |
+ final CompilationUnitElement enclosingUnitElement; |
+ |
+ _LocalReferencesVisitor(this.element, this.enclosingUnitElement); |
+ |
+ @override |
+ visitSimpleIdentifier(SimpleIdentifier node) { |
+ if (node.inDeclarationContext()) { |
+ return; |
+ } |
+ if (node.staticElement == element) { |
+ AstNode parent = node.parent; |
+ SearchResultKind kind = SearchResultKind.REFERENCE; |
+ if (element is FunctionElement) { |
+ if (parent is MethodInvocation && parent.methodName == node) { |
+ kind = SearchResultKind.INVOCATION; |
+ } |
+ } else if (element is VariableElement) { |
+ bool isGet = node.inGetterContext(); |
+ bool isSet = node.inSetterContext(); |
+ if (isGet && isSet) { |
+ kind = SearchResultKind.READ_WRITE; |
+ } else if (isGet) { |
+ if (parent is MethodInvocation && parent.methodName == node) { |
+ kind = SearchResultKind.INVOCATION; |
+ } else { |
+ kind = SearchResultKind.READ; |
+ } |
+ } else if (isSet) { |
+ kind = SearchResultKind.WRITE; |
+ } |
+ } |
+ _addMatch(node, kind); |
+ } |
+ } |
+ |
+ void _addMatch(AstNode node, SearchResultKind kind) { |
+ bool isQualified = node.parent is Label; |
+ var finder = new _ContainingElementFinder(node.offset); |
+ enclosingUnitElement.accept(finder); |
+ matches.add(new SearchResult._(element, finder.containingElement, kind, |
+ node.offset, node.length, true, isQualified)); |
+ } |
+} |