| // 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 'dart:collection'; |
| |
| 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/analysis/file_state.dart'; |
| import 'package:analyzer/src/dart/analysis/index.dart'; |
| import 'package:analyzer/src/dart/ast/utilities.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/member.dart'; |
| import 'package:analyzer/src/summary/idl.dart'; |
| import 'package:collection/collection.dart'; |
| |
| Element _getEnclosingElement(CompilationUnitElement unitElement, int offset) { |
| var finder = new _ContainingElementFinder(offset); |
| unitElement.accept(finder); |
| return finder.containingElement; |
| } |
| |
| /** |
| * An element declaration. |
| */ |
| class Declaration { |
| final int fileIndex; |
| final String name; |
| final DeclarationKind kind; |
| final int offset; |
| final int line; |
| final int column; |
| final int codeOffset; |
| final int codeLength; |
| final String className; |
| final String parameters; |
| |
| Declaration( |
| this.fileIndex, |
| this.name, |
| this.kind, |
| this.offset, |
| this.line, |
| this.column, |
| this.codeOffset, |
| this.codeLength, |
| this.className, |
| this.parameters); |
| } |
| |
| /** |
| * The kind of a [Declaration]. |
| */ |
| enum DeclarationKind { |
| CLASS, |
| CLASS_TYPE_ALIAS, |
| CONSTRUCTOR, |
| ENUM, |
| ENUM_CONSTANT, |
| FIELD, |
| FUNCTION, |
| FUNCTION_TYPE_ALIAS, |
| GETTER, |
| METHOD, |
| SETTER, |
| VARIABLE |
| } |
| |
| /** |
| * Search support for an [AnalysisDriver]. |
| */ |
| class Search { |
| final AnalysisDriver _driver; |
| |
| Search(this._driver); |
| |
| /** |
| * Returns class members with the given [name]. |
| */ |
| Future<List<Element>> classMembers(String name) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| List<Element> elements = <Element>[]; |
| |
| void addElement(Element element) { |
| if (!element.isSynthetic && element.displayName == name) { |
| elements.add(element); |
| } |
| } |
| |
| List<String> files = await _driver.getFilesDefiningClassMemberName(name); |
| for (String file in files) { |
| UnitElementResult unitResult = await _driver.getUnitElement(file); |
| if (unitResult != null) { |
| for (ClassElement clazz in unitResult.element.types) { |
| clazz.accessors.forEach(addElement); |
| clazz.fields.forEach(addElement); |
| clazz.methods.forEach(addElement); |
| } |
| } |
| } |
| return elements; |
| } |
| |
| /** |
| * Return top-level and class member declarations. |
| * |
| * If [regExp] is not `null`, only declaration with names matching it are |
| * returned. Otherwise, all declarations are returned. |
| * |
| * If [maxResults] is not `null`, it sets the maximum number of returned |
| * declarations. |
| * |
| * The path of each file with at least one declaration is added to [files]. |
| */ |
| Future<List<Declaration>> declarations( |
| RegExp regExp, int maxResults, LinkedHashSet<String> files, |
| {String onlyForFile}) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| List<Declaration> declarations = <Declaration>[]; |
| |
| DeclarationKind getExecutableKind( |
| UnlinkedExecutable executable, bool topLevel) { |
| switch (executable.kind) { |
| case UnlinkedExecutableKind.constructor: |
| return DeclarationKind.CONSTRUCTOR; |
| case UnlinkedExecutableKind.functionOrMethod: |
| if (topLevel) { |
| return DeclarationKind.FUNCTION; |
| } |
| return DeclarationKind.METHOD; |
| case UnlinkedExecutableKind.getter: |
| return DeclarationKind.GETTER; |
| break; |
| default: |
| return DeclarationKind.SETTER; |
| } |
| } |
| |
| await _driver.discoverAvailableFiles(); |
| |
| try { |
| List<FileState> knownFiles = _driver.fsState.knownFiles.toList(); |
| for (FileState file in knownFiles) { |
| if (onlyForFile != null && file.path != onlyForFile) { |
| continue; |
| } |
| if (files.contains(file.path)) { |
| continue; |
| } |
| |
| int fileIndex; |
| |
| void addDeclaration(String name, DeclarationKind kind, int offset, |
| int codeOffset, int codeLength, |
| {String className, String parameters}) { |
| if (maxResults != null && declarations.length >= maxResults) { |
| throw const _MaxNumberOfDeclarationsError(); |
| } |
| |
| if (name.endsWith('=')) { |
| name = name.substring(0, name.length - 1); |
| } |
| if (regExp != null && !regExp.hasMatch(name)) { |
| return; |
| } |
| |
| if (fileIndex == null) { |
| fileIndex = files.length; |
| files.add(file.path); |
| } |
| |
| var location = file.lineInfo.getLocation(offset); |
| declarations.add(new Declaration( |
| fileIndex, |
| name, |
| kind, |
| offset, |
| location.lineNumber, |
| location.columnNumber, |
| codeOffset, |
| codeLength, |
| className, |
| parameters)); |
| } |
| |
| UnlinkedUnit unlinkedUnit = file.unlinked; |
| var parameterComposer = new _UnlinkedParameterComposer(unlinkedUnit); |
| |
| String getParametersString(List<UnlinkedParam> parameters) { |
| parameterComposer.clear(); |
| parameterComposer.appendParameters(parameters); |
| return parameterComposer.buffer.toString(); |
| } |
| |
| String getExecutableParameters(UnlinkedExecutable executable) { |
| if (executable.kind == UnlinkedExecutableKind.getter) { |
| return null; |
| } |
| return getParametersString(executable.parameters); |
| } |
| |
| for (var class_ in unlinkedUnit.classes) { |
| String className = class_.name; |
| addDeclaration( |
| className, |
| class_.isMixinApplication |
| ? DeclarationKind.CLASS_TYPE_ALIAS |
| : DeclarationKind.CLASS, |
| class_.nameOffset, |
| class_.codeRange.offset, |
| class_.codeRange.length); |
| parameterComposer.outerTypeParameters = class_.typeParameters; |
| |
| for (var field in class_.fields) { |
| addDeclaration(field.name, DeclarationKind.FIELD, field.nameOffset, |
| field.codeRange.offset, field.codeRange.length, |
| className: className); |
| } |
| |
| for (var executable in class_.executables) { |
| parameterComposer.innerTypeParameters = executable.typeParameters; |
| addDeclaration( |
| executable.name, |
| getExecutableKind(executable, false), |
| executable.nameOffset, |
| executable.codeRange.offset, |
| executable.codeRange.length, |
| className: className, |
| parameters: getExecutableParameters(executable)); |
| parameterComposer.innerTypeParameters = const []; |
| } |
| |
| parameterComposer.outerTypeParameters = const []; |
| } |
| |
| for (var enum_ in unlinkedUnit.enums) { |
| addDeclaration(enum_.name, DeclarationKind.ENUM, enum_.nameOffset, |
| enum_.codeRange.offset, enum_.codeRange.length); |
| for (var value in enum_.values) { |
| addDeclaration(value.name, DeclarationKind.ENUM_CONSTANT, |
| value.nameOffset, value.nameOffset, value.name.length); |
| } |
| } |
| |
| for (var executable in unlinkedUnit.executables) { |
| parameterComposer.outerTypeParameters = executable.typeParameters; |
| addDeclaration( |
| executable.name, |
| getExecutableKind(executable, true), |
| executable.nameOffset, |
| executable.codeRange.offset, |
| executable.codeRange.length, |
| parameters: getExecutableParameters(executable)); |
| } |
| |
| for (var typedef_ in unlinkedUnit.typedefs) { |
| parameterComposer.outerTypeParameters = typedef_.typeParameters; |
| addDeclaration( |
| typedef_.name, |
| DeclarationKind.FUNCTION_TYPE_ALIAS, |
| typedef_.nameOffset, |
| typedef_.codeRange.offset, |
| typedef_.codeRange.length, |
| parameters: getParametersString(typedef_.parameters)); |
| parameterComposer.outerTypeParameters = const []; |
| } |
| |
| for (var variable in unlinkedUnit.variables) { |
| addDeclaration( |
| variable.name, |
| DeclarationKind.VARIABLE, |
| variable.nameOffset, |
| variable.codeRange.offset, |
| variable.codeRange.length); |
| } |
| } |
| } on _MaxNumberOfDeclarationsError {} |
| |
| return declarations; |
| } |
| |
| /** |
| * Returns references to the [element]. |
| */ |
| Future<List<SearchResult>> references( |
| Element element, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| if (element == null) { |
| return const <SearchResult>[]; |
| } |
| |
| ElementKind kind = element.kind; |
| if (kind == ElementKind.CLASS || |
| kind == ElementKind.CONSTRUCTOR || |
| kind == ElementKind.FUNCTION_TYPE_ALIAS || |
| kind == ElementKind.SETTER) { |
| return _searchReferences(element, searchedFiles); |
| } else if (kind == ElementKind.COMPILATION_UNIT) { |
| return _searchReferences_CompilationUnit(element); |
| } else if (kind == ElementKind.GETTER) { |
| return _searchReferences_Getter(element, searchedFiles); |
| } else if (kind == ElementKind.FIELD || |
| kind == ElementKind.TOP_LEVEL_VARIABLE) { |
| return _searchReferences_Field(element, searchedFiles); |
| } else if (kind == ElementKind.FUNCTION || kind == ElementKind.METHOD) { |
| if (element.enclosingElement is ExecutableElement) { |
| return _searchReferences_Local( |
| element, (n) => n is Block, searchedFiles); |
| } |
| return _searchReferences_Function(element, searchedFiles); |
| } else if (kind == ElementKind.IMPORT) { |
| return _searchReferences_Import(element, searchedFiles); |
| } else if (kind == ElementKind.LABEL || |
| kind == ElementKind.LOCAL_VARIABLE) { |
| return _searchReferences_Local(element, (n) => n is Block, searchedFiles); |
| } else if (kind == ElementKind.LIBRARY) { |
| return _searchReferences_Library(element, searchedFiles); |
| } else if (kind == ElementKind.PARAMETER) { |
| return _searchReferences_Parameter(element, searchedFiles); |
| } else if (kind == ElementKind.PREFIX) { |
| return _searchReferences_Prefix(element, searchedFiles); |
| } else if (kind == ElementKind.TYPE_PARAMETER) { |
| return _searchReferences_Local( |
| element, (n) => n.parent is CompilationUnit, searchedFiles); |
| } |
| return const <SearchResult>[]; |
| } |
| |
| /** |
| * Returns subtypes of the given [type]. |
| * |
| * The [searchedFiles] are consulted to see if a file is "owned" by this |
| * [Search] object, so should be only searched by it to avoid duplicate |
| * results; and updated to take ownership if the file is not owned yet. |
| */ |
| Future<List<SearchResult>> subTypes( |
| ClassElement type, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| if (type == null) { |
| return const <SearchResult>[]; |
| } |
| List<SearchResult> results = <SearchResult>[]; |
| await _addResults(results, type, searchedFiles, const { |
| IndexRelationKind.IS_EXTENDED_BY: SearchResultKind.REFERENCE, |
| IndexRelationKind.IS_MIXED_IN_BY: SearchResultKind.REFERENCE, |
| IndexRelationKind.IS_IMPLEMENTED_BY: SearchResultKind.REFERENCE |
| }); |
| return results; |
| } |
| |
| /** |
| * Return direct [SubtypeResult]s for either the [type] or [subtype]. |
| */ |
| Future<List<SubtypeResult>> subtypes( |
| {ClassElement type, SubtypeResult subtype}) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| String name; |
| String id; |
| if (type != null) { |
| name = type.name; |
| id = type.librarySource.uri.toString() + |
| ';' + |
| type.source.uri.toString() + |
| ';' + |
| name; |
| } else { |
| name = subtype.name; |
| id = subtype.id; |
| } |
| |
| await _driver.discoverAvailableFiles(); |
| |
| List<SubtypeResult> results = []; |
| List<FileState> knownFiles = _driver.fsState.knownFiles.toList(); |
| for (FileState file in knownFiles) { |
| if (file.subtypedNames.contains(name)) { |
| AnalysisDriverUnitIndex index = await _driver.getIndex(file.path); |
| if (index != null) { |
| for (AnalysisDriverSubtype subtype in index.subtypes) { |
| if (subtype.supertypes.contains(id)) { |
| FileState library = file.isPart ? file.library : file; |
| results.add(new SubtypeResult( |
| library.uriStr, |
| library.uriStr + ';' + file.uriStr + ';' + subtype.name, |
| subtype.name, |
| subtype.members)); |
| } |
| } |
| } |
| } |
| } |
| |
| return results; |
| } |
| |
| /** |
| * Returns top-level elements with names matching the given [regExp]. |
| */ |
| Future<List<Element>> topLevelElements(RegExp regExp) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| List<Element> elements = <Element>[]; |
| |
| void addElement(Element element) { |
| if (!element.isSynthetic && regExp.hasMatch(element.displayName)) { |
| elements.add(element); |
| } |
| } |
| |
| List<FileState> knownFiles = _driver.fsState.knownFiles.toList(); |
| for (FileState file in knownFiles) { |
| UnitElementResult unitResult = await _driver.getUnitElement(file.path); |
| if (unitResult != null) { |
| CompilationUnitElement unitElement = unitResult.element; |
| unitElement.accessors.forEach(addElement); |
| unitElement.enums.forEach(addElement); |
| unitElement.functions.forEach(addElement); |
| unitElement.functionTypeAliases.forEach(addElement); |
| unitElement.topLevelVariables.forEach(addElement); |
| unitElement.types.forEach(addElement); |
| } |
| } |
| return elements; |
| } |
| |
| /** |
| * Returns unresolved references to the given [name]. |
| */ |
| Future<List<SearchResult>> unresolvedMemberReferences( |
| String name, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| if (name == null) { |
| return const <SearchResult>[]; |
| } |
| |
| // Prepare the list of files that reference the name. |
| List<String> files = await _driver.getFilesReferencingName(name); |
| |
| // Check the index of every file that references the element name. |
| List<SearchResult> results = []; |
| for (String file in files) { |
| if (searchedFiles.add(file, this)) { |
| AnalysisDriverUnitIndex index = await _driver.getIndex(file); |
| if (index != null) { |
| _IndexRequest request = new _IndexRequest(index); |
| var fileResults = await request.getUnresolvedMemberReferences( |
| name, |
| const { |
| IndexRelationKind.IS_READ_BY: SearchResultKind.READ, |
| IndexRelationKind.IS_WRITTEN_BY: SearchResultKind.WRITE, |
| IndexRelationKind.IS_READ_WRITTEN_BY: SearchResultKind.READ_WRITE, |
| IndexRelationKind.IS_INVOKED_BY: SearchResultKind.INVOCATION |
| }, |
| () => _getUnitElement(file), |
| ); |
| results.addAll(fileResults); |
| } |
| } |
| } |
| |
| return results; |
| } |
| |
| Future<Null> _addResults( |
| List<SearchResult> results, |
| Element element, |
| SearchedFiles searchedFiles, |
| Map<IndexRelationKind, SearchResultKind> relationToResultKind) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| // Prepare the element name. |
| String name = element.displayName; |
| if (element is ConstructorElement) { |
| name = element.enclosingElement.displayName; |
| } |
| |
| // Prepare the list of files that reference the element name. |
| List<String> files = <String>[]; |
| String path = element.source.fullName; |
| if (name.startsWith('_')) { |
| String libraryPath = element.library.source.fullName; |
| if (searchedFiles.add(libraryPath, this)) { |
| FileState library = _driver.fsState.getFileForPath(libraryPath); |
| List<FileState> candidates = [library]..addAll(library.partedFiles); |
| for (FileState file in candidates) { |
| if (file.path == path || file.referencedNames.contains(name)) { |
| files.add(file.path); |
| } |
| } |
| } |
| } else { |
| files = await _driver.getFilesReferencingName(name); |
| if (!files.contains(path)) { |
| files.add(path); |
| } |
| } |
| |
| // Check the index of every file that references the element name. |
| for (String file in files) { |
| if (searchedFiles.add(file, this)) { |
| await _addResultsInFile(results, element, relationToResultKind, file); |
| } |
| } |
| } |
| |
| /** |
| * Add results for [element] usage in the given [file]. |
| */ |
| Future<Null> _addResultsInFile( |
| List<SearchResult> results, |
| Element element, |
| Map<IndexRelationKind, SearchResultKind> relationToResultKind, |
| String file) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| AnalysisDriverUnitIndex index = await _driver.getIndex(file); |
| if (index != null) { |
| _IndexRequest request = new _IndexRequest(index); |
| int elementId = request.findElementId(element); |
| if (elementId != -1) { |
| List<SearchResult> fileResults = await request.getRelations( |
| elementId, relationToResultKind, () => _getUnitElement(file)); |
| results.addAll(fileResults); |
| } |
| } |
| } |
| |
| Future<CompilationUnitElement> _getUnitElement(String file) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| UnitElementResult result = await _driver.getUnitElement(file); |
| return result?.element; |
| } |
| |
| Future<List<SearchResult>> _searchReferences( |
| Element element, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| List<SearchResult> results = <SearchResult>[]; |
| await _addResults(results, element, searchedFiles, |
| const {IndexRelationKind.IS_REFERENCED_BY: SearchResultKind.REFERENCE}); |
| return results; |
| } |
| |
| Future<List<SearchResult>> _searchReferences_CompilationUnit( |
| CompilationUnitElement element) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| String path = element.source.fullName; |
| |
| // If the path is not known, then the file is not referenced. |
| if (!_driver.fsState.knownFilePaths.contains(path)) { |
| return const <SearchResult>[]; |
| } |
| |
| // Check every file that references the given path. |
| List<SearchResult> results = <SearchResult>[]; |
| List<FileState> knownFiles = _driver.fsState.knownFiles.toList(); |
| for (FileState file in knownFiles) { |
| for (FileState referencedFile in file.directReferencedFiles) { |
| if (referencedFile.path == path) { |
| await _addResultsInFile( |
| results, |
| element, |
| const { |
| IndexRelationKind.IS_REFERENCED_BY: SearchResultKind.REFERENCE |
| }, |
| file.path); |
| } |
| } |
| } |
| return results; |
| } |
| |
| Future<List<SearchResult>> _searchReferences_Field( |
| PropertyInducingElement field, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| List<SearchResult> results = <SearchResult>[]; |
| PropertyAccessorElement getter = field.getter; |
| PropertyAccessorElement setter = field.setter; |
| if (!field.isSynthetic) { |
| await _addResults(results, field, searchedFiles, const { |
| IndexRelationKind.IS_WRITTEN_BY: SearchResultKind.WRITE, |
| IndexRelationKind.IS_REFERENCED_BY: SearchResultKind.REFERENCE |
| }); |
| } |
| if (getter != null) { |
| await _addResults(results, getter, searchedFiles, const { |
| IndexRelationKind.IS_REFERENCED_BY: SearchResultKind.READ, |
| IndexRelationKind.IS_INVOKED_BY: SearchResultKind.INVOCATION |
| }); |
| } |
| if (setter != null) { |
| await _addResults(results, setter, searchedFiles, |
| const {IndexRelationKind.IS_REFERENCED_BY: SearchResultKind.WRITE}); |
| } |
| return results; |
| } |
| |
| Future<List<SearchResult>> _searchReferences_Function( |
| Element element, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| if (element is Member) { |
| element = (element as Member).baseElement; |
| } |
| List<SearchResult> results = <SearchResult>[]; |
| await _addResults(results, element, searchedFiles, const { |
| IndexRelationKind.IS_REFERENCED_BY: SearchResultKind.REFERENCE, |
| IndexRelationKind.IS_INVOKED_BY: SearchResultKind.INVOCATION |
| }); |
| return results; |
| } |
| |
| Future<List<SearchResult>> _searchReferences_Getter( |
| PropertyAccessorElement getter, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| List<SearchResult> results = <SearchResult>[]; |
| await _addResults(results, getter, searchedFiles, const { |
| IndexRelationKind.IS_REFERENCED_BY: SearchResultKind.REFERENCE, |
| IndexRelationKind.IS_INVOKED_BY: SearchResultKind.INVOCATION |
| }); |
| return results; |
| } |
| |
| Future<List<SearchResult>> _searchReferences_Import( |
| ImportElement element, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| String path = element.source.fullName; |
| if (!searchedFiles.add(path, this)) { |
| return const <SearchResult>[]; |
| } |
| |
| List<SearchResult> results = <SearchResult>[]; |
| LibraryElement libraryElement = element.library; |
| for (CompilationUnitElement unitElement in libraryElement.units) { |
| String unitPath = unitElement.source.fullName; |
| AnalysisResult unitAnalysisResult = await _driver.getResult(unitPath); |
| _ImportElementReferencesVisitor visitor = |
| new _ImportElementReferencesVisitor(element, unitElement); |
| unitAnalysisResult.unit.accept(visitor); |
| results.addAll(visitor.results); |
| } |
| return results; |
| } |
| |
| Future<List<SearchResult>> _searchReferences_Library( |
| LibraryElement element, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| String path = element.source.fullName; |
| if (!searchedFiles.add(path, this)) { |
| return const <SearchResult>[]; |
| } |
| |
| List<SearchResult> results = <SearchResult>[]; |
| for (CompilationUnitElement unitElement in element.units) { |
| String unitPath = unitElement.source.fullName; |
| AnalysisResult unitAnalysisResult = await _driver.getResult(unitPath); |
| CompilationUnit unit = unitAnalysisResult.unit; |
| for (Directive directive in unit.directives) { |
| if (directive is PartOfDirective && directive.element == element) { |
| results.add(new SearchResult._( |
| unit.element, |
| SearchResultKind.REFERENCE, |
| directive.libraryName.offset, |
| directive.libraryName.length, |
| true, |
| false)); |
| } |
| } |
| } |
| return results; |
| } |
| |
| Future<List<SearchResult>> _searchReferences_Local(Element element, |
| bool isRootNode(AstNode n), SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| String path = element.source.fullName; |
| if (!searchedFiles.add(path, this)) { |
| return const <SearchResult>[]; |
| } |
| |
| // Prepare the unit. |
| AnalysisResult analysisResult = await _driver.getResult(path); |
| CompilationUnit unit = analysisResult.unit; |
| if (unit == null) { |
| return const <SearchResult>[]; |
| } |
| |
| // Prepare the node. |
| AstNode node = new NodeLocator(element.nameOffset).searchWithin(unit); |
| if (node == null) { |
| return const <SearchResult>[]; |
| } |
| |
| // Prepare the enclosing node. |
| AstNode enclosingNode = node.getAncestor(isRootNode); |
| if (enclosingNode == null) { |
| return const <SearchResult>[]; |
| } |
| |
| // Find the matches. |
| _LocalReferencesVisitor visitor = |
| new _LocalReferencesVisitor(element, unit.element); |
| enclosingNode.accept(visitor); |
| return visitor.results; |
| } |
| |
| Future<List<SearchResult>> _searchReferences_Parameter( |
| ParameterElement parameter, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| List<SearchResult> results = <SearchResult>[]; |
| results.addAll(await _searchReferences_Local( |
| parameter, |
| (AstNode node) { |
| AstNode parent = node.parent; |
| return parent is ClassDeclaration || parent is CompilationUnit; |
| }, |
| searchedFiles, |
| )); |
| if (parameter.isOptional) { |
| results.addAll(await _searchReferences(parameter, searchedFiles)); |
| } |
| return results; |
| } |
| |
| Future<List<SearchResult>> _searchReferences_Prefix( |
| PrefixElement element, SearchedFiles searchedFiles) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| String path = element.source.fullName; |
| if (!searchedFiles.add(path, this)) { |
| return const <SearchResult>[]; |
| } |
| |
| List<SearchResult> results = <SearchResult>[]; |
| LibraryElement libraryElement = element.library; |
| for (CompilationUnitElement unitElement in libraryElement.units) { |
| String unitPath = unitElement.source.fullName; |
| AnalysisResult unitAnalysisResult = await _driver.getResult(unitPath); |
| _LocalReferencesVisitor visitor = |
| new _LocalReferencesVisitor(element, unitElement); |
| unitAnalysisResult.unit.accept(visitor); |
| results.addAll(visitor.results); |
| } |
| return results; |
| } |
| } |
| |
| /** |
| * Container that keeps track of file owners. |
| */ |
| class SearchedFiles { |
| final Map<Uri, Search> owners = {}; |
| |
| bool add(String path, Search search) { |
| var file = search._driver.fsState.getFileForPath(path); |
| var owner = owners[file.uri]; |
| if (owner == null) { |
| owners[file.uri] = search; |
| return true; |
| } |
| return identical(owner, search); |
| } |
| |
| void ownAdded(Search search) { |
| for (var path in search._driver.addedFiles) { |
| add(path, search); |
| } |
| } |
| } |
| |
| /** |
| * A single search result. |
| */ |
| class SearchResult { |
| /** |
| * 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.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(", enclosingElement="); |
| buffer.write(enclosingElement); |
| 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(")"); |
| return buffer.toString(); |
| } |
| } |
| |
| /** |
| * The kind of reference in a [SearchResult]. |
| */ |
| enum SearchResultKind { READ, READ_WRITE, WRITE, INVOCATION, REFERENCE } |
| |
| /** |
| * A single subtype of a type. |
| */ |
| class SubtypeResult { |
| /** |
| * The URI of the library. |
| */ |
| final String libraryUri; |
| |
| /** |
| * The identifier of the subtype. |
| */ |
| final String id; |
| |
| /** |
| * The name of the subtype. |
| */ |
| final String name; |
| |
| /** |
| * The names of instance members declared in the class. |
| */ |
| final List<String> members; |
| |
| SubtypeResult(this.libraryUri, this.id, this.name, this.members); |
| |
| @override |
| String toString() => id; |
| } |
| |
| /** |
| * 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 references to the [importElement]. |
| */ |
| class _ImportElementReferencesVisitor extends RecursiveAstVisitor { |
| final List<SearchResult> results = <SearchResult>[]; |
| |
| final ImportElement importElement; |
| final CompilationUnitElement enclosingUnitElement; |
| |
| Set<Element> importedElements; |
| |
| _ImportElementReferencesVisitor( |
| ImportElement element, this.enclosingUnitElement) |
| : importElement = element { |
| importedElements = element.namespace.definedNames.values.toSet(); |
| } |
| |
| @override |
| visitExportDirective(ExportDirective node) {} |
| |
| @override |
| visitImportDirective(ImportDirective node) {} |
| |
| @override |
| visitSimpleIdentifier(SimpleIdentifier node) { |
| if (node.inDeclarationContext()) { |
| return; |
| } |
| if (importElement.prefix != null) { |
| if (node.staticElement == importElement.prefix) { |
| AstNode parent = node.parent; |
| if (parent is PrefixedIdentifier && parent.prefix == node) { |
| if (importedElements.contains(parent.staticElement)) { |
| _addResultForPrefix(node, parent.identifier); |
| } |
| } |
| if (parent is MethodInvocation && parent.target == node) { |
| if (importedElements.contains(parent.methodName.staticElement)) { |
| _addResultForPrefix(node, parent.methodName); |
| } |
| } |
| } |
| } else { |
| if (importedElements.contains(node.staticElement)) { |
| _addResult(node.offset, 0); |
| } |
| } |
| } |
| |
| void _addResult(int offset, int length) { |
| Element enclosingElement = |
| _getEnclosingElement(enclosingUnitElement, offset); |
| results.add(new SearchResult._(enclosingElement, SearchResultKind.REFERENCE, |
| offset, length, true, false)); |
| } |
| |
| void _addResultForPrefix(SimpleIdentifier prefixNode, AstNode nextNode) { |
| int prefixOffset = prefixNode.offset; |
| _addResult(prefixOffset, nextNode.offset - prefixOffset); |
| } |
| } |
| |
| class _IndexRequest { |
| final AnalysisDriverUnitIndex index; |
| |
| _IndexRequest(this.index); |
| |
| /** |
| * Return the [element]'s identifier in the [index] or `-1` if the |
| * [element] is not referenced in the [index]. |
| */ |
| int findElementId(Element element) { |
| IndexElementInfo info = new IndexElementInfo(element); |
| element = info.element; |
| // Find the id of the element's unit. |
| int unitId = getUnitId(element); |
| if (unitId == -1) { |
| return -1; |
| } |
| // Prepare information about the element. |
| int unitMemberId = getElementUnitMemberId(element); |
| if (unitMemberId == -1) { |
| return -1; |
| } |
| int classMemberId = getElementClassMemberId(element); |
| if (classMemberId == -1) { |
| return -1; |
| } |
| int parameterId = getElementParameterId(element); |
| if (parameterId == -1) { |
| return -1; |
| } |
| // Try to find the element id using classMemberId, parameterId, and kind. |
| int elementId = |
| _findFirstOccurrence(index.elementNameUnitMemberIds, unitMemberId); |
| if (elementId == -1) { |
| return -1; |
| } |
| for (; |
| elementId < index.elementNameUnitMemberIds.length && |
| index.elementNameUnitMemberIds[elementId] == unitMemberId; |
| elementId++) { |
| if (index.elementUnits[elementId] == unitId && |
| index.elementNameClassMemberIds[elementId] == classMemberId && |
| index.elementNameParameterIds[elementId] == parameterId && |
| index.elementKinds[elementId] == info.kind) { |
| return elementId; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Return the [element]'s class member name identifier, `null` is not a class |
| * member, or `-1` if the [element] is not referenced in the [index]. |
| */ |
| int getElementClassMemberId(Element element) { |
| for (; element != null; element = element.enclosingElement) { |
| if (element.enclosingElement is ClassElement) { |
| return getStringId(element.name); |
| } |
| } |
| return index.nullStringId; |
| } |
| |
| /** |
| * Return the [element]'s class member name identifier, `null` is not a class |
| * member, or `-1` if the [element] is not referenced in the [index]. |
| */ |
| int getElementParameterId(Element element) { |
| for (; element != null; element = element.enclosingElement) { |
| if (element is ParameterElement) { |
| return getStringId(element.name); |
| } |
| } |
| return index.nullStringId; |
| } |
| |
| /** |
| * Return the [element]'s top-level name identifier, `0` is the unit, or |
| * `-1` if the [element] is not referenced in the [index]. |
| */ |
| int getElementUnitMemberId(Element element) { |
| for (; element != null; element = element.enclosingElement) { |
| if (element.enclosingElement is CompilationUnitElement) { |
| return getStringId(element.name); |
| } |
| } |
| return index.nullStringId; |
| } |
| |
| /** |
| * Return a list of results where an element with the given [elementId] has |
| * a relation with the kind from [relationToResultKind]. |
| * |
| * The function [getEnclosingUnitElement] is used to lazily compute the |
| * enclosing [CompilationUnitElement] if there is a relation of an |
| * interesting kind. |
| */ |
| Future<List<SearchResult>> getRelations( |
| int elementId, |
| Map<IndexRelationKind, SearchResultKind> relationToResultKind, |
| Future<CompilationUnitElement> getEnclosingUnitElement()) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| // Find the first usage of the element. |
| int i = _findFirstOccurrence(index.usedElements, elementId); |
| if (i == -1) { |
| return const <SearchResult>[]; |
| } |
| // Create locations for every usage of the element. |
| List<SearchResult> results = <SearchResult>[]; |
| CompilationUnitElement enclosingUnitElement = null; |
| for (; |
| i < index.usedElements.length && index.usedElements[i] == elementId; |
| i++) { |
| IndexRelationKind relationKind = index.usedElementKinds[i]; |
| SearchResultKind resultKind = relationToResultKind[relationKind]; |
| if (resultKind != null) { |
| int offset = index.usedElementOffsets[i]; |
| enclosingUnitElement ??= await getEnclosingUnitElement(); |
| if (enclosingUnitElement != null) { |
| Element enclosingElement = |
| _getEnclosingElement(enclosingUnitElement, offset); |
| results.add(new SearchResult._( |
| enclosingElement, |
| resultKind, |
| offset, |
| index.usedElementLengths[i], |
| true, |
| index.usedElementIsQualifiedFlags[i])); |
| } |
| } |
| } |
| return results; |
| } |
| |
| /** |
| * Return the identifier of [str] in the [index] or `-1` if [str] is not |
| * used in the [index]. |
| */ |
| int getStringId(String str) { |
| return binarySearch(index.strings, str); |
| } |
| |
| /** |
| * Return the identifier of the [CompilationUnitElement] containing the |
| * [element] in the [index] or `-1` if not found. |
| */ |
| int getUnitId(Element element) { |
| CompilationUnitElement unitElement = getUnitElement(element); |
| int libraryUriId = getUriId(unitElement.library.source.uri); |
| if (libraryUriId == -1) { |
| return -1; |
| } |
| int unitUriId = getUriId(unitElement.source.uri); |
| if (unitUriId == -1) { |
| return -1; |
| } |
| for (int i = 0; i < index.unitLibraryUris.length; i++) { |
| if (index.unitLibraryUris[i] == libraryUriId && |
| index.unitUnitUris[i] == unitUriId) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Return a list of results where a class members with the given [name] is |
| * referenced with a qualifier, but is not resolved. |
| */ |
| Future<List<SearchResult>> getUnresolvedMemberReferences( |
| String name, |
| Map<IndexRelationKind, SearchResultKind> relationToResultKind, |
| Future<CompilationUnitElement> getEnclosingUnitElement()) async { |
| // TODO(brianwilkerson) Determine whether this await is necessary. |
| await null; |
| // Find the name identifier. |
| int nameId = getStringId(name); |
| if (nameId == -1) { |
| return const <SearchResult>[]; |
| } |
| |
| // Find the first usage of the name. |
| int i = _findFirstOccurrence(index.usedNames, nameId); |
| if (i == -1) { |
| return const <SearchResult>[]; |
| } |
| |
| // Create results for every usage of the name. |
| List<SearchResult> results = <SearchResult>[]; |
| CompilationUnitElement enclosingUnitElement = null; |
| for (; i < index.usedNames.length && index.usedNames[i] == nameId; i++) { |
| IndexRelationKind relationKind = index.usedNameKinds[i]; |
| SearchResultKind resultKind = relationToResultKind[relationKind]; |
| if (resultKind != null) { |
| int offset = index.usedNameOffsets[i]; |
| enclosingUnitElement ??= await getEnclosingUnitElement(); |
| if (enclosingUnitElement != null) { |
| Element enclosingElement = |
| _getEnclosingElement(enclosingUnitElement, offset); |
| results.add(new SearchResult._(enclosingElement, resultKind, offset, |
| name.length, false, index.usedNameIsQualifiedFlags[i])); |
| } |
| } |
| } |
| |
| return results; |
| } |
| |
| /** |
| * Return the identifier of the [uri] in the [index] or `-1` if the [uri] is |
| * not used in the [index]. |
| */ |
| int getUriId(Uri uri) { |
| String str = uri.toString(); |
| return getStringId(str); |
| } |
| |
| /** |
| * Return the index of the first occurrence of the [value] in the [sortedList], |
| * or `-1` if the [value] is not in the list. |
| */ |
| int _findFirstOccurrence(List<int> sortedList, int value) { |
| // Find an occurrence. |
| int i = binarySearch(sortedList, value); |
| if (i == -1) { |
| return -1; |
| } |
| // Find the first occurrence. |
| while (i > 0 && sortedList[i - 1] == value) { |
| i--; |
| } |
| return i; |
| } |
| } |
| |
| /** |
| * 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> results = <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; |
| } |
| } |
| _addResult(node, kind); |
| } |
| } |
| |
| void _addResult(AstNode node, SearchResultKind kind) { |
| bool isQualified = node.parent is Label; |
| Element enclosingElement = |
| _getEnclosingElement(enclosingUnitElement, node.offset); |
| results.add(new SearchResult._( |
| enclosingElement, kind, node.offset, node.length, true, isQualified)); |
| } |
| } |
| |
| /** |
| * The marker class that is thrown to stop adding declarations. |
| */ |
| class _MaxNumberOfDeclarationsError { |
| const _MaxNumberOfDeclarationsError(); |
| } |
| |
| /** |
| * Helper for composing parameter strings. |
| */ |
| class _UnlinkedParameterComposer { |
| final UnlinkedUnit unlinkedUnit; |
| final StringBuffer buffer = new StringBuffer(); |
| |
| List<UnlinkedTypeParam> outerTypeParameters = const []; |
| List<UnlinkedTypeParam> innerTypeParameters = const []; |
| |
| _UnlinkedParameterComposer(this.unlinkedUnit); |
| |
| void appendParameter(UnlinkedParam parameter) { |
| bool hasType = appendType(parameter.type); |
| if (hasType && parameter.name.isNotEmpty) { |
| buffer.write(' '); |
| } |
| buffer.write(parameter.name); |
| if (parameter.isFunctionTyped) { |
| appendParameters(parameter.parameters); |
| } |
| } |
| |
| void appendParameters(List<UnlinkedParam> parameters) { |
| buffer.write('('); |
| |
| bool isFirstParameter = true; |
| for (var parameter in parameters) { |
| if (isFirstParameter) { |
| isFirstParameter = false; |
| } else { |
| buffer.write(', '); |
| } |
| appendParameter(parameter); |
| } |
| |
| buffer.write(')'); |
| } |
| |
| bool appendType(EntityRef type) { |
| EntityRefKind kind = type?.entityKind; |
| if (kind == EntityRefKind.named) { |
| if (type.reference != 0) { |
| UnlinkedReference typeRef = unlinkedUnit.references[type.reference]; |
| buffer.write(typeRef.name); |
| appendTypeArguments(type); |
| return true; |
| } |
| if (type.paramReference != 0) { |
| int ref = type.paramReference; |
| if (ref <= innerTypeParameters.length) { |
| var param = innerTypeParameters[innerTypeParameters.length - ref]; |
| buffer.write(param.name); |
| return true; |
| } |
| ref -= innerTypeParameters.length; |
| if (ref <= outerTypeParameters.length) { |
| var param = outerTypeParameters[outerTypeParameters.length - ref]; |
| buffer.write(param.name); |
| return true; |
| } |
| return false; |
| } |
| } |
| if (kind == EntityRefKind.genericFunctionType) { |
| if (appendType(type.syntheticReturnType)) { |
| buffer.write(' '); |
| } |
| buffer.write('Function'); |
| appendParameters(type.syntheticParams); |
| return true; |
| } |
| return false; |
| } |
| |
| void appendTypeArguments(EntityRef type) { |
| if (type.typeArguments.isNotEmpty) { |
| buffer.write('<'); |
| bool first = true; |
| for (var arguments in type.typeArguments) { |
| if (first) { |
| first = false; |
| } else { |
| buffer.write(', '); |
| } |
| appendType(arguments); |
| } |
| buffer.write('>'); |
| } |
| } |
| |
| void clear() { |
| buffer.clear(); |
| } |
| } |