mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
Add support for finding the covering node
The intent is for this to replace NodeLocator and several other mechanisms for computing a covering node so that we have a single source of truth. It will also form the basis for some additional utilities needed for code modifying features. Change-Id: I33f48907145efbfe9dcba7b43cebb3284d061d1c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/263442 Reviewed-by: Samuel Rawlins <srawlins@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
be5fe7bfe8
commit
aa7fb9e9e8
63
pkg/analyzer/lib/src/utilities/extensions/ast.dart
Normal file
63
pkg/analyzer/lib/src/utilities/extensions/ast.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
// 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/ast/ast.dart';
|
||||
|
||||
extension AstNodeExtension on AstNode {
|
||||
/// Return the minimal cover node for the range of characters beginning at the
|
||||
/// [offset] with the given [length], or `null` if the range is outside the
|
||||
/// range covered by the receiver.
|
||||
///
|
||||
/// The minimal covering node is the node, rooted at the receiver, with the
|
||||
/// shortest length whose range completely includes the given range.
|
||||
AstNode? nodeCovering({required int offset, int length = 0}) {
|
||||
var end = offset + length;
|
||||
|
||||
/// Return `true` if the [node] contains the range.
|
||||
bool containsOffset(AstNode node) {
|
||||
if (length == 0) {
|
||||
// The selection range is an insertion point. It is considered to be
|
||||
// outside the node if it's
|
||||
// - both immediately before the first token in the node and immediately
|
||||
// after the previous token, or
|
||||
// - both immediately after the last token in the node and immediately
|
||||
// before the next token.
|
||||
if (offset == node.offset && offset == node.beginToken.previous!.end) {
|
||||
return false;
|
||||
}
|
||||
if (offset == node.end && offset == node.endToken.next!.offset) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return node.offset <= offset && node.end >= end;
|
||||
}
|
||||
|
||||
/// Return the child of the [node] that completely contains the [range], or
|
||||
/// `null` if none of the children contain the range (which means that the
|
||||
/// [node] is the covering node).
|
||||
AstNode? childContainingRange(AstNode node) {
|
||||
for (var entity in node.childEntities) {
|
||||
if (entity is AstNode && containsOffset(entity)) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this is CompilationUnit) {
|
||||
if (offset < 0 || end >= this.end) {
|
||||
return null;
|
||||
}
|
||||
} else if (!containsOffset(this)) {
|
||||
return null;
|
||||
}
|
||||
var previousNode = this;
|
||||
var currentNode = childContainingRange(previousNode);
|
||||
while (currentNode != null) {
|
||||
previousNode = currentNode;
|
||||
currentNode = childContainingRange(previousNode);
|
||||
}
|
||||
return previousNode;
|
||||
}
|
||||
}
|
314
pkg/analyzer/test/src/utilities/extensions/ast_test.dart
Normal file
314
pkg/analyzer/test/src/utilities/extensions/ast_test.dart
Normal file
|
@ -0,0 +1,314 @@
|
|||
// 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/ast/ast.dart';
|
||||
import 'package:analyzer/source/source_range.dart';
|
||||
import 'package:analyzer/src/utilities/extensions/ast.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import '../../dart/resolution/context_collection_resolution.dart';
|
||||
|
||||
void main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(NodeCoveringTest);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class NodeCoveringTest extends PubPackageResolutionTest {
|
||||
Future<AstNode> coveringNode(String sourceCode) async {
|
||||
var range = await _range(sourceCode);
|
||||
var node =
|
||||
result.unit.nodeCovering(offset: range.offset, length: range.length);
|
||||
return node!;
|
||||
}
|
||||
|
||||
void test_after_EOF() async {
|
||||
await resolveTestCode('''
|
||||
library myLib;
|
||||
''');
|
||||
var node = result.unit.nodeCovering(offset: 100, length: 20);
|
||||
expect(node, null);
|
||||
}
|
||||
|
||||
Future<void> test_after_lastNonEOF() async {
|
||||
var node = await coveringNode('''
|
||||
library myLib;
|
||||
|
||||
^
|
||||
''');
|
||||
node as CompilationUnit;
|
||||
}
|
||||
|
||||
Future<void> test_before_firstNonEOF() async {
|
||||
var node = await coveringNode('''
|
||||
^
|
||||
|
||||
library myLib;
|
||||
''');
|
||||
node as CompilationUnit;
|
||||
}
|
||||
|
||||
Future<void> test_between_classMembers() async {
|
||||
var node = await coveringNode('''
|
||||
class C {
|
||||
void a() {}
|
||||
^
|
||||
void b() {}
|
||||
}
|
||||
''');
|
||||
node as ClassDeclaration;
|
||||
}
|
||||
|
||||
Future<void> test_between_commaAndIdentifier_arguments() async {
|
||||
var node = await coveringNode('''
|
||||
void f(int a, int b) {
|
||||
f(a,^b);
|
||||
}
|
||||
''');
|
||||
node as ArgumentList;
|
||||
}
|
||||
|
||||
Future<void> test_between_commaAndIdentifier_parameters() async {
|
||||
var node = await coveringNode('''
|
||||
class C {
|
||||
void m(int a,^int b) {}
|
||||
}
|
||||
''');
|
||||
node as FormalParameterList;
|
||||
}
|
||||
|
||||
Future<void> test_between_commaAndIdentifier_typeArguments() async {
|
||||
var node = await coveringNode('''
|
||||
var m = Map<int,^int>();
|
||||
''');
|
||||
node as TypeArgumentList;
|
||||
}
|
||||
|
||||
Future<void> test_between_commaAndIdentifier_typeParameters() async {
|
||||
var node = await coveringNode('''
|
||||
class C<S,^T> {}
|
||||
''');
|
||||
node as TypeParameterList;
|
||||
}
|
||||
|
||||
Future<void> test_between_declarations() async {
|
||||
var node = await coveringNode('''
|
||||
class A {}
|
||||
^
|
||||
class B {}
|
||||
''');
|
||||
node as CompilationUnit;
|
||||
}
|
||||
|
||||
Future<void> test_between_directives() async {
|
||||
var node = await coveringNode('''
|
||||
library myLib;
|
||||
^
|
||||
import 'dart:core';
|
||||
''');
|
||||
node as CompilationUnit;
|
||||
}
|
||||
|
||||
Future<void> test_between_identifierAndComma_arguments() async {
|
||||
var node = await coveringNode('''
|
||||
void f(int a, int b) {
|
||||
f(a^, b);
|
||||
}
|
||||
''');
|
||||
node as ArgumentList;
|
||||
}
|
||||
|
||||
Future<void> test_between_identifierAndComma_parameters() async {
|
||||
var node = await coveringNode('''
|
||||
class C {
|
||||
void m(int a^, int b) {}
|
||||
}
|
||||
''');
|
||||
node as FormalParameterList;
|
||||
}
|
||||
|
||||
Future<void> test_between_identifierAndComma_typeArguments() async {
|
||||
var node = await coveringNode('''
|
||||
var m = Map<int^, int>();
|
||||
''');
|
||||
node as TypeArgumentList;
|
||||
}
|
||||
|
||||
Future<void> test_between_identifierAndComma_typeParameters() async {
|
||||
var node = await coveringNode('''
|
||||
class C<S^, T> {}
|
||||
''');
|
||||
node as TypeParameterList;
|
||||
}
|
||||
|
||||
Future<void> test_between_identifierAndPeriod() async {
|
||||
var node = await coveringNode('''
|
||||
var x = o^.m();
|
||||
''');
|
||||
node as MethodInvocation;
|
||||
}
|
||||
|
||||
Future<void> test_between_modifierAndFunctionBody() async {
|
||||
var node = await coveringNode('''
|
||||
void f() async^{}
|
||||
''');
|
||||
node as BlockFunctionBody;
|
||||
}
|
||||
|
||||
Future<void> test_between_nameAndParameters_function() async {
|
||||
var node = await coveringNode('''
|
||||
void f^() {}
|
||||
''');
|
||||
node as FunctionDeclaration;
|
||||
}
|
||||
|
||||
Future<void> test_between_nameAndParameters_method() async {
|
||||
var node = await coveringNode('''
|
||||
class C {
|
||||
void m^() {}
|
||||
}
|
||||
''');
|
||||
node as MethodDeclaration;
|
||||
}
|
||||
|
||||
Future<void> test_between_periodAndIdentifier() async {
|
||||
var node = await coveringNode('''
|
||||
var x = o.^m();
|
||||
''');
|
||||
node as MethodInvocation;
|
||||
}
|
||||
|
||||
Future<void> test_between_statements() async {
|
||||
var node = await coveringNode('''
|
||||
void f() {
|
||||
var x = 0;
|
||||
^
|
||||
print(x);
|
||||
}
|
||||
''');
|
||||
node as Block;
|
||||
}
|
||||
|
||||
Future<void> test_inComment_beginning() async {
|
||||
var node = await coveringNode('''
|
||||
/// A [^B].
|
||||
class C {}
|
||||
''');
|
||||
node as SimpleIdentifier;
|
||||
}
|
||||
|
||||
Future<void> test_inComment_beginning_qualified() async {
|
||||
var node = await coveringNode('''
|
||||
/// A [B.^b].
|
||||
class C {}
|
||||
''');
|
||||
node as PrefixedIdentifier;
|
||||
}
|
||||
|
||||
Future<void> test_inComment_end() async {
|
||||
var node = await coveringNode('''
|
||||
/// A [B.b^].
|
||||
class C {}
|
||||
''');
|
||||
node as SimpleIdentifier;
|
||||
}
|
||||
|
||||
Future<void> test_inComment_middle() async {
|
||||
var node = await coveringNode('''
|
||||
/// A [B.b^b].
|
||||
class C {}
|
||||
''');
|
||||
node as SimpleIdentifier;
|
||||
}
|
||||
|
||||
Future<void> test_inName_class() async {
|
||||
var node = await coveringNode('''
|
||||
class A^B {}
|
||||
''');
|
||||
node as ClassDeclaration;
|
||||
}
|
||||
|
||||
Future<void> test_inName_function() async {
|
||||
var node = await coveringNode('''
|
||||
void f^f() {}
|
||||
''');
|
||||
node as FunctionDeclaration;
|
||||
}
|
||||
|
||||
Future<void> test_inName_method() async {
|
||||
var node = await coveringNode('''
|
||||
class C {
|
||||
void m^m() {}
|
||||
}
|
||||
''');
|
||||
node as MethodDeclaration;
|
||||
}
|
||||
|
||||
Future<void> test_inOperator_assignment() async {
|
||||
var node = await coveringNode('''
|
||||
void f(int x) {
|
||||
x +^= 3;
|
||||
}
|
||||
''');
|
||||
node as AssignmentExpression;
|
||||
}
|
||||
|
||||
Future<void> test_inOperator_nullAwareAccess() async {
|
||||
var node = await coveringNode('''
|
||||
var x = o?^.m();
|
||||
''');
|
||||
node as MethodInvocation;
|
||||
}
|
||||
|
||||
Future<void> test_inOperator_postfix() async {
|
||||
var node = await coveringNode('''
|
||||
var x = y+^+;
|
||||
''');
|
||||
node as PostfixExpression;
|
||||
}
|
||||
|
||||
Future<void> test_libraryKeyword() async {
|
||||
var node = await coveringNode('''
|
||||
libr^ary myLib;
|
||||
''');
|
||||
node as LibraryDirective;
|
||||
}
|
||||
|
||||
Future<void> test_parentAndChildWithSameRange_blockFunctionBody() async {
|
||||
var node = await coveringNode('''
|
||||
void f() { ^ }
|
||||
''');
|
||||
node as Block;
|
||||
var parent = node.parent;
|
||||
parent as BlockFunctionBody;
|
||||
expect(parent.offset, node.offset);
|
||||
expect(parent.length, node.length);
|
||||
}
|
||||
|
||||
Future<void> test_parentAndChildWithSameRange_implicitCall() async {
|
||||
var node = await coveringNode('''
|
||||
class C { void call() {} } Function f = C^();
|
||||
''');
|
||||
node as InstanceCreationExpression;
|
||||
var parent = node.parent;
|
||||
parent as ImplicitCallReference;
|
||||
expect(parent.offset, node.offset);
|
||||
expect(parent.length, node.length);
|
||||
}
|
||||
|
||||
Future<SourceRange> _range(String sourceCode) async {
|
||||
// TODO(brianwilkerson) Move TestCode to the analyzer package and make use
|
||||
// of it here.
|
||||
var offset = sourceCode.indexOf('^');
|
||||
if (offset < 0 || sourceCode.indexOf('^', offset + 1) >= 0) {
|
||||
fail('Tests must contain a single selection range');
|
||||
}
|
||||
var testCode =
|
||||
sourceCode.substring(0, offset) + sourceCode.substring(offset + 1);
|
||||
await resolveTestCode(testCode);
|
||||
return SourceRange(offset, 0);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import 'ast_test.dart' as ast;
|
||||
import 'collection_test.dart' as collection;
|
||||
import 'object_test.dart' as object;
|
||||
import 'stream_test.dart' as stream;
|
||||
|
@ -11,6 +12,7 @@ import 'string_test.dart' as string;
|
|||
|
||||
main() {
|
||||
defineReflectiveSuite(() {
|
||||
ast.main();
|
||||
collection.main();
|
||||
object.main();
|
||||
stream.main();
|
||||
|
|
Loading…
Reference in a new issue