[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();
+  }
 }