Code completion for super-formal parameters.

Change-Id: I52b0786f7db8e8ec0181b5e4d7eb352b157856b0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/226606
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
index c3a351b..4932c25 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
@@ -28,6 +28,7 @@
 import 'package:analysis_server/src/services/completion/dart/relevance_tables.g.dart';
 import 'package:analysis_server/src/services/completion/dart/static_member_contributor.dart';
 import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
+import 'package:analysis_server/src/services/completion/dart/super_formal_contributor.dart';
 import 'package:analysis_server/src/services/completion/dart/type_member_contributor.dart';
 import 'package:analysis_server/src/services/completion/dart/uri_contributor.dart';
 import 'package:analysis_server/src/services/completion/dart/variable_name_contributor.dart';
@@ -155,6 +156,7 @@
       if (enableOverrideContributor) OverrideContributor(request, builder),
       RedirectingContributor(request, builder),
       StaticMemberContributor(request, builder),
+      SuperFormalContributor(request, builder),
       TypeMemberContributor(request, builder),
       if (enableUriContributor) UriContributor(request, builder),
       VariableNameContributor(request, builder),
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
index a441aa0..b4c063c 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
@@ -877,6 +877,17 @@
         relevance: relevance));
   }
 
+  /// Add a suggestion to reference a [parameter] in a super formal parameter.
+  void suggestSuperFormalParameter(ParameterElement parameter) {
+    _add(
+      _createSuggestion(
+        parameter,
+        kind: CompletionSuggestionKind.IDENTIFIER,
+        relevance: Relevance.superFormalParameter,
+      ),
+    );
+  }
+
   /// Add a suggestion for a top-level [function]. If a [kind] is provided it
   /// will be used as the kind for the suggestion. If the function can only be
   /// referenced using a prefix, then the [prefix] should be provided.
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/super_formal_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/super_formal_contributor.dart
new file mode 100644
index 0000000..fdd99bf
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/completion/dart/super_formal_contributor.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2022, 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:analysis_server/src/provisional/completion/dart/completion_dart.dart';
+import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
+import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:collection/collection.dart';
+
+/// A contributor that produces suggestions for super formal parameters that
+/// are based on the parameters declared by the invoked super-constructor.
+/// The enclosing declaration is expected to be a constructor.
+class SuperFormalContributor extends DartCompletionContributor {
+  SuperFormalContributor(
+    DartCompletionRequest request,
+    SuggestionBuilder builder,
+  ) : super(request, builder);
+
+  @override
+  Future<void> computeSuggestions() async {
+    var node = request.target.containingNode;
+    if (node is! SuperFormalParameter) {
+      return;
+    }
+
+    var element = node.declaredElement as SuperFormalParameterElementImpl;
+
+    var constructor = node.thisOrAncestorOfType<ConstructorDeclaration>();
+    if (constructor == null) {
+      return;
+    }
+
+    var constructorElement = constructor.declaredElement;
+    constructorElement as ConstructorElementImpl;
+
+    var superConstructor = constructorElement.superConstructor;
+    if (superConstructor == null) {
+      return;
+    }
+
+    if (node.isNamed) {
+      var superConstructorInvocation = constructor.initializers
+          .whereType<SuperConstructorInvocation>()
+          .singleOrNull;
+      var specified = <String>{
+        ...constructorElement.parameters.map((e) => e.name),
+        ...?superConstructorInvocation?.argumentList.arguments
+            .whereType<NamedExpression>()
+            .map((e) => e.name.label.name),
+      };
+      for (var superParameter in superConstructor.parameters) {
+        if (superParameter.isNamed &&
+            !specified.contains(superParameter.name)) {
+          builder.suggestSuperFormalParameter(superParameter);
+        }
+      }
+    }
+
+    if (node.isPositional) {
+      var indexOfThis = element.indexIn(constructorElement);
+      var superPositionalList = superConstructor.parameters
+          .where((parameter) => parameter.isPositional)
+          .toList();
+      if (indexOfThis >= 0 && indexOfThis < superPositionalList.length) {
+        var superPositional = superPositionalList[indexOfThis];
+        builder.suggestSuperFormalParameter(superPositional);
+      }
+    }
+  }
+}
diff --git a/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart
index 8128659..7bf72fe 100644
--- a/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart
@@ -55,7 +55,7 @@
         ..docSummary.isEqualTo('aaa')
         ..hasSelection(offset: 5)
         ..element.isNotNull.which((e) => e
-          ..isParameter
+          ..kind.isParameter
           ..name.isEqualTo('fff'))
     ]);
   }
