Version 3.2.0-139.0.dev

Merge 826e97058889c1324fe5864dbd8f2099a6e805be into dev
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 ea205a2..b6d0530 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
@@ -47,9 +47,10 @@
   /// occurring.
   ///
   /// This is normally the covering node, but if the covering node begins with
-  /// an identifier (or keyword) and the [offset] is covered by the identifier,
-  /// then we look for the highest node that also begins with the same token and
-  /// use the parent of that node.
+  /// an identifier (or keyword) and the [offset] is covered by the identifier
+  /// or keyword, then we look for the highest node that also begins with the
+  /// same token, but that isn't part of a list of nodes, and use the parent of
+  /// that node.
   ///
   /// This allows us more context for completing what the user might be trying
   /// to write and also reduces the complexity of the visitor and reduces the
@@ -64,12 +65,15 @@
     }
     var child = coveringNode;
     var parent = child.parent;
-    while (parent != null && parent.beginToken == beginToken) {
+    while (parent != null &&
+        parent.beginToken == beginToken &&
+        !(child is! SimpleIdentifier && parent.isChildInList(child))) {
       child = parent;
       parent = child.parent;
     }
     // The [child] is now the highest node that starts with the [beginToken].
-    if (parent != null) {
+    if (parent != null &&
+        !(child is! SimpleIdentifier && parent.isChildInList(child))) {
       return parent;
     }
     return child;
@@ -103,6 +107,20 @@
   }
 
   @override
+  void visitAssertInitializer(AssertInitializer node) {
+    collector.completionLocation = 'ConstructorDeclaration_initializer';
+    keywordHelper.addConstructorInitializerKeywords(
+        node.parent as ConstructorDeclaration);
+  }
+
+  @override
+  void visitAssertStatement(AssertStatement node) {
+    if (offset <= node.assertKeyword.end) {
+      keywordHelper.addKeyword(Keyword.ASSERT);
+    }
+  }
+
+  @override
   void visitAssignmentExpression(AssignmentExpression node) {
     collector.completionLocation = 'AssignmentExpression_rightHandSide';
     _forExpression(node);
@@ -161,6 +179,13 @@
   }
 
   @override
+  void visitBreakStatement(BreakStatement node) {
+    if (offset <= node.breakKeyword.end) {
+      keywordHelper.addKeyword(Keyword.BREAK);
+    }
+  }
+
+  @override
   void visitCascadeExpression(CascadeExpression node) {
     collector.completionLocation = 'CascadeExpression_cascadeSection';
     _forExpression(node);
@@ -173,6 +198,24 @@
   }
 
   @override
+  void visitCatchClause(CatchClause node) {
+    var onKeyword = node.onKeyword;
+    var catchKeyword = node.catchKeyword;
+    if (onKeyword != null) {
+      if (offset <= onKeyword.end) {
+        keywordHelper.addKeyword(Keyword.ON);
+      } else if (catchKeyword != null && offset < catchKeyword.offset) {
+        _forTypeAnnotation();
+      }
+    }
+    if (catchKeyword != null &&
+        offset >= catchKeyword.offset &&
+        offset <= catchKeyword.end) {
+      keywordHelper.addKeyword(Keyword.CATCH);
+    }
+  }
+
+  @override
   void visitClassDeclaration(ClassDeclaration node) {
     if (offset < node.classKeyword.offset) {
       keywordHelper.addClassModifiers(node);
@@ -234,11 +277,25 @@
   }
 
   @override
+  void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
+    collector.completionLocation = 'ConstructorDeclaration_initializer';
+    keywordHelper.addConstructorInitializerKeywords(
+        node.parent as ConstructorDeclaration);
+  }
+
+  @override
   void visitConstructorReference(ConstructorReference node) {
     _forExpression(node);
   }
 
   @override
+  void visitContinueStatement(ContinueStatement node) {
+    if (offset <= node.continueKeyword.end) {
+      keywordHelper.addKeyword(Keyword.CONTINUE);
+    }
+  }
+
+  @override
   void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
     var name = node.name;
     if (name is SyntheticStringToken) {
@@ -246,7 +303,7 @@
     }
     if (node.keyword != null) {
       var type = node.type;
-      if (!type.isSimpleIdentifier && name.coversOffset(offset)) {
+      if (!type.isSingleIdentifier && name.coversOffset(offset)) {
         // Don't suggest a name for the variable.
         return;
       }
@@ -269,6 +326,13 @@
   }
 
   @override
+  void visitDoStatement(DoStatement node) {
+    if (offset <= node.doKeyword.end) {
+      _forStatement(node);
+    }
+  }
+
+  @override
   void visitDoubleLiteral(DoubleLiteral node) {
     _visitParentIfAtOrBeforeNode(node);
   }
@@ -339,6 +403,20 @@
   }
 
   @override
+  void visitExpressionStatement(ExpressionStatement node) {
+    collector.completionLocation = 'ExpressionStatement_expression';
+    if (_forIncompletePreceedingStatement(node)) {
+      if (node.isSingleIdentifier) {
+        var preceedingStatement = node.preceedingStatement;
+        if (preceedingStatement is TryStatement) {
+          return;
+        }
+      }
+    }
+    _forStatement(node);
+  }
+
+  @override
   void visitExtensionDeclaration(ExtensionDeclaration node) {
     if (offset < node.extensionKeyword.offset) {
       // There are no modifiers for extensions.
@@ -376,6 +454,26 @@
   }
 
   @override
+  void visitFieldDeclaration(FieldDeclaration node) {
+    _forIncompletePreceedingClassMember(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);
+      }
+    } else {
+      if (offset <= type.end) {
+        keywordHelper.addFieldDeclarationKeywords(node);
+        keywordHelper.addKeyword(Keyword.DYNAMIC);
+        keywordHelper.addKeyword(Keyword.VAR);
+        keywordHelper.addKeyword(Keyword.VOID);
+      }
+    }
+  }
+
+  @override
   void visitForElement(ForElement node) {
     var literal = node.thisOrAncestorOfType<TypedLiteral>();
     if (literal is ListLiteral) {
@@ -396,6 +494,13 @@
   }
 
   @override
+  void visitForStatement(ForStatement node) {
+    if (offset <= node.forKeyword.end) {
+      _forStatement(node);
+    }
+  }
+
+  @override
   void visitFunctionDeclaration(FunctionDeclaration node) {
     // If the cursor is at the beginning of the declaration, include the
     // compilation unit keywords. See dartbug.com/41039.
@@ -488,7 +593,10 @@
       return;
     }
     var expression = node.expression;
-    if (offset > expression.end && offset <= node.rightParenthesis.offset) {
+    if (offset <= node.ifKeyword.end) {
+      _forStatement(node);
+    } else if (offset > expression.end &&
+        offset <= node.rightParenthesis.offset) {
       var caseClause = node.caseClause;
       if (caseClause == null) {
         keywordHelper.addKeyword(Keyword.CASE);
@@ -662,6 +770,12 @@
   }
 
   @override
+  void visitNamedType(NamedType node) {
+    keywordHelper.addKeyword(Keyword.DYNAMIC);
+    keywordHelper.addKeyword(Keyword.VOID);
+  }
+
+  @override
   void visitNullLiteral(NullLiteral node) {
     _forExpression(node);
   }
@@ -696,8 +810,12 @@
 
   @override
   void visitPatternField(PatternField node) {
-    collector.completionLocation = 'PatternField_pattern';
     var name = node.name;
+    if (name != null && offset <= name.colon.offset) {
+      // TODO(brianwilkerson) Suggest the properties of the object or fields of
+      //  the record.
+      return;
+    }
     if (name == null) {
       var parent = node.parent;
       if (parent is ObjectPattern) {
@@ -709,8 +827,10 @@
         //  names of any named fields.
       }
     } else if (name.name == null) {
+      collector.completionLocation = 'PatternField_pattern';
       _forVariablePattern();
     } else {
+      collector.completionLocation = 'PatternField_pattern';
       _forPattern();
     }
   }
@@ -752,6 +872,20 @@
   }
 
   @override
+  void visitRecordPattern(RecordPattern node) {
+    _forExpression(node);
+    keywordHelper.addKeyword(Keyword.DYNAMIC);
+  }
+
+  @override
+  void visitRedirectingConstructorInvocation(
+      RedirectingConstructorInvocation node) {
+    collector.completionLocation = 'ConstructorDeclaration_initializer';
+    keywordHelper.addConstructorInitializerKeywords(
+        node.parent as ConstructorDeclaration);
+  }
+
+  @override
   void visitRestPatternElement(RestPatternElement node) {
     collector.completionLocation = 'RestPatternElement_pattern';
     _forPattern();
@@ -760,7 +894,11 @@
   @override
   void visitReturnStatement(ReturnStatement node) {
     collector.completionLocation = 'ReturnStatement_expression';
-    _forExpression(node.expression ?? node);
+    if (offset <= node.returnKeyword.end) {
+      _forStatement(node);
+    } else {
+      _forExpression(node.expression ?? node);
+    }
   }
 
   @override
@@ -789,11 +927,32 @@
   }
 
   @override
+  void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
+    collector.completionLocation = 'ConstructorDeclaration_initializer';
+    keywordHelper.addConstructorInitializerKeywords(
+        node.parent as ConstructorDeclaration);
+  }
+
+  @override
   void visitSwitchCase(SwitchCase node) {
     _forStatement(node);
   }
 
   @override
+  void visitSwitchDefault(SwitchDefault node) {
+    if (offset <= node.keyword.offset) {
+      keywordHelper.addKeyword(Keyword.CASE);
+      keywordHelper.addKeywordFromText(Keyword.DEFAULT, ':');
+    } else if (offset <= node.keyword.end) {
+      if (node.colon.isSynthetic) {
+        keywordHelper.addKeywordFromText(Keyword.DEFAULT, ':');
+      } else {
+        keywordHelper.addKeyword(Keyword.DEFAULT);
+      }
+    }
+  }
+
+  @override
   void visitSwitchExpression(SwitchExpression node) {
     if (offset >= node.leftParenthesis.end &&
         offset <= node.rightParenthesis.offset) {
@@ -803,7 +962,9 @@
 
   @override
   void visitSwitchPatternCase(SwitchPatternCase node) {
-    if (offset <= node.colon.offset) {
+    if (offset <= node.keyword.end) {
+      keywordHelper.addKeyword(Keyword.CASE);
+    } else if (offset <= node.colon.offset) {
       var previous = node.colon.previous!;
       var previousKeyword = previous.keyword;
       if (previousKeyword == null) {
@@ -832,7 +993,9 @@
 
   @override
   void visitSwitchStatement(SwitchStatement node) {
-    if (offset >= node.leftParenthesis.end &&
+    if (offset <= node.switchKeyword.end) {
+      _forStatement(node);
+    } else if (offset >= node.leftParenthesis.end &&
         offset <= node.rightParenthesis.offset) {
       _forExpression(node);
     } else if (offset >= node.leftBracket.end &&
@@ -870,6 +1033,16 @@
 
   @override
   void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
+    if (_forIncompletePreceedingUnitMember(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);
+      return;
+    }
+
     var variableDeclarationList = node.variables;
     var variables = variableDeclarationList.variables;
     if (variables.isEmpty || offset > variables.first.beginToken.end) {
@@ -892,7 +1065,9 @@
 
   @override
   void visitTryStatement(TryStatement node) {
-    if (offset >= node.body.end) {
+    if (offset <= node.tryKeyword.end) {
+      _forStatement(node);
+    } else if (offset >= node.body.end) {
       var finallyKeyword = node.finallyKeyword;
       if (finallyKeyword == null) {
         var catchClauses = node.catchClauses;
@@ -922,6 +1097,92 @@
 
   @override
   void visitVariableDeclaration(VariableDeclaration node) {
+    // The parser often recovers from incomplete code by creating a variable
+    // declaration. Start by checking to see whether the variable declaration is
+    // likely only there for recovery.
+    var parent = node.parent;
+    if (parent is! VariableDeclarationList) {
+      return;
+    }
+    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
+      // identifier, but want to return only if both conditions are true.
+      if (_forIncompletePreceedingClassMember(grandparent) &&
+          grandparent.isSingleIdentifier) {
+        return;
+      }
+    } else if (grandparent is ForPartsWithDeclarations) {
+      if (node.equals == null &&
+          parent.variables.length == 1 &&
+          parent.type is RecordTypeAnnotation) {
+        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.
+      if (_forIncompletePreceedingUnitMember(grandparent) &&
+          grandparent.isSingleIdentifier) {
+        return;
+      }
+    }
+
+    if (offset <= node.name.end) {
+      var container = grandparent?.parent;
+      var keyword = parent.keyword;
+      if (parent.type == null) {
+        if (keyword == null) {
+          keywordHelper.addKeyword(Keyword.CONST);
+          keywordHelper.addKeyword(Keyword.FINAL);
+          keywordHelper.addKeyword(Keyword.VAR);
+        }
+        if (keyword == null || keyword.keyword != Keyword.VAR) {
+          _forTypeAnnotation();
+        }
+      }
+      if (grandparent is FieldDeclaration) {
+        if (grandparent.externalKeyword == null) {
+          keywordHelper.addKeyword(Keyword.EXTERNAL);
+        }
+        if (grandparent.staticKeyword == null) {
+          keywordHelper.addKeyword(Keyword.STATIC);
+          if (container is ClassDeclaration || container is MixinDeclaration) {
+            if (grandparent.abstractKeyword == null) {
+              keywordHelper.addKeyword(Keyword.ABSTRACT);
+            }
+            if (grandparent.covariantKeyword == null) {
+              keywordHelper.addKeyword(Keyword.COVARIANT);
+            }
+          }
+          if (parent.lateKeyword == null &&
+              container is! ExtensionDeclaration) {
+            keywordHelper.addKeyword(Keyword.LATE);
+          }
+        }
+        if (node.name == grandparent.beginToken) {
+          // The parser often recovers from incomplete code by assuming that
+          // the user is typing a field declaration, but it's quite possible
+          // that the user is trying to type a different kind of declaration.
+          keywordHelper.addKeyword(Keyword.CONST);
+          if (container is ClassDeclaration) {
+            keywordHelper.addKeyword(Keyword.FACTORY);
+          }
+          keywordHelper.addKeyword(Keyword.GET);
+          keywordHelper.addKeyword(Keyword.OPERATOR);
+          keywordHelper.addKeyword(Keyword.SET);
+        }
+      } else if (grandparent is TopLevelVariableDeclaration) {
+        if (grandparent.externalKeyword == null) {
+          keywordHelper.addKeyword(Keyword.EXTERNAL);
+        }
+        if (parent.lateKeyword == null && container is! ExtensionDeclaration) {
+          keywordHelper.addKeyword(Keyword.LATE);
+        }
+      }
+      return;
+    }
     var equals = node.equals;
     if (equals != null && offset >= equals.end) {
       collector.completionLocation = 'VariableDeclaration_initializer';
@@ -947,6 +1208,14 @@
   }
 
   @override
+  void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
+    _forIncompletePreceedingStatement(node);
+    if (offset <= node.beginToken.end) {
+      _forStatement(node);
+    }
+  }
+
+  @override
   void visitWhenClause(WhenClause node) {
     var whenKeyword = node.whenKeyword;
     if (!whenKeyword.isSynthetic && offset > whenKeyword.end) {
@@ -954,6 +1223,13 @@
     }
   }
 
+  @override
+  void visitWhileStatement(WhileStatement node) {
+    if (offset <= node.whileKeyword.end) {
+      _forStatement(node);
+    }
+  }
+
   /// Add the suggestions that are appropriate when the selection is at the
   /// beginning of a class member.
   void _forClassMember() {
@@ -1009,7 +1285,107 @@
   /// Add the suggestions that are appropriate when the selection is at the
   /// beginning of an extension member.
   void _forExtensionMember() {
-    keywordHelper.addExtensionMemberKeywords();
+    keywordHelper.addExtensionMemberKeywords(isStatic: 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 _forIncompletePreceedingClassMember(ClassMember member) {
+    if (offset <= member.beginToken.end) {
+      var preceedingMember = member.preceedingMember;
+      if (preceedingMember == 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) {
+        // TODO(brianwilkerson) Add support for other kinds of declarations.
+        case MethodDeclaration declaration:
+          if (declaration.body.isFullySynthetic) {
+            keywordHelper.addFunctionBodyModifiers(declaration.body);
+            return true;
+          }
+        case _:
+      }
+    }
+    return false;
+  }
+
+  /// If the completion offset is within the first token of the given
+  /// [statement], then check to see whether the preceeding 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
+  /// given [statement], so add the suggestions appropriate for that situation.
+  bool _forIncompletePreceedingStatement(Statement statement) {
+    if (offset <= statement.beginToken.end) {
+      var preceedingStatement = statement.preceedingStatement;
+      if (preceedingStatement == 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 (preceedingStatement) {
+        // TODO(brianwilkerson) Add support for other kinds of declarations.
+        case IfStatement declaration:
+          if (declaration.elseKeyword == null) {
+            keywordHelper.addKeyword(Keyword.ELSE);
+            return true;
+          }
+        case TryStatement declaration:
+          if (declaration.finallyBlock == null) {
+            visitTryStatement(declaration);
+            return true;
+          }
+        case _:
+      }
+    }
+    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(AstNode member) {
+    if (offset <= member.beginToken.end) {
+      var parent = member.parent;
+      if (parent is! CompilationUnit) {
+        return false;
+      }
+      var members = parent.sortedDirectivesAndDeclarations;
+      var index = 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 ImportDirective directive:
+          if (directive.semicolon.isSynthetic) {
+            visitImportDirective(directive);
+            return true;
+          }
+      }
+    }
+    return false;
   }
 
   /// Add the suggestions that are appropriate when the selection is at the
@@ -1032,10 +1408,19 @@
   }
 
   /// Add the suggestions that are appropriate when the selection is at the
+  /// beginning of a type annotation.
+  void _forTypeAnnotation() {
+    keywordHelper.addKeyword(Keyword.DYNAMIC);
+    keywordHelper.addKeyword(Keyword.VOID);
+    // TODO(brianwilkerson) Suggest the types available in the current scope.
+    // _addTypesInScope();
+  }
+
+  /// Add the suggestions that are appropriate when the selection is at the
   /// beginning of a variable pattern.
   void _forVariablePattern() {
     keywordHelper.addVariablePatternKeywords();
-    // TODO(brianwilkerson) Suggest the types available in the currentscope.
+    // TODO(brianwilkerson) Suggest the types available in the current scope.
     // _addTypesInScope();
   }
 
@@ -1048,6 +1433,164 @@
   }
 }
 
+extension on AstNode {
+  /// Return `true` if all of the tokens in this node are synthetic.
+  bool get isFullySynthetic {
+    var current = beginToken;
+    var stop = endToken.next!;
+    while (current != stop) {
+      if (!current.isSynthetic) {
+        return false;
+      }
+      current = current.next!;
+    }
+    return true;
+  }
+
+  /// Return `true` if the [child] is an element in a list of children of this
+  /// node.
+  bool isChildInList(AstNode child) {
+    return switch (this) {
+      AdjacentStrings(:var strings) => strings.contains(child),
+      ArgumentList(:var arguments) => arguments.contains(child),
+      AugmentationImportDirective(:var metadata) => metadata.contains(child),
+      Block(:var statements) => statements.contains(child),
+      CascadeExpression(:var cascadeSections) =>
+        cascadeSections.contains(child),
+      ClassDeclaration(:var members, :var metadata) =>
+        members.contains(child) || metadata.contains(child),
+      ClassTypeAlias(:var metadata) => metadata.contains(child),
+      Comment(:var references) => references.contains(child),
+      CompilationUnit(:var directives, :var declarations) =>
+        directives.contains(child) || declarations.contains(child),
+      ConstructorDeclaration(:var initializers, :var metadata) =>
+        initializers.contains(child) || metadata.contains(child),
+      DeclaredIdentifier(:var metadata) => metadata.contains(child),
+      DottedName(:var components) => components.contains(child),
+      EnumConstantDeclaration(:var metadata) => metadata.contains(child),
+      EnumDeclaration(:var constants, :var members, :var metadata) =>
+        constants.contains(child) ||
+            members.contains(child) ||
+            metadata.contains(child),
+      ExportDirective(:var combinators, :var configurations, :var metadata) =>
+        combinators.contains(child) ||
+            configurations.contains(child) ||
+            metadata.contains(child),
+      ExtensionDeclaration(:var members, :var metadata) =>
+        members.contains(child) || metadata.contains(child),
+      ExtensionTypeDeclaration(:var members, :var metadata) =>
+        members.contains(child) || metadata.contains(child),
+      FieldDeclaration(:var metadata) => metadata.contains(child),
+      ForEachPartsWithPattern(:var metadata) => metadata.contains(child),
+      FormalParameter(:var metadata) => metadata.contains(child),
+      FormalParameterList(:var parameters) => parameters.contains(child),
+      ForParts(:var updaters) => updaters.contains(child),
+      FunctionDeclaration(:var metadata) => metadata.contains(child),
+      FunctionTypeAlias(:var metadata) => metadata.contains(child),
+      GenericTypeAlias(:var metadata) => metadata.contains(child),
+      HideCombinator(:var hiddenNames) => hiddenNames.contains(child),
+      ImplementsClause(:var interfaces) => interfaces.contains(child),
+      ImportDirective(:var combinators, :var configurations, :var metadata) =>
+        combinators.contains(child) ||
+            configurations.contains(child) ||
+            metadata.contains(child),
+      LabeledStatement(:var labels) => labels.contains(child),
+      LibraryAugmentationDirective(:var metadata) => metadata.contains(child),
+      LibraryDirective(:var metadata) => metadata.contains(child),
+      LibraryIdentifier(:var components) => components.contains(child),
+      ListLiteral(:var elements) => elements.contains(child),
+      ListPattern(:var elements) => elements.contains(child),
+      MapPattern(:var elements) => elements.contains(child),
+      MethodDeclaration(:var metadata) => metadata.contains(child),
+      MixinDeclaration(:var members, :var metadata) =>
+        members.contains(child) || metadata.contains(child),
+      ObjectPattern(:var fields) => fields.contains(child),
+      OnClause(:var superclassConstraints) =>
+        superclassConstraints.contains(child),
+      PartDirective(:var metadata) => metadata.contains(child),
+      PartOfDirective(:var metadata) => metadata.contains(child),
+      PatternVariableDeclaration(:var metadata) => metadata.contains(child),
+      RecordLiteral(:var fields) => fields.contains(child),
+      RecordPattern(:var fields) => fields.contains(child),
+      RecordTypeAnnotation(:var positionalFields) =>
+        positionalFields.contains(child),
+      RecordTypeAnnotationField(:var metadata) => metadata.contains(child),
+      RecordTypeAnnotationNamedFields(:var fields) => fields.contains(child),
+      RepresentationDeclaration(:var fieldMetadata) =>
+        fieldMetadata.contains(child),
+      SetOrMapLiteral(:var elements) => elements.contains(child),
+      ShowCombinator(:var shownNames) => shownNames.contains(child),
+      SwitchExpression(:var cases) => cases.contains(child),
+      SwitchMember(:var labels, :var statements) =>
+        labels.contains(child) || statements.contains(child),
+      SwitchStatement(:var members) => members.contains(child),
+      TopLevelVariableDeclaration(:var metadata) => metadata.contains(child),
+      TryStatement(:var catchClauses) => catchClauses.contains(child),
+      TypeArgumentList(:var arguments) => arguments.contains(child),
+      TypeParameter(:var metadata) => metadata.contains(child),
+      TypeParameterList(:var typeParameters) => typeParameters.contains(child),
+      VariableDeclaration(:var metadata) => metadata.contains(child),
+      VariableDeclarationList(:var metadata, :var variables) =>
+        metadata.contains(child) || variables.contains(child),
+      WithClause(:var mixinTypes) => mixinTypes.contains(child),
+      AstNode() => false,
+    };
+  }
+}
+
+extension on ClassDeclaration {
+  /// Return `true` if this class declaration doesn't have a body.
+  bool get hasNoBody {
+    return leftBracket.isSynthetic && rightBracket.isSynthetic;
+  }
+}
+
+extension on ClassMember {
+  /// Return the member before `this`, or `null` if this is the first member in
+  /// the body.
+  ClassMember? get preceedingMember {
+    final parent = this.parent;
+    var members = switch (parent) {
+      ClassDeclaration() => parent.members,
+      EnumDeclaration() => parent.members,
+      ExtensionDeclaration() => parent.members,
+      ExtensionTypeDeclaration() => parent.members,
+      MixinDeclaration() => parent.members,
+      _ => null
+    };
+    if (members == null) {
+      return null;
+    }
+    var index = members.indexOf(this);
+    if (index <= 0) {
+      return null;
+    }
+    return members[index - 1];
+  }
+}
+
+extension on ExpressionStatement {
+  /// Return `true` if this statement consists of a single identifier.
+  bool get isSingleIdentifier {
+    var first = beginToken;
+    var last = endToken;
+    return first.isKeywordOrIdentifier &&
+        last.isSynthetic &&
+        first.next == last;
+  }
+}
+
+extension on FieldDeclaration {
+  /// Return `true` if this field declaration consists of a single identifier.
+  bool get isSingleIdentifier {
+    var first = beginToken;
+    var last = endToken;
+    return first.isKeywordOrIdentifier &&
+        last.isSynthetic &&
+        first.next == last;
+  }
+}
+
 extension on GuardedPattern {
   /// Return `true` if this pattern has, or might have, a `when` keyword.
   bool get hasWhen {
@@ -1067,6 +1610,23 @@
   }
 }
 
+extension on Statement {
+  /// Return the statement before `this`, or `null` if this is the first statement in
+  /// the block.
+  Statement? get preceedingStatement {
+    final parent = this.parent;
+    if (parent is! Block) {
+      return null;
+    }
+    var statements = parent.statements;
+    var index = statements.indexOf(this);
+    if (index <= 0) {
+      return null;
+    }
+    return statements[index - 1];
+  }
+}
+
 extension on SyntacticEntity? {
   /// Return `true` if the receiver covers the [offset].
   bool coversOffset(int offset) {
@@ -1075,8 +1635,21 @@
   }
 }
 
+extension on TopLevelVariableDeclaration {
+  /// Return `true` if this top level variable declaration consists of a single
+  /// identifier.
+  bool get isSingleIdentifier {
+    var first = beginToken;
+    var last = endToken;
+    return first.isKeywordOrIdentifier &&
+        last.isSynthetic &&
+        first.next == last;
+  }
+}
+
 extension on TypeAnnotation? {
-  bool get isSimpleIdentifier {
+  /// Return `true` if this type annotation consists of a single identifier.
+  bool get isSingleIdentifier {
     var self = this;
     return self is NamedType &&
         self.question == null &&
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 9342047..e9e7878 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,12 +394,6 @@
   }
 
   @override
-  void visitRecordPattern(RecordPattern node) {
-    _addExpressionKeywords(node);
-    _addSuggestions([Keyword.DYNAMIC]);
-  }
-
-  @override
   void visitRelationalPattern(RelationalPattern node) {
     var operator = node.operator;
     if (request.offset >= operator.end) {
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 a9692f7..149eef1 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
@@ -232,7 +232,7 @@
       }
       if (node is Expression) {
         return !node.inConstantContext;
-      } else if (node is IfStatement) {
+      } else if (node is ExpressionStatement || node is IfStatement) {
         return true;
       } else if (node is PatternVariableDeclaration) {
         return true;
@@ -295,19 +295,53 @@
 
   /// Add the keywords that are appropriate when the selection is at the
   /// beginning of a member in an extension.
-  void addExtensionMemberKeywords() {
+  void addExtensionMemberKeywords({required bool isStatic}) {
     addKeyword(Keyword.CONST);
     addKeyword(Keyword.DYNAMIC);
     addKeyword(Keyword.FINAL);
     addKeyword(Keyword.GET);
-    addKeyword(Keyword.OPERATOR);
+    if (!isStatic) addKeyword(Keyword.OPERATOR);
     addKeyword(Keyword.SET);
-    addKeyword(Keyword.STATIC);
+    if (!isStatic) addKeyword(Keyword.STATIC);
     addKeyword(Keyword.VAR);
     addKeyword(Keyword.VOID);
-    if (featureSet.isEnabled(Feature.non_nullable)) {
+  }
+
+  /// 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) {
+      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);
+    }
+    var firstField = fields.variables.firstOrNull;
+    if (firstField != null) {
+      if (!firstField.isConst) {
+        addKeyword(Keyword.CONST);
+      }
+      if (!firstField.isFinal) {
+        addKeyword(Keyword.FINAL);
+      }
+    }
   }
 
   /// Add the keywords that are appropriate when the selection is before the `{`
diff --git a/pkg/analysis_server/test/client/completion_driver_test.dart b/pkg/analysis_server/test/client/completion_driver_test.dart
index 5fc9af0..30a588a 100644
--- a/pkg/analysis_server/test/client/completion_driver_test.dart
+++ b/pkg/analysis_server/test/client/completion_driver_test.dart
@@ -108,7 +108,7 @@
 
   /// Asserts that the [response] has the [expected] textual dump produced
   /// using [printerConfiguration].
-  void assertResponse(String expected) {
+  void assertResponse(String expected, {String where = ''}) {
     final buffer = StringBuffer();
     printer.CompletionResponsePrinter(
       buffer: buffer,
@@ -118,12 +118,14 @@
     final actual = buffer.toString();
 
     if (actual != expected) {
-      var target = driver.server.server.completionState.currentRequest?.target;
-      var where = '';
-      if (target != null) {
-        var containingNode = target.containingNode.runtimeType;
-        var entity = target.entity;
-        where = ' (containingNode = $containingNode, entity = $entity)';
+      if (where.isEmpty) {
+        var target =
+            driver.server.server.completionState.currentRequest?.target;
+        if (target != null) {
+          var containingNode = target.containingNode.runtimeType;
+          var entity = target.entity;
+          where = ' (containingNode = $containingNode, entity = $entity)';
+        }
       }
       TextExpectationsCollector.add(actual);
       fail('''
diff --git a/pkg/analysis_server/test/completion_test.dart b/pkg/analysis_server/test/completion_test.dart
index 952b392..7882958 100644
--- a/pkg/analysis_server/test/completion_test.dart
+++ b/pkg/analysis_server/test/completion_test.dart
@@ -2105,7 +2105,7 @@
           'K+else',
           'L+return'
         ],
-        failingTests: '3CK');
+        failingTests: '3K');
 
     // operators in function
     buildTests('test015', '''f(a,b,c) => a + b * c !1;''', <String>['1+=='],
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 f0d74d2..e701163 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
@@ -257,7 +257,7 @@
           if (context.isClass) Keyword.FACTORY,
           Keyword.FINAL,
           Keyword.GET,
-          Keyword.LATE,
+          if (!context.isExtension) Keyword.LATE,
           Keyword.OPERATOR,
           Keyword.SET,
           Keyword.STATIC,
@@ -270,7 +270,7 @@
   Object
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
       },
     );
   }
@@ -291,7 +291,7 @@
   Object
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
       },
     );
   }
@@ -308,12 +308,13 @@
 suggestions
   Object
     kind: class
-''');
+''', where: context.where);
         } else {
           _printKeywordsOrClass();
 
           final keywords = {
             Keyword.DYNAMIC,
+            Keyword.EXTERNAL,
             Keyword.VOID,
           };
 
@@ -324,7 +325,7 @@
   Object
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
         }
       },
     );
@@ -346,7 +347,7 @@
   Object
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
       },
     );
   }
@@ -371,7 +372,7 @@
   FutureOr
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
         } else {
           // TODO(scheglov) This is wrong.
           final keywords = {
@@ -382,6 +383,8 @@
             Keyword.EXTERNAL,
             Keyword.FINAL,
             Keyword.LATE,
+            Keyword.VAR,
+            Keyword.VOID,
           };
 
           assertResponse('''
@@ -391,7 +394,7 @@
   FutureOr
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
         }
       },
     );
@@ -413,7 +416,7 @@
   Object
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
       },
     );
   }
@@ -436,7 +439,7 @@
   Object
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
       },
     );
   }
@@ -458,6 +461,7 @@
           Keyword.EXTERNAL,
           Keyword.FINAL,
           Keyword.LATE,
+          Keyword.VAR,
         };
 
         assertResponse('''
@@ -465,7 +469,7 @@
   Object
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
       },
     );
   }
@@ -491,17 +495,18 @@
   String
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
         } else {
           final keywords = {
-            // TODO(scheglov) Not quite right, without static.
+            if (context.isClass || context.isMixin) Keyword.ABSTRACT,
             Keyword.CONST,
             if (context.isClass || context.isMixin) Keyword.COVARIANT,
             Keyword.DYNAMIC,
+            Keyword.EXTERNAL,
             if (context.isClass) Keyword.FACTORY,
             Keyword.FINAL,
             Keyword.GET,
-            Keyword.LATE,
+            if (!context.isExtension) Keyword.LATE,
             Keyword.OPERATOR,
             Keyword.SET,
             Keyword.STATIC,
@@ -516,7 +521,7 @@
   String
     kind: class
 ${keywords.asKeywordSuggestions}
-''');
+''', where: context.where);
         }
       },
     );
@@ -543,7 +548,7 @@
   $line
 }
 ''');
-      validator(_Context());
+      validator(_Context(isEnum: true));
     }
     // extension
     {
@@ -552,7 +557,7 @@
   $line
 }
 ''');
-      validator(_Context());
+      validator(_Context(isExtension: true));
     }
     // mixin
     {
@@ -1175,12 +1180,24 @@
 
 class _Context {
   final bool isClass;
+  final bool isEnum;
+  final bool isExtension;
   final bool isMixin;
 
   _Context({
     this.isClass = false,
+    this.isEnum = false,
+    this.isExtension = false,
     this.isMixin = false,
   });
+
+  String get where => isClass
+      ? ' in class'
+      : isEnum
+          ? ' in enum'
+          : isExtension
+              ? ' in extension'
+              : ' in mixin';
 }
 
 extension on Iterable<Keyword> {
diff --git a/pkg/analysis_server/test/services/completion/dart/location/class_declaration_test.dart b/pkg/analysis_server/test/services/completion/dart/location/class_declaration_test.dart
index 17fed24..04ee4bc 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/class_declaration_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/class_declaration_test.dart
@@ -205,6 +205,8 @@
     kind: keyword
   implements
     kind: keyword
+  with
+    kind: keyword
 ''');
     }
   }
@@ -230,6 +232,8 @@
     kind: keyword
   implements
     kind: keyword
+  with
+    kind: keyword
 ''');
     }
   }
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 749a746..e2e354d 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
@@ -308,6 +308,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -370,6 +372,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -425,6 +429,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -528,6 +534,8 @@
     kind: keyword
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   interface
@@ -636,6 +644,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -781,6 +791,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -904,6 +916,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -1198,6 +1212,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -1263,6 +1279,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
@@ -1432,6 +1450,8 @@
     selection: 8
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   import '';
diff --git a/pkg/analysis_server/test/services/completion/dart/location/extension_body_test.dart b/pkg/analysis_server/test/services/completion/dart/location/extension_body_test.dart
index 228ab7f..3c8fbaa 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/extension_body_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/extension_body_test.dart
@@ -42,8 +42,6 @@
     kind: keyword
   get
     kind: keyword
-  late
-    kind: keyword
   operator
     kind: keyword
   set
@@ -71,8 +69,6 @@
     kind: keyword
   get
     kind: keyword
-  late
-    kind: keyword
   operator
     kind: keyword
   set
@@ -100,8 +96,6 @@
     kind: keyword
   get
     kind: keyword
-  late
-    kind: keyword
   operator
     kind: keyword
   set
@@ -129,8 +123,6 @@
     kind: keyword
   get
     kind: keyword
-  late
-    kind: keyword
   operator
     kind: keyword
   set
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 b797eca..6ae6911 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
@@ -80,6 +80,10 @@
     kind: keyword
   late
     kind: keyword
+  var
+    kind: keyword
+  void
+    kind: keyword
 ''');
     }
   }
@@ -148,6 +152,10 @@
     kind: keyword
   late
     kind: keyword
+  var
+    kind: keyword
+  void
+    kind: keyword
 ''');
     }
   }
@@ -239,6 +247,10 @@
     kind: keyword
   late
     kind: keyword
+  var
+    kind: keyword
+  void
+    kind: keyword
 ''');
     }
   }
@@ -276,6 +288,10 @@
     kind: keyword
   late
     kind: keyword
+  var
+    kind: keyword
+  void
+    kind: keyword
 ''');
     }
   }
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 aa885f4..dc58b73 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
@@ -96,6 +96,8 @@
     kind: keyword
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   interface
@@ -189,6 +191,8 @@
     kind: keyword
   extension
     kind: keyword
+  external
+    kind: keyword
   final
     kind: keyword
   interface
diff --git a/pkg/analysis_server/test/services/completion/dart/location/if_element_test.dart b/pkg/analysis_server/test/services/completion/dart/location/if_element_test.dart
index 5f47757..fbcd1cf 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/if_element_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/if_element_test.dart
@@ -161,7 +161,7 @@
 ''');
   }
 
-  Future<void> test_afterThen_beforeComma_partial() async {
+  Future<void> test_afterThen_beforeComma_partial_list() async {
     await computeSuggestions('''
 void f(int i) {
   [if (true) 1 e^, i];
@@ -390,7 +390,7 @@
 }
 
 mixin IfElementInSetTestCases on AbstractCompletionDriverTest {
-  Future<void> test_afterThen_beforeComma_partial() async {
+  Future<void> test_afterThen_beforeComma_partial_set() async {
     await computeSuggestions('''
 void f(int i) {
   <int>{if (true) 1 e^, i};
diff --git a/pkg/analysis_server/test/services/completion/dart/location/method_declaration_test.dart b/pkg/analysis_server/test/services/completion/dart/location/method_declaration_test.dart
index 811c72c..8857ddf 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/method_declaration_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/method_declaration_test.dart
@@ -68,32 +68,8 @@
     kind: keyword
   async*
     kind: keyword
-  const
-    kind: keyword
-  covariant
-    kind: keyword
-  dynamic
-    kind: keyword
-  factory
-    kind: keyword
-  final
-    kind: keyword
-  get
-    kind: keyword
-  late
-    kind: keyword
-  operator
-    kind: keyword
-  set
-    kind: keyword
-  static
-    kind: keyword
   sync*
     kind: keyword
-  var
-    kind: keyword
-  void
-    kind: keyword
 ''');
     }
   }
@@ -446,6 +422,8 @@
 replacement
   left: 1
 suggestions
+  abstract
+    kind: keyword
   async
     kind: keyword
   async*
@@ -487,6 +465,8 @@
   }
     kind: override
     selection: 90 38
+  abstract
+    kind: keyword
   async
     kind: keyword
   async*
@@ -497,18 +477,12 @@
     kind: keyword
   dynamic
     kind: keyword
-  factory
+  external
     kind: keyword
   final
     kind: keyword
-  get
-    kind: keyword
   late
     kind: keyword
-  operator
-    kind: keyword
-  set
-    kind: keyword
   static
     kind: keyword
   sync*
diff --git a/pkg/analysis_server/test/services/completion/dart/location/try_statement_test.dart b/pkg/analysis_server/test/services/completion/dart/location/try_statement_test.dart
index aa63172..35ebe88 100644
--- a/pkg/analysis_server/test/services/completion/dart/location/try_statement_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/location/try_statement_test.dart
@@ -39,50 +39,18 @@
 suggestions
   catch
     kind: keyword
-  const
-    kind: keyword
 ''');
     } else {
       assertResponse(r'''
 replacement
   left: 1
 suggestions
-  assert
-    kind: keyword
   catch
     kind: keyword
-  const
-    kind: keyword
-  do
-    kind: keyword
-  dynamic
-    kind: keyword
-  final
-    kind: keyword
   finally
     kind: keyword
-  for
-    kind: keyword
-  if
-    kind: keyword
-  late
-    kind: keyword
   on
     kind: keyword
-  return
-    kind: keyword
-  switch
-    kind: keyword
-  throw
-    kind: keyword
-  try
-    kind: keyword
-  var
-    kind: keyword
-  void
-    kind: keyword
-  while
-    kind: keyword
 ''');
     }
   }
@@ -98,50 +66,18 @@
 suggestions
   catch
     kind: keyword
-  const
-    kind: keyword
 ''');
     } else {
       assertResponse(r'''
 replacement
   left: 1
 suggestions
-  assert
-    kind: keyword
   catch
     kind: keyword
-  const
-    kind: keyword
-  do
-    kind: keyword
-  dynamic
-    kind: keyword
-  final
-    kind: keyword
   finally
     kind: keyword
-  for
-    kind: keyword
-  if
-    kind: keyword
-  late
-    kind: keyword
   on
     kind: keyword
-  return
-    kind: keyword
-  switch
-    kind: keyword
-  throw
-    kind: keyword
-  try
-    kind: keyword
-  var
-    kind: keyword
-  void
-    kind: keyword
-  while
-    kind: keyword
 ''');
     }
   }
@@ -553,6 +489,8 @@
   }
 
   Future<void> test_afterTryBlock_beforeOn_partial() async {
+    // This is an odd test because the `catch` belongs after the `on` clause,
+    // which makes it hard to know what the user might be trying to type.
     await computeSuggestions('''
 void f() {try {} c^ on SomeException {}}
 ''');
@@ -563,18 +501,50 @@
 suggestions
   catch
     kind: keyword
+  const
+    kind: keyword
 ''');
     } else {
       assertResponse(r'''
 replacement
   left: 1
 suggestions
+  assert
+    kind: keyword
   catch
     kind: keyword
+  const
+    kind: keyword
+  do
+    kind: keyword
+  dynamic
+    kind: keyword
+  final
+    kind: keyword
   finally
     kind: keyword
+  for
+    kind: keyword
+  if
+    kind: keyword
+  late
+    kind: keyword
   on
     kind: keyword
+  return
+    kind: keyword
+  switch
+    kind: keyword
+  throw
+    kind: keyword
+  try
+    kind: keyword
+  var
+    kind: keyword
+  void
+    kind: keyword
+  while
+    kind: keyword
 ''');
     }
   }
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 201717d..f50e16a 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -736,6 +736,15 @@
         await produceGlobalTypeInferenceResults(closedWorldAndIndices!);
     if (shouldStopAfterGlobalTypeInference) return;
 
