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>
This commit is contained in:
Konstantin Shcheglov 2022-01-06 19:46:15 +00:00 committed by Commit Bot
parent 9bfb74e681
commit 794a42074d
10 changed files with 746 additions and 170 deletions

View file

@ -28,6 +28,7 @@ import 'package:analysis_server/src/services/completion/dart/redirecting_contrib
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 @@ class DartCompletionManager {
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),

View file

@ -877,6 +877,17 @@ class SuggestionBuilder {
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.

View file

@ -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);
}
}
}
}

View file

@ -55,7 +55,7 @@ main() {
..docSummary.isEqualTo('aaa')
..hasSelection(offset: 5)
..element.isNotNull.which((e) => e
..isParameter
..kind.isParameter
..name.isEqualTo('fff'))
]);
}
@ -79,7 +79,7 @@ main() {
..docSummary.isNull
..hasSelection(offset: 5)
..element.isNotNull.which((e) => e
..isParameter
..kind.isParameter
..name.isEqualTo('fff'))
]);
}

View file

@ -55,6 +55,20 @@ class CompletionSuggestionForTesting {
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 @@ extension CompletionResponseExtension
(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 @@ extension CompletionSuggestionExtension
);
}
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 @@ extension CompletionSuggestionExtension
);
}
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 CompletionSuggestionExtension
}
}
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 CompletionSuggestionsExtension
}
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 ElementExtension on CheckTarget<Element> {
);
}
}
extension ElementKindExtension on CheckTarget<ElementKind> {
void get isField {
isEqualTo(ElementKind.FIELD);
}
void get isParameter {
isEqualTo(ElementKind.PARAMETER);
}
}

View file

@ -6,9 +6,10 @@ import 'package:analysis_server/src/provisional/completion/dart/completion_dart.
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 @@ class FieldFormalContributorTest extends DartCompletionContributorTest {
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'),
]);
}
}

View file

@ -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'),
]);
}
}

View file

@ -21,6 +21,7 @@ import 'named_constructor_contributor_test.dart' as named_contributor_test;
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 @@ void main() {
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();

View file

@ -5511,12 +5511,9 @@ class SuperFormalParameterElementImpl extends ParameterElementImpl
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 @@ class SuperFormalParameterElementImpl extends ParameterElementImpl
@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].

View file

@ -54,6 +54,10 @@ abstract class Relevance {
/// 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