Skip to content

Commit 9c77805

Browse files
srawlinsCommit Bot
authored andcommitted
Suggest 'else' in an if-element
Bug: #36733 and #48837 Change-Id: I4c846426c17fa7ebc5dd544b24f1667945a9f1a7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241214 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent be1caec commit 9c77805

File tree

2 files changed

+167
-22
lines changed

2 files changed

+167
-22
lines changed

pkg/analysis_server/lib/src/services/completion/dart/keyword_contributor.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ class _KeywordVisitor extends GeneralizingAstVisitor<void> {
598598
@override
599599
void visitListLiteral(ListLiteral node) {
600600
_addCollectionElementKeywords();
601+
_addElseElementKeyword(node.elements);
601602
super.visitListLiteral(node);
602603
}
603604

@@ -717,6 +718,7 @@ class _KeywordVisitor extends GeneralizingAstVisitor<void> {
717718
@override
718719
void visitSetOrMapLiteral(SetOrMapLiteral node) {
719720
_addCollectionElementKeywords();
721+
_addElseElementKeyword(node.elements);
720722
super.visitSetOrMapLiteral(node);
721723
}
722724

@@ -897,6 +899,20 @@ class _KeywordVisitor extends GeneralizingAstVisitor<void> {
897899
}
898900
}
899901

902+
void _addElseElementKeyword(NodeList<CollectionElement> elements) {
903+
final entity = this.entity;
904+
var token = entity is AstNode ? entity.beginToken : entity as Token;
905+
// Walk through the elements, looking for the element which precedes the
906+
// cursor, backwards with the notion that a user is more likely to be typing
907+
// at the end of the collection than at the beginning.
908+
for (var i = elements.length - 1; i >= 0; i--) {
909+
if (_isTokenAfterIfElementWithoutElse(token, elements[i])) {
910+
_addSuggestions([Keyword.ELSE]);
911+
break;
912+
}
913+
}
914+
}
915+
900916
void _addEnumBodyKeywords() {
901917
_addSuggestions([
902918
Keyword.CONST,
@@ -1063,6 +1079,35 @@ class _KeywordVisitor extends GeneralizingAstVisitor<void> {
10631079
return false;
10641080
}
10651081

1082+
/// Returns whether [token] follows an 'if element' which does not have an
1083+
/// 'else element', either at [element] or at a descendant of [element].
1084+
bool _isTokenAfterIfElementWithoutElse(
1085+
Token token, CollectionElement element) {
1086+
if (element is IfElement) {
1087+
var tokenAfterIf = element.endToken.next!;
1088+
// The parser recovers an identifier (non-'else') after an if-element
1089+
// by inserting a synthetic comma. `[if (true) 1 e]` becomes
1090+
// `[if (true) 1, e]`.
1091+
if (element.elseElement == null &&
1092+
(tokenAfterIf == token ||
1093+
(tokenAfterIf.isSynthetic && tokenAfterIf.next == token))) {
1094+
return true;
1095+
} else {
1096+
if (_isTokenAfterIfElementWithoutElse(token, element.thenElement)) {
1097+
return true;
1098+
}
1099+
if (element.elseElement != null) {
1100+
if (_isTokenAfterIfElementWithoutElse(token, element.elseElement!)) {
1101+
return true;
1102+
}
1103+
}
1104+
}
1105+
} else if (element is ForElement) {
1106+
return _isTokenAfterIfElementWithoutElse(token, element.body);
1107+
}
1108+
return false;
1109+
}
1110+
10661111
static bool _isPreviousTokenSynthetic(Object? entity, TokenType type) {
10671112
if (entity is AstNode) {
10681113
var token = entity.beginToken;

pkg/analysis_server/test/services/completion/dart/keyword_contributor_test.dart

Lines changed: 122 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,11 +1546,65 @@ class A {
15461546
assertSuggestKeywords(statementStartOutsideClass);
15471547
}
15481548

1549-
@failingTest
1550-
Future<void> test_ifElement_noElse_last() async {
1549+
Future<void> test_ifElement_list_hasElse_notLast() async {
15511550
addTestSource('''
1552-
void f() {
1553-
[if (true) 1 ^];
1551+
void f(int i) {
1552+
[if (true) 1 else 2 e^, i, i];
1553+
}
1554+
''');
1555+
await computeSuggestions();
1556+
assertSuggestKeywords([
1557+
...COLLECTION_ELEMENT_START,
1558+
...EXPRESSION_START_NO_INSTANCE,
1559+
]);
1560+
}
1561+
1562+
Future<void> test_ifElement_list_noElse_insideForElement() async {
1563+
addTestSource('''
1564+
void f(int i) {
1565+
[for (var e in []) if (true) i ^];
1566+
}
1567+
''');
1568+
await computeSuggestions();
1569+
assertSuggestKeywords([
1570+
...COLLECTION_ELEMENT_START,
1571+
...EXPRESSION_START_NO_INSTANCE,
1572+
Keyword.ELSE
1573+
]);
1574+
}
1575+
1576+
Future<void> test_ifElement_list_noElse_insideIfElement_else() async {
1577+
addTestSource('''
1578+
void f(int i) {
1579+
[if (false) i else if (true) i ^];
1580+
}
1581+
''');
1582+
await computeSuggestions();
1583+
assertSuggestKeywords([
1584+
...COLLECTION_ELEMENT_START,
1585+
...EXPRESSION_START_NO_INSTANCE,
1586+
Keyword.ELSE
1587+
]);
1588+
}
1589+
1590+
Future<void> test_ifElement_list_noElse_insideIfElement_then() async {
1591+
addTestSource('''
1592+
void f(int i) {
1593+
[if (false) if (true) i ^];
1594+
}
1595+
''');
1596+
await computeSuggestions();
1597+
assertSuggestKeywords([
1598+
...COLLECTION_ELEMENT_START,
1599+
...EXPRESSION_START_NO_INSTANCE,
1600+
Keyword.ELSE
1601+
]);
1602+
}
1603+
1604+
Future<void> test_ifElement_list_noElse_last() async {
1605+
addTestSource('''
1606+
void f(int i) {
1607+
[if (true) i ^];
15541608
}
15551609
''');
15561610
await computeSuggestions();
@@ -1561,7 +1615,7 @@ void f() {
15611615
]);
15621616
}
15631617

1564-
Future<void> test_ifElement_noElse_notInElement() async {
1618+
Future<void> test_ifElement_list_noElse_notInElement() async {
15651619
addTestSource('''
15661620
void f() {
15671621
[if (true) 1, ^];
@@ -1572,11 +1626,17 @@ void f() {
15721626
[...COLLECTION_ELEMENT_START, ...EXPRESSION_START_NO_INSTANCE]);
15731627
}
15741628

1575-
@failingTest
1576-
Future<void> test_ifElement_noElse_notLast() async {
1629+
@FailingTest(
1630+
issue: 'https://github.com/dart-lang/sdk/issues/48837',
1631+
reason:
1632+
'The CompletionTarget for this test is determined to be "j", which '
1633+
'prevents us from suggesting "else". This CompletionTarget bug seems '
1634+
'to stem from the current state of `ListLiteralImpl.childEntities` '
1635+
'not including comma tokens.')
1636+
Future<void> test_ifElement_list_noElse_notLast() async {
15771637
addTestSource('''
1578-
void f(int i) {
1579-
[if (true) 1 ^, i];
1638+
void f(int i, int j) {
1639+
[if (true) i ^, j];
15801640
}
15811641
''');
15821642
await computeSuggestions();
@@ -1587,8 +1647,7 @@ void f(int i) {
15871647
]);
15881648
}
15891649

1590-
@failingTest
1591-
Future<void> test_ifElement_partialElse_last() async {
1650+
Future<void> test_ifElement_list_partialElse_last() async {
15921651
addTestSource('''
15931652
void f() {
15941653
[if (true) 1 e^];
@@ -1602,8 +1661,7 @@ void f() {
16021661
]);
16031662
}
16041663

1605-
@failingTest
1606-
Future<void> test_ifElement_partialElse_notLast() async {
1664+
Future<void> test_ifElement_list_partialElse_notLast() async {
16071665
addTestSource('''
16081666
void f(int i) {
16091667
[if (true) 1 e^, i];
@@ -1617,28 +1675,46 @@ void f(int i) {
16171675
]);
16181676
}
16191677

1620-
Future<void> test_ifOrForElement_forElement() async {
1678+
Future<void> test_ifElement_list_partialElse_thenIsForElement() async {
16211679
addTestSource('''
1622-
f() => [for (var e in c) ^];
1680+
void f(int i) {
1681+
[if (b) for (var e in c) e e^];
1682+
}
16231683
''');
16241684
await computeSuggestions();
1625-
assertSuggestKeywords(COLLECTION_ELEMENT_START);
1685+
assertSuggestKeywords([
1686+
...COLLECTION_ELEMENT_START,
1687+
...EXPRESSION_START_NO_INSTANCE,
1688+
Keyword.ELSE
1689+
]);
16261690
}
16271691

1628-
Future<void> test_ifOrForElement_ifElement_else() async {
1692+
Future<void> test_ifElement_map_partialElse_notLast() async {
16291693
addTestSource('''
1630-
f() => [if (true) 1 else ^];
1694+
void f(int i) {
1695+
<int, int>{if (true) 1: 1 e^, 2: i};
1696+
}
16311697
''');
16321698
await computeSuggestions();
1633-
assertSuggestKeywords(COLLECTION_ELEMENT_START);
1699+
assertSuggestKeywords([
1700+
...COLLECTION_ELEMENT_START,
1701+
...EXPRESSION_START_NO_INSTANCE,
1702+
Keyword.ELSE
1703+
]);
16341704
}
16351705

1636-
Future<void> test_ifOrForElement_ifElement_then() async {
1706+
Future<void> test_ifElement_set_partialElse_notLast() async {
16371707
addTestSource('''
1638-
f() => [if (true) ^];
1708+
void f(int i) {
1709+
<int>{if (true) 1 e^, i};
1710+
}
16391711
''');
16401712
await computeSuggestions();
1641-
assertSuggestKeywords(COLLECTION_ELEMENT_START);
1713+
assertSuggestKeywords([
1714+
...COLLECTION_ELEMENT_START,
1715+
...EXPRESSION_START_NO_INSTANCE,
1716+
Keyword.ELSE
1717+
]);
16421718
}
16431719

16441720
Future<void> test_ifOrForElement_list_empty() async {
@@ -1657,6 +1733,30 @@ f() => [^1, 2];
16571733
assertSuggestKeywords(COLLECTION_ELEMENT_START);
16581734
}
16591735

1736+
Future<void> test_ifOrForElement_list_forElement() async {
1737+
addTestSource('''
1738+
f() => [for (var e in c) ^];
1739+
''');
1740+
await computeSuggestions();
1741+
assertSuggestKeywords(COLLECTION_ELEMENT_START);
1742+
}
1743+
1744+
Future<void> test_ifOrForElement_list_ifElement_else() async {
1745+
addTestSource('''
1746+
f() => [if (true) 1 else ^];
1747+
''');
1748+
await computeSuggestions();
1749+
assertSuggestKeywords(COLLECTION_ELEMENT_START);
1750+
}
1751+
1752+
Future<void> test_ifOrForElement_list_ifElement_then() async {
1753+
addTestSource('''
1754+
f() => [if (true) ^];
1755+
''');
1756+
await computeSuggestions();
1757+
assertSuggestKeywords(COLLECTION_ELEMENT_START);
1758+
}
1759+
16601760
Future<void> test_ifOrForElement_list_last() async {
16611761
addTestSource('''
16621762
f() => [1, 2, ^];

0 commit comments

Comments
 (0)