+    // Allow the original references to these to be GCed and only hold
+    // references to them if we are actually running the dump info task later.
+    JClosedWorld? closedWorldForDumpInfo;
+    DataSourceIndices? globalInferenceIndicesForDumpInfo;
+    if (options.dumpInfoWriteUri != null || options.dumpInfoReadUri != null) {
+      closedWorldForDumpInfo = closedWorldAndIndices.data;
+      globalInferenceIndicesForDumpInfo = globalTypeInferenceResults.indices;
+    }
+
     // Run codegen.
     final sourceLookup = SourceLookup(output.component);
     CodegenResults codegenResults =
@@ -746,8 +755,8 @@
       final dumpInfoData =
           await serializationTask.deserializeDumpInfoProgramData(
               backendStrategy,
-              closedWorldAndIndices.data!,
-              globalTypeInferenceResults.indices);
+              closedWorldForDumpInfo!,
+              globalInferenceIndicesForDumpInfo);
       await runDumpInfo(codegenResults, dumpInfoData);
     } else {
       // Link.
@@ -762,8 +771,8 @@
           serializationTask.serializeDumpInfoProgramData(
               backendStrategy,
               dumpInfoData,
-              closedWorldAndIndices.data!,
-              globalTypeInferenceResults.indices);
+              closedWorldForDumpInfo!,
+              globalInferenceIndicesForDumpInfo);
         }
       }
     }
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 9744d31..443784a 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -4327,9 +4327,11 @@
 
   bool _reifyGenericFunction(Member? m) =>
       m == null ||
