Move more keyword completions

Change-Id: Ibdd1175731317acc91bff3e08ac57f706775bf56
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/326540
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart
index 25870c3..226a4cc 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart
@@ -248,12 +248,15 @@
 
   @override
   void visitCompilationUnit(CompilationUnit node) {
-    var followingMember = node.memberAfter(offset);
-    if (_forIncompletePreceedingUnitMember(node, followingMember)) {
-      // The preceeding member is incomplete, so assume that the user is
-      // completing it rather than starting a new member.
+    // This method is only invoked when the cursor is between two members.
+    var surroundingMembers = node.membersBeforeAndAfterOffset(offset);
+    var before = surroundingMembers.before;
+    if (before != null && _handledIncompletePrecedingUnitMember(node, before)) {
+      // The  member is incomplete, so assume that the user is completing it
+      //rather than starting a new member.
       return;
     }
+    _forCompilationUnitMember(node, surroundingMembers);
   }
 
   @override
@@ -415,10 +418,10 @@
   @override
   void visitExpressionStatement(ExpressionStatement node) {
     collector.completionLocation = 'ExpressionStatement_expression';
-    if (_forIncompletePreceedingStatement(node)) {
+    if (_forIncompletePrecedingStatement(node)) {
       if (node.isSingleIdentifier) {
-        var preceedingStatement = node.preceedingStatement;
-        if (preceedingStatement is TryStatement) {
+        var precedingStatement = node.precedingStatement;
+        if (precedingStatement is TryStatement) {
           return;
         }
       }
@@ -474,18 +477,29 @@
 
   @override
   void visitFieldDeclaration(FieldDeclaration node) {
-    _forIncompletePreceedingClassMember(node);
+    _forIncompletePrecedingClassMember(node);
     var fields = node.fields;
     var type = fields.type;
     if (type == null) {
-      var firstField = fields.variables.firstOrNull;
-      if (firstField != null && offset <= firstField.name.end) {
-        keywordHelper.addFieldDeclarationKeywords(node);
+      var variables = fields.variables;
+      var firstField = variables.firstOrNull;
+      if (firstField != null) {
+        var name = firstField.name;
+        if (variables.length == 1 && name.isKeyword && offset > name.end) {
+          // The parser has recovered by using one of the existing keywords as
+          // the name of a field, which means that there is no type.
+          keywordHelper.addFieldDeclarationKeywords(node,
+              keyword: name.keyword);
+        } else if (offset <= name.end) {
+          keywordHelper.addFieldDeclarationKeywords(node);
+        }
       }
     } else {
       if (offset <= type.end) {
         keywordHelper.addFieldDeclarationKeywords(node);
         keywordHelper.addKeyword(Keyword.DYNAMIC);
+        // TODO(brianwilkerson) `var` should only be suggested if neither
+        //  `static` nor `final` are present.
         keywordHelper.addKeyword(Keyword.VAR);
         keywordHelper.addKeyword(Keyword.VOID);
       }
@@ -493,6 +507,21 @@
   }
 
   @override
+  void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
+    _visitForEachParts(node);
+  }
+
+  @override
+  void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
+    _visitForEachParts(node);
+  }
+
+  @override
+  void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
+    _visitForEachParts(node);
+  }
+
+  @override
   void visitForElement(ForElement node) {
     var literal = node.thisOrAncestorOfType<TypedLiteral>();
     if (literal is ListLiteral) {
@@ -513,9 +542,52 @@
   }
 
   @override
+  void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
+    if (offset >= node.leftSeparator.end &&
+        offset <= node.rightSeparator.offset) {
+      var condition = node.condition;
+      if (condition is SimpleIdentifier &&
+          node.leftSeparator.isSynthetic &&
+          node.rightSeparator.isSynthetic) {
+        // Handle the degenerate case while typing `for (int x i^)`.
+        // Actual: for (int x i^)
+        // Parsed: for (int x; i^;)
+        keywordHelper.addKeyword(Keyword.IN);
+        return;
+      }
+    }
+  }
+
+  @override
   void visitForStatement(ForStatement node) {
     if (offset <= node.forKeyword.end) {
       _forStatement(node);
+    } else if (offset >= node.leftParenthesis.end &&
+        offset <= node.rightParenthesis.offset) {
+      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 && offset >= endGroup.end) {
+              // Actual: for (va^)
+              // Parsed: for (va^; ;)
+              keywordHelper.addKeyword(Keyword.IN);
+            }
+          }
+        }
+      } else if (parts is ForPartsWithExpression &&
+          parts.leftSeparator.isSynthetic &&
+          parts.initialization is SimpleIdentifier) {
+        keywordHelper.addKeyword(Keyword.FINAL);
+        keywordHelper.addKeyword(Keyword.VAR);
+      }
     }
   }
 
@@ -559,8 +631,17 @@
   }
 
   @override
