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