Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit df2e8fe

Browse files
bwilkersonCommit Bot
authored andcommitted
Start to refactor ElementMatcher
This is the first of multiple smaller CLs to refactor `ElementMatcher`. I was starting to fix a bug and realized that the current implementation allows the number of components to be out of sync with the element kind. The primary goal is to prevent that by eventually merging the methods `_componentsForNode` and `_kindsForNode` into a single method (`buildMatchersForNode`). In the process I realized that we can sometimes match either a top-level declaraation or an instance member from a superclass, and that means that we need multiple matchers (otherwise I think the number of components will continue to be out-of-sync with the element kinds). This CL also includes the failing test that started the whole investigation into refactoring this class. Change-Id: I0c3a56f29f0f6c6d0cad6ac80145b26201931518 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/228722 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent 3fbe18b commit df2e8fe

File tree

5 files changed

+181
-45
lines changed

5 files changed

+181
-45
lines changed

pkg/analysis_server/lib/src/services/correction/dart/data_driven.dart

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,17 @@ class DataDriven extends MultiCorrectionProducer {
3333
importedUris.add(Uri.parse(uri));
3434
}
3535
}
36-
var matcher = ElementMatcher.forNode(node);
37-
if (matcher == null) {
38-
// The node doesn't represent an element that can be transformed.
36+
var matchers = ElementMatcher.matchersForNode(node);
37+
if (matchers.isEmpty) {
38+
// The node doesn't represent any element that can be transformed.
3939
return;
4040
}
4141
for (var set in _availableTransformSetsForLibrary(library)) {
42-
for (var transform
43-
in set.transformsFor(matcher, applyingBulkFixes: applyingBulkFixes)) {
44-
yield DataDrivenFix(transform);
42+
for (var matcher in matchers) {
43+
for (var transform in set.transformsFor(matcher,
44+
applyingBulkFixes: applyingBulkFixes)) {
45+
yield DataDrivenFix(transform);
46+
}
4547
}
4648
}
4749
}

pkg/analysis_server/lib/src/services/correction/fix/data_driven/element_matcher.dart

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ class ElementMatcher {
2626
final List<ElementKind> validKinds;
2727

2828
/// Initialize a newly created matcher representing a reference to an element
29-
/// with the given [name] in a library that imports the [importedUris].
29+
/// whose name matches the given [components] and element [kinds] in a library
30+
/// that imports the [importedUris].
3031
ElementMatcher(
3132
{required this.importedUris,
3233
required this.components,
33-
List<ElementKind>? kinds})
34+
required List<ElementKind> kinds})
3435
: assert(components.isNotEmpty),
35-
validKinds = kinds ?? const [];
36+
validKinds = kinds;
3637

