Update UNUSED_ELEMENT_PARAMETER to support redirected factory constructors.

Change-Id: Id296fbce50316f872b505f034a58608d54b75be3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/226962
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/error/unused_local_elements_verifier.dart b/pkg/analyzer/lib/src/error/unused_local_elements_verifier.dart
index c7a1de2..8a93e39 100644
--- a/pkg/analyzer/lib/src/error/unused_local_elements_verifier.dart
+++ b/pkg/analyzer/lib/src/error/unused_local_elements_verifier.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:collection';
+import 'dart:math' as math;
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/token.dart';
@@ -79,6 +80,27 @@
   }
 
   @override
+  void visitConstructorDeclaration(ConstructorDeclaration node) {
+    var element = node.declaredElement!;
+    var redirectedConstructor = node.redirectedConstructor;
+    if (redirectedConstructor != null) {
+      var redirectedElement = redirectedConstructor.staticElement;
+      if (redirectedElement != null) {
+        // TODO(scheglov) Only if not _isPubliclyAccessible
+        _matchParameters(
+          element.parameters,
+          redirectedElement.parameters,
+          (first, second) {
+            usedElements.addElement(second);
+          },
+        );
+      }
+    }
+
+    super.visitConstructorDeclaration(node);
+  }
+
+  @override
   void visitFunctionDeclaration(FunctionDeclaration node) {
     var enclosingExecOld = _enclosingExec;
     try {
@@ -170,6 +192,10 @@
 
   @override
   void visitSimpleIdentifier(SimpleIdentifier node) {
+    // TODO(scheglov) Remove after https://dart-review.googlesource.com/c/sdk/+/226960
+    if (node.parent is FieldFormalParameter) {
+      return;
+    }
     if (node.inDeclarationContext()) {
       return;
     }
@@ -341,6 +367,51 @@
     // OK
     return true;
   }
+
+  /// Invokes [f] for corresponding positional and named parameters.
+  /// Ignores parameters that don't have a corresponding pair.
+  /// TODO(scheglov) There might be a better place for this function.
+  static void _matchParameters(
+    List<ParameterElement> firstList,
+    List<ParameterElement> secondList,
+    void Function(ParameterElement first, ParameterElement second) f,
+  ) {
+    Map<String, ParameterElement>? firstNamed;
+    Map<String, ParameterElement>? secondNamed;
+    var firstPositional = <ParameterElement>[];
+    var secondPositional = <ParameterElement>[];
+    for (var element in firstList) {
+      if (element.isNamed) {
+        (firstNamed ??= {})[element.name] = element;
+      } else {
+        firstPositional.add(element);
+      }
+    }
+    for (var element in secondList) {
+      if (element.isNamed) {
+        (secondNamed ??= {})[element.name] = element;
+      } else {
+        secondPositional.add(element);
+      }
+    }
+
+    var positionalLength = math.min(
+      firstPositional.length,
+      secondPositional.length,
+    );
+    for (var i = 0; i < positionalLength; i++) {
+      f(firstPositional[i], secondPositional[i]);
+    }
+
+    if (firstNamed != null && secondNamed != null) {
+      for (var firstEntry in firstNamed.entries) {
+        var second = secondNamed[firstEntry.key];
+        if (second != null) {
+          f(firstEntry.value, second);
+        }
+      }
+    }
+  }
 }
 
 /// Instances of the class [UnusedLocalElementsVerifier] traverse an AST
diff --git a/pkg/analyzer/test/src/diagnostics/unused_element_test.dart b/pkg/analyzer/test/src/diagnostics/unused_element_test.dart
index b9104c2..fb070cc 100644
--- a/pkg/analyzer/test/src/diagnostics/unused_element_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/unused_element_test.dart
@@ -1858,6 +1858,98 @@
     expect(result.errors, isNotEmpty);
   }
 
+  test_parameter_optionalNamed_fieldFormal_isUsed_constructorInvocation() async {
+    await assertNoErrorsInCode(r'''
+class _A {
+  final int? f;
+  _A({this.f});
+}
+f() => _A(f: 0);
+''');
+  }
+
+  test_parameter_optionalNamed_fieldFormal_isUsed_factoryRedirect() async {
+    await assertNoErrorsInCode(r'''
+class _A {
+  final int? f;
+  _A({this.f});
+  factory _A.named({int? f}) = _A;
+}
+f() => _A.named(f: 0);
+''');
+  }
+
+  test_parameter_optionalNamed_fieldFormal_notUsed() async {
+    await assertErrorsInCode(r'''
+class _A {
+  final int? f;
+  _A({this.f});
+}
+f() => _A();
+''', [
+      error(HintCode.UNUSED_ELEMENT_PARAMETER, 38, 1),
+    ]);
+  }
+
+  test_parameter_optionalNamed_fieldFormal_notUsed_factoryRedirect() async {
+    await assertErrorsInCode(r'''
+class _A {
+  final int? f;
+  _A({this.f});
+  factory _A.named() = _A;
+}
+f() => _A.named();
+''', [
+      error(HintCode.UNUSED_ELEMENT_PARAMETER, 38, 1),
+    ]);
+  }
+
+  test_parameter_optionalPositional_fieldFormal_isUsed_constructorInvocation() async {
+    await assertNoErrorsInCode(r'''
+class _A {
+  final int? f;
+  _A([this.f]);
+}
+f() => _A(0);
+''');
+  }
+
+  test_parameter_optionalPositional_fieldFormal_isUsed_factoryRedirect() async {
+    await assertNoErrorsInCode(r'''
+class _A {
+  final int? f;
+  _A([this.f]);
+  factory _A.named([int a]) = _A;
+}
+f() => _A.named(0);
+''');
+  }
+
+  test_parameter_optionalPositional_fieldFormal_notUsed() async {
+    await assertErrorsInCode(r'''
+class _A {
+  final int? f;
+  _A([this.f]);
+}
+f() => _A();
+''', [
+      error(HintCode.UNUSED_ELEMENT_PARAMETER, 38, 1),
+    ]);
+  }
+
+  test_parameter_optionalPositional_fieldFormal_notUsed_factoryRedirect() async {
+    await assertErrorsInCode(r'''
+class _A {
+  final int? f;
+  _A([this.f]);
+  factory _A.named() = _A;
+}
+f() => _A.named();
+''', [
+      error(HintCode.UNUSED_ELEMENT_PARAMETER, 38, 1),
+    ]);
+  }
+
   test_typeAlias_interfaceType_isUsed_typeName_isExpression() async {
     await assertNoErrorsInCode(r'''
 typedef _A = List<int>;