-      !m.enclosingLibrary.importUri.isScheme('dart') ||
-      !m.annotations
-          .any((a) => isBuiltinAnnotation(a, '_js_helper', 'NoReifyGeneric'));
+      // JS interop members should not pass type arguments.
+      !isJsMember(m) &&
+          !(m.enclosingLibrary.importUri.isScheme('dart') &&
+              m.annotations.any((a) =>
+                  isBuiltinAnnotation(a, '_js_helper', 'NoReifyGeneric')));
 
   js_ast.Statement _nullParameterCheck(js_ast.Expression param) {
     var call = runtimeCall('argumentError((#))', [param]);
diff --git a/pkg/dev_compiler/lib/src/kernel/js_interop.dart b/pkg/dev_compiler/lib/src/kernel/js_interop.dart
index 1f2520c..e6c13da 100644
--- a/pkg/dev_compiler/lib/src/kernel/js_interop.dart
+++ b/pkg/dev_compiler/lib/src/kernel/js_interop.dart
@@ -47,11 +47,13 @@
 }
 
 bool isJsMember(Member member) {
-  // TODO(vsm): If we ever use external outside the SDK for non-JS interop,
-  // we're need to fix this.
-  return !_isLibrary(member.enclosingLibrary, ['dart:*']) &&
-      member.isExternal &&
-      !isNative(member);
+  return member.isExternal &&
+      !_isLibrary(member.enclosingLibrary, ['dart:*']) &&
+      !isNative(member) &&
+      // Non-JS interop external members might exist e.g. the ones generated by
+      // Angular, so we should check to see that this member actually uses JS
+      // interop.
+      usesJSInterop(member);
 }
 
 bool _annotationIsFromJSLibrary(String expectedName, Expression value) {
@@ -120,7 +122,7 @@
 bool isUndefinedAnnotation(Expression value) =>
     isBuiltinAnnotation(value, '_js_helper', '_Undefined');
 
-/// Returns true iff the class has an `@JS(...)` annotation from
+/// Returns true iff the annotatable has an `@JS(...)` annotation from
 /// `package:js`, `dart:_js_annotations`, or `dart:js_interop`.
 ///
 /// Note: usually [usesJSInterop] should be used instead of this.
@@ -132,8 +134,11 @@
 // class itself, other places we require it on the library. Also members are
 // inconsistent: sometimes they need to have `@JS` on them, other times they
 // need to be `external` in an `@JS` class.
-bool hasJSInteropAnnotation(Class c) =>
-    c.annotations.any(isJSInteropAnnotation);
+//
+// TODO(srujzs): We should replace many of these helpers with the ones defined
+// in pkg:_js_interop_checks.
+bool hasJSInteropAnnotation(Annotatable a) =>
+    a.annotations.any(isJSInteropAnnotation);
 
 /// Returns true iff this element is a JS interop member.
 ///
@@ -144,9 +149,10 @@
 /// the class or library.
 bool usesJSInterop(NamedNode n) {
   if (n is Member && n.isExternal) {
-    return n.enclosingLibrary.annotations.any(isJSInteropAnnotation) ||
-        n.annotations.any(isJSInteropAnnotation) ||
-        (n.enclosingClass?.annotations.any(isJSInteropAnnotation) ?? false);
+    return hasJSInteropAnnotation(n) ||
+        (n.enclosingClass == null
+            ? hasJSInteropAnnotation(n.enclosingLibrary)
+            : hasJSInteropAnnotation(n.enclosingClass!));
   } else if (n is Class) {
     return n.annotations.any(isJSInteropAnnotation);
   }
diff --git a/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart b/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart
index 1401a6c..1221b62 100644
--- a/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart
+++ b/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart
@@ -86,6 +86,7 @@
     if (result.isRequestedName) {
       var element = result.element;
       return element is ClassElement ||
+          element is ExtensionTypeElement ||
           element is TypeAliasElement ||
           element is TypeParameterElement;
     }
diff --git a/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart b/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart
index 331db90..8c717f7 100644
--- a/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart
+++ b/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart
@@ -17,6 +17,16 @@
   @override
   String get lintRule => 'avoid_types_as_parameter_names';
 
+  test_extensionType() async {
+    await assertDiagnostics(r'''
+extension type E(int i) { }
+
+void f(E) { }
+''', [
+      lint(36, 1),
+    ]);
+  }
+
   test_super() async {
     await assertDiagnostics(r'''
 class A {
diff --git a/tests/lib/js/type_parameter_lowering_test.dart b/tests/lib/js/type_parameter_lowering_test.dart
new file mode 100644
index 0000000..35e1f835
--- /dev/null
+++ b/tests/lib/js/type_parameter_lowering_test.dart
@@ -0,0 +1,50 @@
+// 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.
+
+// Check that the compilers lower interop calls that use type parameters do not
+// pass the type parameter.
+
+@JS()
+library type_parameter_lowering_test;
+
+import 'package:js/js.dart';
+
+@JS()
+external void eval(String code);
+
+@JS()
+external void topLevelMethod<T extends int>(T t);
+
+@JS()
+class TypeParam<T extends int> {
+  external TypeParam(T t);
+  external static void staticMethod<U extends int>(U u);
+  external static void staticMethodShadow<T extends int>(T t);
+  external void genericMethod<U extends int>(U u);
+  external void genericMethodShadow<T extends int>(T t);
+}
+
+void main() {
+  eval('''
+    const checkValue = function(value) {
+      if (value != 0) {
+        throw new Error(`Expected value to be 0, but got \${value}.`);
+      }
+    }
+    globalThis.topLevelMethod = checkValue;
+    globalThis.TypeParam = function (value) {
+      checkValue(value);
+      this.genericMethod = checkValue;
+      this.genericMethodShadow = checkValue;
+    }
+    globalThis.TypeParam.staticMethod = checkValue;
+    globalThis.TypeParam.staticMethodShadow = checkValue;
+  ''');
+  topLevelMethod(0);
+  final typeParam = TypeParam(0);
+  TypeParam.staticMethod(0);
+  TypeParam.staticMethodShadow(0);
+  typeParam.genericMethod(0);
+  typeParam.genericMethodShadow(0);
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 691a449..56a005c 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -86,6 +86,7 @@
 js/static_interop_test/native_error_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/static_interop_test/typed_data_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/type_parameter_lowering_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 
 [ $simulator ]
 convert/utf85_test: Skip # Pass, Slow Issue 20111.
diff --git a/tests/lib_2/js/type_parameter_lowering_test.dart b/tests/lib_2/js/type_parameter_lowering_test.dart
new file mode 100644
index 0000000..35e1f835
--- /dev/null
+++ b/tests/lib_2/js/type_parameter_lowering_test.dart
@@ -0,0 +1,50 @@
+// 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.
+
+// Check that the compilers lower interop calls that use type parameters do not
+// pass the type parameter.
+
+@JS()
+library type_parameter_lowering_test;
+
+import 'package:js/js.dart';
+
+@JS()
+external void eval(String code);
+
+@JS()
+external void topLevelMethod<T extends int>(T t);
+
+@JS()
+class TypeParam<T extends int> {
+  external TypeParam(T t);
+  external static void staticMethod<U extends int>(U u);
+  external static void staticMethodShadow<T extends int>(T t);
+  external void genericMethod<U extends int>(U u);
+  external void genericMethodShadow<T extends int>(T t);
+}
+
+void main() {
+  eval('''
+    const checkValue = function(value) {
+      if (value != 0) {
+        throw new Error(`Expected value to be 0, but got \${value}.`);
+      }
+    }
+    globalThis.topLevelMethod = checkValue;
+    globalThis.TypeParam = function (value) {
+      checkValue(value);
+      this.genericMethod = checkValue;
+      this.genericMethodShadow = checkValue;
+    }
+    globalThis.TypeParam.staticMethod = checkValue;
+    globalThis.TypeParam.staticMethodShadow = checkValue;
+  ''');
+  topLevelMethod(0);
+  final typeParam = TypeParam(0);
+  TypeParam.staticMethod(0);
+  TypeParam.staticMethodShadow(0);
+  typeParam.genericMethod(0);
+  typeParam.genericMethodShadow(0);
+}
diff --git a/tests/lib_2/lib_2.status b/tests/lib_2/lib_2.status
index 1382f5f..05c235d 100644
--- a/tests/lib_2/lib_2.status
+++ b/tests/lib_2/lib_2.status
@@ -67,6 +67,7 @@
 js/static_interop_test/external_static_member_lowerings_trusttypes_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/type_parameter_lowering_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
 
 [ $simulator ]
 convert/utf85_test: Skip # Pass, Slow Issue 20111.
diff --git a/tools/VERSION b/tools/VERSION
index 64a456e..43571ff 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 3
 MINOR 2
 PATCH 0
-PRERELEASE 138
+PRERELEASE 139
 PRERELEASE_PATCH 0