[wildcards] fix `ConvertToWildcardVariable` to convert variable references
Change-Id: Ie9b9d03adf11bf2e18d5a3da6e28be8abf4eb53d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384943
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Phil Quitslund <pquitslund@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_wildcard_variable.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_wildcard_variable.dart
index 587ff8f..3daedd4 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_wildcard_variable.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_wildcard_variable.dart
@@ -3,9 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/correction/util.dart';
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
@@ -30,8 +32,29 @@
var node = this.node;
if (node is! VariableDeclaration) return;
+ var nameToken = node.name;
+ var element = node.declaredElement;
+
+ List<AstNode>? references;
+ if (element is LocalVariableElement) {
+ var root = node.thisOrAncestorOfType<Block>();
+ if (root != null) {
+ references = findLocalElementReferences2(root, element);
+ }
+ }
+ if (references == null) return;
+
+ // Only assigned variable patterns can be safely converted to wildcards.
+ if (references.any((r) => r is! AssignedVariablePattern)) return;
+
+ var sourceRanges = {
+ range.token(nameToken),
+ ...references.map(range.node),
+ };
await builder.addDartFileEdit(file, (builder) {
- builder.addSimpleReplacement(range.token(node.name), '_');
+ for (var sourceRange in sourceRanges) {
+ builder.addSimpleReplacement(sourceRange, '_');
+ }
});
}
}
diff --git a/pkg/analysis_server/lib/src/services/correction/util.dart b/pkg/analysis_server/lib/src/services/correction/util.dart
index 9d2dbc6..cf294f5 100644
--- a/pkg/analysis_server/lib/src/services/correction/util.dart
+++ b/pkg/analysis_server/lib/src/services/correction/util.dart
@@ -32,6 +32,7 @@
}
/// Return references to the [element] inside the [root] node.
+// TODO(pq): update to `findLocalElementReferences2`.
List<SimpleIdentifier> findLocalElementReferences(
AstNode root, LocalElement element) {
var collector = _ElementReferenceCollector(element);
@@ -40,6 +41,14 @@
}
/// Return references to the [element] inside the [root] node.
+/// (Unlike [findLocalElementReferences], visits list and record pattern assignments).
+List<AstNode> findLocalElementReferences2(AstNode root, LocalElement element) {
+ var collector = _ElementReferenceCollector2(element);
+ root.accept(collector);
+ return collector.references;
+}
+
+/// Return references to the [element] inside the [root] node.
List<SimpleIdentifier> findPrefixElementReferences(
AstNode root, PrefixElement element) {
var collector = _ElementReferenceCollector(element);
@@ -396,6 +405,50 @@
}
}
+class _ElementReferenceCollector2 extends RecursiveAstVisitor<void> {
+ final Element element;
+ final List<AstNode> references = [];
+
+ _ElementReferenceCollector2(this.element);
+
+ @override
+ void visitImportPrefixReference(ImportPrefixReference node) {
+ if (node.element == element) {
+ references.add(SimpleIdentifierImpl(node.name));
+ }
+ }
+
+ @override
+ void visitListPattern(ListPattern node) {
+ for (var item in node.elements) {
+ if (item is AssignedVariablePattern) {
+ if (item.element == element) {
+ references.add(item);
+ }
+ }
+ }
+ }
+
+ @override
+ void visitRecordPattern(RecordPattern node) {
+ for (var field in node.fields) {
+ var pattern = field.pattern.unparenthesized;
+ if (pattern is AssignedVariablePattern) {
+ if (pattern.element == element) {
+ references.add(field.pattern);
+ }
+ }
+ }
+ }
+
+ @override
+ void visitSimpleIdentifier(SimpleIdentifier node) {
+ if (node.staticElement == element) {
+ references.add(node);
+ }
+ }
+}
+
/// Visitor that collects defined [LocalElement]s.
class _LocalElementsCollector extends RecursiveAstVisitor<void> {
final elements = <LocalElement>[];
@@ -410,3 +463,11 @@
super.visitVariableDeclaration(node);
}
}
+
+extension on DartPattern {
+ DartPattern get unparenthesized {
+ var self = this;
+ if (self is! ParenthesizedPattern) return self;
+ return self.pattern.unParenthesized;
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/convert_to_wildcard_variable_test.dart b/pkg/analysis_server/test/src/services/correction/fix/convert_to_wildcard_variable_test.dart
index cac0372..0741797 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/convert_to_wildcard_variable_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/convert_to_wildcard_variable_test.dart
@@ -47,6 +47,21 @@
''');
}
+ Future<void> test_convertUnusedLocalVariable_listPatternAssignment() async {
+ await resolveTestCode('''
+void f() {
+ var x = 0;
+ [x, _] = [1, 2];
+}
+''');
+ await assertHasFix('''
+void f() {
+ var _ = 0;
+ [_, _] = [1, 2];
+}
+''');
+ }
+
Future<void> test_convertUnusedLocalVariable_preWildcards() async {
await resolveTestCode('''
// @dart = 3.4
@@ -58,4 +73,46 @@
''');
await assertNoFix();
}
+
+ Future<void> test_convertUnusedLocalVariable_recordAssignment() async {
+ await resolveTestCode('''
+void f() {
+ var x = 0;
+ (x, _) = (1, 2);
+}
+''');
+ await assertHasFix('''
+void f() {
+ var _ = 0;
+ (_, _) = (1, 2);
+}
+''');
+ }
+
+ Future<void>
+ test_convertUnusedLocalVariable_recordAssignment_parenthesized() async {
+ await resolveTestCode('''
+void f() {
+ var x = 0;
+ ((x, _)) = (1, 2);
+}
+''');
+ await assertHasFix('''
+void f() {
+ var _ = 0;
+ ((_, _)) = (1, 2);
+}
+''');
+ }
+
+ Future<void> test_convertUnusedLocalVariable_reference() async {
+ await resolveTestCode('''
+void f() {
+ var x = '';
+ x = '';
+}
+''');
+ // Converting the simple identifier `x` would result in invalid code.
+ await assertNoFix();
+ }
}