Migrate statement completion support

Change-Id: I9d6751888286e4a068fc1dac192c93ffb8089b69
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194963
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart b/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
index 679b457..9bd0039 100644
--- a/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
+++ b/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart = 2.9
-
 import 'dart:math';
 
 import 'package:analysis_server/src/protocol_server.dart' hide Element;
@@ -21,6 +19,7 @@
 import 'package:analyzer/src/generated/java_core.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer_plugin/utilities/range_factory.dart';
+import 'package:collection/collection.dart';
 
 /// An enumeration of possible statement completion kinds.
 class DartStatementCompletion {
@@ -105,20 +104,23 @@
 
   final StatementCompletionContext statementContext;
   final CorrectionUtils utils;
-  AstNode node;
-  StatementCompletion completion;
+
+  /// TODO(brianwilkerson) Refactor the code so that the completion is returned
+  ///  from the methods in which it's computed rather than being a field that we
+  ///  have to test.
+  StatementCompletion? completion;
   SourceChange change = SourceChange('statement-completion');
   List<engine.AnalysisError> errors = [];
   final Map<String, LinkedEditGroup> linkedPositionGroups =
       <String, LinkedEditGroup>{};
-  Position exitPosition;
+  Position? exitPosition;
 
   StatementCompletionProcessor(this.statementContext)
       : utils = CorrectionUtils(statementContext.resolveResult);
 
   String get eol => utils.endOfLine;
 
-  String get file => statementContext.resolveResult.path;
+  String get file => statementContext.resolveResult.path!;
 
   LineInfo get lineInfo => statementContext.resolveResult.lineInfo;
 
@@ -126,29 +128,27 @@
 
   Source get source => unitElement.source;
 
-  CompilationUnit get unit => statementContext.resolveResult.unit;
+  CompilationUnit get unit => statementContext.resolveResult.unit!;
 
-  CompilationUnitElement get unitElement =>
-      statementContext.resolveResult.unit.declaredElement;
+  CompilationUnitElement get unitElement => unit.declaredElement!;
 
   Future<StatementCompletion> compute() async {
-    node = _selectedNode();
+    var node = _selectedNode();
     if (node == null) {
       return NO_COMPLETION;
     }
     node = node.thisOrAncestorMatching(
         (n) => n is Statement || _isNonStatementDeclaration(n));
     if (node == null) {
-      return _complete_simpleEnter() ? completion : NO_COMPLETION;
+      return _complete_simpleEnter() ? completion! : NO_COMPLETION;
     }
     if (node is Block) {
-      Block blockNode = node;
-      if (blockNode.statements.isNotEmpty) {
-        node = blockNode.statements.last;
+      if (node.statements.isNotEmpty) {
+        node = node.statements.last;
       }
     }
     if (_isEmptyStatementOrEmptyBlock(node)) {
-      node = node.parent;
+      node = node.parent!;
     }
     for (var error in statementContext.resolveResult.errors) {
       if (error.offset >= node.offset && error.offset <= node.end) {
@@ -158,41 +158,41 @@
       }
     }
 
-    _checkExpressions();
+    _checkExpressions(node);
     if (node is Statement) {
       if (errors.isEmpty) {
-        if (_complete_ifStatement() ||
-            _complete_forStatement2() ||
-            _complete_whileStatement() ||
-            _complete_controlFlowBlock()) {
-          return completion;
+        if (_complete_ifStatement(node) ||
+            _complete_forStatement2(node) ||
+            _complete_whileStatement(node) ||
+            _complete_controlFlowBlock(node)) {
+          return completion!;
         }
       } else {
-        if (_complete_ifStatement() ||
-            _complete_doStatement() ||
-            _complete_forStatement2() ||
-            _complete_functionDeclarationStatement() ||
-            _complete_switchStatement() ||
-            _complete_tryStatement() ||
-            _complete_whileStatement() ||
-            _complete_controlFlowBlock() ||
-            _complete_simpleSemicolon() ||
-            _complete_methodCall()) {
-          return completion;
+        if (_complete_ifStatement(node) ||
+            _complete_doStatement(node) ||
+            _complete_forStatement2(node) ||
+            _complete_functionDeclarationStatement(node) ||
+            _complete_switchStatement(node) ||
+            _complete_tryStatement(node) ||
+            _complete_whileStatement(node) ||
+            _complete_controlFlowBlock(node) ||
+            _complete_simpleSemicolon(node) ||
+            _complete_methodCall(node)) {
+          return completion!;
         }
       }
     } else if (node is Declaration) {
       if (errors.isNotEmpty) {
-        if (_complete_classDeclaration() ||
-            _complete_variableDeclaration() ||
-            _complete_simpleSemicolon() ||
-            _complete_functionDeclaration()) {
-          return completion;
+        if (_complete_classDeclaration(node) ||
+            _complete_variableDeclaration(node) ||
+            _complete_simpleSemicolon(node) ||
+            _complete_functionDeclaration(node)) {
+          return completion!;
         }
       }
     }
     if (_complete_simpleEnter()) {
-      return completion;
+      return completion!;
     }
     return NO_COMPLETION;
   }
@@ -259,16 +259,16 @@
     return text;
   }
 
-  void _checkExpressions() {
+  void _checkExpressions(AstNode node) {
     // Note: This may queue edits that have to be accounted for later.
     // See _lengthOfInsertions().
-    AstNode errorMatching(errorCode, {partialMatch}) {
+    AstNode? errorMatching(errorCode, {partialMatch}) {
       var error = _findError(errorCode, partialMatch: partialMatch);
       if (error == null) {
         return null;
       }
       var expr = _selectedNode();
-      return (expr.thisOrAncestorOfType<StringInterpolation>() == null)
+      return (expr?.thisOrAncestorOfType<StringInterpolation>() == null)
           ? expr
           : null;
     }
@@ -306,9 +306,8 @@
         errorMatching(ScannerErrorCode.EXPECTED_TOKEN, partialMatch: "']'");
     if (expr != null) {
       expr = expr.thisOrAncestorOfType<ListLiteral>();
-      if (expr != null) {
-        ListLiteral lit = expr;
-        if (lit.rightBracket.isSynthetic) {
+      if (expr is ListLiteral) {
+        if (expr.rightBracket.isSynthetic) {
           var src = utils.getNodeText(expr).trim();
           var loc = expr.offset + src.length;
           if (src.contains(eol)) {
@@ -351,14 +350,13 @@
     */
   }
 
-  bool _complete_classDeclaration() {
+  bool _complete_classDeclaration(AstNode node) {
     if (node is! ClassDeclaration) {
       return false;
     }
-    ClassDeclaration decl = node;
-    if (decl.leftBracket.isSynthetic && errors.length == 1) {
+    if (node.leftBracket.isSynthetic && errors.length == 1) {
       // The space before the left brace is assumed to exist, even if it does not.
-      var sb = SourceBuilder(file, decl.end - 1);
+      var sb = SourceBuilder(file, node.end - 1);
       sb.append(' ');
       _appendEmptyBraces(sb, true);
       _insertBuilder(sb);
@@ -368,19 +366,18 @@
     return false;
   }
 
-  bool _complete_controlFlowBlock() {
+  bool _complete_controlFlowBlock(AstNode node) {
     var expr = (node is ExpressionStatement)
-        ? (node as ExpressionStatement).expression
-        : (node is ReturnStatement
-            ? (node as ReturnStatement).expression
-            : null);
+        ? node.expression
+        : (node is ReturnStatement ? node.expression : null);
     if (!(node is ReturnStatement || expr is ThrowExpression)) {
       return false;
     }
-    if (node.parent is! Block) {
+    var parent = node.parent;
+    if (parent is! Block) {
       return false;
     }
-    var outer = node.parent.parent;
+    var outer = parent.parent;
     if (!(outer is DoStatement ||
         outer is ForStatement ||
         outer is IfStatement ||
@@ -399,12 +396,10 @@
         // Because of this, check for length == 0 rather than isSynthetic.
         if (expr == null || expr.length == 0) {
           if (node is ReturnStatement) {
-            insertOffset = (node as ReturnStatement).returnKeyword.end;
+            insertOffset = node.returnKeyword.end;
           } else if (node is ExpressionStatement) {
             insertOffset =
-                ((node as ExpressionStatement).expression as ThrowExpression)
-                    .throwKeyword
-                    .end;
+                (node.expression as ThrowExpression).throwKeyword.end;
           } else {
             insertOffset = node.end; // Not reached.
           }
@@ -417,71 +412,65 @@
         delta = 1;
       }
     }
-    var offset = _appendNewlinePlusIndentAt(node.parent.end);
+    var offset = _appendNewlinePlusIndentAt(parent.end);
     exitPosition = Position(file, offset + delta + previousInsertions);
     _setCompletion(DartStatementCompletion.COMPLETE_CONTROL_FLOW_BLOCK);
     return true;
   }
 
-  bool _complete_doStatement() {
+  bool _complete_doStatement(AstNode node) {
     if (node is! DoStatement) {
       return false;
     }
-    DoStatement statement = node;
-    var sb = _sourceBuilderAfterKeyword(statement.doKeyword);
-    var hasWhileKeyword =
-        statement.whileKeyword != null && !statement.whileKeyword.isSynthetic;
+    var sb = _sourceBuilderAfterKeyword(node, node.doKeyword);
+    var hasWhileKeyword = !node.whileKeyword.isSynthetic;
     var exitDelta = 0;
-    if (!_statementHasValidBody(statement.doKeyword, statement.body)) {
-      var text = utils.getNodeText(statement.body);
+    if (!_statementHasValidBody(node.doKeyword, node.body)) {
+      var text = utils.getNodeText(node.body);
       var delta = 0;
       if (text.startsWith(';')) {
         delta = 1;
-        _addReplaceEdit(range.startLength(statement.body, delta), '');
+        _addReplaceEdit(range.startLength(node.body, delta), '');
         if (hasWhileKeyword) {
-          text = utils.getNodeText(statement);
+          text = utils.getNodeText(node);
           if (text.indexOf(RegExp(r'do\s*;\s*while')) == 0) {
             var end = text.indexOf('while');
             var start = text.indexOf(';') + 1;
             delta += end - start - 1;
-            _addReplaceEdit(
-                SourceRange(start + statement.offset, end - start), ' ');
+            _addReplaceEdit(SourceRange(start + node.offset, end - start), ' ');
           }
         }
         sb = SourceBuilder(file, sb.offset + delta);
         sb.append(' ');
       }
-      _appendEmptyBraces(sb,
-          !(hasWhileKeyword && _isSyntheticExpression(statement.condition)));
+      _appendEmptyBraces(
+          sb, !(hasWhileKeyword && _isSyntheticExpression(node.condition)));
       if (delta != 0) {
         exitDelta = sb.length - delta;
       }
-    } else if (_isEmptyBlock(statement.body)) {
-      sb = SourceBuilder(sb.file, statement.body.end);
+    } else if (_isEmptyBlock(node.body)) {
+      sb = SourceBuilder(sb.file, node.body.end);
     }
-    SourceBuilder sb2;
+    SourceBuilder? sb2;
     if (hasWhileKeyword) {
-      var stmt = _KeywordConditionBlockStructure(
-          statement.whileKeyword,
-          statement.leftParenthesis,
-          statement.condition,
-          statement.rightParenthesis,
-          null);
-      sb2 = _complete_keywordCondition(stmt);
+      var stmt = _KeywordConditionBlockStructure(node.whileKeyword,
+          node.leftParenthesis, node.condition, node.rightParenthesis, null);
+      sb2 = _complete_keywordCondition(node, stmt);
       if (sb2 == null) {
         return false;
       }
       if (sb2.length == 0) {
         // true if condition is '()'
+        final exitPosition = this.exitPosition;
         if (exitPosition != null) {
-          if (statement.semicolon.isSynthetic) {
+          if (node.semicolon.isSynthetic) {
             _insertBuilder(sb);
             sb = SourceBuilder(file, exitPosition.offset + 1);
             sb.append(';');
           }
         }
       } else {
-        if (sb.exitOffset == null && sb2?.exitOffset != null) {
+        if (sb.exitOffset == null && sb2.exitOffset != null) {
           _insertBuilder(sb);
           sb = sb2;
           sb.append(';');
@@ -497,7 +486,7 @@
     _insertBuilder(sb);
     if (exitDelta != 0) {
       exitPosition =
-          Position(exitPosition.file, exitPosition.offset + exitDelta);
+          Position(exitPosition!.file, exitPosition!.offset + exitDelta);
     }
     _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT);
     return true;
@@ -514,6 +503,7 @@
       throw StateError('Unrecognized for loop parts');
     }
     return _complete_forEachStatementRest(
+        forNode,
         forNode.forKeyword,
         forNode.leftParenthesis,
         name,
@@ -524,11 +514,12 @@
   }
 
   bool _complete_forEachStatementRest(
+      AstNode node,
       Token forKeyword,
       Token leftParenthesis,
-      AstNode name,
+      AstNode? name,
       Token inKeyword,
-      Expression iterable,
+      Expression? iterable,
       Token rightParenthesis,
       Statement body) {
     if (inKeyword.isSynthetic) {
@@ -577,7 +568,7 @@
         return false;
       }
       // keywordOnly (unit test name suffix that exercises this branch)
-      sb = _sourceBuilderAfterKeyword(forNode.forKeyword);
+      sb = _sourceBuilderAfterKeyword(forNode, forNode.forKeyword);
       sb.append('(');
       sb.setExitOffset();
       sb.append(')');
@@ -625,18 +616,19 @@
         sb = SourceBuilder(file, forNode.rightParenthesis.offset);
       } else if (forParts is ForPartsWithExpression &&
           forParts.initialization is SimpleIdentifier &&
-          forParts.initialization.beginToken.lexeme == 'in') {
+          forParts.initialization!.beginToken.lexeme == 'in') {
         // looks like a for/each statement missing the loop variable
         return _complete_forEachStatementRest(
+            forNode,
             forNode.forKeyword,
             forNode.leftParenthesis,
             null,
-            forParts.initialization.beginToken,
+            forParts.initialization!.beginToken,
             null,
             forNode.rightParenthesis,
             forNode.body);
       } else {
-        var start = forParts.condition.offset + forParts.condition.length;
+        var start = forParts.condition!.offset + forParts.condition!.length;
         var text = utils.getNodeText(forNode).substring(start - forNode.offset);
         if (text.startsWith(RegExp(r'\s*\)'))) {
           // missingLeftSeparator
@@ -651,12 +643,12 @@
         }
       }
     }
-    if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) {
+    var body = forNode.body;
+    if (!_statementHasValidBody(forNode.forKeyword, body)) {
       // keywordOnly, noError
       sb.append(' ');
       _appendEmptyBraces(sb, true /*exitPosition == null*/);
-    } else if (forNode.body is Block) {
-      Block body = forNode.body;
+    } else if (body is Block) {
       if (body.rightBracket.end <= selectionOffset) {
         // emptyInitializersAfterBody
         errors = []; // Ignore errors; they are for previous statement.
@@ -668,8 +660,7 @@
     return true;
   }
 
-  bool _complete_forStatement2() {
-    var node = this.node;
+  bool _complete_forStatement2(AstNode node) {
     if (node is ForStatement) {
       var forLoopParts = node.forLoopParts;
       if (forLoopParts is ForParts) {
@@ -681,7 +672,7 @@
     return false;
   }
 
-  bool _complete_functionDeclaration() {
+  bool _complete_functionDeclaration(AstNode node) {
     if (node is! MethodDeclaration && node is! FunctionDeclaration) {
       return false;
     }
@@ -698,11 +689,19 @@
 
     int paramListEnd;
     if (node is FunctionDeclaration) {
-      FunctionDeclaration func = node;
-      paramListEnd = computeExitPos(func.functionExpression.parameters);
+      var parameters = node.functionExpression.parameters;
+      if (parameters == null) {
+        return false;
+      }
+      paramListEnd = computeExitPos(parameters);
+    } else if (node is MethodDeclaration) {
+      var parameters = node.parameters;
+      if (parameters == null) {
+        return false;
+      }
+      paramListEnd = computeExitPos(parameters);
     } else {
-      MethodDeclaration meth = node;
-      paramListEnd = computeExitPos(meth.parameters);
+      return false;
     }
     var sb = SourceBuilder(file, paramListEnd);
     if (needsParen) {
@@ -715,26 +714,23 @@
     return true;
   }
 
-  bool _complete_functionDeclarationStatement() {
+  bool _complete_functionDeclarationStatement(AstNode node) {
     if (node is! FunctionDeclarationStatement) {
       return false;
     }
     var error = _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'");
     if (error != null) {
-      FunctionDeclarationStatement stmt = node;
-      var src = utils.getNodeText(stmt);
-      var insertOffset = stmt.functionDeclaration.end - 1;
-      if (stmt.functionDeclaration.functionExpression.body
-          is ExpressionFunctionBody) {
-        ExpressionFunctionBody fnb =
-            stmt.functionDeclaration.functionExpression.body;
-        var fnbOffset = fnb.functionDefinition.offset;
-        var fnSrc = src.substring(fnbOffset - stmt.offset);
+      var src = utils.getNodeText(node);
+      var insertOffset = node.functionDeclaration.end - 1;
+      var body = node.functionDeclaration.functionExpression.body;
+      if (body is ExpressionFunctionBody) {
+        var fnbOffset = body.functionDefinition.offset;
+        var fnSrc = src.substring(fnbOffset - node.offset);
         if (!fnSrc.startsWith('=>')) {
           return false;
         }
         var delta = 0;
-        if (fnb.expression.isSynthetic) {
+        if (body.expression.isSynthetic) {
           if (!fnSrc.startsWith('=> ')) {
             _addInsertEdit(insertOffset, ' ');
             delta = 1;
@@ -754,12 +750,12 @@
     return false;
   }
 
-  bool _complete_ifOrWhileStatement(
+  bool _complete_ifOrWhileStatement(AstNode node,
       _KeywordConditionBlockStructure statement, StatementCompletionKind kind) {
-    if (_statementHasValidBody(statement.keyword, statement.block)) {
+    if (_statementHasValidBody(statement.keyword, statement.block!)) {
       return false;
     }
-    var sb = _complete_keywordCondition(statement);
+    var sb = _complete_keywordCondition(node, statement);
     if (sb == null) {
       return false;
     }
@@ -768,24 +764,24 @@
     _appendEmptyBraces(sb, exitPosition == null);
     _insertBuilder(sb);
     if (overshoot != 0) {
-      exitPosition = _newPosition(exitPosition.offset - overshoot);
+      exitPosition = _newPosition(exitPosition!.offset - overshoot);
     }
     _setCompletion(kind);
     return true;
   }
 
-  bool _complete_ifStatement() {
+  bool _complete_ifStatement(AstNode node) {
     if (node is! IfStatement) {
       return false;
     }
-    IfStatement ifNode = node;
-    if (ifNode.elseKeyword != null) {
-      if (selectionOffset >= ifNode.elseKeyword.end &&
-          _isEmptyStatement(ifNode.elseStatement)) {
+    var elseKeyword = node.elseKeyword;
+    if (elseKeyword != null) {
+      if (selectionOffset >= elseKeyword.end &&
+          _isEmptyStatement(node.elseStatement)) {
         var sb = SourceBuilder(file, selectionOffset);
-        var src = utils.getNodeText(ifNode);
+        var src = utils.getNodeText(node);
         if (!src
-            .substring(ifNode.elseKeyword.end - node.offset)
+            .substring(elseKeyword.end - node.offset)
             .startsWith(RegExp(r'[ \t]'))) {
           sb.append(' ');
         }
@@ -797,24 +793,24 @@
       return false;
     }
     var stmt = _KeywordConditionBlockStructure(
-        ifNode.ifKeyword,
-        ifNode.leftParenthesis,
-        ifNode.condition,
-        ifNode.rightParenthesis,
-        ifNode.thenStatement);
+        node.ifKeyword,
+        node.leftParenthesis,
+        node.condition,
+        node.rightParenthesis,
+        node.thenStatement);
     return _complete_ifOrWhileStatement(
-        stmt, DartStatementCompletion.COMPLETE_IF_STMT);
+        node, stmt, DartStatementCompletion.COMPLETE_IF_STMT);
   }
 
-  SourceBuilder _complete_keywordCondition(
-      _KeywordConditionBlockStructure statement) {
+  SourceBuilder? _complete_keywordCondition(
+      AstNode node, _KeywordConditionBlockStructure statement) {
     SourceBuilder sb;
     if (statement.leftParenthesis.isSynthetic) {
       if (!statement.rightParenthesis.isSynthetic) {
         // Quite unlikely to see this so don't try to fix it.
         return null;
       }
-      sb = _sourceBuilderAfterKeyword(statement.keyword);
+      sb = _sourceBuilderAfterKeyword(node, statement.keyword);
       sb.append('(');
       sb.setExitOffset();
       sb.append(')');
@@ -841,18 +837,19 @@
     return sb;
   }
 
-  bool _complete_methodCall() {
+  bool _complete_methodCall(AstNode node) {
     var parenError =
         _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "')'") ??
             _findError(ScannerErrorCode.EXPECTED_TOKEN, partialMatch: "')'");
     if (parenError == null) {
       return false;
     }
-    AstNode argList =
-        _selectedNode(at: selectionOffset).thisOrAncestorOfType<ArgumentList>();
+    var argList = _selectedNode(at: selectionOffset)
+        ?.thisOrAncestorOfType<ArgumentList>();
     argList ??= _selectedNode(at: parenError.offset)
-        .thisOrAncestorOfType<ArgumentList>();
-    if (argList?.thisOrAncestorMatching((n) => n == node) == null) {
+        ?.thisOrAncestorOfType<ArgumentList>();
+    if (argList == null ||
+        argList.thisOrAncestorMatching((n) => n == node) == null) {
       return false;
     }
     var previousInsertions = _lengthOfInsertions();
@@ -891,7 +888,7 @@
     return true;
   }
 
-  bool _complete_simpleSemicolon() {
+  bool _complete_simpleSemicolon(AstNode node) {
     if (errors.length != 1) {
       return false;
     }
@@ -908,41 +905,39 @@
     return false;
   }
 
-  bool _complete_switchStatement() {
+  bool _complete_switchStatement(AstNode node) {
     if (node is! SwitchStatement) {
       return false;
     }
     SourceBuilder sb;
-    SwitchStatement switchNode = node;
-    if (switchNode.leftParenthesis.isSynthetic &&
-        switchNode.rightParenthesis.isSynthetic) {
-      exitPosition = Position(file, switchNode.switchKeyword.end + 2);
-      var src = utils.getNodeText(switchNode);
+    if (node.leftParenthesis.isSynthetic && node.rightParenthesis.isSynthetic) {
+      exitPosition = Position(file, node.switchKeyword.end + 2);
+      var src = utils.getNodeText(node);
       if (src
-          .substring(switchNode.switchKeyword.end - switchNode.offset)
+          .substring(node.switchKeyword.end - node.offset)
           .startsWith(RegExp(r'[ \t]+'))) {
-        sb = SourceBuilder(file, switchNode.switchKeyword.end + 1);
+        sb = SourceBuilder(file, node.switchKeyword.end + 1);
       } else {
-        sb = SourceBuilder(file, switchNode.switchKeyword.end);
+        sb = SourceBuilder(file, node.switchKeyword.end);
         sb.append(' ');
       }
       sb.append('()');
-    } else if (switchNode.leftParenthesis.isSynthetic ||
-        switchNode.rightParenthesis.isSynthetic) {
+    } else if (node.leftParenthesis.isSynthetic ||
+        node.rightParenthesis.isSynthetic) {
       return false;
     } else {
-      sb = SourceBuilder(file, switchNode.rightParenthesis.offset + 1);
-      if (_isSyntheticExpression(switchNode.expression)) {
-        exitPosition = Position(file, switchNode.leftParenthesis.offset + 1);
+      sb = SourceBuilder(file, node.rightParenthesis.offset + 1);
+      if (_isSyntheticExpression(node.expression)) {
+        exitPosition = Position(file, node.leftParenthesis.offset + 1);
       }
     }
-    if (switchNode
+    if (node
         .leftBracket.isSynthetic /*&& switchNode.rightBracket.isSynthetic*/) {
       // See https://github.com/dart-lang/sdk/issues/29391
       sb.append(' ');
       _appendEmptyBraces(sb, exitPosition == null);
     } else {
-      var member = _findInvalidElement(switchNode.members);
+      var member = _findInvalidElement(node.members);
       if (member != null) {
         if (member.colon.isSynthetic) {
           var loc =
@@ -958,98 +953,103 @@
     return true;
   }
 
-  bool _complete_tryStatement() {
+  bool _complete_tryStatement(AstNode node) {
     if (node is! TryStatement) {
       return false;
     }
-    TryStatement tryNode = node;
-    SourceBuilder sb;
-    CatchClause catchNode;
     var addSpace = true;
-    if (tryNode.body.leftBracket.isSynthetic) {
-      var src = utils.getNodeText(tryNode);
+    if (node.body.leftBracket.isSynthetic) {
+      var src = utils.getNodeText(node);
+      SourceBuilder sb;
       if (src
-          .substring(tryNode.tryKeyword.end - tryNode.offset)
+          .substring(node.tryKeyword.end - node.offset)
           .startsWith(RegExp(r'[ \t]+'))) {
         // keywordSpace
-        sb = SourceBuilder(file, tryNode.tryKeyword.end + 1);
+        sb = SourceBuilder(file, node.tryKeyword.end + 1);
       } else {
         // keywordOnly
-        sb = SourceBuilder(file, tryNode.tryKeyword.end);
+        sb = SourceBuilder(file, node.tryKeyword.end);
         sb.append(' ');
       }
       _appendEmptyBraces(sb, true);
       _insertBuilder(sb);
-      sb = null;
-    } else if ((catchNode = _findInvalidElement(tryNode.catchClauses)) !=
-        null) {
-      if (catchNode.onKeyword != null) {
-        if (catchNode.exceptionType.length == 0 ||
-            null !=
-                _findError(CompileTimeErrorCode.NON_TYPE_IN_CATCH_CLAUSE,
-                    partialMatch: "name 'catch")) {
-          var src = utils.getNodeText(catchNode);
-          if (src.startsWith(RegExp(r'on[ \t]+'))) {
-            if (src.startsWith(RegExp(r'on[ \t][ \t]+'))) {
-              // onSpaces
-              exitPosition = Position(file, catchNode.onKeyword.end + 1);
-              sb = SourceBuilder(file, catchNode.onKeyword.end + 2);
-              addSpace = false;
+    } else {
+      SourceBuilder? sb;
+      var catchNode = _findInvalidElement(node.catchClauses);
+      if (catchNode != null) {
+        var onKeyword = catchNode.onKeyword;
+        var exceptionType = catchNode.exceptionType;
+        if (onKeyword != null && exceptionType != null) {
+          if (exceptionType.length == 0 ||
+              _findError(CompileTimeErrorCode.NON_TYPE_IN_CATCH_CLAUSE,
+                      partialMatch: "name 'catch") !=
+                  null) {
+            var src = utils.getNodeText(catchNode);
+            if (src.startsWith(RegExp(r'on[ \t]+'))) {
+              if (src.startsWith(RegExp(r'on[ \t][ \t]+'))) {
+                // onSpaces
+                exitPosition = Position(file, onKeyword.end + 1);
+                sb = SourceBuilder(file, onKeyword.end + 2);
+                addSpace = false;
+              } else {
+                // onSpace
+                sb = SourceBuilder(file, onKeyword.end + 1);
+                sb.setExitOffset();
+              }
             } else {
-              // onSpace
-              sb = SourceBuilder(file, catchNode.onKeyword.end + 1);
+              // onOnly
+              sb = SourceBuilder(file, onKeyword.end);
+              sb.append(' ');
               sb.setExitOffset();
             }
           } else {
-            // onOnly
-            sb = SourceBuilder(file, catchNode.onKeyword.end);
-            sb.append(' ');
-            sb.setExitOffset();
+            // onType
+            sb = SourceBuilder(file, exceptionType.end);
           }
-        } else {
-          // onType
-          sb = SourceBuilder(file, catchNode.exceptionType.end);
         }
-      }
-      if (catchNode.catchKeyword != null) {
-        // catchOnly
-        var struct = _KeywordConditionBlockStructure(
-            catchNode.catchKeyword,
-            catchNode.leftParenthesis,
-            catchNode.exceptionParameter,
-            catchNode.rightParenthesis,
-            catchNode.body);
+        var catchKeyword = catchNode.catchKeyword;
+        if (catchKeyword != null) {
+          // catchOnly
+          var struct = _KeywordConditionBlockStructure(
+              catchKeyword,
+              catchNode.leftParenthesis!,
+              catchNode.exceptionParameter!,
+              catchNode.rightParenthesis!,
+              catchNode.body);
+          if (sb != null) {
+            // onCatch
+            _insertBuilder(sb);
+          }
+          sb = _complete_keywordCondition(node, struct);
+          if (sb == null) {
+            return false;
+          }
+        }
         if (sb != null) {
-          // onCatch
+          if (catchNode.body.leftBracket.isSynthetic) {
+            // onOnly and others
+            if (addSpace) {
+              sb.append(' ');
+            }
+            _appendEmptyBraces(sb, exitPosition == null);
+          }
           _insertBuilder(sb);
         }
-        sb = _complete_keywordCondition(struct);
-        if (sb == null) {
-          return false;
-        }
-      }
-      if (catchNode.body.leftBracket.isSynthetic) {
-        // onOnly and others
-        if (addSpace) {
+      } else if (node.finallyKeyword != null) {
+        if (node.finallyBlock!.leftBracket.isSynthetic) {
+          // finallyOnly
+          sb = SourceBuilder(file, node.finallyKeyword!.end);
           sb.append(' ');
+          _appendEmptyBraces(sb, true);
+          _insertBuilder(sb);
         }
-        _appendEmptyBraces(sb, exitPosition == null);
-      }
-      _insertBuilder(sb);
-    } else if (tryNode.finallyKeyword != null) {
-      if (tryNode.finallyBlock.leftBracket.isSynthetic) {
-        // finallyOnly
-        sb = SourceBuilder(file, tryNode.finallyKeyword.end);
-        sb.append(' ');
-        _appendEmptyBraces(sb, true);
-        _insertBuilder(sb);
       }
     }
     _setCompletion(DartStatementCompletion.COMPLETE_TRY_STMT);
     return true;
   }
 
-  bool _complete_variableDeclaration() {
+  bool _complete_variableDeclaration(AstNode node) {
     if (node is! VariableDeclaration) {
       return false;
     }
@@ -1059,36 +1059,25 @@
     return true;
   }
 
-  bool _complete_whileStatement() {
+  bool _complete_whileStatement(AstNode node) {
     if (node is! WhileStatement) {
       return false;
     }
-    WhileStatement whileNode = node;
-    if (whileNode != null) {
-      var stmt = _KeywordConditionBlockStructure(
-          whileNode.whileKeyword,
-          whileNode.leftParenthesis,
-          whileNode.condition,
-          whileNode.rightParenthesis,
-          whileNode.body);
-      return _complete_ifOrWhileStatement(
-          stmt, DartStatementCompletion.COMPLETE_WHILE_STMT);
-    }
-    return false;
+    var stmt = _KeywordConditionBlockStructure(node.whileKeyword,
+        node.leftParenthesis, node.condition, node.rightParenthesis, node.body);
+    return _complete_ifOrWhileStatement(
+        node, stmt, DartStatementCompletion.COMPLETE_WHILE_STMT);
   }
 
-  engine.AnalysisError _findError(ErrorCode code, {partialMatch}) {
-    return errors.firstWhere(
-        (err) =>
-            err.errorCode == code &&
-            (partialMatch == null ? true : err.message.contains(partialMatch)),
-        orElse: () => null);
+  engine.AnalysisError? _findError(ErrorCode code, {partialMatch}) {
+    return errors.firstWhereOrNull((err) =>
+        err.errorCode == code &&
+        (partialMatch == null ? true : err.message.contains(partialMatch)));
   }
 
-  T _findInvalidElement<T extends AstNode>(NodeList<T> list) {
-    return list.firstWhere(
-        (item) => selectionOffset >= item.offset && selectionOffset <= item.end,
-        orElse: () => null);
+  T? _findInvalidElement<T extends AstNode>(NodeList<T> list) {
+    return list.firstWhereOrNull((item) =>
+        selectionOffset >= item.offset && selectionOffset <= item.end);
   }
 
   void _insertBuilder(SourceBuilder builder, [int length = 0]) {
@@ -1110,7 +1099,7 @@
     return stmt is Block && stmt.statements.isEmpty;
   }
 
-  bool _isEmptyStatement(AstNode stmt) {
+  bool _isEmptyStatement(AstNode? stmt) {
     if (stmt is ExpressionStatement) {
       var expression = stmt.expression;
       if (expression is SimpleIdentifier) {
@@ -1137,7 +1126,7 @@
         p?.parent?.parent is! Statement;
   }
 
-  bool _isSyntheticExpression(Expression expr) {
+  bool _isSyntheticExpression(Expression? expr) {
     return expr is SimpleIdentifier && expr.isSynthetic;
   }
 
@@ -1187,10 +1176,10 @@
     }
   }
 
-  AstNode _selectedNode({int at}) =>
+  AstNode? _selectedNode({int? at}) =>
       NodeLocator(at ?? selectionOffset).searchWithin(unit);
 
-  void _setCompletion(StatementCompletionKind kind, [List args]) {
+  void _setCompletion(StatementCompletionKind kind, [List? args]) {
     assert(exitPosition != null);
     change.selection = exitPosition;
     change.message = formatList(kind.message, args);
@@ -1199,12 +1188,13 @@
     completion = StatementCompletion(kind, change);
   }
 
-  void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) {
+  void _setCompletionAt(StatementCompletionKind kind, int offset,
+      [List? args]) {
     exitPosition = _newPosition(offset);
     _setCompletion(kind, args);
   }
 
-  SourceBuilder _sourceBuilderAfterKeyword(Token keyword) {
+  SourceBuilder _sourceBuilderAfterKeyword(AstNode node, Token keyword) {
     SourceBuilder sb;
     var text = _baseNodeText(node);
     text = text.substring(keyword.offset - node.offset);
@@ -1239,7 +1229,7 @@
   final Token keyword;
   final Token leftParenthesis, rightParenthesis;
   final Expression condition;
-  final Statement block;
+  final Statement? block;
 
   _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis,
       this.condition, this.rightParenthesis, this.block);
diff --git a/pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart b/pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart
index 6d787d6..741715f 100644
--- a/pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart
+++ b/pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart
@@ -2,8 +2,6 @@
 // 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.
 
-// @dart = 2.9
-
 import 'package:analysis_server/src/protocol_server.dart';
 import 'package:analysis_server/src/services/completion/statement/statement_completion.dart';
 import 'package:test/test.dart';
@@ -28,7 +26,7 @@
 }
 
 class StatementCompletionTest extends AbstractSingleUnitTest {
-  SourceChange change;
+  late SourceChange change;
 
   int _after(String source, String match) =>
       source.indexOf(match) + match.length;
@@ -39,7 +37,7 @@
   void _assertHasChange(
     String message,
     String expectedCode, [
-    int Function(String) cmp,
+    int Function(String)? cmp,
   ]) {
     if (change.message == message) {
       if (change.edits.isNotEmpty) {
@@ -48,13 +46,13 @@
         expect(resultCode, expectedCode.replaceAll('////', ''));
         if (cmp != null) {
           var offset = cmp(resultCode);
-          expect(change.selection.offset, offset);
+          expect(change.selection!.offset, offset);
         }
       } else {
         expect(testCode, expectedCode.replaceAll('////', ''));
         if (cmp != null) {
           var offset = cmp(testCode);
-          expect(change.selection.offset, offset);
+          expect(change.selection!.offset, offset);
         }
       }
       return;