Support completion in for loops with patterns
Change-Id: I9d5c57e4e9c42abfbdc2a7dd85fc998826fa8342
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282390
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart
index 5af44c5..0b49665 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart
@@ -394,6 +394,8 @@
} else {
_addSuggestion(Keyword.IN);
}
+ } else if (!node.inKeyword.isSynthetic && node.iterable.isSynthetic) {
+ _addSuggestion(Keyword.AWAIT);
}
}
@@ -504,7 +506,25 @@
// Actual: for (va^)
// Parsed: for (va^; ;)
if (node.forLoopParts == entity) {
- _addSuggestion(Keyword.VAR);
+ _addSuggestions([Keyword.FINAL, Keyword.VAR]);
+ } else if (node.rightParenthesis == entity) {
+ var parts = node.forLoopParts;
+ if (parts is ForPartsWithDeclarations) {
+ var variables = parts.variables;
+ var keyword = variables.keyword;
+ if (variables.variables.length == 1 &&
+ variables.variables[0].name.isSynthetic &&
+ keyword != null &&
+ parts.leftSeparator.isSynthetic) {
+ var afterKeyword = keyword.next!;
+ if (afterKeyword.type == TokenType.OPEN_PAREN) {
+ var endGroup = afterKeyword.endGroup;
+ if (endGroup != null && request.offset >= endGroup.end) {
+ _addSuggestion(Keyword.IN);
+ }
+ }
+ }
+ }
}
}
@@ -935,12 +955,16 @@
void visitVariableDeclarationList(VariableDeclarationList node) {
var keyword = node.keyword;
var variables = node.variables;
- if (variables.isNotEmpty &&
- entity == variables[0] &&
- node.type == null &&
- (keyword == null || keyword.lexeme != 'var')) {
- _addSuggestion(Keyword.DYNAMIC);
- _addSuggestion(Keyword.VOID);
+ if (variables.isNotEmpty && entity == variables[0]) {
+ var type = node.type;
+ if (type == null && keyword?.keyword != Keyword.VAR) {
+ _addSuggestion(Keyword.DYNAMIC);
+ _addSuggestion(Keyword.VOID);
+ } else if (type is RecordTypeAnnotation) {
+ // This might be a record pattern that happens to look like a type, in
+ // which case the user might be typing `in`.
+ _addSuggestion(Keyword.IN);
+ }
}
}
diff --git a/pkg/analysis_server/test/services/completion/dart/keyword_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/keyword_contributor_test.dart
index 36107e8..76cab69 100644
--- a/pkg/analysis_server/test/services/completion/dart/keyword_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/keyword_contributor_test.dart
@@ -1177,12 +1177,6 @@
assertSuggestKeywords(EXPRESSION_START_NO_INSTANCE);
}
- Future<void> test_for_initialization_var() async {
- addTestSource('void f() {for (^)}');
- await computeSuggestions();
- assertSuggestKeywords([Keyword.VAR]);
- }
-
Future<void> test_formalParameterList_beforeFunctionType() async {
addTestSource('void f(^void Function() g) {}');
await computeSuggestions();
diff --git a/pkg/analysis_server/test/services/completion/dart/location/for_statement_test.dart b/pkg/analysis_server/test/services/completion/dart/location/for_statement_test.dart
new file mode 100644
index 0000000..dbc1091
--- /dev/null
+++ b/pkg/analysis_server/test/services/completion/dart/location/for_statement_test.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2023, 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:test_reflective_loader/test_reflective_loader.dart';
+
+import '../../../../client/completion_driver_test.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(ForStatementTest1);
+ defineReflectiveTests(ForStatementTest2);
+ });
+}
+
+/// Tests specific to a for-each statement.
+mixin ForEachPartsTestCases on AbstractCompletionDriverTest {
+ Future<void> test_afterIn() async {
+ await computeSuggestions('''
+void f(List<(int, int)> x01) {
+ for (final (a, b) in ^)
+}
+''');
+ assertResponse('''
+suggestions
+ x01
+ kind: parameter
+ await
+ kind: keyword
+''');
+ }
+
+ Future<void> test_afterPattern() async {
+ await computeSuggestions('''
+void f(Object x) {
+ for (final (a, b) ^)
+}
+''');
+ assertResponse('''
+suggestions
+ in
+ kind: keyword
+''');
+ }
+
+ Future<void> test_afterPattern_partial() async {
+ await computeSuggestions('''
+void f(List<(int, int)> rl) {
+ for (final (a, b) i^)
+}
+''');
+ assertResponse('''
+replacement
+ left: 1
+suggestions
+ in
+ kind: keyword
+''');
+ }
+}
+
+/// Tests specific to a traditional for statement.
+mixin ForPartsTestCases on AbstractCompletionDriverTest {}
+
+@reflectiveTest
+class ForStatementTest1 extends AbstractCompletionDriverTest
+ with ForEachPartsTestCases, ForPartsTestCases, ForStatementTestCases {
+ @override
+ TestingCompletionProtocol get protocol => TestingCompletionProtocol.version1;
+}
+
+@reflectiveTest
+class ForStatementTest2 extends AbstractCompletionDriverTest
+ with ForEachPartsTestCases, ForPartsTestCases, ForStatementTestCases {
+ @override
+ TestingCompletionProtocol get protocol => TestingCompletionProtocol.version2;
+}
+
+/// Tests that apply to both traditional for statements and for-each statements.
+mixin ForStatementTestCases on AbstractCompletionDriverTest {
+ Future<void> test_forParts_empty() async {
+ await computeSuggestions('''
+void f(Object x) {
+ for (^)
+}
+''');
+ assertResponse('''
+suggestions
+ var
+ kind: keyword
+ final
+ kind: keyword
+''');
+ }
+
+ Future<void> test_statements_empty() async {
+ await computeSuggestions('''
+void f(Object x) {
+ for (^)
+}
+''');
+ assertResponse('''
+suggestions
+ var
+ kind: keyword
+ final
+ kind: keyword
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/services/completion/dart/location/test_all.dart b/pkg/analysis_server/test/services/completion/dart/location/test_all.dart
index dc4ec61..2ad6556 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/test_all.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/test_all.dart
@@ -13,6 +13,7 @@
import 'enum_constant_test.dart' as enum_constant;
import 'enum_test.dart' as enum_;
import 'field_formal_parameter_test.dart' as field_formal_parameter;
+import 'for_statement_test.dart' as for_statement;
import 'if_element_test.dart' as if_element;
import 'if_statement_test.dart' as if_statement;
import 'list_pattern_test.dart' as list_pattern;
@@ -41,6 +42,7 @@
enum_constant.main();
enum_.main();
field_formal_parameter.main();
+ for_statement.main();
if_element.main();
if_statement.main();
list_pattern.main();
diff --git a/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart b/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart
index 1f40b42..675bd62 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart
@@ -723,6 +723,17 @@
}
@override
+ void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
+ if (identical(entity, node.iterable)) {
+ optype.completionLocation = 'visitForEachPartsWithPattern_iterable';
+ optype.includeReturnValueSuggestions = true;
+ optype.includeTypeNameSuggestions = true;
+ } else {
+ visitForEachParts(node);
+ }
+ }
+
+ @override
void visitForElement(ForElement node) {
// for (^) {}
// for (Str^ str = null;) {}