mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:19:48 +00:00
Update selection support for patterns
Change-Id: Ib301f4afc5d0989a805b269fdca25dfa527072c9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286941 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
af6e1bf5a4
commit
27e874ad26
|
@ -139,11 +139,21 @@ class _ChildrenFinder extends SimpleAstVisitor<void> {
|
|||
_fromList(node.metadata) || _fromList(node.members);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldDeclaration(FieldDeclaration node) {
|
||||
_fromList(node.metadata);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldFormalParameter(FieldFormalParameter node) {
|
||||
_fromList(node.metadata);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
|
||||
_fromList(node.metadata);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFormalParameterList(FormalParameterList node) {
|
||||
var delimiter = node.leftDelimiter;
|
||||
|
@ -269,6 +279,11 @@ class _ChildrenFinder extends SimpleAstVisitor<void> {
|
|||
_fromList(node.metadata);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPatternVariableDeclaration(PatternVariableDeclaration node) {
|
||||
_fromList(node.metadata);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitRecordLiteral(RecordLiteral node) {
|
||||
_fromList(node.fields);
|
||||
|
@ -317,6 +332,11 @@ class _ChildrenFinder extends SimpleAstVisitor<void> {
|
|||
_fromList(node.metadata);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitStringInterpolation(StringInterpolation node) {
|
||||
_fromList(node.elements);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitSuperFormalParameter(SuperFormalParameter node) {
|
||||
_fromList(node.metadata);
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
// Copyright (c) 2023, 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/analysis_context_collection.dart';
|
||||
import 'package:analyzer/dart/analysis/results.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/file_system/physical_file_system.dart';
|
||||
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
|
||||
import 'package:analyzer_utilities/package_root.dart' as package_root;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
void main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(SelectionCoverageTest);
|
||||
});
|
||||
}
|
||||
|
||||
class AstImplData {
|
||||
final List<ClassElement> instantiableInterfaces = [];
|
||||
|
||||
AstImplData();
|
||||
}
|
||||
|
||||
class AstInterfaceData {
|
||||
/// A table mapping the element for a class to a list of the elements for the
|
||||
/// supertypes of that class.
|
||||
final Map<ClassElement, List<ClassElement>> supertypes = {};
|
||||
|
||||
/// A table mapping class element to a list of the getters declared directly
|
||||
/// in that class that return a `NodeList`.
|
||||
final Map<ClassElement, List<ExecutableElement>> declaredLists = {};
|
||||
|
||||
AstInterfaceData();
|
||||
|
||||
List<ExecutableElement> nodeListsFor(ClassElement class_) {
|
||||
var lists = <ExecutableElement>[];
|
||||
_addListsFor(lists, class_, <ClassElement>{});
|
||||
return lists;
|
||||
}
|
||||
|
||||
void _addListsFor(List<ExecutableElement> lists, ClassElement class_,
|
||||
Set<ClassElement> visited) {
|
||||
if (!visited.add(class_)) {
|
||||
return;
|
||||
}
|
||||
lists.addAll(declaredLists[class_] ?? const []);
|
||||
var supertypes = this.supertypes[class_];
|
||||
if (supertypes != null) {
|
||||
for (var supertype in supertypes) {
|
||||
_addListsFor(lists, supertype, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class SelectionCoverageTest {
|
||||
AstImplData processAstImpl(ResolvedUnitResult result) {
|
||||
var data = AstImplData();
|
||||
|
||||
for (var declaration in result.unit.declarations) {
|
||||
if (declaration is ClassDeclaration &&
|
||||
declaration.abstractKeyword == null) {
|
||||
var interfaceName = declaration.name.lexeme;
|
||||
if (!interfaceName.endsWith('Impl')) {
|
||||
continue;
|
||||
}
|
||||
interfaceName = interfaceName.substring(0, interfaceName.length - 4);
|
||||
var implementsClause = declaration.implementsClause;
|
||||
if (implementsClause != null) {
|
||||
for (var type in implementsClause.interfaces) {
|
||||
var element = type.type?.element;
|
||||
if (element is ClassElement && element.name == interfaceName) {
|
||||
data.instantiableInterfaces.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
AstInterfaceData processAstInterface(ResolvedUnitResult result) {
|
||||
var data = AstInterfaceData();
|
||||
|
||||
for (var declaration in result.unit.declarations) {
|
||||
if (declaration is ClassDeclaration) {
|
||||
// Build the subtype map.
|
||||
var subtypeElement = declaration.declaredElement!;
|
||||
var implementsClause = declaration.implementsClause;
|
||||
if (implementsClause != null) {
|
||||
for (var supertype in implementsClause.interfaces) {
|
||||
var supertypeElement = supertype.type?.element;
|
||||
if (supertypeElement is ClassElement) {
|
||||
data.supertypes
|
||||
.putIfAbsent(subtypeElement, () => [])
|
||||
.add(supertypeElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the node list map.
|
||||
var nodeLists = <ExecutableElement>[];
|
||||
for (var member in declaration.members) {
|
||||
if (member is MethodDeclaration && member.isGetter) {
|
||||
var returnType = member.returnType;
|
||||
if (returnType != null &&
|
||||
returnType.toSource().startsWith('NodeList<')) {
|
||||
nodeLists.add(member.declaredElement!);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodeLists.isNotEmpty) {
|
||||
data.declaredLists[subtypeElement] = nodeLists;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
SelectionData processSelection(ResolvedUnitResult result) {
|
||||
var data = SelectionData();
|
||||
|
||||
for (var declaration in result.unit.declarations) {
|
||||
if (declaration is ClassDeclaration &&
|
||||
declaration.name.lexeme == '_ChildrenFinder') {
|
||||
for (var member in declaration.members) {
|
||||
if (member is MethodDeclaration &&
|
||||
member.name.lexeme.startsWith('visit')) {
|
||||
var visitedClass = member
|
||||
.parameters?.parameters.first.declaredElement?.type.element;
|
||||
if (visitedClass is ClassElement) {
|
||||
var visitor = VisitMethodVisitor();
|
||||
member.body.accept(visitor);
|
||||
data.visitedLists[visitedClass] = visitor.visitedLists;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<void> test_visitorCoverage() async {
|
||||
var provider = PhysicalResourceProvider.INSTANCE;
|
||||
var pathContext = provider.pathContext;
|
||||
var packageRoot = pathContext.normalize(package_root.packageRoot);
|
||||
var pathToAsInterface = pathContext.join(
|
||||
packageRoot, 'analyzer', 'lib', 'dart', 'ast', 'ast.dart');
|
||||
var pathToAsImpl = pathContext.join(
|
||||
packageRoot, 'analyzer', 'lib', 'src', 'dart', 'ast', 'ast.dart');
|
||||
var pathToSelection = pathContext.join(packageRoot, 'analysis_server',
|
||||
'lib', 'src', 'utilities', 'selection.dart');
|
||||
|
||||
var collection =
|
||||
AnalysisContextCollection(includedPaths: [pathToSelection]);
|
||||
var context = collection.contexts.first;
|
||||
var astInterfaceResult =
|
||||
await context.currentSession.getResolvedUnit(pathToAsInterface);
|
||||
var astImplResult =
|
||||
await context.currentSession.getResolvedUnit(pathToAsImpl);
|
||||
var selectionResult =
|
||||
await context.currentSession.getResolvedUnit(pathToSelection);
|
||||
|
||||
var astInterfaceData =
|
||||
processAstInterface(astInterfaceResult as ResolvedUnitResult);
|
||||
var astImplData = processAstImpl(astImplResult as ResolvedUnitResult);
|
||||
var selectionData = processSelection(selectionResult as ResolvedUnitResult);
|
||||
var visitedLists = selectionData.visitedLists;
|
||||
var inheritanceManager = InheritanceManager3();
|
||||
|
||||
var buffer = StringBuffer();
|
||||
for (var interface in astImplData.instantiableInterfaces) {
|
||||
if (interface.name == 'Comment' ||
|
||||
interface.name == 'VariableDeclaration') {
|
||||
// The class `Comment` has references, but we don't support selecting a
|
||||
// portion of a comment in order to operate on it.
|
||||
//
|
||||
// The class `VariableDeclaration` has metadata, but the list is never
|
||||
// populated, so we don't visit the class, and hence are required to
|
||||
// special case it here. If the class hierarchy is ever cleaned up, we
|
||||
// can remove this special casing.
|
||||
continue;
|
||||
}
|
||||
var declaredNodeLists = astInterfaceData.nodeListsFor(interface);
|
||||
if (declaredNodeLists.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
var visitedNodeLists = visitedLists[interface];
|
||||
if (visitedNodeLists == null) {
|
||||
var interfaceName = interface.name;
|
||||
buffer.writeln('Missing implementation of visit$interfaceName:');
|
||||
buffer.writeln();
|
||||
buffer.writeln('@override');
|
||||
buffer.writeln('void visit$interfaceName($interfaceName node) {');
|
||||
for (var nodeList in declaredNodeLists) {
|
||||
buffer.writeln(' _fromList(node.${nodeList.name});');
|
||||
}
|
||||
buffer.writeln('}');
|
||||
buffer.writeln();
|
||||
} else {
|
||||
var unvisitedNodeLists = {...declaredNodeLists};
|
||||
for (var visitedNodeList in visitedNodeLists) {
|
||||
unvisitedNodeLists.remove(visitedNodeList);
|
||||
var overridden = inheritanceManager.getOverridden2(
|
||||
visitedNodeList.enclosingElement as InterfaceElement,
|
||||
Name(visitedNodeList.library.source.uri, visitedNodeList.name));
|
||||
if (overridden != null) {
|
||||
unvisitedNodeLists.removeAll(overridden);
|
||||
}
|
||||
}
|
||||
if (unvisitedNodeLists.isNotEmpty) {
|
||||
buffer.writeln('Missing lines in visit${interface.name}:');
|
||||
buffer.writeln();
|
||||
for (var nodeList in unvisitedNodeLists) {
|
||||
buffer.writeln(' _fromList(node.${nodeList.name});');
|
||||
}
|
||||
buffer.writeln();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (buffer.length > 0) {
|
||||
fail(buffer.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionData {
|
||||
final Map<ClassElement, List<ExecutableElement>> visitedLists = {};
|
||||
|
||||
SelectionData();
|
||||
}
|
||||
|
||||
class VisitMethodVisitor extends RecursiveAstVisitor {
|
||||
List<ExecutableElement> visitedLists = [];
|
||||
|
||||
VisitMethodVisitor();
|
||||
|
||||
@override
|
||||
visitMethodInvocation(MethodInvocation node) {
|
||||
if (node.methodName.name == '_fromList') {
|
||||
var argument = node.argumentList.arguments.first;
|
||||
if (argument is PrefixedIdentifier) {
|
||||
visitedLists
|
||||
.add(argument.identifier.staticElement as ExecutableElement);
|
||||
} else if (argument is PropertyAccess) {
|
||||
visitedLists
|
||||
.add(argument.propertyName.staticElement as ExecutableElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -291,6 +291,15 @@ extension on int {}
|
|||
''');
|
||||
}
|
||||
|
||||
Future<void> test_fieldDeclaration_metadata() async {
|
||||
await assertMetadata(prefix: '''
|
||||
class C {
|
||||
''', postfix: '''
|
||||
int? f;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_fieldFormalParameter_metadata() async {
|
||||
await assertMetadata(prefix: '''
|
||||
class C {
|
||||
|
@ -302,6 +311,16 @@ this.x);
|
|||
''');
|
||||
}
|
||||
|
||||
Future<void> test_forEachPartsWithPattern_metadata() async {
|
||||
await assertMetadata(prefix: '''
|
||||
void f(List<(int, int)> r) {
|
||||
for (
|
||||
''', postfix: '''
|
||||
var (x, y) in r) {}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_formalParameterList_parameters_mixed() async {
|
||||
var nodes = await nodesInRange('''
|
||||
void f(a, [!b, {c!], d}) {}
|
||||
|
@ -591,6 +610,15 @@ part of '';
|
|||
''');
|
||||
}
|
||||
|
||||
Future<void> test_patternVariableDeclaration_metadata() async {
|
||||
await assertMetadata(prefix: '''
|
||||
void f((int, int) r) {
|
||||
''', postfix: '''
|
||||
var (x, y) = r;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_recordLiteral_fields() async {
|
||||
var nodes = await nodesInRange('''
|
||||
var r = ('0', [!1, 2.0!], '3');
|
||||
|
@ -677,6 +705,17 @@ int x) {}
|
|||
''');
|
||||
}
|
||||
|
||||
Future<void> test_stringInterpolation_elements() async {
|
||||
var nodes = await nodesInRange(r'''
|
||||
void f(cd, gh) {
|
||||
var s = 'ab${c[!d}e!]f${gh}';
|
||||
}
|
||||
''');
|
||||
expect(nodes, hasLength(2));
|
||||
nodes[0] as InterpolationExpression;
|
||||
nodes[1] as InterpolationString;
|
||||
}
|
||||
|
||||
Future<void> test_superFormalParameter_metadata() async {
|
||||
await assertMetadata(prefix: '''
|
||||
class C extends B {
|
||||
|
|
|
@ -5,19 +5,21 @@
|
|||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import 'extensions/test_all.dart' as extensions;
|
||||
import 'flutter_test.dart' as flutter_test;
|
||||
import 'flutter_test.dart' as flutter;
|
||||
import 'import_analyzer_test.dart' as import_analyzer;
|
||||
import 'profiling_test.dart' as profiling_test;
|
||||
import 'selection_test.dart' as selection_test;
|
||||
import 'strings_test.dart' as strings_test;
|
||||
import 'profiling_test.dart' as profiling;
|
||||
import 'selection_coverage_test.dart' as selection_coverage;
|
||||
import 'selection_test.dart' as selection;
|
||||
import 'strings_test.dart' as strings;
|
||||
|
||||
void main() {
|
||||
defineReflectiveSuite(() {
|
||||
extensions.main();
|
||||
flutter_test.main();
|
||||
flutter.main();
|
||||
import_analyzer.main();
|
||||
profiling_test.main();
|
||||
selection_test.main();
|
||||
strings_test.main();
|
||||
profiling.main();
|
||||
selection_coverage.main();
|
||||
selection.main();
|
||||
strings.main();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue