[analyzer] Add the ability to locate an Element from an ElementLocation

Change-Id: If2f3bfcdaa330dc1bcb9c1078500e982660ae8c7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/264300
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny 2022-10-21 15:26:46 +00:00 committed by Commit Queue
parent 8995807b2b
commit 3e587da7f0
8 changed files with 470 additions and 99 deletions

View file

@ -481,6 +481,10 @@ abstract class DirectiveUriWithUnit extends DirectiveUriWithSource {
///
/// Clients may not extend, implement or mix-in this class.
abstract class Element implements AnalysisTarget {
/// A list of this element's children.
/// There is no guarantee of the order in which the children will be included.
List<Element> get children;
/// Return the analysis context in which this element is defined.
AnalysisContext get context;

View file

@ -100,6 +100,16 @@ abstract class AbstractClassElementImpl extends _ExistingElementImpl
return library.session.classHierarchy.implementedInterfaces(this);
}
@override
List<Element> get children => [
...super.children,
...accessors,
...fields,
...constructors,
...methods,
...typeParameters,
];
@override
String get displayName => name;
@ -384,16 +394,6 @@ abstract class AbstractClassElementImpl extends _ExistingElementImpl
}));
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
safelyVisitChildren(accessors, visitor);
safelyVisitChildren(fields, visitor);
safelyVisitChildren(constructors, visitor);
safelyVisitChildren(methods, visitor);
safelyVisitChildren(typeParameters, visitor);
}
/// Return an iterable containing all of the implementations of a getter with
/// the given [getterName] that are defined in this class any any superclass
/// of this class (but not in interfaces).
@ -1126,6 +1126,19 @@ class CompilationUnitElementImpl extends UriReferencedElementImpl
_accessors = accessors;
}
@override
List<Element> get children => [
...super.children,
...accessors,
...classes,
...enums2,
...extensions,
...functions,
...mixins2,
...typeAliases,
...topLevelVariables,
];
@override
List<ClassElement> get classes {
return _classes;
@ -1299,19 +1312,6 @@ class CompilationUnitElementImpl extends UriReferencedElementImpl
this.linkedData = linkedData;
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
safelyVisitChildren(accessors, visitor);
safelyVisitChildren(classes, visitor);
safelyVisitChildren(enums2, visitor);
safelyVisitChildren(extensions, visitor);
safelyVisitChildren(functions, visitor);
safelyVisitChildren(mixins2, visitor);
safelyVisitChildren(typeAliases, visitor);
safelyVisitChildren(topLevelVariables, visitor);
}
}
/// A [FieldElement] for a 'const' or 'final' field that has an initializer.
@ -2204,6 +2204,9 @@ abstract class ElementImpl implements Element {
reference?.element = this;
}
@override
List<Element> get children => const [];
/// The length of the element's code, or `null` if the element is synthetic.
int? get codeLength => _codeLength;
@ -2675,13 +2678,6 @@ abstract class ElementImpl implements Element {
_metadataFlags = 0;
}
/// Use the given [visitor] to visit all of the [children] in the given array.
void safelyVisitChildren(List<Element> children, ElementVisitor visitor) {
for (Element child in children) {
child.accept(visitor);
}
}
/// Set the code range for this element.
void setCodeRange(int offset, int length) {
_codeOffset = offset;
@ -2723,9 +2719,13 @@ abstract class ElementImpl implements Element {
return getDisplayString(withNullability: true);
}
/// Use the given [visitor] to visit all of the children of this element.
/// There is no guarantee of the order in which the children will be visited.
@override
void visitChildren(ElementVisitor visitor) {
// There are no children to visit
for (Element child in children) {
child.accept(visitor);
}
}
/// Return flags that denote presence of a few specific annotations.
@ -3025,6 +3025,13 @@ abstract class ExecutableElementImpl extends _ExistingElementImpl
/// [offset].
ExecutableElementImpl(String super.name, super.offset, {super.reference});
@override
List<Element> get children => [
...super.children,
...typeParameters,
...parameters,
];
@override
Element get enclosingElement => super.enclosingElement!;
@ -3198,13 +3205,6 @@ abstract class ExecutableElementImpl extends _ExistingElementImpl
this.linkedData = linkedData;
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
safelyVisitChildren(typeParameters, visitor);
safelyVisitChildren(parameters, visitor);
}
}
/// A concrete implementation of an [ExtensionElement].
@ -3243,6 +3243,15 @@ class ExtensionElementImpl extends _ExistingElementImpl
_accessors = accessors;
}
@override
List<Element> get children => [
...super.children,
...accessors,
...fields,
...methods,
...typeParameters,
];
@override
String get displayName => name ?? '';
@ -3386,15 +3395,6 @@ class ExtensionElementImpl extends _ExistingElementImpl
this.linkedData = linkedData;
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
safelyVisitChildren(accessors, visitor);
safelyVisitChildren(fields, visitor);
safelyVisitChildren(methods, visitor);
safelyVisitChildren(typeParameters, visitor);
}
}
/// A concrete implementation of a [FieldElement].
@ -3581,6 +3581,13 @@ class GenericFunctionTypeElementImpl extends _ExistingElementImpl
GenericFunctionTypeElementImpl.forOffset(int nameOffset)
: super("", nameOffset);
@override
List<Element> get children => [
...super.children,
...typeParameters,
...parameters,
];
@override
String get identifier => '-';
@ -3657,13 +3664,6 @@ class GenericFunctionTypeElementImpl extends _ExistingElementImpl
void appendTo(ElementDisplayStringBuilder builder) {
builder.writeGenericFunctionTypeElement(this);
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
safelyVisitChildren(typeParameters, visitor);
safelyVisitChildren(parameters, visitor);
}
}
/// This mixins is added to elements that can have cache completion data.
@ -3895,6 +3895,13 @@ class LibraryElementImpl extends LibraryOrAugmentationElementImpl
return super.augmentationImports;
}
@override
List<Element> get children => [
...super.children,
...parts2,
..._partUnits,
];
@override
CompilationUnitElementImpl get enclosingUnit {
return _definingCompilationUnit;
@ -4233,15 +4240,6 @@ class LibraryElementImpl extends LibraryOrAugmentationElementImpl
return NullabilityEliminator.perform(typeProvider, type);
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
safelyVisitChildren(parts2, visitor);
for (final partUnit in _partUnits) {
partUnit.accept(visitor);
}
}
List<LibraryAugmentationElementImpl> _computeAugmentations() {
final result = <LibraryAugmentationElementImpl>[];
@ -4444,6 +4442,14 @@ abstract class LibraryOrAugmentationElementImpl extends ElementImpl
_augmentationImports = imports;
}
@override
List<Element> get children => [
...super.children,
definingCompilationUnit,
...libraryExports,
...libraryImports,
];
@override
CompilationUnitElementImpl get definingCompilationUnit =>
_definingCompilationUnit;
@ -4517,14 +4523,6 @@ abstract class LibraryOrAugmentationElementImpl extends ElementImpl
return _definingCompilationUnit.source;
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
_definingCompilationUnit.accept(visitor);
safelyVisitChildren(libraryExports, visitor);
safelyVisitChildren(libraryImports, visitor);
}
void _readLinkedData();
static List<PrefixElement> buildPrefixesFromImports(
@ -4890,6 +4888,9 @@ class MultiplyDefinedElementImpl implements MultiplyDefinedElement {
MultiplyDefinedElementImpl(
this.context, this.session, this.name, this.conflictingElements);
@override
List<Element> get children => const [];
@override
Element? get declaration => null;
@ -5077,9 +5078,13 @@ class MultiplyDefinedElementImpl implements MultiplyDefinedElement {
return buffer.toString();
}
/// Use the given [visitor] to visit all of the children of this element.
/// There is no guarantee of the order in which the children will be visited.
@override
void visitChildren(ElementVisitor visitor) {
// There are no children to visit
for (Element child in children) {
child.accept(visitor);
}
}
}
@ -5186,6 +5191,9 @@ class ParameterElementImpl extends VariableElementImpl
return element;
}
@override
List<Element> get children => parameters;
@override
ParameterElement get declaration => this;
@ -5261,12 +5269,6 @@ class ParameterElementImpl extends VariableElementImpl
void appendTo(ElementDisplayStringBuilder builder) {
builder.writeFormalParameter(this);
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
safelyVisitChildren(parameters, visitor);
}
}
/// The parameter of an implicit setter.