+  void visitFunctionTypeAlias(FunctionTypeAlias node) {
+    if (node.typedefKeyword.coversOffset(offset)) {
+      keywordHelper.addKeyword(Keyword.TYPEDEF);
+    }
+  }
+
+  @override
   void visitGenericTypeAlias(GenericTypeAlias node) {
-    if (offset >= node.equals.end && offset <= node.semicolon.offset) {
+    if (node.typedefKeyword.coversOffset(offset)) {
+      keywordHelper.addKeyword(Keyword.TYPEDEF);
+    } else if (offset >= node.equals.end && offset <= node.semicolon.offset) {
       keywordHelper.addKeyword(Keyword.DYNAMIC);
       keywordHelper.addKeyword(Keyword.VOID);
     }
@@ -676,6 +757,20 @@
   }
 
   @override
+  void visitLibraryDirective(LibraryDirective node) {
+    if (offset >= node.end) {
+      var unit = node.parent;
+      if (unit is CompilationUnit) {
+        _forDirective(unit, node);
+        var (before: _, :after) = unit.membersBeforeAndAfterMember(node);
+        if (after is CompilationUnitMember?) {
+          _forCompilationUnitDeclaration();
+        }
+      }
+    }
+  }
+
+  @override
   void visitListLiteral(ListLiteral node) {
     final offset = this.offset;
     if (offset >= node.leftBracket.end && offset <= node.rightBracket.offset) {
@@ -905,6 +1000,16 @@
   }
 
   @override
+  void visitRelationalPattern(RelationalPattern node) {
+    var operand = node.operand;
+    if (operand is SimpleIdentifier &&
+        offset >= node.operator.end &&
+        offset <= operand.end) {
+      keywordHelper.addExpressionKeywords(node);
+    }
+  }
+
+  @override
   void visitRestPatternElement(RestPatternElement node) {
     collector.completionLocation = 'RestPatternElement_pattern';
     _forPattern();
@@ -930,6 +1035,20 @@
   }
 
   @override
+  void visitSimpleFormalParameter(SimpleFormalParameter node) {
+    var type = node.type;
+    if (type != null) {
+      if (type.beginToken.coversOffset(offset)) {
+        _forTypeAnnotation();
+      } else if (type is GenericFunctionType &&
+          offset < type.functionKeyword.offset &&
+          type.returnType == null) {
+        _forTypeAnnotation();
+      }
+    }
+  }
+
+  @override
   void visitSimpleStringLiteral(SimpleStringLiteral node) {
     _visitParentIfAtOrBeforeNode(node);
   }
@@ -1052,15 +1171,7 @@
 
   @override
   void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
-    var unit = node.parent;
-    if (unit is CompilationUnit &&
-        _forIncompletePreceedingUnitMember(unit, node)) {
-      return;
-    } else if (node.isSingleIdentifier) {
-      // The parser recovers from a simple identifier by assuming that it's a
-      // variable declaration. But a simple identifier could be the start of
-      // any kind of member, so defer to the compilation unit.
-      node.parent?.accept(this);
+    if (_handledRecovery(node)) {
       return;
     }
 
@@ -1128,9 +1239,9 @@
     var grandparent = parent.parent;
     if (grandparent is FieldDeclaration) {
       // The order of these conditions is critical. We need to check for an
-      // incomplete preceeding member even when the grandparent isn't a single
+      // incomplete preceding member even when the grandparent isn't a single
       // identifier, but want to return only if both conditions are true.
-      if (_forIncompletePreceedingClassMember(grandparent) &&
+      if (_forIncompletePrecedingClassMember(grandparent) &&
           grandparent.isSingleIdentifier) {
         return;
       }
@@ -1141,13 +1252,7 @@
         keywordHelper.addKeyword(Keyword.IN);
       }
     } else if (grandparent is TopLevelVariableDeclaration) {
-      // The order of these conditions is critical. We need to check for an
-      // incomplete preceeding member even when the grandparent isn't a single
-      // identifier, but want to return only if both conditions are true.
-      var unit = grandparent.parent;
-      if (unit is CompilationUnit &&
-          _forIncompletePreceedingUnitMember(unit, grandparent) &&
-          grandparent.isSingleIdentifier) {
+      if (_handledRecovery(grandparent)) {
         return;
       }
     }
@@ -1232,7 +1337,7 @@
 
   @override
   void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
-    _forIncompletePreceedingStatement(node);
+    _forIncompletePrecedingStatement(node);
     if (offset <= node.beginToken.end) {
       _forStatement(node);
     }
@@ -1277,6 +1382,17 @@
     keywordHelper.addCompilationUnitDeclarationKeywords();
   }
 
+  void _forCompilationUnitMember(CompilationUnit unit,
+      ({AstNode? before, AstNode? after}) surroundingMembers) {
+    var before = surroundingMembers.before;
+    if (before is Directive?) {
+      _forDirective(unit, before);
+    }
+    if (surroundingMembers.after is CompilationUnitMember?) {
+      _forCompilationUnitDeclaration();
+    }
+  }
+
   /// Add the suggestions that are appropriate when the selection is at the
   /// beginning of a constant expression. The [node] provides context to
   /// determine which keywords to include.
@@ -1290,6 +1406,13 @@
   }
 
   /// Add the suggestions that are appropriate when the selection is at the
+  /// beginning of a directive. The [before] directive is the directive before
+  /// the one being added.
+  void _forDirective(CompilationUnit unit, Directive? before) {
+    keywordHelper.addDirectiveKeywords(unit, before);
+  }
+
+  /// Add the suggestions that are appropriate when the selection is at the
   /// beginning of an enum member.
   void _forEnumMember() {
     keywordHelper.addEnumMemberKeywords();
@@ -1311,23 +1434,23 @@
     keywordHelper.addExtensionMemberKeywords(isStatic: false);
   }
 
-  /// Return `true` if the preceeding member is incomplete.
+  /// Return `true` if the preceding member is incomplete.
   ///
   /// If the completion offset is within the first token of the given [member],
-  /// then check to see whether the preceeding member is incomplete. If it is,
-  /// then the user might be attempting to complete the preceeding member rather
+  /// then check to see whether the preceding member is incomplete. If it is,
+  /// then the user might be attempting to complete the preceding member rather
   /// than attempting to prepend something to the given [member], so add the
   /// suggestions appropriate for that situation.
-  bool _forIncompletePreceedingClassMember(ClassMember member) {
+  bool _forIncompletePrecedingClassMember(ClassMember member) {
     if (offset <= member.beginToken.end) {
-      var preceedingMember = member.preceedingMember;
-      if (preceedingMember == null) {
+      var precedingMember = member.precedingMember;
+      if (precedingMember == null) {
         return false;
       }
-      // Ideally we'd visit the preceeding member in order to avoid
-      // duplicating code, but the offset will be past where the parser
-      // inserted sythetic tokens, preventing that from working.
-      switch (preceedingMember) {
+      // Ideally we'd visit the preceding member in order to avoid duplicating
+      // code, but the offset will be past where the parser inserted sythetic
+      // tokens, preventing that from working.
+      switch (precedingMember) {
         // TODO(brianwilkerson) Add support for other kinds of declarations.
         case MethodDeclaration declaration:
           if (declaration.body.isFullySynthetic) {
@@ -1341,20 +1464,20 @@
   }
 
   /// If the completion offset is within the first token of the given
-  /// [statement], then check to see whether the preceeding statement is
+  /// [statement], then check to see whether the preceding statement is
   /// incomplete. If it is, then the user might be attempting to complete the
-  /// preceeding statement rather than attempting to prepend something to the
+  /// preceding statement rather than attempting to prepend something to the
   /// given [statement], so add the suggestions appropriate for that situation.
-  bool _forIncompletePreceedingStatement(Statement statement) {
+  bool _forIncompletePrecedingStatement(Statement statement) {
     if (offset <= statement.beginToken.end) {
-      var preceedingStatement = statement.preceedingStatement;
-      if (preceedingStatement == null) {
+      var precedingStatement = statement.precedingStatement;
+      if (precedingStatement == null) {
         return false;
       }
-      // Ideally we'd visit the preceeding member in order to avoid
+      // Ideally we'd visit the preceding member in order to avoid
       // duplicating code, but the offset will be past where the parser
       // inserted sythetic tokens, preventing that from working.
-      switch (preceedingStatement) {
+      switch (precedingStatement) {
         // TODO(brianwilkerson) Add support for other kinds of declarations.
         case IfStatement declaration:
           if (declaration.elseKeyword == null) {
@@ -1372,47 +1495,6 @@
     return false;
   }
 
-  /// Return `true` if the preceeding member is incomplete.
-  ///
-  /// If the completion offset is within the first token of the given [member],
-  /// then check to see whether the preceeding member is incomplete. If it is,
-  /// then the user might be attempting to complete the preceeding member rather
-  /// than attempting to prepend something to the given [member], so add the
-  /// suggestions appropriate for that situation.
-  bool _forIncompletePreceedingUnitMember(
-      CompilationUnit parent, AstNode? member) {
-    if (member == null || offset <= member.beginToken.end) {
-      var members = parent.sortedDirectivesAndDeclarations;
-      var index = member == null ? members.length : members.indexOf(member);
-      if (index <= 0) {
-        return false;
-      }
-      var preceedingMember = members[index - 1];
-      // Ideally we'd visit the preceeding member in order to avoid duplicating
-      // code, but in some cases the offset will be past where the parser
-      // inserted sythetic tokens, preventing that from working.
-      switch (preceedingMember) {
-        // TODO(brianwilkerson) Add support for other kinds of declarations.
-        case ClassDeclaration declaration:
-          if (declaration.hasNoBody) {
-            keywordHelper.addClassDeclarationKeywords(declaration);
-            return true;
-          }
-        case ExtensionTypeDeclaration declaration:
-          if (declaration.hasNoBody) {
-            visitExtensionTypeDeclaration(declaration);
-            return true;
-          }
-        case ImportDirective directive:
-          if (directive.semicolon.isSynthetic) {
-            visitImportDirective(directive);
-            return true;
-          }
-      }
-    }
-    return false;
-  }
-
   /// Add the suggestions that are appropriate when the selection is at the
   /// beginning of a mixin member.
   void _forMixinMember() {
@@ -1449,6 +1531,105 @@
     // _addTypesInScope();
   }
 
+  /// Return `true` if the [precedingMember] is incomplete.
+  ///
+  /// If it's incomplete, assume that the user is attempting to complete it and
+  /// offer appropriate suggestions.
+  bool _handledIncompletePrecedingUnitMember(
+      CompilationUnit unit, AstNode precedingMember) {
+    // Ideally we'd visit the preceding member in order to avoid duplicating
+    // code, but in some cases the offset will be past where the parser inserted
+    // sythetic tokens, preventing that from working.
+    switch (precedingMember) {
+      // TODO(brianwilkerson) Add support for other kinds of declarations.
+      case ClassDeclaration declaration:
+        if (declaration.hasNoBody) {
+          keywordHelper.addClassDeclarationKeywords(declaration);
+          return true;
+        }
+      //   case ExtensionDeclaration declaration:
+      //     if (declaration.leftBracket.isSynthetic) {
+      //       // If the prior member is an unfinished extension declaration then the
+      //       // user is probably finishing that.
+      //       _addExtensionDeclarationKeywords(declaration);
+      //       return;
+      //     }
+      //   }
+      case ExtensionTypeDeclaration declaration:
+        if (declaration.hasNoBody) {
+          visitExtensionTypeDeclaration(declaration);
+          return true;
+        }
+      case FunctionDeclaration declaration:
+        var body = declaration.functionExpression.body;
+        if (body.isEmpty) {
+          keywordHelper.addFunctionBodyModifiers(body);
+        }
+      case ImportDirective directive:
+        if (directive.semicolon.isSynthetic) {
+          visitImportDirective(directive);
+          return true;
+        }
+      //   case MixinDeclaration declaration:
+      //     if (declaration.leftBracket.isSynthetic) {
+      //       // If the prior member is an unfinished mixin declaration
+      //       // then the user is probably finishing that.
+      //       _addMixinDeclarationKeywords(declaration);
+      //       return;
+      //     }
+      //   }
+    }
+    return false;
+  }
+
+  /// Return `true` if the given [declaration] is the result of recovery and
+  /// suggestions have already been produced.
+  ///
+  /// The parser recovers from a simple identifier by assuming that it's a
+  /// top-level variable declaration. But a simple identifier could be the start
+  /// of any kind of member, so defer to the compilation unit.
+  bool _handledRecovery(TopLevelVariableDeclaration declaration) {
+    var unit = declaration.parent;
+    if (unit is CompilationUnit) {
+      ({AstNode? before, AstNode? after})? surroundingMembers;
+      if (offset <= declaration.beginToken.end) {
+        surroundingMembers = unit.membersBeforeAndAfterMember(declaration);
+        var before = surroundingMembers.before;
+        if (before != null &&
+            _handledIncompletePrecedingUnitMember(unit, before)) {
+          // The preceding member is incomplete, so assume that the user is
+          // completing it rather than starting a new member.
+          return true;
+        }
+      }
+      if (declaration.isSingleIdentifier) {
+        surroundingMembers ??= unit.membersBeforeAndAfterMember(declaration);
+        _forCompilationUnitMember(unit, surroundingMembers);
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void _visitForEachParts(ForEachParts node) {
+    if (node.inKeyword.coversOffset(offset)) {
+      var previous = node.findPrevious(node.inKeyword);
+      if (previous is SyntheticStringToken && previous.lexeme == 'in') {
+        previous = node.findPrevious(previous);
+      }
+      if (previous != null && previous.type == TokenType.EQ) {
+        keywordHelper.addKeyword(Keyword.CONST);
+        keywordHelper.addKeyword(Keyword.FALSE);
+        keywordHelper.addKeyword(Keyword.NULL);
+        keywordHelper.addKeyword(Keyword.TRUE);
+      } else {
+        keywordHelper.addKeyword(Keyword.IN);
+      }
+    } else if (!node.inKeyword.isSynthetic && node.iterable.isSynthetic) {
+      keywordHelper.addKeyword(Keyword.AWAIT);
+    }
+  }
+
   /// If the completion offset is at or before the offset of the [node], then
   /// visit the parent of the node.
   void _visitParentIfAtOrBeforeNode(AstNode node) {
@@ -1573,7 +1754,7 @@
 extension on ClassMember {
   /// Return the member before `this`, or `null` if this is the first member in
   /// the body.
-  ClassMember? get preceedingMember {
+  ClassMember? get precedingMember {
     final parent = this.parent;
     var members = switch (parent) {
       ClassDeclaration() => parent.members,
@@ -1595,16 +1776,36 @@
 }
 
 extension on CompilationUnit {
-  /// Return the member that is immediately after the given [offset] or `null`
-  /// if the offset isn't before a member.
-  AstNode? memberAfter(int offset) {
+  /// Return a record whose fields are the members in this compilation unit
+  /// that are lexically immediately before and after the given [member].
+  ({AstNode? before, AstNode? after}) membersBeforeAndAfterMember(
+      AstNode? member) {
     var members = sortedDirectivesAndDeclarations;
-    for (var member in members) {
-      if (offset < member.offset) {
-        return member;
+    AstNode? before, after;
+    if (member != null) {
+      var index = members.indexOf(member);
+      if (index > 0) {
+        before = members[index - 1];
+      }
+      if (index + 1 < members.length) {
+        after = members[index + 1];
       }
     }
-    return null;
+    return (before: before, after: after);
+  }
+
+  /// Return a record whose fields are the members in the compilation [unit]
+  /// that are lexically immediately before and after the given [member].
+  ({AstNode? before, AstNode? after}) membersBeforeAndAfterOffset(int offset) {
+    var members = sortedDirectivesAndDeclarations;
+    AstNode? previous;
+    for (var member in members) {
+      if (offset < member.offset) {
+        return (before: previous, after: member);
+      }
+      previous = member;
+    }
+    return (before: previous, after: null);
   }
 }
 
@@ -1659,7 +1860,7 @@
 extension on Statement {
   /// Return the statement before `this`, or `null` if this is the first statement in
   /// the block.
-  Statement? get preceedingStatement {
+  Statement? get precedingStatement {
     final parent = this.parent;
     if (parent is! Block) {
       return null;
@@ -1686,10 +1887,11 @@
   /// identifier.
   bool get isSingleIdentifier {
     var first = beginToken;
+    var next = first.next;
     var last = endToken;
     return first.isKeywordOrIdentifier &&
         last.isSynthetic &&
-        first.next == last;
+        (next == last || (next?.isSynthetic == true && next?.next == last));
   }
 }
 
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 5a853ad..d76f20a 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
@@ -12,7 +12,6 @@
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/src/dart/ast/extensions.dart';
-import 'package:analyzer/src/dart/ast/token.dart';
 import 'package:analyzer/src/util/performance/operation_performance.dart';
 import 'package:analyzer_plugin/src/utilities/completion/optype.dart';
 
@@ -105,137 +104,6 @@
   }
 
   @override
-  void visitCompilationUnit(CompilationUnit node) {
-    SyntacticEntity? previousMember;
-    for (var member in node.childEntities) {
-      if (entity == member) {
-        break;
-      }
-      previousMember = member;
-    }
-    if (previousMember is ClassDeclaration) {
-      if (previousMember.leftBracket.isSynthetic) {
-        // If the prior member is an unfinished class declaration
-        // then the user is probably finishing that.
-        _addClassDeclarationKeywords(previousMember);
-        return;
-      }
-    }
-    if (previousMember is ExtensionDeclaration) {
-      if (previousMember.leftBracket.isSynthetic) {
-        // If the prior member is an unfinished extension declaration then the
-        // user is probably finishing that.
-        _addExtensionDeclarationKeywords(previousMember);
-        return;
-      }
-    }
-    if (previousMember is MixinDeclaration) {
-      if (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.isSynthetic) {
-        // If the prior member is an unfinished import directive
-        // then the user is probably finishing that
-        _addImportDirectiveKeywords(previousMember);
-        return;
-      }
-    }
-    if (previousMember is ExtensionTypeDeclaration) {
-      // Already handled by the in-scope completion pass.
-      return;
-    }
-    if (previousMember == null || previousMember is Directive) {
-      if (previousMember == null &&
-          !node.directives.any((d) => d is LibraryDirective)) {
-        _addSuggestions([Keyword.LIBRARY]);
-      }
-      _addSuggestion2(IMPORT_STATEMENT, offset: 8);
-      _addSuggestion2(EXPORT_STATEMENT, offset: 8);
-      _addSuggestion2(PART_STATEMENT, offset: 6);
-    }
-    if (entity == null || entity is Declaration) {
-      if (previousMember is FunctionDeclaration &&
-          previousMember.functionExpression.body.isEmpty) {
-        _addSuggestion(Keyword.ASYNC);
-        _addSuggestion2(ASYNC_STAR);
-        _addSuggestion2(SYNC_STAR);
-      }
-      _addCompilationUnitKeywords();
-    }
-  }
-
-  @override
-  void visitFieldDeclaration(FieldDeclaration node) {
-    if (request.opType.completionLocation == 'FieldDeclaration_static') {
-      _addSuggestion(Keyword.CONST);
-      _addSuggestion(Keyword.DYNAMIC);
-      _addSuggestion(Keyword.FINAL);
-      _addSuggestion(Keyword.LATE);
-      return;
-    }
-
-    if (request.opType.completionLocation == 'FieldDeclaration_static_late') {
-      _addSuggestion(Keyword.DYNAMIC);
-      _addSuggestion(Keyword.FINAL);
-      return;
-    }
-
-    var fields = node.fields;
-    if (entity != fields) {
-      return;
-    }
-    var variables = fields.variables;
-    if (variables.isEmpty || request.offset > variables.first.beginToken.end) {
-      return;
-    }
-    if (node.abstractKeyword == null) {
-      _addSuggestion(Keyword.ABSTRACT);
-    }
-    if (node.covariantKeyword == null) {
-      _addSuggestion(Keyword.COVARIANT);
-    }
-    if (node.externalKeyword == null) {
-      _addSuggestion(Keyword.EXTERNAL);
-    }
-    if (node.fields.lateKeyword == null &&
-        request.featureSet.isEnabled(Feature.non_nullable)) {
-      _addSuggestion(Keyword.LATE);
-    }
-    if (node.fields.type == null) {
-      _addSuggestion(Keyword.DYNAMIC);
-    }
-    if (!node.isStatic) {
-      _addSuggestion(Keyword.STATIC);
-    }
-    if (!variables.first.isConst) {
-      _addSuggestion(Keyword.CONST);
-    }
-    if (!variables.first.isFinal) {
-      _addSuggestion(Keyword.FINAL);
-    }
-  }
-
-  @override
-  void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
-    _visitForEachParts(node);
-  }
-
-  @override
-  void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
-    _visitForEachParts(node);
-  }
-
-  @override
-  void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
-    _visitForEachParts(node);
-  }
-
-  @override
   void visitFormalParameterList(FormalParameterList node) {
     var constructorDeclaration =
         node.thisOrAncestorOfType<ConstructorDeclaration>();
@@ -316,175 +184,6 @@
     }
   }
 
-  @override
-  void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
-    _visitForParts(node);
-  }
-
-  @override
-  void visitForPartsWithExpression(ForPartsWithExpression node) {
-    _visitForParts(node);
-  }
-
-  @override
-  void visitForPartsWithPattern(ForPartsWithPattern node) {
-    _visitForParts(node);
-  }
-
-  @override
-  void visitForStatement(ForStatement node) {
-    // Actual: for (va^)
-    // Parsed: for (va^; ;)
-    if (node.forLoopParts == entity) {
-      _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);
-            }
-          }
-        }
-      }
-    }
-  }
-
-  // @override
-  // void visitMethodDeclaration(MethodDeclaration node) {
-  //   if (entity == node.body) {
-  //     if (node.body.isEmpty) {
-  //       _addClassBodyKeywords();
-  //       _addSuggestion(Keyword.ASYNC);
-  //       _addSuggestion2(ASYNC_STAR);
-  //       _addSuggestion2(SYNC_STAR);
-  //     } else {
-  //       _addSuggestion(Keyword.ASYNC);
-  //       if (node.body is! ExpressionFunctionBody) {
-  //         _addSuggestion2(ASYNC_STAR);
-  //         _addSuggestion2(SYNC_STAR);
-  //       }
-  //     }
-  //   } else if (entity == node.returnType || entity == node.name) {
-  //     // If the cursor is at the beginning of the declaration, include the class
-  //     // body keywords.  See dartbug.com/41039.
-  //     _addClassBodyKeywords();
-  //   }
-  // }
-
-  @override
-  void visitMethodInvocation(MethodInvocation node) {
-    if (entity == node.methodName) {
-      // no keywords in '.' expressions
-    } else if (entity == node.argumentList) {
-      // Note that we're checking the argumentList rather than the typeArgumentList
-      // as you'd expect. For some reason, when the cursor is in a type argument
-      // list (f<^>()), the entity is the invocation's argumentList...
-      // See similar logic in `imported_reference_contributor`.
-
-      _addSuggestion(Keyword.DYNAMIC);
-      _addSuggestion(Keyword.VOID);
-    } else {
-      _addExpressionKeywords(node);
-    }
-  }
-
-  @override
-  void visitRelationalPattern(RelationalPattern node) {
-    var operator = node.operator;
-    if (request.offset >= operator.end) {
-      if (request.opType.completionLocation == 'TypeArgumentList_argument') {
-        // This is most likely a type argument list.
-        _addSuggestions([Keyword.DYNAMIC, Keyword.VOID]);
-        return;
-      }
-      _addConstantExpressionKeywords(node);
-      _addSuggestions([Keyword.DYNAMIC, Keyword.VOID]);
-    }
-  }
-
-  @override
-  void visitSimpleFormalParameter(SimpleFormalParameter node) {
-    var entity = this.entity;
-    if (node.type == entity && entity is GenericFunctionType) {
-      var offset = request.offset;
-      var returnType = entity.returnType;
-      if ((returnType == null && offset < entity.offset) ||
-          (returnType != null &&
-              offset >= returnType.offset &&
-              offset < returnType.end)) {
-        _addSuggestion(Keyword.DYNAMIC);
-        _addSuggestion(Keyword.VOID);
-      }
-    }
-  }
-
-  @override
-  void visitSimpleIdentifier(SimpleIdentifier node) {
-    _addExpressionKeywords(node);
-  }
-
-  void _addClassDeclarationKeywords(ClassDeclaration node) {
-    // Very simplistic suggestion because analyzer will warn if
-    // the extends / with / implements keywords are out of order
-    if (node.extendsClause == null) {
-      _addSuggestion(Keyword.EXTENDS);
-    } else if (node.withClause == null) {
-      _addSuggestion(Keyword.WITH);
-    }
-    if (node.implementsClause == null) {
-      _addSuggestion(Keyword.IMPLEMENTS);
-    }
-  }
-
-  void _addCompilationUnitKeywords() {
-    _addSuggestions([
-      Keyword.ABSTRACT,
-      Keyword.CLASS,
-      Keyword.CONST,
-      Keyword.COVARIANT,
-      Keyword.DYNAMIC,
-      Keyword.FINAL,
-      Keyword.MIXIN,
-      Keyword.TYPEDEF,
-      Keyword.VAR,
-      Keyword.VOID
-    ]);
-    if (request.featureSet.isEnabled(Feature.extension_methods)) {
-      _addSuggestion(Keyword.EXTENSION);
-    }
-    if (request.featureSet.isEnabled(Feature.non_nullable)) {
-      _addSuggestion(Keyword.LATE);
-    }
-    if (request.featureSet.isEnabled(Feature.class_modifiers)) {
-      _addSuggestions([Keyword.BASE, Keyword.INTERFACE]);
-    }
-    if (request.featureSet.isEnabled(Feature.sealed_class)) {
-      _addSuggestion(Keyword.SEALED);
-    }
-  }
-
-  void _addConstantExpressionKeywords(AstNode node) {
-    // TODO(brianwilkerson) Use this method in place of `_addExpressionKeywords`
-    //  when in a constant context.
-    _addSuggestions([
-      Keyword.FALSE,
-      Keyword.NULL,
-      Keyword.TRUE,
-    ]);
-    if (!request.inConstantContext) {
-      _addSuggestions([Keyword.CONST]);
-    }
-  }
-
   void _addExpressionKeywords(AstNode node) {
     _addSuggestions([
       Keyword.FALSE,
@@ -505,44 +204,6 @@
     }
   }
 
-  void _addExtensionDeclarationKeywords(ExtensionDeclaration node) {
-    if (node.onKeyword.isSynthetic) {
-      _addSuggestion(Keyword.ON);
-    }
-  }
-
-  void _addImportDirectiveKeywords(ImportDirective node) {
-    var hasDeferredKeyword = node.deferredKeyword != null;
-    var hasAsKeyword = node.asKeyword != null;
-    if (!hasAsKeyword) {
-      _addSuggestion(Keyword.AS);
-    }
-    if (!hasDeferredKeyword) {
-      if (!hasAsKeyword) {
-        _addSuggestion2(DEFERRED_AS);
-      } else if (entity == node.asKeyword) {
-        _addSuggestion(Keyword.DEFERRED);
-      }
-    }
-    if (!hasDeferredKeyword || hasAsKeyword) {
-      if (node.combinators.isEmpty) {
-        _addSuggestion(Keyword.SHOW);
-        _addSuggestion(Keyword.HIDE);
-      }
-    }
-  }
-
-  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);
-    }
-    if (node.implementsClause == null) {
-      _addSuggestion(Keyword.IMPLEMENTS);
-    }
-  }
-
   void _addSuggestion(Keyword keyword) {
     _addSuggestion2(keyword.lexeme);
   }
@@ -556,45 +217,4 @@
       _addSuggestion(keyword);
     }
   }
-
-  void _visitForEachParts(ForEachParts node) {
-    if (entity == node.inKeyword) {
-      var previous = node.findPrevious(node.inKeyword);
-      if (previous is SyntheticStringToken && previous.lexeme == 'in') {
-        previous = node.findPrevious(previous);
-      }
-      if (previous != null && previous.type == TokenType.EQ) {
-        _addSuggestions(
-            [Keyword.CONST, Keyword.FALSE, Keyword.NULL, Keyword.TRUE]);
-      } else {
-        _addSuggestion(Keyword.IN);
-      }
-    } else if (!node.inKeyword.isSynthetic && node.iterable.isSynthetic) {
-      _addSuggestion(Keyword.AWAIT);
-    }
-  }
-
-  void _visitForParts(ForParts node) {
-    // Actual: for (int x i^)
-    // Parsed: for (int x; i^;)
-    // Handle the degenerate case while typing - for (int x i^)
-    if (node.condition == entity &&
-        entity is SimpleIdentifier &&
-        node is ForPartsWithDeclarations) {
-      if (_isPreviousTokenSynthetic(entity, TokenType.SEMICOLON)) {
-        _addSuggestion(Keyword.IN);
-      }
-    }
-  }
-
-  static bool _isPreviousTokenSynthetic(Object? entity, TokenType type) {
-    if (entity is AstNode) {
-      var token = entity.beginToken;
-      var previousToken = entity.findPrevious(token);
-      return previousToken != null &&
-          previousToken.isSynthetic &&
-          previousToken.type == type;
-    }
-    return false;
-  }
 }
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart b/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart
index 3a43f4a..eb311fb 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/keyword_helper.dart
@@ -35,13 +35,13 @@
     // in order to help users discover what keywords are available. If the
     // keywords are in the wrong order a diagnostic (and fix) will help them get
     // the keywords in the correct location.
-    if (node.extendsClause == null) {
+    if (_isAbsentOrIn(node.extendsClause?.extendsKeyword)) {
       addKeyword(Keyword.EXTENDS);
     }
-    if (node.withClause == null) {
+    if (_isAbsentOrIn(node.withClause?.withKeyword)) {
       addKeyword(Keyword.WITH);
     }
-    if (node.implementsClause == null) {
+    if (_isAbsentOrIn(node.implementsClause?.implementsKeyword)) {
       addKeyword(Keyword.IMPLEMENTS);
     }
   }
@@ -69,6 +69,9 @@
   /// declaration before the `class` keyword. The [node] is the class
   /// declaration containing the selection point.
   void addClassModifiers(ClassDeclaration node) {
+    if (_isAbsentOrIn(node.abstractKeyword) && node.sealedKeyword == null) {
+      addKeyword(Keyword.ABSTRACT);
+    }
     if (featureSet.isEnabled(Feature.class_modifiers) &&
         featureSet.isEnabled(Feature.sealed_class)) {
       if (node.baseKeyword == null &&
@@ -86,12 +89,12 @@
           addKeyword(Keyword.MIXIN);
         }
       }
-      if (node.baseKeyword != null && node.mixinKeyword == null) {
+      if (node.baseKeyword != null && _isAbsentOrIn(node.mixinKeyword)) {
         // base ^ class A {}
         // abstract base ^ class A {}
         addKeyword(Keyword.MIXIN);
       }
-      if (node.mixinKeyword != null && node.baseKeyword == null) {
+      if (node.mixinKeyword != null && _isAbsentOrIn(node.baseKeyword)) {
         // abstract ^ mixin class A {}
         addKeyword(Keyword.BASE);
       }
@@ -130,6 +133,7 @@
     addKeyword(Keyword.CONST);
     addKeyword(Keyword.COVARIANT);
     addKeyword(Keyword.DYNAMIC);
+    addKeyword(Keyword.EXTERNAL);
     addKeyword(Keyword.FINAL);
     addKeyword(Keyword.MIXIN);
     addKeyword(Keyword.TYPEDEF);
@@ -190,6 +194,20 @@
     }
   }
 
+  /// Add the keywords that are appropriate when the selection is at the
+  /// beginning of a directive in a compilation unit. The [before] directive is
+  /// the directive before the one being added.
+  void addDirectiveKeywords(CompilationUnit unit, Directive? before) {
+    // TODO(brianwilkerson) If we had both the members before and after the new
+    //  directive, we could limit the keywords based on surrounding members.
+    if (before == null && !unit.directives.any((d) => d is LibraryDirective)) {
+      addKeyword(Keyword.LIBRARY);
+    }
+    addKeywordFromText(Keyword.IMPORT, " '^';");
+    addKeywordFromText(Keyword.EXPORT, " '^';");
+    addKeywordFromText(Keyword.PART, " '^';");
+  }
+
   /// Add the keywords that are appropriate when the selection is in an enum
   /// declaration between the name of the enum and the body. The [node] is the
   /// enum declaration containing the selection point.
@@ -198,10 +216,10 @@
     // in order to help users discover what keywords are available. If the
     // keywords are in the wrong order a diagnostic (and fix) will help them get
     // the keywords in the correct location.
-    if (node.withClause == null) {
+    if (_isAbsentOrIn(node.withClause?.withKeyword)) {
       addKeyword(Keyword.WITH);
     }
-    if (node.implementsClause == null) {
+    if (_isAbsentOrIn(node.implementsClause?.implementsKeyword)) {
       addKeyword(Keyword.IMPLEMENTS);
     }
   }
@@ -312,37 +330,47 @@
 
   /// Add the keywords that are appropriate when the selection is at the
   /// beginning of field declaration.
-  void addFieldDeclarationKeywords(FieldDeclaration node) {
-    if (node.abstractKeyword == null) {
-      addKeyword(Keyword.ABSTRACT);
-    }
-    if (node.covariantKeyword == null) {
-      addKeyword(Keyword.COVARIANT);
-    }
-    if (node.externalKeyword == null) {
+  ///
+  /// If the declaration consists of a single variable and the name of the
+  /// variable is a keyword, then the parser used a keyword as the name of the
+  /// variable as part of recovery and the [keyword] should be treated like the
+  /// keyword it really is.
+  void addFieldDeclarationKeywords(FieldDeclaration node, {Keyword? keyword}) {
+    if (_isAbsentOrIn(node.externalKeyword) && keyword != Keyword.EXTERNAL) {
       addKeyword(Keyword.EXTERNAL);
     }
-    if (node.fields.lateKeyword == null &&
-        featureSet.isEnabled(Feature.non_nullable)) {
-      addKeyword(Keyword.LATE);
-    }
-    if (node.fields.type == null) {
-      addKeyword(Keyword.DYNAMIC);
-    }
-    if (!node.isStatic) {
-      addKeyword(Keyword.STATIC);
-    }
     var fields = node.fields;
     if (fields.type == null) {
-      addKeyword(Keyword.VAR);
+      addKeyword(Keyword.DYNAMIC);
+      addKeyword(Keyword.VOID);
+    }
+    if (!node.isStatic && keyword != Keyword.STATIC) {
+      if (_isAbsentOrIn(node.abstractKeyword) && keyword != Keyword.ABSTRACT) {
+        addKeyword(Keyword.ABSTRACT);
+      }
+      if (_isAbsentOrIn(node.covariantKeyword) &&
+          keyword != Keyword.COVARIANT) {
+        addKeyword(Keyword.COVARIANT);
+      }
+      if (_isAbsentOrIn(fields.lateKeyword) &&
+          keyword != Keyword.LATE &&
+          featureSet.isEnabled(Feature.non_nullable)) {
+        addKeyword(Keyword.LATE);
+      }
+      addKeyword(Keyword.STATIC);
     }
     var firstField = fields.variables.firstOrNull;
     if (firstField != null) {
-      if (!firstField.isConst) {
+      if (!firstField.isConst &&
+          !firstField.isFinal &&
+          keyword != Keyword.CONST &&
+          keyword != Keyword.FINAL &&
+          keyword != Keyword.VAR) {
         addKeyword(Keyword.CONST);
-      }
-      if (!firstField.isFinal) {
         addKeyword(Keyword.FINAL);
+        if (fields.type == null) {
+          addKeyword(Keyword.VAR);
+        }
       }
     }
   }
@@ -351,7 +379,7 @@
   /// or `=>` in a function body. The [body] is used to determine which keywords
   /// are appropriate.
   void addFunctionBodyModifiers(FunctionBody? body) {
-    if (body?.keyword == null) {
+    if (_isAbsentOrIn(body?.keyword)) {
       addKeyword(Keyword.ASYNC);
       if (body is! ExpressionFunctionBody) {
         addKeywordFromText(Keyword.ASYNC, '*');
@@ -422,10 +450,10 @@
     // in order to help users discover what keywords are available. If the
     // keywords are in the wrong order a diagnostic (and fix) will help them get
     // the keywords in the correct location.
-    if (node.onClause == null) {
+    if (_isAbsentOrIn(node.onClause?.onKeyword)) {
       addKeyword(Keyword.ON);
     }
-    if (node.implementsClause == null) {
+    if (_isAbsentOrIn(node.implementsClause?.implementsKeyword)) {
       addKeyword(Keyword.IMPLEMENTS);
     }
   }
@@ -452,7 +480,7 @@
   /// declaration before the `mixin` keyword. The [node] is the mixin
   /// declaration containing the selection point.
   void addMixinModifiers(MixinDeclaration node) {
-    if (node.baseKeyword == null) {
+    if (_isAbsentOrIn(node.baseKeyword)) {
       addKeyword(Keyword.BASE);
     }
   }
@@ -529,6 +557,12 @@
     addKeyword(Keyword.FINAL);
     addKeyword(Keyword.VAR);
   }
+
+  /// Return `true` if the [token] is `null` or if the offset is toughing the
+  /// [token].
+  bool _isAbsentOrIn(Token? token) {
+    return token == null || (token.offset <= offset && offset <= token.end);
+  }
 }
 
 extension on CollectionElement? {
diff --git a/pkg/analysis_server/test/completion_test.dart b/pkg/analysis_server/test/completion_test.dart
index 7882958..8a993f6 100644
--- a/pkg/analysis_server/test/completion_test.dart
+++ b/pkg/analysis_server/test/completion_test.dart
@@ -54,21 +54,17 @@
 class Date{}final num M = Dat!1''', <String>['1+Date']);
 
     // space, char, eol are important
-    buildTests(
-        'testCommentSnippets009',
-        '''
-class Maps{}class x extends!5 !2M!3 !4implements!6 !1\n{}''',
-        <String>[
-          '1+Map',
-          '2+Maps',
-          '3+Maps',
-          '4-Maps',
-          '4+implements',
-          '5-Maps',
-          '6-Map',
-          '6+implements'
-        ],
-        failingTests: '46');
+    buildTests('testCommentSnippets009', '''
+class Maps{}class x extends!5 !2M!3 !4implements!6 !1\n{}''', <String>[
+      '1+Map',
+      '2+Maps',
+      '3+Maps',
+      '4-Maps',
+      '4+implements',
+      '5-Maps',
+      '6-Map',
+      '6+implements'
+    ]);
 
     // space, char, eol are important
     buildTests('testCommentSnippets010', '''
@@ -1950,7 +1946,7 @@
           '7-Dclass',
           '7-Ctype',
         ],
-        failingTests: '2346');
+        failingTests: '4');
 
     // keywords
     buildTests(
@@ -2135,7 +2131,10 @@
           '7+show',
           '8-null'
         ],
-        failingTests: '234567'); //TODO(jwren) 234 failing as correct selection
+        // Test 1 fails because we don't suggest other directives when at the
+        //  beginning of a directive. Some clients will replace the existing
+        //  keyword rather than insert a new one.
+        failingTests: '1234567'); //TODO(jwren) 234 failing as correct selection
     // offset assertions can't be passed into buildTests(..)
 
     // keywords
diff --git a/pkg/analysis_server/test/services/completion/dart/location/class_body_test.dart b/pkg/analysis_server/test/services/completion/dart/location/class_body_test.dart
index e701163..54f1050 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/class_body_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/class_body_test.dart
@@ -374,15 +374,11 @@
 ${keywords.asKeywordSuggestions}
 ''', where: context.where);
         } else {
-          // TODO(scheglov) This is wrong.
           final keywords = {
-            Keyword.ABSTRACT,
             Keyword.CONST,
-            Keyword.COVARIANT,
             Keyword.DYNAMIC,
             Keyword.EXTERNAL,
             Keyword.FINAL,
-            Keyword.LATE,
             Keyword.VAR,
             Keyword.VOID,
           };
@@ -407,8 +403,12 @@
         _printKeywordsOrClass();
 
         final keywords = {
+          Keyword.CONST,
           Keyword.DYNAMIC,
+          Keyword.EXTERNAL,
           Keyword.FINAL,
+          Keyword.VAR,
+          Keyword.VOID,
         };
 
         assertResponse('''
@@ -430,8 +430,10 @@
         final keywords = {
           Keyword.CONST,
           Keyword.DYNAMIC,
+          Keyword.EXTERNAL,
           Keyword.FINAL,
-          Keyword.LATE,
+          Keyword.VAR,
+          Keyword.VOID,
         };
 
         assertResponse('''
@@ -451,17 +453,13 @@
         _printKeywordsOrClass();
 
         final keywords = {
-          // TODO(scheglov) This does not look right.
-          Keyword.ABSTRACT,
           Keyword.CONST,
-          // TODO(scheglov) This does not look right.
-          Keyword.COVARIANT,
           Keyword.DYNAMIC,
           // TODO(scheglov) This does not look right.
           Keyword.EXTERNAL,
           Keyword.FINAL,
-          Keyword.LATE,
           Keyword.VAR,
+          Keyword.VOID,
         };
 
         assertResponse('''
diff --git a/pkg/analysis_server/test/services/completion/dart/location/compilation_unit_member_test.dart b/pkg/analysis_server/test/services/completion/dart/location/compilation_unit_member_test.dart
index e2e354d..598dc3b 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/compilation_unit_member_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/compilation_unit_member_test.dart
@@ -113,6 +113,8 @@
 ''');
     assertResponse(r'''
 suggestions
+  abstract
+    kind: keyword
   mixin
     kind: keyword
 ''');
@@ -144,6 +146,8 @@
 ''');
     assertResponse('''
 suggestions
+  abstract
+    kind: keyword
 ''');
   }
 
@@ -170,6 +174,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -224,6 +230,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -291,49 +299,17 @@
 replacement
   left: 3
 suggestions
-  abstract
-    kind: keyword
-  base
-    kind: keyword
-  class
-    kind: keyword
-  const
-    kind: keyword
-  covariant
-    kind: keyword
-  dynamic
-    kind: keyword
   export '';
     kind: keyword
     selection: 8
-  extension
-    kind: keyword
-  external
-    kind: keyword
-  final
-    kind: keyword
   import '';
     kind: keyword
     selection: 8
-  interface
-    kind: keyword
-  late
-    kind: keyword
   library
     kind: keyword
-  mixin
-    kind: keyword
   part '';
     kind: keyword
     selection: 6
-  sealed
-    kind: keyword
-  typedef
-    kind: keyword
-  var
-    kind: keyword
-  void
-    kind: keyword
 ''');
     }
   }
@@ -407,54 +383,21 @@
 ^imp
 import "package:foo/foo.dart";
 ''');
-    // TODO(danrubel) should not suggest declaration keywords
     assertResponse(r'''
 replacement
   right: 3
 suggestions
-  abstract
-    kind: keyword
-  base
-    kind: keyword
-  class
-    kind: keyword
-  const
-    kind: keyword
-  covariant
-    kind: keyword
-  dynamic
-    kind: keyword
   export '';
     kind: keyword
     selection: 8
-  extension
-    kind: keyword
-  external
-    kind: keyword
-  final
-    kind: keyword
   import '';
     kind: keyword
     selection: 8
-  interface
-    kind: keyword
-  late
-    kind: keyword
   library
     kind: keyword
-  mixin
-    kind: keyword
   part '';
     kind: keyword
     selection: 6
-  sealed
-    kind: keyword
-  typedef
-    kind: keyword
-  var
-    kind: keyword
-  void
-    kind: keyword
 ''');
   }
 
@@ -479,6 +422,8 @@
     kind: keyword
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   interface
@@ -580,6 +525,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -678,6 +625,8 @@
 ''');
     assertResponse(r'''
 suggestions
+  abstract
+    kind: keyword
 ''');
   }
 
@@ -696,6 +645,8 @@
 ''');
     assertResponse('''
 suggestions
+  abstract
+    kind: keyword
 ''');
   }
 
@@ -731,6 +682,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -825,6 +778,8 @@
 ''');
     assertResponse(r'''
 suggestions
+  abstract
+    kind: keyword
   base
     kind: keyword
 ''');
@@ -855,6 +810,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -894,54 +851,21 @@
  ^imp
  import "package:foo/foo.dart";
  ''');
-    // TODO(danrubel) should not suggest declaration keywords
     assertResponse(r'''
 replacement
   right: 3
 suggestions
-  abstract
-    kind: keyword
-  base
-    kind: keyword
-  class
-    kind: keyword
-  const
-    kind: keyword
-  covariant
-    kind: keyword
-  dynamic
-    kind: keyword
   export '';
     kind: keyword
     selection: 8
-  extension
-    kind: keyword
-  external
-    kind: keyword
-  final
-    kind: keyword
   import '';
     kind: keyword
     selection: 8
-  interface
-    kind: keyword
-  late
-    kind: keyword
   library
     kind: keyword
-  mixin
-    kind: keyword
   part '';
     kind: keyword
     selection: 6
-  sealed
-    kind: keyword
-  typedef
-    kind: keyword
-  var
-    kind: keyword
-  void
-    kind: keyword
 ''');
   }
 
@@ -985,6 +909,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -1035,6 +961,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -1083,6 +1011,8 @@
     kind: keyword
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   interface
@@ -1180,7 +1110,6 @@
 imp^
 import "package:foo/foo.dart";
 ''');
-    // TODO(danrubel) should not suggest declaration keywords
     if (isProtocolVersion2) {
       assertResponse(r'''
 replacement
@@ -1195,47 +1124,15 @@
 replacement
   left: 3
 suggestions
-  abstract
-    kind: keyword
-  base
-    kind: keyword
-  class
-    kind: keyword
-  const
-    kind: keyword
-  covariant
-    kind: keyword
-  dynamic
-    kind: keyword
   export '';
     kind: keyword
     selection: 8
-  extension
-    kind: keyword
-  external
-    kind: keyword
-  final
-    kind: keyword
   import '';
     kind: keyword
     selection: 8
-  interface
-    kind: keyword
-  late
-    kind: keyword
-  mixin
-    kind: keyword
   part '';
     kind: keyword
     selection: 6
-  sealed
-    kind: keyword
-  typedef
-    kind: keyword
-  var
-    kind: keyword
-  void
-    kind: keyword
 ''');
     }
   }
@@ -1246,8 +1143,7 @@
 imp^
 import "package:foo/foo.dart";
 ''');
-    // TODO(danrubel) should not suggest declaration keywords
-    // TODO(brianwilkerson) Should not suggest library, export or part directives.
+    // TODO(brianwilkerson) Should not suggest `export` or `part` directives.
     if (isProtocolVersion2) {
       assertResponse(r'''
 replacement
@@ -1262,47 +1158,15 @@
 replacement
   left: 3
 suggestions
-  abstract
-    kind: keyword
-  base
-    kind: keyword
-  class
-    kind: keyword
-  const
-    kind: keyword
-  covariant
-    kind: keyword
-  dynamic
-    kind: keyword
   export '';
     kind: keyword
     selection: 8
-  extension
-    kind: keyword
-  external
-    kind: keyword
-  final
-    kind: keyword
   import '';
     kind: keyword
     selection: 8
-  interface
-    kind: keyword
-  late
-    kind: keyword
-  mixin
-    kind: keyword
   part '';
     kind: keyword
     selection: 6
-  sealed
-    kind: keyword
-  typedef
-    kind: keyword
-  var
-    kind: keyword
-  void
-    kind: keyword
 ''');
     }
   }
@@ -1330,6 +1194,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
diff --git a/pkg/analysis_server/test/services/completion/dart/location/field_declaration_test.dart b/pkg/analysis_server/test/services/completion/dart/location/field_declaration_test.dart
index 6ae6911..6736e4b 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/field_declaration_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/field_declaration_test.dart
@@ -27,8 +27,6 @@
 suggestions
   const
     kind: keyword
-  covariant
-    kind: keyword
 ''');
     } else {
       assertResponse(r'''
@@ -66,20 +64,14 @@
   }
     kind: override
     selection: 90 38
-  abstract
-    kind: keyword
   const
     kind: keyword
-  covariant
-    kind: keyword
   dynamic
     kind: keyword
   external
     kind: keyword
   final
     kind: keyword
-  late
-    kind: keyword
   var
     kind: keyword
   void
@@ -138,20 +130,14 @@
   }
     kind: override
     selection: 90 38
-  abstract
-    kind: keyword
   const
     kind: keyword
-  covariant
-    kind: keyword
   dynamic
     kind: keyword
   external
     kind: keyword
   final
     kind: keyword
-  late
-    kind: keyword
   var
     kind: keyword
   void
@@ -225,28 +211,20 @@
 suggestions
   const
     kind: keyword
-  covariant
-    kind: keyword
 ''');
     } else {
       assertResponse(r'''
 replacement
   left: 1
 suggestions
-  abstract
-    kind: keyword
   const
     kind: keyword
-  covariant
-    kind: keyword
   dynamic
     kind: keyword
   external
     kind: keyword
   final
     kind: keyword
-  late
-    kind: keyword
   var
     kind: keyword
   void
@@ -274,20 +252,14 @@
 replacement
   left: 1
 suggestions
-  abstract
-    kind: keyword
   const
     kind: keyword
-  covariant
-    kind: keyword
   dynamic
     kind: keyword
   external
     kind: keyword
   final
     kind: keyword
-  late
-    kind: keyword
   var
     kind: keyword
   void
diff --git a/pkg/analysis_server/test/services/completion/dart/location/function_declaration_test.dart b/pkg/analysis_server/test/services/completion/dart/location/function_declaration_test.dart
index dc58b73..c6a098c 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/function_declaration_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/function_declaration_test.dart
@@ -152,7 +152,7 @@
 ''');
   }
 
-  Future<void> test_afterRightParent_beforeVariable_partial() async {
+  Future<void> test_afterRightParen_beforeVariable_partial() async {
     await computeSuggestions('''
 void f()a^ Foo foo;
 ''');
@@ -161,8 +161,6 @@
 replacement
   left: 1
 suggestions
-  abstract
-    kind: keyword
   async
     kind: keyword
   async*
@@ -173,44 +171,20 @@
 replacement
   left: 1
 suggestions
-  abstract
-    kind: keyword
   async
     kind: keyword
   async*
     kind: keyword
-  base
-    kind: keyword
-  class
-    kind: keyword
   const
     kind: keyword
-  covariant
-    kind: keyword
-  dynamic
-    kind: keyword
-  extension
-    kind: keyword
   external
     kind: keyword
   final
     kind: keyword
-  interface
-    kind: keyword
   late
     kind: keyword
-  mixin
-    kind: keyword
-  sealed
-    kind: keyword
   sync*
     kind: keyword
-  typedef
-    kind: keyword
-  var
-    kind: keyword
-  void
-    kind: keyword
 ''');
     }
   }
diff --git a/pkg/analysis_server/test/services/completion/dart/location/relational_pattern_test.dart b/pkg/analysis_server/test/services/completion/dart/location/relational_pattern_test.dart
index aae9928..d51afeb 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/relational_pattern_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/relational_pattern_test.dart
@@ -71,17 +71,13 @@
     kind: class
   B01
     kind: constructorInvocation
-  const
-    kind: keyword
-  dynamic
-    kind: keyword
   false
     kind: keyword
   null
     kind: keyword
-  true
+  switch
     kind: keyword
-  void
+  true
     kind: keyword
 ''');
     }
@@ -99,6 +95,7 @@
 class A02 {}
 class B01 {}
 ''');
+    // TODO(brianwilkerson) We lost `const`.
     assertResponse(r'''
 suggestions
   A01
@@ -113,17 +110,13 @@
     kind: class
   B01
     kind: constructorInvocation
-  const
-    kind: keyword
-  dynamic
-    kind: keyword
   false
     kind: keyword
   null
     kind: keyword
-  true
+  switch
     kind: keyword
-  void
+  true
     kind: keyword
 ''');
   }
diff --git a/pkg/analysis_server/test/services/completion/dart/location/type_argument_list_test.dart b/pkg/analysis_server/test/services/completion/dart/location/type_argument_list_test.dart
index c2fe3b3..35d86c8 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/type_argument_list_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/type_argument_list_test.dart
@@ -160,9 +160,13 @@
     kind: class
   B01
     kind: class
-  dynamic
+  false
     kind: keyword
-  void
+  null
+    kind: keyword
+  switch
+    kind: keyword
+  true
     kind: keyword
 ''');
   }