[analyzer] Suggest named args between partially-typed names and values
Fixes https://github.com/dart-lang/sdk/issues/35414.
Change-Id: I847aa87c56de248e8e704790c163de4f83813bbf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/189081
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart
index 0741ff5..271b7fb 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart
@@ -62,9 +62,15 @@
{int replacementLength}) {
var name = parameter.name;
+ // Check whether anything after the caret is being replaced. If so, we will
+ // suppress inserting colons/commas. We check only replacing _after_ the
+ // caret as some replacements (before) will still want colons, for example:
+ // foo(mySt^'bar');
+ var replacementEnd = request.replacementRange.offset +
+ (replacementLength ?? request.replacementRange.length);
var willReplace =
request.completionPreference == CompletionPreference.replace &&
- (replacementLength ?? request.replacementRange.length) > 0;
+ replacementEnd > request.offset;
if (name != null && name.isNotEmpty && !namedArgs.contains(name)) {
builder.suggestNamedArgument(parameter,
@@ -154,8 +160,27 @@
bool _isAddingLabelToPositional() {
if (argumentList != null) {
var entity = request.target.entity;
- if (entity is! NamedExpression && request.offset <= entity.offset) {
- return true;
+ if (entity is! NamedExpression) {
+ // Caret is in front of a value
+ // f(one: 1, ^2);
+ if (request.offset <= entity.offset) {
+ return true;
+ }
+
+ // Caret is in the between two values that are not seperated by a comma.
+ // f(one: 1, tw^'foo');
+ // must be at least two and the target not last.
+ var args = argumentList.arguments;
+ if (args.length >= 2 && entity != args.last) {
+ var index = args.indexOf(entity);
+ if (index != -1) {
+ var next = args[index + 1];
+ // Check the two tokens are adjacent without any comma.
+ if (entity.end == next.offset) {
+ return true;
+ }
+ }
+ }
}
}
return false;
diff --git a/pkg/analysis_server/test/domain_completion_test.dart b/pkg/analysis_server/test/domain_completion_test.dart
index 95ab896..bfb2c73 100644
--- a/pkg/analysis_server/test/domain_completion_test.dart
+++ b/pkg/analysis_server/test/domain_completion_test.dart
@@ -102,6 +102,24 @@
expect(replacementLength, equals(0));
}
+ Future<void> test_ArgumentList_function_named_partiallyTyped() async {
+ addTestFile('''
+ class C {
+ void m(String firstString, {String secondString}) {}
+
+ void n() {
+ m('a', se^'b');
+ }
+ }
+ ''');
+ await getSuggestions();
+ assertHasResult(CompletionSuggestionKind.NAMED_ARGUMENT, 'secondString: ');
+ expect(suggestions, hasLength(1));
+ // Ensure we replace the correct section.
+ expect(replacementOffset, equals(completionOffset - 2));
+ expect(replacementLength, equals(2));
+ }
+
Future<void> test_ArgumentList_imported_function_named_param() async {
addTestFile('main() { int.parse("16", ^);}');
await getSuggestions();
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 78e5d60..4ad9170 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -708,19 +708,19 @@
String expectedInsert,
}) async {
final content = '''
-class A { const A({int argOne, int argTwo}); }
+class A { const A({int argOne, int argTwo, String argThree}); }
final varOne = '';
$code
main() { }
''';
final expectedReplaced = '''
-class A { const A({int argOne, int argTwo}); }
+class A { const A({int argOne, int argTwo, String argThree}); }
final varOne = '';
$expectedReplace
main() { }
''';
final expectedInserted = '''
-class A { const A({int argOne, int argTwo}); }
+class A { const A({int argOne, int argTwo, String argThree}); }
final varOne = '';
$expectedInsert
main() { }
@@ -779,6 +779,15 @@
expectedReplace: '@A(argTwo: ^, argOne: 1)',
expectedInsert: '@A(argTwo: ^, argOne: 1)',
);
+
+ // Partially typed names in front of values (that aren't considered part of
+ // the same identifier) should also suggest name labels.
+ await check(
+ '''@A(argOne: 1, argTh^'Foo')''',
+ 'argThree: ',
+ expectedReplace: '''@A(argOne: 1, argThree: 'Foo')''',
+ expectedInsert: '''@A(argOne: 1, argThree: 'Foo')''',
+ );
}
Future<void> test_namedArg_offsetBeforeCompletionTarget() async {