3738
/// Return `true` if this matcher matches the given [element].
3839
bool matches(ElementDescriptor element) {
@@ -93,25 +94,68 @@ class ElementMatcher {
9394
return false;
9495
}
9596

96-
/// Return an element matcher that will match the element that is, or should
97-
/// be, associated with the given [node], or `null` if there is no appropriate
98-
/// matcher for the node.
99-
static ElementMatcher? forNode(AstNode? node) {
97+
/// Return a list of element matchers that will match the element that is, or
98+
/// should be, associated with the given [node]. The list will be empty if
99+
/// there are no appropriate matchers for the [node].
100+
static List<ElementMatcher> matchersForNode(AstNode? node) {
100101
if (node == null) {
101-
return null;
102+
return const <ElementMatcher>[];
102103
}
103104
var importedUris = _importElementsForNode(node);
104105
if (importedUris == null) {
106+
return const <ElementMatcher>[];
107+
}
108+
var builder = _MatcherBuilder(importedUris);
109+
builder.buildMatchersForNode(node);
110+
return builder.matchers.toList();
111+
}
112+
113+
/// Return the URIs of the imports in the library containing the [node], or
114+
/// `null` if the imports can't be determined.
115+
static List<Uri>? _importElementsForNode(AstNode node) {
116+
var root = node.root;
117+
if (root is! CompilationUnit) {
105118
return null;
106119
}
120+
var importedUris = <Uri>[];
121+
var library = root.declaredElement?.library;
122+
if (library == null) {
123+
return null;
124+
}
125+
for (var importElement in library.imports) {
126+
// TODO(brianwilkerson) Filter based on combinators to help avoid making
127+
// invalid suggestions.
128+
var uri = importElement.importedLibrary?.source.uri;
129+
if (uri != null) {
130+
// The [uri] is `null` if the literal string is not a valid URI.
131+
importedUris.add(uri);
132+
}
133+
}
134+
return importedUris;
135+
}
136+
}
137+
138+
/// A helper class used to build a list of element matchers.
139+
class _MatcherBuilder {
140+
final List<ElementMatcher> matchers = [];
141+
142+
final List<Uri> importedUris;
143+
144+
_MatcherBuilder(this.importedUris);
145+
146+
void buildMatchersForNode(AstNode? node) {
107147
var components = _componentsForNode(node);
108148
if (components == null) {
109-
return null;
149+
return;
110150
}
111-
return ElementMatcher(
112-
importedUris: importedUris,
113-
components: components,
114-
kinds: _kindsForNode(node));
151+
var kinds = _kindsForNode(node) ?? [];
152+
_addMatcher(components: components, kinds: kinds);
153+
}
154+
155+
void _addMatcher(
156+
{required List<String> components, required List<ElementKind> kinds}) {
157+
matchers.add(ElementMatcher(
158+
importedUris: importedUris, components: components, kinds: kinds));
115159
}
116160

117161
/// Return the components of the path of the element associated with the given
@@ -244,30 +288,6 @@ class ElementMatcher {
244288
return null;
245289
}
246290

247-
/// Return the URIs of the imports in the library containing the [node], or
248-
/// `null` if the imports can't be determined.
249-
static List<Uri>? _importElementsForNode(AstNode node) {
250-
var root = node.root;
251-
if (root is! CompilationUnit) {
252-
return null;
253-
}
254-
var importedUris = <Uri>[];
255-
var library = root.declaredElement?.library;
256-
if (library == null) {
257-
return null;
258-
}
259-
for (var importElement in library.imports) {
260-
// TODO(brianwilkerson) Filter based on combinators to help avoid making
261-
// invalid suggestions.
262-
var uri = importElement.importedLibrary?.source.uri;
263-
if (uri != null) {
264-
// The [uri] is `null` if the literal string is not a valid URI.
265-
importedUris.add(uri);
266-
}
267-
}
268-
return importedUris;
269-
}
270-
271291
/// Return `true` if the [node] is a prefix
272292
static bool _isPrefix(AstNode? node) {
273293
return node is SimpleIdentifier && node.staticElement is PrefixElement;
@@ -312,6 +332,7 @@ class ElementMatcher {
312332
}
313333
return const [
314334
ElementKind.classKind,
335+
// ElementKind.constructorKind,
315336
ElementKind.enumKind,
316337
ElementKind.mixinKind,
317338
ElementKind.typedefKind

pkg/analysis_server/test/src/services/correction/fix/data_driven/element_matcher_test.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ abstract class AbstractElementMatcherTest extends DataDrivenFixProcessorTest {
2323
List<ElementKind>? expectedKinds,
2424
List<String>? expectedUris}) {
2525
var node = findNode.any(search);
26-
var matcher = ElementMatcher.forNode(node)!;
26+
var matchers = ElementMatcher.matchersForNode(node);
27+
expect(matchers, hasLength(1));
28+
var matcher = matchers[0];
2729
if (expectedUris != null) {
2830
expect(matcher.importedUris,
2931
unorderedEquals(expectedUris.map((uri) => Uri.parse(uri))));

pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_use_case_test.dart

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,117 @@ void f() {
706706
''');
707707
}
708708

709+
@failingTest
710+
Future<void> test_material_FlatButton_deprecated() async {
711+
setPackageContent('''
712+
@deprecated
713+
class FlatButton {
714+
factory FlatButton.icon({
715+
Key? key,
716+
required VoidCallback? onPressed,
717+
VoidCallback? onLongPress,
718+
ValueChanged<bool>? onHighlightChanged,
719+
ButtonTextTheme? textTheme,
720+
Color? highlightColor,
721+
Brightness? colorBrightness,
722+
Clip? clipBehavior,
723+
FocusNode? focusNode,
724+
bool autofocus = true,
725+
required Widget icon,
726+
required Widget label,
727+
}) => FlatButton();
728+
FlatButton();
729+
}
730+
class Key {}
731+
class UniqueKey extends Key {}
732+
typedef VoidCallback = void Function();
733+
typedef ValueChanged<T> = void Function(T value);
734+
class ButtonTextTheme {}
735+
class Color {}
736+
class Colors {
737+
static Color blue = Color();
738+
}
739+
class Brightness {
740+
static Brightness dark = Brightness();
741+
}
742+
class Clip {
743+
static Clip hardEdge = Clip();
744+
}
745+
class FocusNode {}
746+
class Widget {}
747+
class Icon extends Widget {
748+
Icon(String icon);
749+
}
750+
class Text extends Widget {
751+
Text(String content);
752+
}
753+
class Icons {
754+
static String ten_k_outlined = '';
755+
}
756+
''');
757+
addPackageDataFile('''
758+
version: 1
759+
transforms:
760+
# Changes made in https://github.com/flutter/flutter/pull/73352
761+
- title: "Migrate to 'TextButton.icon'"
762+
date: 2021-01-08
763+
element:
764+
uris: [ '$importUri' ]
765+
constructor: 'icon'
766+
inClass: 'FlatButton'
767+
changes:
768+
- kind: 'removeParameter'
769+
name: 'onHighlightChanged'
770+
- kind: 'removeParameter'
771+
name: 'textTheme'
772+
- kind: 'removeParameter'
773+
name: 'highlightColor'
774+
- kind: 'removeParameter'
775+
name: 'colorBrightness'
776+
- kind: 'replacedBy'
777+
newElement:
778+
uris: [ '$importUri' ]
779+
constructor: 'icon'
780+
inClass: 'TextButton'
781+
''');
782+
await resolveTestCode('''
783+
import '$importUri';
784+
785+
void f() {
786+
FlatButton.icon(
787+
key: UniqueKey(),
788+
icon: Icon(Icons.ten_k_outlined),
789+
label: Text('FlatButton'),
790+
onPressed: (){},
791+
onLongPress: (){},
792+
clipBehavior: Clip.hardEdge,
793+
focusNode: FocusNode(),
794+
autofocus: true,
795+
onHighlightChanged: (_) {},
796+
textTheme: ButtonTextTheme(),
797+
highlightColor: Colors.blue,
798+
colorBrightness: Brightness.dark,
799+
);
800+
}
801+
''');
802+
await assertHasFix('''
803+
import '$importUri';
804+
805+
void f() {
806+
TextButton.icon(
807+
key: UniqueKey(),
808+
icon: Icon(Icons.ten_k_outlined),
809+
label: Text('FlatButton'),
810+
onPressed: (){},
811+
onLongPress: (){},
812+
clipBehavior: Clip.hardEdge,
813+
focusNode: FocusNode(),
814+
autofocus: true,
815+
);
816+
}
817+
''');
818+
}
819+
709820
Future<void>
710821
test_material_InputDecoration_defaultConstructor_matchFirstCase_deprecated() async {
711822
setPackageContent('''

pkg/analysis_server/test/src/services/correction/fix/data_driven/transform_set_parser_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ transforms:
784784
(transform.changesSelector as UnconditionalChangesSelector).changes;
785785

786786
ElementMatcher _matcher(String name) =>
787-
ElementMatcher(importedUris: uris, components: [name]);
787+
ElementMatcher(importedUris: uris, components: [name], kinds: const []);
788788

789789
List<Transform> _transforms(String name) =>
790790
result!.transformsFor(_matcher(name), applyingBulkFixes: false);

0 commit comments

Comments
 (0)