@@ -79,7 +79,7 @@
         ..docSummary.isNull
         ..hasSelection(offset: 5)
         ..element.isNotNull.which((e) => e
-          ..isParameter
+          ..kind.isParameter
           ..name.isEqualTo('fff'))
     ]);
   }
diff --git a/pkg/analysis_server/test/services/completion/dart/completion_check.dart b/pkg/analysis_server/test/services/completion/dart/completion_check.dart
index f9cdaf1..4c15bd2 100644
--- a/pkg/analysis_server/test/services/completion/dart/completion_check.dart
+++ b/pkg/analysis_server/test/services/completion/dart/completion_check.dart
@@ -55,6 +55,20 @@
 
 extension CompletionResponseExtension
     on CheckTarget<CompletionResponseForTesting> {
+  CheckTarget<int> get replacementLength {
+    return nest(
+      value.replacementLength,
+      (selected) => 'has replacementLength ${valueStr(selected)}',
+    );
+  }
+
+  CheckTarget<int> get replacementOffset {
+    return nest(
+      value.replacementOffset,
+      (selected) => 'has replacementOffset ${valueStr(selected)}',
+    );
+  }
+
   CheckTarget<List<CompletionSuggestionForTesting>> get suggestions {
     var suggestions = value.suggestions.map((e) {
       return CompletionSuggestionForTesting(
@@ -67,6 +81,19 @@
       (selected) => 'suggestions ${valueStr(selected)}',
     );
   }
+
+  /// Check that the replacement offset is the completion request offset,
+  /// and the length of the replacement is zero.
+  void hasEmptyReplacement() {
+    hasReplacement(left: 0, right: 0);
+  }
+
+  /// Check that the replacement offset is the completion request offset
+  /// minus [left], and the length of the replacement is `left + right`.
+  void hasReplacement({int left = 0, int right = 0}) {
+    replacementOffset.isEqualTo(value.requestOffset - left);
+    replacementLength.isEqualTo(left + right);
+  }
 }
 
 extension CompletionSuggestionExtension
@@ -113,6 +140,23 @@
     );
   }
 
+  void get isField {
+    kind.isIdentifier;
+    element.isNotNull.kind.isField;
+  }
+
+  void get isParameter {
+    kind.isIdentifier;
+    element.isNotNull.kind.isParameter;
+  }
+
+  CheckTarget<CompletionSuggestionKind> get kind {
+    return nest(
+      value.suggestion.kind,
+      (selected) => 'has kind ${valueStr(selected)}',
+    );
+  }
+
   CheckTarget<String?> get parameterType {
     return nest(
       value.suggestion.parameterType,
@@ -136,6 +180,13 @@
     );
   }
 
+  CheckTarget<String?> get returnType {
+    return nest(
+      value.suggestion.returnType,
+      (selected) => 'has returnType ${valueStr(selected)}',
+    );
+  }
+
   CheckTarget<int> get selectionLength {
     return nest(
       value.suggestion.selectionLength,
@@ -169,8 +220,22 @@
   }
 }
 