View file

@ -155,6 +155,9 @@ abstract class ExecutableMember extends Member implements ExecutableElement {
this.typeParameters,
);
@override
List<Element> get children => parameters;
@override
ExecutableElement get declaration => super.declaration as ExecutableElement;
@ -226,12 +229,6 @@ abstract class ExecutableMember extends Member implements ExecutableElement {
builder.writeExecutableElement(this, displayName);
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
safelyVisitChildren(parameters, visitor);
}
static ExecutableElement from2(
ExecutableElement element,
MapSubstitution substitution,
@ -493,6 +490,9 @@ abstract class Member implements Element {
}
}
@override
List<Element> get children => const [];
@override
AnalysisContext get context => _declaration.context;
@ -648,14 +648,6 @@ abstract class Member implements Element {
bool isAccessibleIn2(LibraryElement library) =>
_declaration.isAccessibleIn2(library);
/// Use the given [visitor] to visit all of the [children].
void safelyVisitChildren(List<Element> children, ElementVisitor visitor) {
// TODO(brianwilkerson) Make this private
for (Element child in children) {
child.accept(visitor);
}
}
@override
E? thisOrAncestorMatching<E extends Element>(
bool Function(Element) predicate,
@ -672,9 +664,13 @@ abstract class Member implements Element {
return getDisplayString(withNullability: false);
}
/// Use the given [visitor] to visit all of the children of this element.
/// There is no guarantee of the order in which the children will be visited.
@override
void visitChildren(ElementVisitor visitor) {
// There are no children to visit
for (Element child in children) {
child.accept(visitor);
}
}
/// If this member is a legacy view, erase nullability from the [type].
@ -876,6 +872,9 @@ class ParameterMember extends VariableMember
this.typeParameters,
);
@override
List<Element> get children => parameters;
@override
ParameterElement get declaration => super.declaration as ParameterElement;
@ -932,12 +931,6 @@ class ParameterMember extends VariableMember
builder.writeFormalParameter(this);
}
@override
void visitChildren(ElementVisitor visitor) {
super.visitChildren(visitor);
safelyVisitChildren(parameters, visitor);
}
static ParameterElement from(
ParameterElement element, MapSubstitution substitution) {
TypeProviderImpl? typeProvider;

View file

@ -0,0 +1,30 @@
// Copyright (c) 2022, 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/analysis/session.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/utilities/extensions/library_element.dart';
extension AnalysisSessionExtension on AnalysisSession {
/// Locates the [Element] that [location] represents.
///
/// Local elements such as variables inside functions cannot be found using
/// this method.
///
/// Returns `null` if the element cannot be found.
Future<Element?> locateElement(ElementLocation location) async {
final components = location.components;
if (location.components.isEmpty) {
return null;
}
// The first component is the library which we'll use to start the search.
final libraryUri = components.first;
final result = await getLibraryByUri(libraryUri);
return result is LibraryElementResult
? result.element.locateElement(location)
: null;
}
}

View file

@ -0,0 +1,42 @@
// Copyright (c) 2022, 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/element/element.dart';
import 'package:analyzer/src/dart/element/element.dart';
extension LibraryElementExtension on LibraryElement {
/// Locates an [Element] in this library by its [ElementLocation].
///
/// It is assumed that the first component of [location] matches this library.
///
/// Local elements such as variables inside functions cannot be found using
/// this method.
Element? locateElement(ElementLocation location) =>
_locateElement(location, 0);
}
extension _ElementExtension on Element {
/// Locates an [Element] by its [ElementLocation] assuming this element
/// is a match up to [thisIndex].
Element? _locateElement(ElementLocation location, int thisIndex) {
final components = location.components;
RangeError.checkValidIndex(thisIndex, components);
final nextIndex = thisIndex + 1;
if (nextIndex == components.length) {
// This element matches the last component so return it.
return this;
}
// Search for a matching child.
final identifier = components[nextIndex];
for (final child in children) {
if ((child as ElementImpl).identifier == identifier) {
return child._locateElement(location, nextIndex);
}
}
return null;
}
}

View file

@ -0,0 +1,107 @@
// Copyright (c) 2022, 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/analysis/session.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/results.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:analyzer/src/utilities/extensions/analysis_session.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(LocateElementTest);
});
}
/// Tests `locateElement()` on [AnalysisSession].
///
/// This extension method largely delegates to `LibraryElement.locateElement`
/// which is tested more comprehensively in
/// 'test/src/utilities/extensions/library_element_test.dart'.
@reflectiveTest
class LocateElementTest extends PubPackageResolutionTest {
late _MockAnalysisSession session;
File get testFile2 => getFile('$testPackageLibPath/test2.dart');
/// Create a library and (unless [addToSession] is `false`) add it to [session].
Future<LibraryElement> createLibrary(
String path,
String content, {
bool addToSession = true,
}) async {
final code = TestCode.parse(content);
newFile(path, code.code);
final library = (await resolveFile(path)).libraryElement;
if (addToSession) {
session.addLibrary(library);
}
return library;
}
/// Find class [name] in [library].
ClassElement findClass(LibraryElement library, String name) {
return library.definingCompilationUnit.getClass(name)!;
}
/// Locate the element referenced by [location] in [session].
Future<Element?> getElement(ElementLocation? location) =>
session.locateElement(location!);
@override
void setUp() {
super.setUp();
session = _MockAnalysisSession();
}
void test_elementInLibrary() async {
final libraryOne = await createLibrary(testFile.path, 'class C {}');
final libraryTwo = await createLibrary(testFile2.path, 'class C {}');
final classOne = findClass(libraryOne, 'C');
final classTwo = findClass(libraryTwo, 'C');
expect(await getElement(classOne.location!), classOne);
expect(await getElement(classTwo.location!), classTwo);
}
void test_invalid() async {
final library =
await createLibrary(testFile.path, 'class C {}', addToSession: false);
final class_ = findClass(library, 'C');
expect(await getElement(class_.location!), isNull);
}
void test_library() async {
final libraryOne = await createLibrary(testFile.path, 'class C {}');
final libraryTwo = await createLibrary(testFile2.path, 'class C {}');
expect(await getElement(libraryOne.location!), libraryOne);
expect(await getElement(libraryTwo.location!), libraryTwo);
}
}
class _MockAnalysisSession implements AnalysisSession {
final _libraries = <String, LibraryElement>{};
void addLibrary(LibraryElement library) =>
_libraries[library.identifier] = library;
@override
Future<SomeLibraryElementResult> getLibraryByUri(String uri) async {
final library = _libraries[uri];
return library != null
? LibraryElementResultImpl(library)
: CannotResolveUriResult();
}
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

View file

@ -0,0 +1,189 @@
// Copyright (c) 2022, 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/src/dart/ast/element_locator.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:analyzer/src/utilities/extensions/ast.dart';
import 'package:analyzer/src/utilities/extensions/library_element.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../dart/resolution/context_collection_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(LocateElementTest);
});
}
@reflectiveTest
class LocateElementTest extends PubPackageResolutionTest {
Future<void> assertLocation(String content) async {
final code = TestCode.parse(content);
await resolveTestCode(code.code);
// Get the element we'll be searching for from the marker in code.
final node = result.unit.nodeCovering(offset: code.position.offset);
final expectedElement = ElementLocator.locate(node)!;
// Verify locating the element using its location finds the same element.
final actualElement =
result.libraryElement.locateElement(expectedElement.location!);
expect(actualElement, expectedElement);
}
void test_class() async {
await assertLocation('''
class C^ {}
''');
}
void test_class_const() async {
await assertLocation('''
class C {
static const ^c = '';
}
''');
}
void test_class_constructor() async {
await assertLocation('''
class C {
C.named() {}
^C() {}
}
''');
}
void test_class_constructor_named() async {
await assertLocation('''
class C {
C.named() {}
}
class C2 {
C.nam^ed() {}
}
''');
}
void test_class_field() async {
await assertLocation('''
class C {
int f = 0;
}
class C2 {
int f^ = 0;
}
''');
}
void test_class_getter() async {
await assertLocation('''
class C {
String get s => '';
}
class C2 {
String get s^ => '';
}
''');
}
void test_class_setter() async {
await assertLocation('''
class C {
set s(String a) {}
}
class C2 {
set ^s(String a) {}
}
''');
}
void test_const() async {
await assertLocation('''
const ^c = '';
''');
}
void test_enum() async {
await assertLocation('''
enum ^E {}
''');
}
void test_enum_const2() async {
await assertLocation('''
enum E {
o^ne(1);
final int n;
const E(this.n);
}
''');
}
void test_enum_constructor() async {
await assertLocation('''
enum E {
final int n;
const ^E(this.n);
}
''');
}
void test_extension() async {
await assertLocation('''
extension on int {}
exten^sion on String {}
extension on int {}
''');
}
void test_extension_named() async {
await assertLocation('''
extension StringEx^tension on String {}
''');
}
void test_getter() async {
await assertLocation('''
String get ^g => '';
''');
}
void test_method() async {
await assertLocation('''
class C {
void m() {}
}
class C2 {
void ^m() {}
}
''');
}
void test_mixin() async {
await assertLocation('''
mixin ^M {}
''');
}
void test_setter() async {
await assertLocation('''
Set f^(String v) => '';
''');
}
void test_topLevelVariable() async {
await assertLocation('''
int ^a = 1;
''');
}
void test_typedef() async {
await assertLocation('''
typedef ^S = String;
''');
}
}

View file

@ -4,16 +4,20 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'analysis_session_test.dart' as analysis_session;
import 'ast_test.dart' as ast;
import 'collection_test.dart' as collection;
import 'library_element_test.dart' as library_element;
import 'object_test.dart' as object;
import 'stream_test.dart' as stream;
import 'string_test.dart' as string;
main() {
defineReflectiveSuite(() {
analysis_session.main();
ast.main();
collection.main();
library_element.main();
object.main();
stream.main();
string.main();