Add completion support for the new mixin syntax

Change-Id: I3a8744e03f32092c15f1e340e0ee6bef5245a859
Reviewed-on: https://dart-review.googlesource.com/72520
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Dan Rubel <danrubel@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 8079bf0..1db3c05 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
@@ -188,11 +188,20 @@
       if (previousMember.leftBracket == null ||
           previousMember.leftBracket.isSynthetic) {
         // If the prior member is an unfinished class declaration
-        // then the user is probably finishing that
+        // then the user is probably finishing that.
         _addClassDeclarationKeywords(previousMember);
         return;
       }
     }
+    if (previousMember is MixinDeclaration) {
+      if (previousMember.leftBracket == null ||
+          previousMember.leftBracket.isSynthetic) {
+        // If the prior member is an unfinished mixin declaration
+        // then the user is probably finishing that.
+        _addMixinDeclarationKeywords(previousMember);
+        return;
+      }
+    }
     if (previousMember is ImportDirective) {
       if (previousMember.semicolon == null ||
           previousMember.semicolon.isSynthetic) {
@@ -464,6 +473,28 @@
   }
 
   @override
+  visitMixinDeclaration(MixinDeclaration node) {
+    // Don't suggest mixin name
+    if (entity == node.name) {
+      return;
+    }
+    if (entity == node.rightBracket) {
+      _addClassBodyKeywords();
+    } else if (entity is ClassMember) {
+      _addClassBodyKeywords();
+      int index = node.members.indexOf(entity);
+      ClassMember previous = index > 0 ? node.members[index - 1] : null;
+      if (previous is MethodDeclaration && isEmptyBody(previous.body)) {
+        _addSuggestion(Keyword.ASYNC);
+        _addSuggestion2(ASYNC_STAR);
+        _addSuggestion2(SYNC_STAR);
+      }
+    } else {
+      _addMixinDeclarationKeywords(node);
+    }
+  }
+
+  @override
   visitNamedExpression(NamedExpression node) {
     if (entity is SimpleIdentifier && entity == node.expression) {
       _addExpressionKeywords(node);
@@ -644,6 +675,17 @@
     }
   }
 
+  void _addMixinDeclarationKeywords(MixinDeclaration node) {
+    // Very simplistic suggestion because analyzer will warn if
+    // the on / implements clauses are out of order
+    if (node.onClause == null) {
+      _addSuggestion(Keyword.ON, DART_RELEVANCE_HIGH);
+    }
+    if (node.implementsClause == null) {
+      _addSuggestion(Keyword.IMPLEMENTS, DART_RELEVANCE_HIGH);
+    }
+  }
+
   void _addStatementKeywords(AstNode node) {
     if (_inClassMemberBody(node)) {
       _addSuggestions([Keyword.SUPER, Keyword.THIS]);
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 8568c03..f2e2ba2 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
@@ -1668,6 +1668,19 @@
     assertSuggestKeywords(EXPRESSION_START_NO_INSTANCE);
   }
 
+  test_mixin() async {
+    addTestSource('mixin M o^ { }');
+    await computeSuggestions();
+    assertSuggestKeywords([Keyword.ON, Keyword.IMPLEMENTS],
+        relevance: DART_RELEVANCE_HIGH);
+  }
+
+  test_mixin_afterOnClause() async {
+    addTestSource('mixin M on A i^ { } class A {}');
+    await computeSuggestions();
+    assertSuggestKeywords([Keyword.IMPLEMENTS], relevance: DART_RELEVANCE_HIGH);
+  }
+
   test_named_constructor_invocation() async {
     addTestSource('void main() {new Future.^}');
     await computeSuggestions();