+extension CompletionSuggestionKindExtension
+    on CheckTarget<CompletionSuggestionKind> {
+  void get isIdentifier {
+    isEqualTo(CompletionSuggestionKind.IDENTIFIER);
+  }
+}
+
 extension CompletionSuggestionsExtension
     on CheckTarget<Iterable<CompletionSuggestionForTesting>> {
+  CheckTarget<List<String>> get completions {
+    return nest(
+      value.map((e) => e.suggestion.completion).toList(),
+      (selected) => 'completions ${valueStr(selected)}',
+    );
+  }
+
   CheckTarget<Iterable<CompletionSuggestionForTesting>> get namedArguments {
     var result = value
         .where((suggestion) =>
@@ -185,10 +250,6 @@
 }
 
 extension ElementExtension on CheckTarget<Element> {
-  void get isParameter {
-    kind.isEqualTo(ElementKind.PARAMETER);
-  }
-
   CheckTarget<ElementKind> get kind {
     return nest(
       value.kind,
@@ -203,3 +264,13 @@
     );
   }
 }
+
+extension ElementKindExtension on CheckTarget<ElementKind> {
+  void get isField {
+    isEqualTo(ElementKind.FIELD);
+  }
+
+  void get isParameter {
+    isEqualTo(ElementKind.PARAMETER);
+  }
+}
diff --git a/pkg/analysis_server/test/services/completion/dart/field_formal_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/field_formal_contributor_test.dart
index ff209f1..d63a695 100644
--- a/pkg/analysis_server/test/services/completion/dart/field_formal_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/field_formal_contributor_test.dart
@@ -6,9 +6,10 @@
 import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
 import 'package:analysis_server/src/services/completion/dart/field_formal_contributor.dart';
 import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
-import 'package:test/test.dart';
+import 'package:analyzer_utilities/check/check.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
+import 'completion_check.dart';
 import 'completion_contributor_util.dart';
 
 void main() {
@@ -31,183 +32,120 @@
   Future<void> test_mixin_constructor() async {
     addTestSource('''
 mixin M {
+  var field = 0;
   M(this.^);
 }
 ''');
-    await computeSuggestions();
-    expect(suggestions, isEmpty);
+
+    var response = await computeSuggestions2();
+    check(response).suggestions.isEmpty;
   }
 
-  Future<void> test_ThisExpression_constructor_param() async {
-    // SimpleIdentifier  FieldFormalParameter  FormalParameterList
+  Future<void> test_replacement_left() async {
     addTestSource('''
-        main() { }
-        class I {X get f => new A();get _g => new A();}
-        class A implements I {
-          A(this.^) {}
-          A.z() {}
-          var b; X _c; static sb;
-          X get d => new A();get _e => new A();
-          // no semicolon between completion point and next statement
-          set s1(I x) {} set _s2(I x) {m(null);}
-          m(X x) {} I _n(X x) {}}
-        class X{}''');
-    await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(replacementLength, 0);
-    assertSuggestField('b', null);
-    assertSuggestField('_c', 'X');
-    assertNotSuggested('sb');
-    assertNotSuggested('d');
-    assertNotSuggested('_e');
-    assertNotSuggested('f');
-    assertNotSuggested('_g');
-    assertNotSuggested('m');
-    assertNotSuggested('_n');
-    assertNotSuggested('s1');
-    assertNotSuggested('_s2');
-    assertNotSuggested('z');
-    assertNotSuggested('I');
-    assertNotSuggested('A');
-    assertNotSuggested('X');
-    assertNotSuggested('Object');
-    assertNotSuggested('==');
+class A {
+  var field = 0;
+  A(this.f^);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasReplacement(left: 1)
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('field')
+          ..isField
+          ..returnType.isEqualTo('int'),
+      ]);
   }
 
-  Future<void> test_ThisExpression_constructor_param2() async {
-    // SimpleIdentifier  FieldFormalParameter  FormalParameterList
+  Future<void> test_replacement_right() async {
     addTestSource('''
-        main() { }
-        class I {X get f => new A();get _g => new A();}
-        class A implements I {
-          A(this.b^) {}
-          A.z() {}
-          var b; X _c;
-          X get d => new A();get _e => new A();
-          // no semicolon between completion point and next statement
-          set s1(I x) {} set _s2(I x) {m(null);}
-          m(X x) {} I _n(X x) {}}
-        class X{}''');
-    await computeSuggestions();
-    expect(replacementOffset, completionOffset - 1);
-    expect(replacementLength, 1);
-    assertSuggestField('b', null);
-    assertSuggestField('_c', 'X');
-    assertNotSuggested('d');
-    assertNotSuggested('_e');
-    assertNotSuggested('f');
-    assertNotSuggested('_g');
-    assertNotSuggested('m');
-    assertNotSuggested('_n');
-    assertNotSuggested('s1');
-    assertNotSuggested('_s2');
-    assertNotSuggested('z');
-    assertNotSuggested('I');
-    assertNotSuggested('A');
-    assertNotSuggested('X');
-    assertNotSuggested('Object');
-    assertNotSuggested('==');
+class A {
+  var field = 0;
+  A(this.^f);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasReplacement(right: 1)
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('field')
+          ..isField
+          ..returnType.isEqualTo('int'),
+      ]);
   }
 
-  Future<void> test_ThisExpression_constructor_param3() async {
-    // SimpleIdentifier  FieldFormalParameter  FormalParameterList
+  Future<void> test_suggestions_onlyLocal() async {
     addTestSource('''
-        main() { }
-        class I {X get f => new A();get _g => new A();}
-        class A implements I {
-          A(this.^b) {}
-          A.z() {}
-          var b; X _c;
-          X get d => new A();get _e => new A();
-          // no semicolon between completion point and next statement
-          set s1(I x) {} set _s2(I x) {m(null);}
-          m(X x) {} I _n(X x) {}}
-        class X{}''');
-    await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(replacementLength, 1);
-    assertSuggestField('b', null);
-    assertSuggestField('_c', 'X');
-    assertNotSuggested('d');
-    assertNotSuggested('_e');
-    assertNotSuggested('f');
-    assertNotSuggested('_g');
-    assertNotSuggested('m');
-    assertNotSuggested('_n');
-    assertNotSuggested('s1');
-    assertNotSuggested('_s2');
-    assertNotSuggested('z');
-    assertNotSuggested('I');
-    assertNotSuggested('A');
-    assertNotSuggested('X');
-    assertNotSuggested('Object');
-    assertNotSuggested('==');
+class A {
+  var inherited = 0;
+}
+
+class B extends A {
+  var first = 0;
+  var second = 1.2;
+  B(this.^);
+  B.constructor() {}
+  void method() {}
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isField
+          ..returnType.isEqualTo('int'),
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isField
+          ..returnType.isEqualTo('double'),
+      ]);
   }
 
-  Future<void> test_ThisExpression_constructor_param4() async {
-    // SimpleIdentifier  FieldFormalParameter  FormalParameterList
+  Future<void> test_suggestions_onlyNotSpecified_optionalNamed() async {
     addTestSource('''
-        main() { }
-        class I {X get f => new A();get _g => new A();}
-        class A implements I {
-          A(this.b, this.^) {}
-          A.z() {}
-          var b; X _c;
-          X get d => new A();get _e => new A();
-          // no semicolon between completion point and next statement
-          set s1(I x) {} set _s2(I x) {m(null);}
-          m(X x) {} I _n(X x) {}}
-        class X{}''');
-    await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(replacementLength, 0);
-    assertNotSuggested('b');
-    assertSuggestField('_c', 'X');
-    assertNotSuggested('d');
-    assertNotSuggested('_e');
-    assertNotSuggested('f');
-    assertNotSuggested('_g');
-    assertNotSuggested('m');
-    assertNotSuggested('_n');
-    assertNotSuggested('s1');
-    assertNotSuggested('_s2');
-    assertNotSuggested('z');
-    assertNotSuggested('I');
-    assertNotSuggested('A');
-    assertNotSuggested('X');
-    assertNotSuggested('Object');
-    assertNotSuggested('==');
+class Point {
+  final int x;
+  final int y;
+  Point({this.x, this.^});
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('y')
+          ..isField
+          ..returnType.isEqualTo('int'),
+      ]);
   }
 
-  Future<void> test_ThisExpression_constructor_param_optional() async {
-    // SimpleIdentifier  FieldFormalParameter  FormalParameterList
+  Future<void> test_suggestions_onlyNotSpecified_requiredPositional() async {
     addTestSource('''
-        main() { }
-        class Point {
-          int x;
-          int y;
-          Point({this.x, this.^}) {}
-          ''');
-    await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(replacementLength, 0);
-    assertSuggestField('y', 'int');
-    assertNotSuggested('x');
-  }
+class Point {
+  final int x;
+  final int y;
+  Point(this.x, this.^);
+}
+''');
 
-  Future<void> test_ThisExpression_constructor_param_positional() async {
-    // SimpleIdentifier  FieldFormalParameter  FormalParameterList
-    addTestSource('''
-        main() { }
-        class Point {
-          int x;
-          int y;
-          Point({this.x, this.^}) {}
-          ''');
-    await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(replacementLength, 0);
-    assertSuggestField('y', 'int');
-    assertNotSuggested('x');
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('y')
+          ..isField
+          ..returnType.isEqualTo('int'),
+      ]);
   }
 }
diff --git a/pkg/analysis_server/test/services/completion/dart/super_formal_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/super_formal_contributor_test.dart
new file mode 100644
index 0000000..ff135e7
--- /dev/null
+++ b/pkg/analysis_server/test/services/completion/dart/super_formal_contributor_test.dart
@@ -0,0 +1,471 @@
+// Copyright (c) 2022, 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:analysis_server/src/provisional/completion/dart/completion_dart.dart';
+import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
+import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
+import 'package:analysis_server/src/services/completion/dart/super_formal_contributor.dart';
+import 'package:analyzer_utilities/check/check.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'completion_check.dart';
+import 'completion_contributor_util.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SuperFormalContributorTest);
+  });
+}
+
+@reflectiveTest
+class SuperFormalContributorTest extends DartCompletionContributorTest {
+  @override
+  DartCompletionContributor createContributor(
+    DartCompletionRequest request,
+    SuggestionBuilder builder,
+  ) {
+    return SuperFormalContributor(request, builder);
+  }
+
+  Future<void> test_explicit_optionalNamed_hasArgument_named() async {
+    addTestSource('''
+class A {
+  A({int first, double second});
+}
+
+class B extends A {
+  B({super.^}) : super(first: 0);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_explicit_optionalNamed_hasArgument_positional() async {
+    addTestSource('''
+class A {
+  A({int first, double second});
+}
+
+class B extends A {
+  B({super.^}) : super(0);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  /// It is an error, but the user already typed `super.`, so maybe do it.
+  Future<void> test_explicit_requiredPositional_hasArgument_positional() async {
+    addTestSource('''
+class A {
+  A(int first, double second);
+}
+
+class B extends A {
+  B(super.^) : super(0);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+      ]);
+  }
+
+  Future<void> test_explicitNamed_noOther() async {
+    addTestSource('''
+class A {
+  A.named(int first, double second);
+  A(int third)
+}
+
+class B extends A {
+  B(super.^) : super.named();
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalNamed_hasNamed_notSuper() async {
+    addTestSource('''
+class A {
+  A({int first, double second});
+}
+
+class B extends A {
+  B({int a, super.^});
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalNamed_hasNamed_notSuper2() async {
+    addTestSource('''
+class A {
+  A({int first, double second});
+}
+
+class B extends A {
+  B({int first, super.^});
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalNamed_hasNamed_super() async {
+    addTestSource('''
+class A {
+  A({int first, double second});
+}
+
+class B extends A {
+  B({super.first, super.^});
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalNamed_hasNamed_super2() async {
+    addTestSource('''
+class A {
+  A({int first, double second});
+}
+
+class B extends A {
+  B({super.second, super.^});
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalNamed_hasPositional_notSuper() async {
+    addTestSource('''
+class A {
+  A({int first, double second});
+}
+
+class B extends A {
+  B(int a, {super.^});
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalNamed_hasPositional_super() async {
+    addTestSource('''
+class A {
+  A({int first, double second});
+}
+
+class B extends A {
+  B(super.first, {super.^});
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalNamed_noOther() async {
+    addTestSource('''
+class A {
+  A(bool first, {int second, double third});
+}
+
+class B extends A {
+  B({super.^});
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+        (suggestion) => suggestion
+          ..completion.isEqualTo('third')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalPositional_hasPositional_notSuper() async {
+    addTestSource('''
+class A {
+  A([int first, double second]);
+}
+
+class B extends A {
+  B([int one, super.^]);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalPositional_hasPositional_super() async {
+    addTestSource('''
+class A {
+  A([int first, double second, bool third]);
+}
+
+class B extends A {
+  B([super.one, super.^]);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalPositional_hasPositional_super2() async {
+    addTestSource('''
+class A {
+  A([int first, double second, bool third]);
+}
+
+class B extends A {
+  B([super.second, super.^]);
+}
+''');
+
+    // It does not matter what is the name of the positional parameter.
+    // Here `super.second` consumes `int first`.
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_optionalPositional_noOther() async {
+    addTestSource('''
+class A {
+  A([int first, double second]);
+}
+
+class B extends A {
+  B(super.^);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+      ]);
+  }
+
+  Future<void> test_implicit_requiredPositional_hasPositional_notSuper() async {
+    addTestSource('''
+class A {
+  A(int first, double second);
+}
+
+class B extends A {
+  B(int one, super.^);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+      ]);
+  }
+
+  Future<void> test_implicit_requiredPositional_hasPositional_super() async {
+    addTestSource('''
+class A {
+  A(int first, double second, bool third);
+}
+
+class B extends A {
+  B(super.one, super.^);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_requiredPositional_hasPositional_super2() async {
+    addTestSource('''
+class A {
+  A(int first, double second, bool third);
+}
+
+class B extends A {
+  B(super.second, super.^);
+}
+''');
+
+    // It does not matter what is the name of the positional parameter.
+    // Here `super.second` consumes `int first`.
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('second')
+          ..isParameter
+          ..returnType.isEqualTo('double'),
+      ]);
+  }
+
+  Future<void> test_implicit_requiredPositional_noOther() async {
+    addTestSource('''
+class A {
+  A(int first, double second);
+  A.named(int third)
+}
+
+class B extends A {
+  B(super.^);
+}
+''');
+
+    var response = await computeSuggestions2();
+    check(response)
+      ..hasEmptyReplacement()
+      ..suggestions.matchesInAnyOrder([
+        (suggestion) => suggestion
+          ..completion.isEqualTo('first')
+          ..isParameter
+          ..returnType.isEqualTo('int'),
+      ]);
+  }
+}
diff --git a/pkg/analysis_server/test/services/completion/dart/test_all.dart b/pkg/analysis_server/test/services/completion/dart/test_all.dart
index 4ed7cd0..384422b 100644
--- a/pkg/analysis_server/test/services/completion/dart/test_all.dart
+++ b/pkg/analysis_server/test/services/completion/dart/test_all.dart
@@ -21,6 +21,7 @@
 import 'override_contributor_test.dart' as override_contributor_test;
 import 'relevance/test_all.dart' as relevance_tests;
 import 'static_member_contributor_test.dart' as static_contributor_test;
+import 'super_formal_contributor_test.dart' as super_formal_contributor;
 import 'type_member_contributor_test.dart' as type_member_contributor_test;
 import 'uri_contributor_test.dart' as uri_contributor_test;
 import 'variable_name_contributor_test.dart' as variable_name_contributor_test;
@@ -44,6 +45,7 @@
     override_contributor_test.main();
     relevance_tests.main();
     static_contributor_test.main();
+    super_formal_contributor.main();
     type_member_contributor_test.main();
     uri_contributor_test.main();
     variable_name_contributor_test.main();
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 5ac9fe5..d666d5c 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -5511,12 +5511,9 @@
           return superParameters
               .firstWhereOrNull((e) => e.isNamed && e.name == name);
         } else {
+          var index = indexIn(enclosingElement);
           var positionalSuperParameters =
               superParameters.where((e) => e.isPositional).toList();
-          var index = enclosingElement.parameters
-              .whereType<SuperFormalParameterElementImpl>()
-              .toList()
-              .indexOf(this);
           if (index >= 0 && index < positionalSuperParameters.length) {
             return positionalSuperParameters[index];
           }
@@ -5529,6 +5526,14 @@
   @override
   T? accept<T>(ElementVisitor<T> visitor) =>
       visitor.visitSuperFormalParameterElement(this);
+
+  /// Return the index of this super-formal parameter among other super-formals.
+  int indexIn(ConstructorElementImpl enclosingElement) {
+    return enclosingElement.parameters
+        .whereType<SuperFormalParameterElementImpl>()
+        .toList()
+        .indexOf(this);
+  }
 }
 
 /// A concrete implementation of a [TopLevelVariableElement].
diff --git a/pkg/analyzer_plugin/lib/utilities/completion/relevance.dart b/pkg/analyzer_plugin/lib/utilities/completion/relevance.dart
index 96f4683..1209c3f 100644
--- a/pkg/analyzer_plugin/lib/utilities/completion/relevance.dart
+++ b/pkg/analyzer_plugin/lib/utilities/completion/relevance.dart
@@ -54,6 +54,10 @@
   /// The relevance used when suggesting a named argument corresponding to a
   /// named parameter that is required.
   static const int requiredNamedArgument = 950;
+
+  /// The relevance used when suggesting a super-constructor parameter as
+  /// a super formal parameter.
+  static const int superFormalParameter = 1000;
 }
 
 /// A name scope for constants that are related to the relevance of completion