// 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.

import 'package:analysis_server/src/services/completion/dart/candidate_suggestion.dart';
import 'package:analysis_server/src/services/completion/dart/completion_state.dart';
import 'package:analysis_server/src/services/completion/dart/declaration_helper.dart';
import 'package:analysis_server/src/services/completion/dart/identifier_helper.dart';
import 'package:analysis_server/src/services/completion/dart/keyword_helper.dart';
import 'package:analysis_server/src/services/completion/dart/label_helper.dart';
import 'package:analysis_server/src/services/completion/dart/not_imported_completion_pass.dart';
import 'package:analysis_server/src/services/completion/dart/override_helper.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
import 'package:analysis_server/src/services/completion/dart/uri_helper.dart';
import 'package:analysis_server/src/services/completion/dart/utilities.dart';
import 'package:analysis_server/src/services/completion/dart/visibility_tracker.dart';
import 'package:analysis_server/src/utilities/extensions/ast.dart';
import 'package:analysis_server/src/utilities/extensions/completion_request.dart';
import 'package:analysis_server/src/utilities/extensions/object.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/utilities/extensions/ast.dart';
import 'package:analyzer/src/utilities/extensions/flutter.dart';

/// A completion pass that will create candidate suggestions based on the
/// elements in scope in the library containing the selection, as well as
/// suggestions that are not related to elements, such as keywords.
//
// The visit methods in this class are allowed to visit the parent of the
// visited node (the covering node), but are not allowed to visit children. This
// rule will prevent the introduction of an infinite loop between the visit
// methods of the parent and child.
class InScopeCompletionPass extends SimpleAstVisitor<void> {
  /// The state used to compute the candidate suggestions.
  final CompletionState state;

  /// The suggestion collector to which suggestions will be added.
  final SuggestionCollector collector;

  /// Whether the generation of suggestions for imports should be skipped. This
  /// exists as a temporary measure that will be removed after all of the
  /// suggestions are being produced by the various passes.
  final bool skipImports;

  /// Whether suggestions for overrides should be produced.
  final bool suggestOverrides;

  /// Whether suggestions for URIs should be produced.
  final bool suggestUris;

  /// The helper used to suggest names at the declaration site.
  IdentifierHelper? _identifierHelper;

  /// The helper used to suggest keywords.
  late final keywordHelper = KeywordHelper(
    state: state,
    collector: collector,
    featureSet: featureSet,
    offset: offset,
  );

  /// The helper used to suggest labels.
  late final labelHelper = LabelHelper(state: state, collector: collector);

  /// The helper used to suggest declarations that are in scope.
  DeclarationHelper? _declarationHelper;

  /// The helper used to suggest overrides of inherited members.
  late final overrideHelper = OverrideHelper(
    collector: collector,
    state: state,
  );

  /// Initialize a newly created completion visitor that can use the [state] to
  /// add candidate suggestions to the [collector].
  ///
  /// The flag [skipImports] is a temporary measure that will be removed after
  /// all of the suggestions are being produced by the various passes.
  new({
    required this.state,
    required this.collector,
    required this.skipImports,
    required this.suggestOverrides,
    required this.suggestUris,
  });

  /// The feature set that applies to the library for which completions are
  /// being computed.
  FeatureSet get featureSet => state.libraryElement.featureSet;

  /// The operation that should be executed in the [NotImportedCompletionPass].
  ///
  /// The list will be empty if the pass does not need to be run.
  List<NotImportedOperation> get notImportedOperations =>
      _declarationHelper?.notImportedOperations ?? [];

  /// The offset at which completion was requested.
  int get offset => state.selection.offset;

  /// The visibility tracker used by this pass.
  VisibilityTracker? get visibilityTracker =>
      _declarationHelper?.visibilityTracker;

  /// The node that should be used as the context in which completion is
  /// 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
  /// 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
  /// amount of code duplication.
  AstNode get _completionNode {
    var selection = state.selection;
    var coveringNode = selection.coveringNode;
    var beginToken = coveringNode.beginToken;
    if (!beginToken.isKeywordOrIdentifier) {
      // The parser will occasionally recover by using a non-identifier token as
      // if it were an identifier. In such cases we don't want to return the
      // `SimpleIdentifier`, we want to move up the AST as if it had been an
      // identifier token.
      if (coveringNode is! SimpleIdentifier) {
        return coveringNode;
      }
    }
    if (!selection.isCoveredByToken(beginToken)) {
      return coveringNode;
    }
    var child = coveringNode;
    var parent = child.parent;
    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 &&
        !(child is! SimpleIdentifier && parent.isChildInList(child))) {
      return parent;
    }
    return child;
  }

  bool get _isDotShorthandEnabled =>
      state.request.featureSet.isEnabled(.dot_shorthands);

  /// Computes the candidate suggestions associated with this pass.
  void computeSuggestions() {
    var completionNode = _completionNode;
    // TODO(scheglov): update the visitor properly
    if (completionNode is BlockClassBody) {
      completionNode = completionNode.parent!;
    }
    if (completionNode is EnumBody) {
      completionNode = completionNode.parent!;
    }
    completionNode.accept(this);
  }

  /// Returns the helper used to suggest declarations that are in scope.
  DeclarationHelper declarationHelper({
    bool mustBeAssignable = false,
    bool mustBeConstant = false,
    bool mustBeExtensible = false,
    bool mustBeImplementable = false,
    bool mustBeMixable = false,
    bool mustBeNonVoid = false,
    bool mustBeStatic = false,
    bool mustBeType = false,
    bool excludeTypeNames = false,
    bool objectPatternAllowed = false,
    bool preferNonInvocation = false,
    bool suggestingDotShorthand = false,
    bool suggestUnnamedAsNew = false,
    Set<AstNode> excludedNodes = const {},
  }) {
    var contextType = state.contextType;
    if (contextType?.isDartCoreFunction ?? false) {
      contextType = _voidFunctionNoParameters();
    }
    if (contextType is FunctionType) {
      // TODO(brianwilkerson): Consider passing the context type to the
      //  declaration helper so that we can limit which functions are suggested
      //  to only include those that are a subtype of the context type.
      if (contextType.returnType is VoidType) {
        mustBeNonVoid = false;
        preferNonInvocation = true;
      }
    }
    // Ensure that we aren't attempting to create multiple declaration helpers
    // with inconsistent states.
    assert(() {
      var helper = _declarationHelper;
      return helper == null ||
          (helper.mustBeAssignable == mustBeAssignable &&
              helper.mustBeConstant == mustBeConstant &&
              helper.mustBeExtendable == mustBeExtensible &&
              helper.mustBeImplementable == mustBeImplementable &&
              helper.mustBeMixable == mustBeMixable &&
              helper.mustBeNonVoid == mustBeNonVoid &&
              helper.mustBeStatic == mustBeStatic &&
              helper.mustBeType == mustBeType &&
              helper.preferNonInvocation == preferNonInvocation &&
              helper.objectPatternAllowed == objectPatternAllowed &&
              helper.suggestingDotShorthand == suggestingDotShorthand);
    }());
    return _declarationHelper ??= DeclarationHelper(
      request: state.request,
      collector: collector,
      offset: offset,
      state: state,
      mustBeAssignable: mustBeAssignable,
      mustBeConstant: mustBeConstant,
      mustBeExtendable: mustBeExtensible,
      mustBeImplementable: mustBeImplementable,
      mustBeMixable: mustBeMixable,
      mustBeNonVoid: mustBeNonVoid,
      mustBeStatic: mustBeStatic,
      mustBeType: mustBeType,
      excludeTypeNames: excludeTypeNames,
      objectPatternAllowed: objectPatternAllowed,
      preferNonInvocation: preferNonInvocation,
      suggestingDotShorthand: suggestingDotShorthand,
      suggestUnnamedAsNew: suggestUnnamedAsNew,
      skipImports: skipImports,
      excludedNodes: excludedNodes,
    );
  }

  /// Returns the helper used to suggest names at the declaration site.
  IdentifierHelper identifierHelper({required bool includePrivateIdentifiers}) {
    // Ensure that we aren't attempting to create multiple declaration helpers
    // with inconsistent states.
    assert(() {
      var helper = _identifierHelper;
      return helper == null ||
          (helper.includePrivateIdentifiers == includePrivateIdentifiers);
    }());
    return _identifierHelper ??= IdentifierHelper(
      collector: collector,
      includePrivateIdentifiers: includePrivateIdentifiers,
      state: state,
    );
  }

  @override
  void visitAdjacentStrings(AdjacentStrings node) {
    _visitParentIfAtOrBeforeNode(node);
  }

  @override
  void visitAnnotation(Annotation node) {
    collector.completionLocation = 'Annotation_name';
    _forAnnotation(node);

    // Look for `@override` with an empty line before the next token.
    // So, it is not an annotation for a method, but override request.
    if (node.constructorName == null) {
      if (node.name case SimpleIdentifier name) {
        var classNode = node.parent.parent?.parent;
        if (classNode is Declaration) {
          var lineInfo = state.request.unit.lineInfo;
          var nameLocation = lineInfo.getLocation(name.offset);
          var nextToken = name.token.next;
          if (nextToken != null) {
            var nextLocation = lineInfo.getLocation(nextToken.offset);
            if (nextLocation.lineNumber > nameLocation.lineNumber + 1) {
              _tryOverrideAnnotation(name.token, classNode);
            }
          }
        }
      }
    }
  }

  @override
  void visitArgumentList(ArgumentList node) {
    if (offset <= node.leftParenthesis.offset) {
      node.parent?.accept(this);
    } else if (offset <= node.rightParenthesis.offset) {
      // TODO(brianwilkerson): Consider moving most of this method (and some of
      //  `visitNamedExpression`) into an `ArgumentListHelper`.
      var parent = node.parent;
      if (parent == null) {
        return;
      }
      collector.completionLocation = _locationFor(node, isNamed: false);
      // Compute the index of the positional argument that the user might be
      // trying to complete.
      var arguments = node.arguments;
      var (:before, :after) = node.argumentsBeforeAndAfterOffset(offset);
      var argumentIndex = 0;
      if (before != null) {
        if (_handledPossibleClosure(before)) {
          _forExpression(before, mustBeNonVoid: true);
          return;
        }
        argumentIndex = arguments.indexOf(before);
        if (offset > before.end) {
          argumentIndex = argumentIndex + 1;
        }
      }
      var (:positionalArgumentCount, :usedNames) = node.argumentContext(
        argumentIndex,
      );

      var parameters = node.invokedFormalParameters;
      if (parameters != null) {
        var positionalParameterCount = 0;
        var availableNamedParameters = <FormalParameterElement>[];
        for (int i = 0; i < parameters.length; i++) {
          var parameter = parameters[i];
          if (parameter.isNamed) {
            if (!usedNames.contains(parameter.name)) {
              availableNamedParameters.add(parameter);
            }
          } else {
            positionalParameterCount++;
          }
        }
        // Only suggest expression keywords if it's possible that the user is
        // completing a positional argument.
        if (positionalArgumentCount < positionalParameterCount) {
          // This assumes that the positional parameters will always be first in
          // the list of parameters.
          var parameter = parameters[positionalArgumentCount];
          var parameterType = parameter.type;
          while (parameterType is TypeParameterType) {
            parameterType = parameterType.bound;
          }
          if (parameterType.isDartCoreFunction) {
            parameterType = _voidFunctionNoParameters();
          }
          var isFunctionType = parameterType is FunctionType;
          Expression? argument;
          if (isFunctionType && argumentIndex < arguments.length) {
            argument = arguments[argumentIndex].argumentExpression;
          }
          _forExpression(
            parent,
            mustBeNonVoid: true,
            canBeNull: _canBeNull(parameterType),
            canBeBool: _canBeBool(parameterType),
            includeTrailingCommaAfterClosure:
                argument == null || !argument.isFollowedByComma,
            // TODO(FMorschel): Determine if the parameter type has a constant
            //  constructor.
            // Function tear-offs and closures cannot have the `const` keyword
            // before it
            canSuggestConst: !isFunctionType,
          );
        } else {
          collector.completionLocation = _locationFor(node, isNamed: true);
        }
        // Suggest the names of all named parameters that are not already in the
        // argument list.
        var appendComma = false;
        var commaAfterBefore = before?.endToken.next;
        if (before != null &&
            offset <= before.end &&
            commaAfterBefore != null &&
            commaAfterBefore.type == TokenType.COMMA &&
            !commaAfterBefore.isSynthetic) {
          appendComma = false;
        } else if (after != null) {
          var possibleComma = after.beginToken.previous;
          if (after.isSynthetic) {
            // TODO(brianwilkerson): [argumentsBeforeAndAfterOffset] should
            //  probably be updated so that it doesn't return a synthetic token
            //  as the following argument, but treats it like the offset is
            //  inside the synthetic argument.
            possibleComma = after.endToken.next;
          }
          if (possibleComma != null && possibleComma.type == TokenType.COMMA) {
            if (possibleComma.isSynthetic) {
              if (after is NamedArgument ||
                  before is! SimpleIdentifier ||
                  offset > before.end) {
                appendComma = true;
              }
            } else if (offset >= possibleComma.end) {
              appendComma = true;
            }
          } else {
            appendComma = true;
          }
        } else if (parent is InstanceCreationExpression &&
            parent.isWidgetCreation) {
          appendComma = true;
        }
        int? replacementLength;
        if (offset == before?.offset) {
          replacementLength = 0;
          appendComma = false;
        }
        for (var parameter in availableNamedParameters) {
          var matcherScore = state.matcher.score(parameter.displayName);
          if (matcherScore != -1) {
            var isWidget = isFlutterWidgetParameter(parameter);
            collector.addSuggestion(
              NamedArgumentSuggestion(
                parameter: parameter,
                appendColon: true,
                appendComma: appendComma,
                replacementLength: replacementLength,
                matcherScore: matcherScore,
                preferredQuoteForStrings: state.preferredQuoteForStrings,
                isWidget: isWidget,
              ),
            );
          }
        }
      } else if (parent is Expression) {
        _forExpression(parent, mustBeNonVoid: true);
      }
    } else {
      // There might not be a good location at this point. The cursor is
      // immediately after the right paren, so the only valid next characters
      // are a selector or puctuation.
      //
      // This was added for conformance with now-removed `OpType`.
      collector.completionLocation = 'ExpressionStatement_expression';
    }
  }

  @override
  void visitAsExpression(AsExpression node) {
    if (offset <= node.expression.end) {
      declarationHelper(
        mustBeNonVoid: true,
        mustBeStatic: node.inStaticContext,
      ).addLexicalDeclarations(node);
      return;
    }

    if (node.asOperator.coversOffset(offset)) {
      if (node.expression is ParenthesizedExpression) {
        // If the user has typed `as` after something that could be either a
        // parenthesized expression or a parameter list, the parser will recover
        // by parsing an `as` expression. This handles the case where the user is
        // actually trying to write a function expression.
        // TODO(brianwilkerson): Decide whether we should do more to ensure that
        //  the expression could be a parameter list.
        keywordHelper.addFunctionBodyModifiers(null);
      } else {
        keywordHelper.addKeyword(Keyword.AS);
      }
      return;
    }

    var type = node.type;
    if (type.isFullySynthetic || type.beginToken.coversOffset(offset)) {
      collector.completionLocation = 'AsExpression_type';
      _forTypeAnnotation(node, mustBeNonVoid: true);
    }
  }

  @override
  void visitAssertInitializer(AssertInitializer node) {
    // `assert(^)`
    // `assert(^, '')`
    // `assert(x, ^)`
    if (node.leftParenthesis.end <= offset) {
      var comma = node.comma;
      if (comma == null || offset <= comma.offset) {
        collector.completionLocation = 'AssertInitializer_condition';
        _forExpression(node.condition);
      } else {
        collector.completionLocation = 'AssertInitializer_message';
        _forExpression(node.condition);
      }
      return;
    }

    collector.completionLocation = 'ConstructorDeclaration_initializer';
    keywordHelper.addConstructorInitializerKeywords(
      node.parent as ConstructorDeclaration,
      node,
    );
  }

  @override
  void visitAssertStatement(AssertStatement node) {
    // `assert(^)`
    // `assert(^, '')`
    // `assert(x, ^)`
    if (!node.leftParenthesis.isSynthetic &&
        node.leftParenthesis.end <= offset) {
      var comma = node.comma;
      if (comma == null || offset <= comma.offset) {
        collector.completionLocation = 'AssertStatement_condition';
        _forExpression(node.condition);
      } else {
        collector.completionLocation = 'AssertStatement_message';
        _forExpression(node.condition);
      }
      return;
    }

    if (offset <= node.assertKeyword.end) {
      collector.completionLocation = 'Block_statement';
      _forStatement(node);
    }
  }

  @override
  void visitAssignmentExpression(AssignmentExpression node) {
    collector.completionLocation = 'AssignmentExpression_rightHandSide';
    _forExpression(node, mustBeNonVoid: true);
    // TODO(brianwilkerson): Consider proposing a closure when the left-hand
    //  side is a function-typed variable.
  }

  @override
  void visitAwaitExpression(AwaitExpression node) {
    collector.completionLocation = 'AwaitExpression_expression';
    _forExpression(node, mustBeNonVoid: true);
  }

  @override
  void visitBinaryExpression(BinaryExpression node) {
    var operator = node.operator.lexeme;
    collector.completionLocation = 'BinaryExpression_${operator}_rightOperand';
    _forExpression(node, mustBeNonVoid: true);
  }

  @override
  void visitBlock(Block node) {
    if (node.leftBracket.isSynthetic && node.rightBracket.isSynthetic) {
      node.parent?.accept(this);
    }
    if (offset <= node.leftBracket.offset) {
      var parent = node.parent;
      if (parent is BlockFunctionBody) {
        parent.parent?.accept(this);
      }
      return;
    }
    collector.completionLocation = 'Block_statement';
    var previousStatement = node.statements.elementBefore(offset);
    if (previousStatement is TryStatement) {
      if (previousStatement.finallyBlock == null) {
        // TODO(brianwilkerson): Consider adding `on ^ {}`, `catch (e) {^}`, and
        //  `finally {^}`.
        keywordHelper.addKeyword(Keyword.ON);
        keywordHelper.addKeyword(Keyword.CATCH);
        keywordHelper.addKeyword(Keyword.FINALLY);
        if (previousStatement.catchClauses.isEmpty) {
          // If the try statement has no catch, on, or finally then only suggest
          // these keywords, because at least one of these clauses is required.
          return;
        }
      }
    } else if (previousStatement is IfStatement &&
        previousStatement.elseKeyword == null) {
      keywordHelper.addKeyword(Keyword.ELSE);
    }
    _forStatement(node);
  }

  @override
  void visitBlockEnumBody(BlockEnumBody node) {
    node.parent?.accept(this);
  }

  @override
  void visitBooleanLiteral(BooleanLiteral node) {
    _forExpression(node);
  }

  @override
  void visitBreakStatement(BreakStatement node) {
    var breakEnd = node.breakKeyword.end;
    if (offset <= breakEnd) {
      collector.completionLocation = 'Block_statement';
      keywordHelper.addKeyword(Keyword.BREAK);
    } else if (breakEnd < offset && offset <= node.semicolon.offset) {
      labelHelper.addLabels(node);
    }
  }

  @override
  void visitCascadeExpression(CascadeExpression node) {
    collector.completionLocation = 'CascadeExpression_cascadeSection';
    _forExpression(node);
  }

  @override
  void visitCaseClause(CaseClause node) {
    collector.completionLocation = 'CaseClause_pattern';
    _forPattern(node);
  }

  @override
  void visitCastPattern(CastPattern node) {
    if (node.asToken.coversOffset(offset)) {
      keywordHelper.addKeyword(Keyword.AS);
    } else {
      collector.completionLocation = 'CastPattern_type';
      _forTypeAnnotation(node, mustBeNonVoid: true);
    }
  }

  @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 <= node.body.offset) {
        collector.completionLocation = 'CatchClause_exceptionType';
        _forTypeAnnotation(node);
      } else if (catchKeyword != null && offset < catchKeyword.offset) {
        collector.completionLocation = 'CatchClause_exceptionType';
        _forTypeAnnotation(node);
      }
    }
    if (catchKeyword != null &&
        offset >= catchKeyword.offset &&
        offset <= catchKeyword.end) {
      keywordHelper.addKeyword(Keyword.CATCH);
    }
  }

  @override
  void visitClassDeclaration(ClassDeclaration node) {
    var body = node.body;
    if (body is! BlockClassBody) {
      return;
    }

    // `class X { final String in^; }` is parsed as
    // `final <NoType> String ; in^`, where `in` is dropped.
    var dropped = state.request.target.droppedToken;
    if (dropped != null && dropped.end == offset) {
      if (dropped.type.isKeyword) {
        for (var fieldDeclaration in body.members) {
          if (fieldDeclaration is FieldDeclaration) {
            var fields = fieldDeclaration.fields;
            if (fields.type == null) {
              if (fields.variables case [var field]) {
                var shouldBeTypeName = field.name;
                var semicolon = shouldBeTypeName.next;
                if (semicolon != null &&
                    semicolon.type == TokenType.SEMICOLON &&
                    semicolon.next == dropped) {
                  collector.completionLocation = 'ClassDeclaration_member';
                  identifierHelper(
                    includePrivateIdentifiers: false,
                  ).addSuggestionsFromTypeName(shouldBeTypeName.lexeme);
                  return;
                }
              }
            }
          }
        }
      }
    }

    if (offset == node.offset) {
      _forCompilationUnitMemberBefore(node);
    } else if (offset < node.classKeyword.offset) {
      // TODO(brianwilkerson): The cursor might be before an annotation, in
      //  which case suggesting class modifiers isn't appropriate.
      keywordHelper.addClassModifiers(node);
    } else if (offset <= node.classKeyword.end) {
      keywordHelper.addKeyword(Keyword.CLASS);
    } else if (offset <= node.namePart.typeName.end) {
      if (offset < node.namePart.typeName.offset &&
          featureSet.isEnabled(Feature.primary_constructors) &&
          !node.namePart.hasConst) {
        keywordHelper.addKeyword(Keyword.CONST);
      }
      var hasSyntheticBody =
          body.leftBracket.isSynthetic && body.rightBracket.isSynthetic;
      identifierHelper(
        includePrivateIdentifiers: false,
      ).addTopLevelName(includeBody: hasSyntheticBody);
    } else if (offset <= body.leftBracket.offset) {
      keywordHelper.addClassDeclarationKeywords(node);
    } else if (offset >= body.leftBracket.end &&
        offset <= body.rightBracket.offset) {
      if (_tryAnnotationAtEndOfClassBody(node)) {
        return;
      }

      collector.completionLocation = 'ClassDeclaration_member';
      var members = body.members;
      var precedingMember = members.elementBefore(offset);
      var token = precedingMember?.beginToken ?? body.leftBracket.next!;
      if (token.keyword == Keyword.FINAL) {
        // The user is completing after the keyword `final`, so they're likely
        // trying to declare a field.
        _forTypeAnnotation(node);
        return;
      }
      _forClassMember(node);
      var element = body.members.elementBefore(offset);
      if (element is MethodDeclaration) {
        var body = element.body;
        if (body.isEmpty) {
          keywordHelper.addFunctionBodyModifiers(body);
        }
      }
    } else {
      // The cursor is immediately to the right of the right bracket, so the
      // user is starting a new top-level declaration.
      node.parent?.accept(this);
    }
  }

  @override
  void visitComment(Comment node) {
    // This should never call its children. They should, instead, be called
    // directly by the containing node. Making sure all annotated nodes have
    // the comments on the children call is the right way to handle this case.
  }

  @override
  void visitCommentReference(CommentReference node) {
    collector.completionLocation = 'CommentReference_identifier';
    declarationHelper(preferNonInvocation: true).addLexicalDeclarations(node);
  }

  @override
  void visitCompilationUnit(CompilationUnit node) {
    // This method is only invoked when the cursor is between two members.
    var surroundingMembers = node.membersBeforeAndAfterOffset(offset);
    var before = surroundingMembers.before;
    if (before != null && _handledIncompletePrecedingUnitMember(node, before)) {
      // The  member is incomplete, so assume that the user is completing it
      // rather than starting a new member.
      return;
    }
    _forCompilationUnitMember(node, surroundingMembers);
  }

  @override
  void visitConditionalExpression(ConditionalExpression node) {
    // TODO(brianwilkerson): Consider adding a location for the condition.
    if (offset >= node.question.end && offset <= node.colon.offset) {
      collector.completionLocation = 'ConditionalExpression_thenExpression';
    } else if (offset >= node.colon.end) {
      collector.completionLocation = 'ConditionalExpression_elseExpression';
    }
    _forExpression(node);
  }

  @override
  void visitConstantPattern(ConstantPattern node) {
    var expression = node.expression;
    if (expression is SimpleIdentifier) {
      node.parent?.accept(this);
    }
  }

  @override
  void visitConstructorDeclaration(ConstructorDeclaration node) {
    if (offset <=
        (node.typeName?.end ??
            node.newKeyword?.end ??
            node.factoryKeyword?.end ??
            node.beginToken.offset)) {
      collector.completionLocation = 'ClassDeclaration_member';
      var parent = node.parent?.parent;
      if (parent != null) {
        _forClassLikeMember(parent);
      }
      return;
    }
    var separator = node.separator;
    if (separator == null) {
      return;
    }
    var type = separator.type;
    if (type == TokenType.COLON) {
      if (offset >= separator.end && offset <= node.body.offset) {
        collector.completionLocation = 'ConstructorDeclaration_initializer';
        _forConstructorInitializer(node, null);
      }
    } else if (type == TokenType.EQ) {
      var constructorElement = node.declaredFragment?.element;
      if (constructorElement == null) {
        return;
      }
      var libraryElement = state.libraryElement;
      declarationHelper(
        mustBeConstant: constructorElement.isConst,
      ).addPossibleRedirectionsInLibrary(constructorElement, libraryElement);
    }
  }

  @override
  void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
    var constructor = node.parent;
    if (constructor is! ConstructorDeclaration) {
      return;
    }
    if (offset <= node.equals.offset) {
      collector.completionLocation = 'ConstructorDeclaration_initializer';
      _forConstructorInitializer(constructor, node);
    } else {
      collector.completionLocation = 'ConstructorFieldInitializer_expression';
      if (node.fieldName.isSynthetic && node.equals.isSynthetic) {
        var expression = node.expression;
        if (expression is PropertyAccess &&
            expression.target is ThisExpression) {
          if (expression.operator.isSynthetic) {
            // The parser recovers from `this` by treating it as a property
            // access on the right side of a field initializer. The user appears
            // to be attempting to complete an initializer.
            _forConstructorInitializer(constructor, node);
          } else {
            // The parser recovers from `this.` by treating it as a property
            // access on the right side of a field initializer. The user appears
            // to be attempting to complete the name of a constructor.
            _forRedirectingConstructorInvocation(constructor);
          }
          return;
        }
      }
      _forExpression(node, mustBeNonVoid: true);
    }
  }

  @override
  void visitConstructorName(ConstructorName node) {
    if (node.parent is ConstructorReference) {
      var element = node.type.element;
      if (element is InterfaceElement) {
        declarationHelper(
          preferNonInvocation: true,
        ).addStaticMembersOfElement(element);
      }
    } else {
      var type = node.type.type;
      if (type is InterfaceType) {
        // Suggest factory redirects.
        if (node.parent case ConstructorDeclaration factoryConstructor) {
          if (factoryConstructor.factoryKeyword != null &&
              factoryConstructor.redirectedConstructor == node) {
            declarationHelper(
              mustBeConstant: factoryConstructor.constKeyword != null,
              preferNonInvocation: true,
            ).addConstructorNamesForType(
              type: type,
              exclude: factoryConstructor.name?.lexeme,
            );
            return;
          }
        }
        // Suggest invocations.
        declarationHelper().addStaticMembersOfElement(
          type.element,
          onlyInvocations: true,
        );
      }
    }

    super.visitConstructorName(node);
  }

  @override
  void visitConstructorReference(ConstructorReference node) {
    _forExpression(node);
  }

  @override
  void visitConstructorSelector(ConstructorSelector node) {
    collector.completionLocation = 'ConstructorSelector_name';
    if (!featureSet.isEnabled(Feature.enhanced_enums)) {
      return;
    }

    var arguments = node.parent;
    if (arguments is! EnumConstantArguments) {
      return;
    }

    var enumConstant = arguments.parent;
    if (enumConstant is! EnumConstantDeclaration) {
      return;
    }

    var enumDeclaration = enumConstant.parent?.parent;
    if (enumDeclaration is! EnumDeclaration) {
      return;
    }

    var enumElement = enumDeclaration.declaredFragment!.element;
    declarationHelper(
      suggestUnnamedAsNew: true,
    ).addConstructorNamesForElement(element: enumElement);
  }

  @override
  void visitContinueStatement(ContinueStatement node) {
    var continueEnd = node.continueKeyword.end;
    if (offset <= continueEnd) {
      collector.completionLocation = 'Block_statement';
      keywordHelper.addKeyword(Keyword.CONTINUE);
    } else if (continueEnd < offset && offset <= node.semicolon.offset) {
      labelHelper.addLabels(node);
    }
  }

  @override
  void visitDeclaredIdentifier(DeclaredIdentifier node) {
    node.parent?.accept(this);
  }

  @override
  void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
    var name = node.name;
    if (name.isSynthetic) {
      if (node.type == null && node.varKeyword == null) {
        _forTypeAnnotation(node, mustBeNonVoid: true);
        return;
      }
      _forNameInDeclaredVariablePattern(node);
      return;
    } else if (name.coversOffset(offset)) {
      _forNameInDeclaredVariablePattern(node);
      return;
    }
    if (node.keyword != null) {
      var type = node.type;
      if (type == null && offset < name.offset) {
        declarationHelper(mustBeType: true).addLexicalDeclarations(node);
        return;
      }
      if (!type.isSingleIdentifier && name.coversOffset(offset)) {
        // Don't suggest a name for the variable.
        return;
      }
      // Otherwise it's possible that the type is actually the name and the name
      // is the going to be the keyword `when`.
    }
    var parent = node.parent;
    if (!(parent is GuardedPattern && parent.hasWhen)) {
      keywordHelper.addKeyword(Keyword.WHEN);
    }
  }

  @override
  void visitDoStatement(DoStatement node) {
    if (offset <= node.doKeyword.end) {
      collector.completionLocation = 'Block_statement';
      _forStatement(node);
    } else if (node.leftParenthesis.end <= offset &&
        offset <= node.rightParenthesis.offset) {
      if (node.condition.isSynthetic ||
          offset <= node.condition.offset ||
          offset == node.condition.end) {
        collector.completionLocation = 'DoStatement_condition';
        _forExpression(node, mustBeNonVoid: true);
      }
    }
  }

  @override
  void visitDotShorthandConstructorInvocation(
    DotShorthandConstructorInvocation node,
  ) {
    var period = node.period;
    if (offset >= period.end && offset <= node.constructorName.end) {
      var contextType = _computeContextType(node);
      contextType = _resolveFutureOrType(contextType);
      if (contextType is! InterfaceType) return;
      if (contextType.element.isAccessibleIn(state.libraryElement)) {
        declarationHelper(
          mustBeConstant: node.isConst,
          suggestingDotShorthand: true,
          suggestUnnamedAsNew:
              _isDotShorthandEnabled ||
              state.request.isReplacingKeywordOrIdentifier,
        ).addConstructorNamesForType(type: contextType);
      }
    }
  }

  @override
  void visitDotShorthandInvocation(DotShorthandInvocation node) {
    var period = node.period;
    if (offset >= period.end && offset <= node.memberName.end) {
      var contextType = _computeContextType(node);
      contextType = _resolveFutureOrType(contextType);
      if (contextType == null) return;

      var element = contextType.element;
      if (element == null) return;
      if (element.isAccessibleIn(state.libraryElement)) {
        declarationHelper(
          mustBeConstant: node.inConstantContext,
          suggestingDotShorthand: true,
          suggestUnnamedAsNew:
              _isDotShorthandEnabled ||
              state.request.isReplacingKeywordOrIdentifier,
        ).addStaticMembersOfElement(element);
      }
    }
  }

  @override
  void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
    var contextType = _computeContextType(node);
    contextType = _resolveFutureOrType(contextType);
    if (contextType == null) return;

    var element = contextType.element;
    if (element == null) return;

    // Add all getters, methods, and constructors.
    // When the user needs completing with a `.` or a `.prefix`, the suggestions
    // can be any of the three.
    if (element.isAccessibleIn(state.libraryElement)) {
      declarationHelper(
        suggestingDotShorthand: true,
        mustBeConstant: node.inConstantContext,
        preferNonInvocation:
            element is InterfaceElement &&
            state.request.shouldSuggestTearOff(element),
        suggestUnnamedAsNew:
            _isDotShorthandEnabled ||
            state.request.isReplacingKeywordOrIdentifier,
      ).addStaticMembersOfElement(element);
    }
  }

  @override
  void visitDoubleLiteral(DoubleLiteral node) {
    _visitParentIfAtOrBeforeNode(node);
  }

  @override
  void visitEmptyStatement(EmptyStatement node) {
    var parent = node.parent;
    if (parent is Block) {
      collector.completionLocation = 'Block_statement';
      var statements = parent.statements;
      var index = statements.indexOf(node);
      if (index > 0) {
        var previousStatement = statements[index - 1];
        if (previousStatement is TryStatement &&
            previousStatement.finallyBlock == null) {
          keywordHelper.addTryClauseKeywords(canHaveFinally: true);
          if (previousStatement.catchClauses.isEmpty) {
            // Don't suggest a new statement because the `try` statement is
            // incomplete.
            return;
          }
        }
      }
    } else if (parent is IfStatement) {
      if (node == parent.thenStatement) {
        collector.completionLocation = 'IfStatement_thenStatement';
      } else {
        collector.completionLocation = 'IfStatement_elseStatement';
      }
    }
    if (offset <= node.semicolon.offset) {
      _forStatement(node);
    }
  }

  @override
  void visitEnumDeclaration(EnumDeclaration node) {
    if (!featureSet.isEnabled(Feature.enhanced_enums)) {
      return;
    }
    if (offset < node.enumKeyword.offset) {
      // There are no modifiers for enums.
      return;
    }
    if (offset <= node.enumKeyword.end) {
      keywordHelper.addKeyword(Keyword.ENUM);
      return;
    }

    if (node.body case BlockEnumBody body) {
      if (offset <= node.namePart.typeName.end) {
        identifierHelper(includePrivateIdentifiers: false).addTopLevelName(
          includeBody:
              body.leftBracket.isSynthetic && body.rightBracket.isSynthetic,
        );
        return;
      }

      if (offset <= body.leftBracket.offset) {
        keywordHelper.addEnumDeclarationKeywords(node);
        return;
      }

      if (!body.rightBracket.isSynthetic && offset >= body.rightBracket.end) {
        return;
      }

      var semicolon = body.semicolon;
      if (semicolon != null && offset >= semicolon.end) {
        collector.completionLocation = 'EnumDeclaration_member';
        _forEnumMember(node);
      }
    }
  }

  @override
  void visitExportDirective(ExportDirective node) {
    if (offset <= node.exportKeyword.end) {
      _forCompilationUnitMemberBefore(node);
    }
  }

  @override
  void visitExpressionFunctionBody(ExpressionFunctionBody node) {
    var expression = node.expression;
    if (offset >= node.functionDefinition.end && offset <= expression.end) {
      collector.completionLocation = 'ExpressionFunctionBody_expression';
      _forExpression(expression);
    }
  }

  @override
  void visitExpressionStatement(ExpressionStatement node) {
    var parent = node.parent;
    if (parent is SwitchPatternCase || parent is SwitchCase) {
      keywordHelper.addKeyword(Keyword.CASE);
      keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':');
      collector.completionLocation = 'SwitchMember_statement';
    } else {
      collector.completionLocation = 'Block_statement';
    }
    if (_forIncompletePrecedingStatement(node)) {
      if (node.isSingleIdentifier) {
        var precedingStatement = node.precedingStatement;
        if (precedingStatement is TryStatement) {
          return;
        }
      }
    }
    var semicolon = node.semicolon;
    if (semicolon != null &&
        !semicolon.isSynthetic &&
        offset >= semicolon.end) {
      _forStatement(node);
      return;
    }
    // TODO(brianwilkerson): If the cursor is in the expression, consider
    //  returning the expression as the completion node and moving the following
    //  conditions into the visit methods for the respective classes.
    var expression = node.expression;
    if (expression is AsExpression) {
      expression.accept(this);
    } else if (expression is AssignmentExpression) {
      var leftHandSide = expression.leftHandSide;
      if (offset <= leftHandSide.end) {
        switch (leftHandSide) {
          case PrefixedIdentifier():
            leftHandSide.accept(this);
          case SimpleIdentifier():
            _forStatement(node);
        }
      }
    } else if (expression is CascadeExpression) {
      if (offset <= expression.target.end) {
        declarationHelper(
          mustBeNonVoid: true,
          mustBeStatic: node.inStaticContext,
        ).addLexicalDeclarations(node);
      }
    } else if (expression is InstanceCreationExpression) {
      if (offset <= expression.beginToken.end) {
        _forStatement(node);
      }
    } else if (expression is IsExpression) {
      expression.accept(this);
    } else if (expression is FunctionReference) {
      if (offset > expression.end) {
        var function = expression.function;
        if (function is SimpleIdentifier) {
          /// This might be the beginning of a local variable declatation
          /// consisting of a type name with type arguments.
          identifierHelper(
            includePrivateIdentifiers: false,
          ).addSuggestionsFromTypeName(function.name);
        }
      }
    } else if (expression is MethodInvocation) {
      if (offset <= expression.beginToken.end) {
        _forStatement(node);
      }
    } else if (expression is TypeLiteral) {
      if (offset <= expression.end) {
        _forStatement(node);
      } else {
        /// This might be the beginning of a local variable declaration
        /// consisting of a type name.
        identifierHelper(
          includePrivateIdentifiers: false,
        ).addSuggestionsFromTypeName(expression.type.name.lexeme);
      }
    } else if (expression is PrefixedIdentifier) {
      if (offset <= expression.prefix.end) {
        declarationHelper(
          mustBeNonVoid: true,
          mustBeStatic: node.inStaticContext,
        ).addLexicalDeclarations(node);
      } else if (offset <= expression.identifier.end) {
        // TODO(brianwilkerson): Suggest members of the identifier's type.
      } else {
        /// This might be the beginning of a local variable declatation
        /// consisting of a prefixed type name.
        identifierHelper(
          includePrivateIdentifiers: false,
        ).addSuggestionsFromTypeName(expression.identifier.name);
      }
    } else if (expression is SimpleIdentifier) {
      if (offset <= expression.end) {
        _forStatement(node);
      } else {
        /// This might be the beginning of a local variable declatation
        /// consisting of a simple type name.
        identifierHelper(
          includePrivateIdentifiers: false,
        ).addSuggestionsFromTypeName(expression.name);
      }
    } else {
      _forExpression(node);
    }
  }

  @override
  void visitExtendsClause(ExtendsClause node) {
    var extendsKeyword = node.extendsKeyword;
    if (offset <= extendsKeyword.end) {
      keywordHelper.addKeyword(Keyword.EXTENDS);
    } else if (node.superclass.isFullySynthetic ||
        node.superclass.name.coversOffset(offset)) {
      collector.completionLocation = 'ExtendsClause_superclass';
      _forTypeAnnotation(node, mustBeExtensible: true);
    }
  }

  @override
  void visitExtensionDeclaration(ExtensionDeclaration node) {
    if (offset == node.offset) {
      _forCompilationUnitMemberBefore(node);
      return;
    } else if (node.documentationComment.coversOffset(offset)) {
      return node.documentationComment?.accept(this);
    } else if (offset < node.extensionKeyword.offset) {
      // There are no modifiers for extensions.
      return;
    }
    if (offset <= node.extensionKeyword.end) {
      keywordHelper.addKeyword(Keyword.EXTENSION);
      return;
    }
    var name = node.name;
    if (name != null && offset <= name.end) {
      keywordHelper.addKeyword(Keyword.ON);
      if (featureSet.isEnabled(Feature.inline_class)) {
        keywordHelper.addText('type');
      }
      // TODO(brianwilkerson): Consider adding both an `on` keyword and a body
      //  when none of them already exist.
      identifierHelper(
        includePrivateIdentifiers: false,
      ).addTopLevelName(includeBody: false);
      return;
    } else {
      // TODO(brianwilkerson): Consider adding both an `on` keyword and a body
      //  when none of them already exist.
      identifierHelper(
        includePrivateIdentifiers: false,
      ).addTopLevelName(includeBody: false);
    }

    if (node.body case BlockClassBody body) {
      if (offset <= body.leftBracket.offset) {
        collector.completionLocation = 'ExtensionDeclaration_onClause';
        if (node.onClause case var onClause?) {
          if (onClause.onKeyword.isSynthetic) {
            keywordHelper.addExtensionDeclarationKeywords(node);
          }
        }
        return;
      }
      if (offset >= body.leftBracket.end &&
          offset <= body.rightBracket.offset) {
        collector.completionLocation = 'ExtensionDeclaration_member';
        _forExtensionMember(node);
      }
    }
  }

  @override
  void visitExtensionOnClause(ExtensionOnClause node) {
    if (offset <= node.onKeyword.end) {
      keywordHelper.addKeyword(Keyword.ON);
      return;
    }

    collector.completionLocation = 'ExtensionOnClause_extendedType';
    _forTypeAnnotation(node);
  }

  @override
  void visitExtensionOverride(ExtensionOverride node) {
    _forExpression(node);
  }

  @override
  void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
    var body = node.body;
    if (body is! BlockClassBody) {
      return;
    }

    var namePart = node.namePart;
    if (offset == node.offset) {
      _forCompilationUnitMemberBefore(node);
    } else if (offset <= namePart.typeName.end) {
      if (offset < namePart.typeName.offset &&
          featureSet.isEnabled(Feature.primary_constructors) &&
          namePart is PrimaryConstructorDeclaration &&
          !namePart.hasConst) {
        keywordHelper.addKeyword(Keyword.CONST);
      }
      var hasSyntheticBody =
          body.leftBracket.isSynthetic && body.rightBracket.isSynthetic;
      identifierHelper(
        includePrivateIdentifiers: false,
      ).addTopLevelName(includeBody: hasSyntheticBody);
    } else if (offset >= namePart.end &&
        (offset <= body.leftBracket.offset || body.leftBracket.isSynthetic)) {
      keywordHelper.addKeyword(Keyword.IMPLEMENTS);
    } else if (offset >= body.leftBracket.end &&
        offset <= body.rightBracket.offset) {
      collector.completionLocation = 'ExtensionTypeDeclaration_member';
      _forExtensionTypeMember(node);
    }
  }

  @override
  void visitFieldDeclaration(FieldDeclaration node) {
    _forIncompletePrecedingClassMember(node);
    if (offset <= node.firstTokenAfterCommentAndMetadata.end) {
      collector.completionLocation = 'ClassDeclaration_member';
    }
    var fields = node.fields;
    var type = fields.type;
    if (type == null) {
      var variables = fields.variables;
      var firstField = variables.firstOrNull;
      if (firstField != null) {
        collector.completionLocation = 'FieldDeclaration_fields';
        var name = firstField.name;
        if (variables.length == 1 && name.isKeyword && offset > name.end) {
          // The parser has recovered by using one of the existing keywords as
          // the name of a field, which means that there is no type.
          keywordHelper.addFieldDeclarationKeywords(
            node,
            keyword: name.keyword,
          );
          declarationHelper(mustBeType: true).addLexicalDeclarations(node);
        } else if (offset < name.offset) {
          keywordHelper.addFieldDeclarationKeywords(node);
          _forTypeAnnotation(
            node,
            mustBeNonVoid: firstField.equals != null,
            isInDeclaration: true,
          );
        } else if (offset <= name.end) {
          keywordHelper.addFieldDeclarationKeywords(node);
        }
      }
    } else {
      var precedingMember = node.precedingMember;
      if (offset <= type.offset &&
          (precedingMember == null || offset >= precedingMember.end)) {
        var parent = node.parent?.parent;
        if (parent != null) {
          _forClassLikeMember(parent);
        }
      } else if (offset <= type.end) {
        // TODO(brianwilkerson): Add support for the failing test
        //  `OverrideTestCases.test_class_method_beforeField`.
        if (node.isSingleIdentifier) {
          // The user has typed only part of an identifier / keyword. Recovery
          // sees this as a field, but it could be the start of any kind of
          // member.
          var parent = node.parent;
          if (parent != null) {
            _forClassLikeMember(parent);
          }
        } else {
          keywordHelper.addFieldDeclarationKeywords(node);
          collector.completionLocation = 'ClassDeclaration_member';
          // TODO(brianwilkerson): `var` should only be suggested if neither
          //  `static` nor `final` are present.
          keywordHelper.addKeyword(Keyword.VAR);
          declarationHelper(mustBeType: true).addLexicalDeclarations(node);
        }
      }
    }
  }

  @override
  void visitFieldFormalParameter(FieldFormalParameter node) {
    var constructor = node.parent?.parent;
    if (constructor is FormalParameterList) {
      constructor = constructor.parent;
    }
    if (constructor is ConstructorDeclaration) {
      var declaredElement = node.declaredFragment?.element;
      FieldElement? field;
      if (declaredElement is FieldFormalParameterElement) {
        field = declaredElement.field;
      }
      if (node.thisKeyword.offset >= offset && node.requiredKeyword == null) {
        keywordHelper.addKeyword(.REQUIRED);
      }
      if (node.thisKeyword.offset >= offset &&
          (node.requiredKeyword == null ||
              node.requiredKeyword!.end <= offset)) {
        declarationHelper(mustBeType: true).addLexicalDeclarations(node);
      }
      if (node.period.end <= offset) {
        declarationHelper().addFieldsForInitializers(constructor, field);
      }
    }
  }

  @override
  void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
    collector.completionLocation = 'ForEachPartsWithDeclaration_iterable';
    _visitForEachParts(node);
  }

  @override
  void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
    collector.completionLocation = 'ForEachPartsWithDeclaration_iterable';
    _visitForEachParts(node);
  }

  @override
  void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
    // TODO(brianwilkerson): Rename the completion location.
    collector.completionLocation = 'visitForEachPartsWithPattern_iterable';
    _visitForEachParts(node);
  }

  @override
  void visitForElement(ForElement node) {
    collector.completionLocation = 'ForElement_body';
    var literal = node.thisOrAncestorOfType<TypedLiteral>();
    if (literal is ListLiteral) {
      _forCollectionElement(literal, literal.elements);
    } else if (literal is SetOrMapLiteral) {
      _forCollectionElement(literal, literal.elements);
    }
  }

  @override
  void visitFormalParameterDefaultClause(FormalParameterDefaultClause node) {
    var defaultValue = node.value;
    if (defaultValue.coversOffset(offset)) {
      collector.completionLocation = 'DefaultFormalParameter_defaultValue';
      _forExpression(defaultValue, mustBeNonVoid: true);
    }
  }

  @override
  void visitFormalParameterList(FormalParameterList node) {
    if (node.parent case PrimaryConstructorDeclaration primary) {
      primary.accept(this);
      return;
    }

    if (offset >= node.end) {
      var parent = node.parent;
      if (parent is FunctionExpression) {
        visitFunctionExpression(parent);
        return;
      }
    }
    collector.completionLocation = 'FormalParameterList_parameter';
    var parameters = node.parameters;
    var precedingParameter = parameters.elementBefore(offset);
    if (precedingParameter != null) {
      if (precedingParameter.isIncomplete) {
        precedingParameter.accept(this);
        return;
      }
      if (precedingParameter is RegularFormalParameter) {
        if (precedingParameter.type == null &&
            precedingParameter.functionTypedSuffix == null &&
            offset > precedingParameter.end) {
          // The name might be a type and the user might be trying to type a
          // name for the parameter.
          var name = precedingParameter.name?.lexeme;
          if (name != null) {
            identifierHelper(
              includePrivateIdentifiers: false,
            ).addSuggestionsFromTypeName(name);
          }
        }
      }
    }

    keywordHelper.addFormalParameterKeywords(
      node,
      suggestRequired: true,
      suggestVariableName: true,
    );
    _forTypeAnnotation(node);
  }

  @override
  void visitForPartsWithDeclarations(ForPartsWithDeclarations node) {
    if (offset >= node.leftSeparator.end &&
        offset <= node.rightSeparator.offset) {
      collector.completionLocation = 'ForParts_condition';
      var condition = node.condition;
      if (condition is SimpleIdentifier &&
          node.leftSeparator.isSynthetic &&
          node.rightSeparator.isSynthetic) {
        // Handle the degenerate case while typing `for (int x i^)`.
        // Actual: for (int x i^)
        // Parsed: for (int x; i^;)
        keywordHelper.addKeyword(Keyword.IN);
        return;
      }
      _forExpression(node);
    } else if (offset >= node.rightSeparator.end) {
      collector.completionLocation = 'ForParts_updater';
      _forExpression(node);
    }
  }

  @override
  void visitForPartsWithExpression(ForPartsWithExpression node) {
    if (node.isFullySynthetic) {
      node.parent.accept(this);
    }
  }

  @override
  void visitForStatement(ForStatement node) {
    if (offset <= node.forKeyword.end) {
      collector.completionLocation = 'Block_statement';
      _forStatement(node);
    } else if (offset >= node.leftParenthesis.end &&
        offset <= node.rightParenthesis.offset) {
      // The cursor is between the parentheses, but outside the range of the for
      // parts, either before it or after it.
      collector.completionLocation = 'ForStatement_forLoopParts';
      var parts = node.forLoopParts;
      switch (parts) {
        case ForEachPartsWithDeclaration():
          var variable = parts.loopVariable;
          if (offset < variable.name.offset) {
            var type = variable.type;
            if (type == null ||
                (type is NamedType && offset <= type.name.end)) {
              _forTypeAnnotation(node);
            }
          }
        case ForEachPartsWithIdentifier():
          if (offset < parts.identifier.offset) {
            _forTypeAnnotation(node);
          }
        case ForEachPartsWithPattern():
          // TODO(brianwilkerson): Implement this.
          return;
        case ForPartsWithDeclarations():
          var variables = parts.variables;
          var keyword = variables.keyword;
          if (variables.variables.length == 1 &&
              variables.variables[0].name.isSynthetic &&
              keyword != null &&
              parts.leftSeparator.isSynthetic) {
            var afterKeyword = keyword.next!;
            if (afterKeyword.type == TokenType.OPEN_PAREN) {
              var endGroup = afterKeyword.endGroup;
              if (endGroup != null && offset >= endGroup.end) {
                // Actual: for (va^)
                // Parsed: for (va^; ;)
                keywordHelper.addKeyword(Keyword.IN);
              }
            }
          }
          var type = variables.type;
          if (type != null && type.coversOffset(offset)) {
            type.accept(this);
          }
        case ForPartsWithExpression():
          if (parts.leftSeparator.isSynthetic &&
              parts.initialization is SimpleIdentifier) {
            keywordHelper.addKeyword(Keyword.FINAL);
            keywordHelper.addKeyword(Keyword.VAR);
            _forTypeAnnotation(node);
          }
        case ForPartsWithPattern():
          // TODO(brianwilkerson): Implement this.
          return;
      }
    } else {
      collector.completionLocation = 'ForStatement_body';
      _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.
    if (offset == node.offset) {
      _forCompilationUnitMemberBefore(node);
    }
    var returnType = node.returnType;
    if ((returnType == null || returnType.beginToken == returnType.endToken) &&
        offset <= node.name.offset) {
      collector.completionLocation = 'FunctionDeclaration_returnType';
      _forTypeAnnotation(node);
    }
  }

  @override
  void visitFunctionExpression(FunctionExpression node) {
    if (offset >=
            (node.parameters?.end ?? node.typeParameters?.end ?? node.offset) &&
        offset <= node.body.offset) {
      var body = node.body;
      keywordHelper.addFunctionBodyModifiers(body);
      var grandParent = node.parent;
      var unit = grandParent?.parent;
      if (body is EmptyFunctionBody &&
          grandParent is FunctionDeclaration &&
          unit is CompilationUnit) {
        _forCompilationUnitDeclaration(unit);
      }
    }
  }

  @override
  void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
    _forExpression(node);
  }

  @override
  void visitFunctionReference(FunctionReference node) {
    _forExpression(node);
  }

  @override
  void visitFunctionTypeAlias(FunctionTypeAlias node) {
    var typedefKeyword = node.typedefKeyword;
    if (offset == node.offset) {
      _forCompilationUnitMemberBefore(node);
    } else if (offset <= typedefKeyword.end) {
      collector.completionLocation = 'CompilationUnit_declaration';
      keywordHelper.addKeyword(Keyword.TYPEDEF);
    } else if (offset <= typedefKeyword.next!.end) {
      declarationHelper(mustBeType: true).addLexicalDeclarations(node);
    }
  }

  @override
  void visitFunctionTypedFormalParameterSuffix(
    FunctionTypedFormalParameterSuffix node,
  ) {
    collector.completionLocation = 'FormalParameterList_parameter';
    var parent = node.parent;
    if (parent is! FormalParameter) {
      return;
    }
    var returnType = parent.type;
    if (returnType != null && offset <= returnType.end) {
      keywordHelper.addFormalParameterKeywords(
        parent.parentFormalParameterList,
        suggestRequired: parent.requiredKeyword == null,
        suggestVariableName: parent.name?.lexeme.isEmpty ?? false,
      );
      _forTypeAnnotation(parent);
    } else if (returnType == null) {
      var name = parent.name;
      if (name != null && offset < name.offset) {
        _forTypeAnnotation(parent);
      }
    }
  }

  @override
  void visitGenericTypeAlias(GenericTypeAlias node) {
    if (offset == node.offset) {
      _forCompilationUnitMemberBefore(node);
    } else if (node.typedefKeyword.coversOffset(offset)) {
      keywordHelper.addKeyword(Keyword.TYPEDEF);
    } else if (offset >= node.equals.end && offset <= node.semicolon.offset) {
      collector.completionLocation = 'GenericTypeAlias_type';
      _forTypeAnnotation(node);
    } else if (node.documentationComment.coversOffset(offset)) {
      node.documentationComment?.accept(this);
    }
  }

  @override
  void visitHideCombinator(HideCombinator node) {
    collector.completionLocation = 'HideCombinator_hiddenName';
    _forCombinator(node, node.hiddenNames);
  }

  @override
  void visitIfElement(IfElement node) {
    var expression = node.expression;
    if (offset > expression.end && offset <= node.rightParenthesis.offset) {
      var caseClause = node.caseClause;
      if (caseClause == null) {
        keywordHelper.addKeyword(Keyword.CASE);
        keywordHelper.addKeyword(Keyword.IS);
      } else if (caseClause.guardedPattern.hasWhen) {
        if (caseClause.guardedPattern.whenClause?.expression == null) {
          // TODO(brianwilkerson): Figure out whether the next line should be
          //  replaced by an invocation of `_forExpression`.
          keywordHelper.addExpressionKeywords(
            node,
            mustBeStatic: node.inStaticContext,
          );
        }
      } else {
        keywordHelper.addKeyword(Keyword.WHEN);
      }
    } else if (offset >= node.leftParenthesis.end &&
        offset <= node.rightParenthesis.offset) {
      collector.completionLocation = 'IfElement_condition';
      _forExpression(node, mustBeNonVoid: true);
    } else if (offset >= node.rightParenthesis.end) {
      var elseKeyword = node.elseKeyword;
      if (elseKeyword == null || offset <= elseKeyword.offset) {
        collector.completionLocation = 'IfElement_thenElement';
      } else {
        collector.completionLocation = 'IfElement_elseElement';
      }
      var literal = node.thisOrAncestorOfType<TypedLiteral>();
      if (literal is ListLiteral) {
        _forCollectionElement(literal, literal.elements);
      } else if (literal is SetOrMapLiteral) {
        _forCollectionElement(literal, literal.elements);
      }
      // TODO(brianwilkerson): Ensure that we are suggesting `else` after the
      //  then expression.
      // var thenElement = node.thenElement;
      // if (offset >= thenElement.end &&
      //     !thenElement.isSynthetic &&
      //     node.elseKeyword == null) {
      //   keywordHelper.addKeyword(Keyword.ELSE);
      // }
    }
  }

  @override
  void visitIfStatement(IfStatement node) {
    if (node.rightParenthesis.isSynthetic &&
        !node.leftParenthesis.isSynthetic) {
      // analyzer parser
      // Actual: if (x i^)
      // Parsed: if (x) i^
      keywordHelper.addKeyword(Keyword.IS);
      return;
    }
    var expression = node.expression;
    if (offset <= node.ifKeyword.end) {
      collector.completionLocation = 'Block_statement';
      _forStatement(node);
    } else if (offset > expression.end &&
        offset <= node.rightParenthesis.offset) {
      collector.completionLocation = 'IfStatement_condition';
      var caseClause = node.caseClause;
      if (caseClause == null) {
        keywordHelper.addKeyword(Keyword.CASE);
        keywordHelper.addKeyword(Keyword.IS);
      } else if (caseClause.guardedPattern.hasWhen) {
        if (caseClause.guardedPattern.whenClause?.expression == null) {
          _forExpression(node);
        }
      } else {
        keywordHelper.addKeyword(Keyword.WHEN);

        // `case Name ^`
        // The user wants the pattern variable name.
        var pattern = caseClause.guardedPattern.pattern;
        if (pattern is ConstantPattern) {
          if (pattern.expression case TypeLiteral typeLiteral) {
            var namedType = typeLiteral.type;
            if (namedType.end < offset) {
              identifierHelper(
                includePrivateIdentifiers: false,
              ).addSuggestionsFromTypeName(namedType.name.lexeme);
              return;
            }
          }
        }
      }
    } else if (offset >= node.leftParenthesis.end &&
        offset <= node.rightParenthesis.offset) {
      collector.completionLocation = 'IfStatement_condition';
      _forExpression(node, mustBeNonVoid: true);
    } else if (offset >= node.rightParenthesis.end) {
      var elseKeyword = node.elseKeyword;
      if (elseKeyword == null || offset <= elseKeyword.offset) {
        collector.completionLocation = 'IfStatement_thenStatement';
      } else {
        collector.completionLocation = 'IfStatement_elseStatement';
      }
      _forStatement(node);
    }
  }

  @override
  void visitImplementsClause(ImplementsClause node) {
    var implementsKeyword = node.implementsKeyword;
    if (offset <= implementsKeyword.end) {
      keywordHelper.addKeyword(Keyword.IMPLEMENTS);
    } else {
      collector.completionLocation = 'ImplementsClause_interface';
      _forTypeAnnotation(node, mustBeImplementable: true);
    }
  }

  @override
  void visitImportDirective(ImportDirective node) {
    if (offset <= node.importKeyword.end) {
      collector.completionLocation = 'CompilationUnit_directive';
      _forCompilationUnitMemberBefore(node);
    } else if (offset <= node.uri.offset) {
      return;
    } else if (offset >= node.uri.end) {
      collector.completionLocation = 'CompilationUnit_directive';
      keywordHelper.addImportDirectiveKeywords(node);
    }
  }

  @override
  void visitImportPrefixReference(ImportPrefixReference node) {
    var parent = node.parent;
    if (parent is NamedType && offset <= parent.name.offset) {
      var element = node.element;
      DartType type;
      collector.completionLocation = 'PropertyAccess_propertyName';
      if (element is FunctionTypedElement) {
        if (element is GetterElement) {
          type = element.type.returnType;
        } else {
          type = element.type;
        }
      } else if (element is PrefixElement) {
        var isInstanceCreation =
            node.parent?.parent?.parent is InstanceCreationExpression;
        declarationHelper(
          excludeTypeNames: isInstanceCreation,
          mustBeType: !isInstanceCreation,
          mustBeNonVoid: isInstanceCreation,
        ).addDeclarationsThroughImportPrefix(element);
        return;
      } else if (element is VariableElement) {
        type = element.type;
      } else {
        if (element is InterfaceElement || element is ExtensionElement) {
          declarationHelper().addStaticMembersOfElement(element!);
        }
        return;
      }
      declarationHelper().addInstanceMembersOfType(type);
    }
  }

  @override
  void visitIndexExpression(IndexExpression node) {
    var beginToken = node.beginToken;
    if (offset <= beginToken.end && beginToken.isKeywordOrIdentifier) {
      // The user is completing at the beginning of the expression, so let the
      // parent node determine the right set of suggestions.
      node.parent?.accept(this);
      return;
    }
    if (offset >= node.leftBracket.end && offset <= node.rightBracket.offset) {
      collector.completionLocation = 'IndexExpression_index';
      _forExpression(node, mustBeNonVoid: true);
    }
  }

  @override
  void visitInstanceCreationExpression(InstanceCreationExpression node) {
    var keyword = node.keyword;
    if (keyword != null && offset > keyword.end) {
      if (offset <= keyword.end) {
        _forExpression(node);
      } else {
        var constructorName = node.constructorName;
        if (constructorName.isSynthetic ||
            offset < constructorName.offset ||
            constructorName.coversOffset(offset)) {
          collector.completionLocation =
              'InstanceCreationExpression_constructorName';
          declarationHelper()
            ..addConstructorInvocations()
            ..addImportPrefixes();
        }
      }
    } else {
      _forExpression(node);
    }
  }

  @override
  void visitIntegerLiteral(IntegerLiteral node) {
    _visitParentIfAtOrBeforeNode(node);
  }

  @override
  void visitInterpolationExpression(InterpolationExpression node) {
    collector.completionLocation = 'InterpolationExpression_expression';
    declarationHelper(
      mustBeStatic: node.inStaticContext,
      mustBeNonVoid: true,
    ).addLexicalDeclarations(node);
  }

  @override
  void visitIsExpression(IsExpression node) {
    var isOperator = node.isOperator;

    if (node.expression.isSynthetic &&
        node.type.isSynthetic &&
        isOperator.end == offset) {
      declarationHelper(
        mustBeStatic: node.inStaticContext,
      ).addLexicalDeclarations(node);
      return;
    }

    if (isOperator.coversOffset(offset)) {
      keywordHelper.addKeyword(Keyword.IS);
    } else if (offset < isOperator.offset) {
      _forExpression(node);
    } else if (offset > isOperator.end) {
      collector.completionLocation = 'IsExpression_type';
      declarationHelper(mustBeType: true).addLexicalDeclarations(node);
    }
  }

  @override
  void visitLabel(Label node) {
    var label = node.name;
    if (!label.isSynthetic && offset >= label.end) {
      var parent = node.parent;
      if (parent is NamedArgument) {
        parent.accept(this);
      }
    }
  }

  @override
  void visitLibraryDirective(LibraryDirective node) {
    if (offset >= node.end) {
      var unit = node.parent;
      if (unit is CompilationUnit) {
        _forDirective(unit, node);
        var (before: _, :after) = unit.membersBeforeAndAfterMember(node);
        if (after is CompilationUnitMember?) {
          _forCompilationUnitDeclaration(unit);
        }
      }
    }
  }

  @override
  void visitListLiteral(ListLiteral node) {
    var offset = this.offset;
    if (offset >= node.leftBracket.end && offset <= node.rightBracket.offset) {
      collector.completionLocation = 'ListLiteral_element';
      _forCollectionElement(node, node.elements);
    }
  }

  @override
  void visitListPattern(ListPattern node) {
    collector.completionLocation = 'ListPattern_element';
    _forPattern(node);
  }

  @override
  void visitLogicalAndPattern(LogicalAndPattern node) {
    collector.completionLocation = 'LogicalAndPattern_rightOperand';
    _forPattern(node);
  }

  @override
  void visitLogicalOrPattern(LogicalOrPattern node) {
    collector.completionLocation = 'LogicalOrPattern_rightOperand';
    _forPattern(node);
  }

  @override
  void visitMapLiteralEntry(MapLiteralEntry node) {
    if (offset == node.offset) {
      node.parent?.accept(this);
    } else if (node.keyQuestion != null && offset == node.key.offset) {
      // This is the case of the marker standing after the null-aware marker
      // '?', but before the key expression. It is a place where an expression
      // is expected.
      node.parent?.accept(this);
      collector.completionLocation = 'NullAwareElement_value';
    } else if (node.valueQuestion != null &&
        (offset == node.separator.end ||
            offset == node.valueQuestion!.offset ||
            offset == node.value.offset)) {
      // This is the case of the marker standing either before or after the
      // null-aware marker '?' in the value. It is a place where an expression
      // is expected.
      node.parent?.accept(this);
      collector.completionLocation = 'NullAwareElement_value';
    } else if (offset >= node.separator.end) {
      collector.completionLocation = 'MapLiteralEntry_value';
      _forExpression(node, mustBeNonVoid: true);
    }
  }

  @override
  void visitMapPattern(MapPattern node) {
    collector.completionLocation = 'MapPatternEntry_key';
    _forConstantExpression(node);
  }

  @override
  void visitMapPatternEntry(MapPatternEntry node) {
    var separator = node.separator;
    if (separator.isSynthetic || offset <= separator.offset) {
      node.parent?.accept(this);
      return;
    }
    collector.completionLocation = 'MapPatternEntry_value';
    _forPattern(node, mustBeConst: false);
  }

  @override
  void visitMethodDeclaration(MethodDeclaration node) {
    collector.completionLocation = 'ClassDeclaration_member';
    if (offset >= node.firstTokenAfterCommentAndMetadata.previous!.offset &&
        offset <= node.name.end) {
      _forTypeAnnotation(node, isInDeclaration: true);
      // If the cursor is at the beginning of the declaration, include the class
      // member keywords.  See dartbug.com/41039.
      keywordHelper.addClassMemberKeywords();
    }
    var body = node.body;
    var tokenBeforeBody = body.beginToken.previous!;
    if (offset >= tokenBeforeBody.end && offset <= body.offset) {
      if (body.keyword == null) {
        keywordHelper.addFunctionBodyModifiers(body);
      }
      if (body.isEmpty) {
        keywordHelper.addClassMemberKeywords();
      }
    }
  }

  @override
  void visitMethodInvocation(MethodInvocation node) {
    var beginToken = node.beginToken;
    if (offset <= beginToken.end && beginToken.isKeywordOrIdentifier) {
      // The user is completing at the beginning of the expression, so let the
      // parent node determine the right set of suggestions.
      node.parent?.accept(this);
      return;
    }
    var operator = node.operator;
    if (operator == null) {
      if (node.coversOffset(offset)) {
        // TODO(keertip): Also check for more cases, RHS of assignment operator,
        // a field in record literal, an operand to an operator.
        var parent = node.parent;
        var mustBeNonVoid = false;
        if (parent is ArgumentList) {
          collector.completionLocation = _locationFor(parent, isNamed: false);
          mustBeNonVoid = true;
        } else if (parent is NamedArgument) {
          var grandparent = parent.parent;
          if (grandparent is ArgumentList) {
            collector.completionLocation = _locationFor(
              grandparent,
              isNamed: true,
            );
          }
          mustBeNonVoid = true;
        } else if (parent is RecordLiteral) {
          collector.completionLocation = 'RecordLiteral_fields';
          mustBeNonVoid = true;
        }
        _forExpression(node, mustBeNonVoid: mustBeNonVoid);
      }
      return;
    }
    if ((node.isCascaded && offset + 1 == operator.end) ||
        (offset >= operator.end && offset <= node.methodName.end)) {
      var target = node.realTarget;
      var type = target?.staticType;
      if (type != null) {
        _forMemberAccess(node, type);
      }
      var element = target == null ? null : _staticMemberTargetElement(target);
      if ((type == null || type is InvalidType || type.isDartCoreType) &&
          element != null &&
          (!node.isCascaded || offset + 1 == operator.end)) {
        if (element is InterfaceElement || element is ExtensionTypeElement) {
          declarationHelper().addStaticMembersOfElement(element);
        }
        if (element is PrefixElement) {
          declarationHelper().addDeclarationsThroughImportPrefix(element);
        }
      }
    }
  }

  @override
  void visitMixinDeclaration(MixinDeclaration node) {
    if (offset == node.offset) {
      _forCompilationUnitMemberBefore(node);
      return;
    }
    if (offset < node.mixinKeyword.offset) {
      keywordHelper.addMixinModifiers(node);
      return;
    }
    if (offset <= node.mixinKeyword.end) {
      keywordHelper.addKeyword(Keyword.MIXIN);
      return;
    }

    if (node.body case BlockClassBody body) {
      if (offset <= node.name.end) {
        var hasSyntheticBody =
            body.leftBracket.isSynthetic && body.rightBracket.isSynthetic;
        identifierHelper(
          includePrivateIdentifiers: false,
        ).addTopLevelName(includeBody: hasSyntheticBody);
        return;
      }

      if (offset <= body.leftBracket.offset) {
        keywordHelper.addMixinDeclarationKeywords(node);
        return;
      }

      if (offset >= body.leftBracket.end &&
          offset <= body.rightBracket.offset) {
        collector.completionLocation = 'MixinDeclaration_member';
        if (_tryAnnotationAtEndOfClassBody(node)) {
          return;
        }
        _forMixinMember(node);
        var element = body.members.elementBefore(offset);
        if (element is MethodDeclaration) {
          var body = element.body;
          if (body.isEmpty) {
            keywordHelper.addFunctionBodyModifiers(body);
          }
        }
        // TODO(brianwilkerson): Consider enabling the generation of overrides in
        //  this location.
      }
    }
  }

  @override
  void visitMixinOnClause(MixinOnClause node) {
    var onKeyword = node.onKeyword;
    if (offset <= onKeyword.end) {
      keywordHelper.addKeyword(Keyword.ON);
    } else {
      _forTypeAnnotation(node);
    }
  }

  @override
  void visitNamedArgument(NamedArgument node) {
    if (offset <= node.name.end) {
      switch (node.parent) {
        case ArgumentList argumentList:
          collector.completionLocation = _locationFor(
            argumentList,
            isNamed: true,
          );
          var parameters = argumentList.invokedFormalParameters;
          if (parameters != null) {
            var (positionalArgumentCount: _, :usedNames) = argumentList
                .argumentContext(-1);
            usedNames.remove(node.name.lexeme);

            var appendColon = node.colon.isSynthetic;
            for (int i = 0; i < parameters.length; i++) {
              var parameter = parameters[i];
              if (parameter.isNamed) {
                if (!usedNames.contains(parameter.name)) {
                  var matcherScore = state.matcher.score(parameter.displayName);
                  if (matcherScore != -1) {
                    var isWidget = isFlutterWidgetParameter(parameter);
                    collector.addSuggestion(
                      NamedArgumentSuggestion(
                        parameter: parameter,
                        matcherScore: matcherScore,
                        appendColon: appendColon,
                        appendComma: false,
                        isWidget: isWidget,
                        preferredQuoteForStrings:
                            state.preferredQuoteForStrings,
                      ),
                    );
                  }
                }
              }
            }
          }
        case RecordLiteral recordLiteral:
          collector.completionLocation = 'RecordLiteral_fields';
          _suggestRecordLiteralNamedFields(
            contextType: _computeContextType(recordLiteral),
            containerNode: node,
            recordLiteral: recordLiteral,
            isNewField: false,
          );
      }
    } else if (offset >= node.colon.end) {
      var inArgumentList = node.parent is ArgumentList;
      if (inArgumentList) {
        collector.completionLocation = 'ArgumentList_method_named';
      }
      var parameterType =
          node.correspondingParameter?.type ?? DynamicTypeImpl.instance;
      while (parameterType is TypeParameterType) {
        parameterType = parameterType.bound;
      }
      if (parameterType.isDartCoreFunction) {
        parameterType = _voidFunctionNoParameters();
      }
      var isFunctionType = parameterType is FunctionType;
      _forExpression(
        node.argumentExpression,
        mustBeNonVoid: inArgumentList,
        canBeNull: _canBeNull(parameterType),
        canBeBool: _canBeBool(parameterType),
        // TODO(FMorschel): Determine if the parameter type has a constant
        //  constructor.
        // Function tear-offs and closures cannot have the `const` keyword
        // before it
        includeTrailingCommaAfterClosure: !node.isFollowedByComma,
        canSuggestConst: !isFunctionType,
      );
    }
  }

  @override
  void visitNamedType(NamedType node) {
    var importPrefix = node.importPrefix;
    var prefixElement = importPrefix?.element;

    // `prefix.x^ print(0);` is recovered as `prefix.x print; (0);`.
    if (prefixElement is PrefixElement) {
      if (node.parent case VariableDeclarationList variableList) {
        if (variableList.parent case VariableDeclarationStatement statement) {
          if (statement.semicolon.isSynthetic) {
            declarationHelper().addDeclarationsThroughImportPrefix(
              prefixElement,
            );
            return;
          }
        }
      }
    }

    var parent = node.parent;
    if (parent is ImplementsClause) {
      collector.completionLocation = 'ImplementsClause_interface';
    } else if (parent is TypeArgumentList) {
      collector.completionLocation = 'TypeArgumentList_argument';
    } else if (parent is WithClause) {
      collector.completionLocation = 'WithClause_mixinType';
    }
    _forTypeAnnotation(
      node,
      excludeTypeNames: node.parent?.parent is InstanceCreationExpression,
    );
  }

  @override
  void visitNullAwareElement(NullAwareElement node) {
    collector.completionLocation = 'NullAwareElement_value';
    _forExpression(node);
  }

  @override
  void visitNullLiteral(NullLiteral node) {
    _forExpression(node);
  }

  @override
  void visitObjectPattern(ObjectPattern node) {
    if (node.leftParenthesis.end <= offset &&
        offset <= node.rightParenthesis.offset) {
      collector.completionLocation = 'ObjectPattern_fieldName';
      declarationHelper(
        mustBeNonVoid: true,
        preferNonInvocation: true,
      ).addGetters(
        type: node.type.typeOrThrow,
        excludedGetters: node.fields.fieldNames,
        isKeywordNeeded: false,
        isTypeNeeded: false,
      );
    }
  }

  @override
  void visitParenthesizedExpression(ParenthesizedExpression node) {
    var expression = node.expression;
    if (expression is Identifier || expression is PropertyAccess) {
      if (offset == node.rightParenthesis.offset) {
        var next = expression.endToken.next;
        if (next?.type == TokenType.IDENTIFIER) {
          // Fasta parses `if (x i^)` as `if (x ^)` where the `i` is in the
          // token stream but not part of the `ParenthesizedExpression`.
          keywordHelper.addKeyword(Keyword.IS);
          return;
        }
      }
    }
    collector.completionLocation = 'ParenthesizedExpression_expression';

    if (node.expression case SimpleIdentifier()) {
      _suggestRecordLiteralNamedFields(
        contextType: _computeContextType(node),
        containerNode: node,
        recordLiteral: null,
        isNewField: true,
      );
    }

    _forExpression(node);
  }

  @override
  void visitParenthesizedPattern(ParenthesizedPattern node) {
    collector.completionLocation = 'ParenthesizedPattern_expression';
    _forPattern(node);
  }

  @override
  void visitPartDirective(PartDirective node) {
    if (offset <= node.partKeyword.end) {
      collector.completionLocation = 'CompilationUnit_directive';
      _forCompilationUnitMemberBefore(node);
    }
  }

  @override
  void visitPartOfDirective(PartOfDirective node) {
    if (offset <= node.partKeyword.end) {
      collector.completionLocation = 'CompilationUnit_directive';
      _forCompilationUnitMemberBefore(node);
    }
  }

  @override
  void visitPatternAssignment(PatternAssignment node) {
    collector.completionLocation = 'PatternAssignment_expression';
    _forExpression(node);
  }

  @override
  void visitPatternField(PatternField node) {
    var name = node.name;
    if (name != null && offset <= name.colon.offset) {
      var parent = node.parent;
      if (parent is ObjectPattern) {
        collector.completionLocation = 'ObjectPattern_fieldName';
      } else {
        collector.completionLocation = 'PatternField_pattern';
      }
      _forPatternFieldName(name, isKeywordNeeded: false, isTypeNeeded: false);
      return;
    }
    if (name == null) {
      var parent = node.parent;
      if (parent is ObjectPattern) {
        collector.completionLocation = 'ObjectPattern_fieldName';
        declarationHelper(mustBeNonVoid: true).addGetters(
          type: parent.type.typeOrThrow,
          excludedGetters: parent.fields.fieldNames,
          isKeywordNeeded: false,
          isTypeNeeded: false,
        );
      } else if (parent is RecordPattern) {
        collector.completionLocation = 'PatternField_pattern';
        _forPattern(node);
        // TODO(brianwilkerson): If we know the expected record type, add the
        //  names of any named fields.
      }
    } else if (name.name == null) {
      collector.completionLocation = 'PatternField_pattern';
      var parent = node.thisOrAncestorOfType<PatternVariableDeclarationImpl>();
      if (parent == null) {
        _forVariablePattern();
      }
      var isKeywordNeeded = false;
      if (node.thisOrAncestorOfType<GuardedPattern>() != null) {
        isKeywordNeeded = true;
      }
      _forPatternFieldName(
        name,
        isKeywordNeeded: isKeywordNeeded,
        isTypeNeeded: true,
      );
    } else {
      collector.completionLocation = 'PatternField_pattern';
      _forPattern(node, mustBeConst: false);
    }
  }

  @override
  void visitPatternFieldName(PatternFieldName node) {
    if (offset <= node.colon.offset) {
      var parent = node.parent?.parent;
      if (parent is ObjectPattern) {
        collector.completionLocation = 'ObjectPattern_fieldName';
      } else {
        collector.completionLocation = 'PatternField_pattern';
      }
      _forPatternFieldName(node, isKeywordNeeded: false, isTypeNeeded: false);
      return;
    }
    node.parent?.accept(this);
  }

  @override
  void visitPatternVariableDeclaration(PatternVariableDeclaration node) {
    collector.completionLocation = 'PatternVariableDeclaration_expression';
    _forExpression(node);
  }

  @override
  void visitPostfixExpression(PostfixExpression node) {
    var type = node.operator.type;
    collector.completionLocation = 'PrefixExpression_${type.lexeme}_operand';
    _forExpression(
      node,
      mustBeAssignable:
          type == TokenType.PLUS_PLUS || type == TokenType.MINUS_MINUS,
    );
  }

  @override
  void visitPrefixedIdentifier(PrefixedIdentifier node) {
    if (offset <= node.period.offset) {
      _forExpression(node);
    } else {
      collector.completionLocation = 'PropertyAccess_propertyName';
      var target = node.prefix;
      var type = target.staticType;
      if (type != null && type is! InvalidType) {
        _forMemberAccess(node, type, onlySuper: target is SuperExpression);
      } else {
        var element = target.element;
        if (element != null) {
          var parent = node.parent;
          var mustBeAssignable =
              parent is AssignmentExpression && node == parent.leftHandSide;
          if (element is PrefixElement) {
            declarationHelper(
              mustBeAssignable: mustBeAssignable,
            ).addDeclarationsThroughImportPrefix(element);
          } else {
            var helper = declarationHelper(
              mustBeAssignable: mustBeAssignable,
              preferNonInvocation:
                  node.parent is CommentReference ||
                  (element is InterfaceElement &&
                      state.request.shouldSuggestTearOff(element)),
            );
            if (node.parent is CommentReference) {
              if (element is InterfaceElement) {
                helper.addInstanceMembersOfType(element.thisType);
              } else if (element is ExtensionElement) {
                helper.addMembersFromExtensionElement(
                  element,
                  excludedGetters: {},
                  includeMethods: true,
                  includeSetters: true,
                );
              }
            }
            helper.addStaticMembersOfElement(element);
          }
        }
      }
    }
  }

  @override
  void visitPrefixExpression(PrefixExpression node) {
    collector.completionLocation = 'PropertyAccess_propertyName';
    var type = node.operator.type;
    _forExpression(
      node,
      mustBeAssignable:
          type == TokenType.PLUS_PLUS || type == TokenType.MINUS_MINUS,
    );
  }

  @override
  void visitPrimaryConstructorDeclaration(PrimaryConstructorDeclaration node) {
    var formalParameters = node.formalParameters;
    var parameter = formalParameters.parameters.firstOrNull;
    if (parameter == null) {
      collector.completionLocation = 'PrimaryConstructorDeclaration_fieldType';
      keywordHelper.addFormalParameterKeywords(
        formalParameters,
        suggestRequired: true,
        suggestVariableName: true,
      );
      _forTypeAnnotation(node);
      return;
    }

    var nameToken = parameter.name;
    if (nameToken == null) {
      return;
    }

    bool hasIncompleteAnnotation() {
      var next = formalParameters.leftParenthesis.next;
      if (next == null) {
        return false;
      }
      var following = next.next;
      return next.type == TokenType.AT ||
          (next.isSynthetic && following?.type == TokenType.AT);
    }

    bool offsetIsAfterCovariant() {
      var next = parameter.lastNonSynthetic;
      return next is KeywordToken &&
          next.keyword == Keyword.COVARIANT &&
          offset >= next.end;
    }

    bool offsetIsAfterRequired() {
      var next = parameter.lastNonSynthetic;
      return next is KeywordToken &&
          next.keyword == Keyword.REQUIRED &&
          offset >= next.end;
    }

    bool offsetIsAfterFinalOrVar() {
      var next = parameter.lastNonSynthetic;
      return next is KeywordToken &&
          (next.keyword == Keyword.FINAL || next.keyword == Keyword.VAR) &&
          offset >= next.end;
    }

    bool offsetIsAfterComma() {
      var next = parameter.endToken.next!;
      return next.type == TokenType.COMMA && offset >= next.end;
    }

    if (offsetIsAfterCovariant()) {
      collector.completionLocation = 'PrimaryConstructorDeclaration_fieldName';
      keywordHelper.addFormalParameterKeywords(
        formalParameters,
        suggestCovariant: false,
        suggestRequired: true,
        suggestVariableName: true,
      );
      _forTypeAnnotation(node);
    } else if (offsetIsAfterRequired()) {
      collector.completionLocation = 'PrimaryConstructorDeclaration_fieldName';
      keywordHelper.addFormalParameterKeywords(
        formalParameters,
        suggestCovariant: false,
        suggestRequired: false,
        suggestVariableName: true,
      );
      _forTypeAnnotation(node);
    } else if (offsetIsAfterFinalOrVar()) {
      collector.completionLocation = 'PrimaryConstructorDeclaration_fieldName';
      keywordHelper.addFormalParameterKeywords(
        formalParameters,
        suggestCovariant: false,
        suggestFinalOrVar: false,
        suggestRequired: true,
        suggestVariableName: true,
      );
      _forTypeAnnotation(node);
    } else if (offset <= nameToken.end) {
      var parameterType = parameter.type;
      if (parameterType == null || parameterType.isFullySynthetic) {
        if (nameToken.isSynthetic && hasIncompleteAnnotation()) {
          collector.completionLocation = 'Annotation_name';
          _forAnnotation(parameter);
        } else {
          collector.completionLocation =
              'PrimaryConstructorDeclaration_fieldType';
          declarationHelper(mustBeType: true).addLexicalDeclarations(parameter);
        }
      } else {
        collector.completionLocation =
            'PrimaryConstructorDeclaration_fieldName';
        identifierHelper(
          includePrivateIdentifiers: true,
        ).addVariable(parameterType);
      }
    } else if (offsetIsAfterComma()) {
      // The user is adding a new parameter after [parameter].
      collector.completionLocation = 'PrimaryConstructorDeclaration_fieldType';
      keywordHelper.addFormalParameterKeywords(
        formalParameters,
        suggestRequired: true,
        suggestVariableName: true,
      );
      _forTypeAnnotation(node);
    } else {
      collector.completionLocation = 'PrimaryConstructorDeclaration_fieldName';
      identifierHelper(
        includePrivateIdentifiers: true,
      ).addSuggestionsFromTypeName(nameToken.lexeme);
    }
  }

  @override
  void visitPropertyAccess(PropertyAccess node) {
    var operator = node.operator;
    if (offset <= operator.offset) {
      // We will only get here if the target is a `SimpleIdentifier`, in which
      // case the user is attempting to complete that identifier.
      _forExpression(node);
    } else {
      collector.completionLocation = 'PropertyAccess_propertyName';
      var target = node.realTarget;
      var parent = node.parent;
      if (target is ThisExpression && parent is ConstructorFieldInitializer) {
        // The parser recovers from `this` by treating it as a property access
        // on the right side of a field initializer. The user appears to be
        // attempting to complete an initializer.
        node.parent?.accept(this);
        return;
      }
      var type = target.staticType;
      if (type != null) {
        _forMemberAccess(node, type, onlySuper: target is SuperExpression);
      }
      var element = _staticMemberTargetElement(target);
      if ((type == null || type is InvalidType || type.isDartCoreType) &&
          element != null &&
          (!node.isCascaded || offset + 1 == operator.end)) {
        if (element is InterfaceElement || element is ExtensionTypeElement) {
          declarationHelper().addStaticMembersOfElement(element);
        }
        if (element is PrefixElement) {
          declarationHelper().addDeclarationsThroughImportPrefix(element);
        }
      }
      if (type == null && target is ExtensionOverride) {
        declarationHelper().addMembersFromExtensionElement(
          target.element,
          excludedGetters: {},
          includeMethods: true,
          includeSetters: true,
        );
      }
    }
  }

  @override
  void visitRecordLiteral(RecordLiteral node) {
    collector.completionLocation = 'RecordLiteral_fields';

    _suggestRecordLiteralNamedFields(
      contextType: _computeContextType(node),
      containerNode: node,
      recordLiteral: node,
      isNewField: true,
    );

    _forExpression(node);
  }

  @override
  void visitRecordLiteralNamedField(RecordLiteralNamedField node) {
    if (offset <= node.name.end) {
      if (node.parent case RecordLiteral recordLiteral) {
        collector.completionLocation = 'RecordLiteral_fields';
        _suggestRecordLiteralNamedFields(
          contextType: _computeContextType(recordLiteral),
          containerNode: node,
          recordLiteral: recordLiteral,
          isNewField: false,
        );
      }
    } else if (offset >= node.colon.end) {
      collector.completionLocation = 'RecordLiteral_fields';
      _forExpression(node.fieldExpression);
    }
  }

  @override
  void visitRecordPattern(RecordPattern node) {
    // `^()` to become object pattern.
    if (offset == node.leftParenthesis.offset) {
      collector.completionLocation = 'ObjectPattern_type';
      declarationHelper(
        mustBeType: true,
        mustBeNonVoid: true,
      ).addLexicalDeclarations(node);
      return;
    }

    if (node.leftParenthesis.end <= offset &&
        offset <= node.rightParenthesis.offset) {
      // TODO(brianwilkerson): Is there a reason we aren't suggesting 'void'?
      collector.completionLocation = 'PatternField_pattern';
      keywordHelper.addKeyword(Keyword.DYNAMIC);
      _forExpression(node);
      var targetField = node.fields.skipWhile((field) {
        return field.end < offset;
      }).firstOrNull;
      if (targetField != null) {
        var nameNode = targetField.name;
        if (nameNode != null && offset <= nameNode.colon.offset) {
          declarationHelper(mustBeNonVoid: true).addGetters(
            type: node.matchedValueTypeOrThrow,
            excludedGetters: node.fields.fieldNames,
            isKeywordNeeded: false,
            isTypeNeeded: false,
          );
        }
      }
    }
  }

  @override
  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
    if (offset <= node.offset) {
      var parent = node.parent;
      if (parent is FormalParameter && offset <= parent.offset) {
        // The user might be starting a new formal parameter before the one
        // containing the `node`.
        collector.completionLocation = 'FormalParameterList_parameter';
        _forTypeAnnotation(node);
      }
    } else if (offset <= node.rightParenthesis.offset) {
      collector.completionLocation = 'RecordTypeAnnotation_positionalFields';
      _forTypeAnnotation(node);
    }
  }

  @override
  void visitRecordTypeAnnotationNamedField(
    RecordTypeAnnotationNamedField node,
  ) {
    if (node.type.coversOffset(offset)) {
      collector.completionLocation = 'RecordTypeAnnotationNamedFields_fields';
      _forTypeAnnotation(node);
    } else if (node.name.coversOffset(offset)) {
      collector.completionLocation = 'RecordTypeAnnotationNamedField_name';
      identifierHelper(includePrivateIdentifiers: false).addVariable(node.type);
    }
  }

  @override
  void visitRecordTypeAnnotationNamedFields(
    RecordTypeAnnotationNamedFields node,
  ) {
    collector.completionLocation = 'RecordTypeAnnotationNamedFields_fields';
    _forTypeAnnotation(node);
  }

  @override
  void visitRecordTypeAnnotationPositionalField(
    RecordTypeAnnotationPositionalField node,
  ) {
    if (node.type.coversOffset(offset)) {
      collector.completionLocation = 'RecordTypeAnnotation_positionalFields';
      _forTypeAnnotation(node);
    }
  }

  @override
  void visitRedirectingConstructorInvocation(
    RedirectingConstructorInvocation node,
  ) {
    var constructor = node.parent;
    if (constructor is! ConstructorDeclaration) {
      return;
    }
    collector.completionLocation = 'ConstructorDeclaration_initializer';
    if (offset <= node.thisKeyword.end && node.argumentList.isFullySynthetic) {
      keywordHelper.addConstructorInitializerKeywords(constructor, node);
      return;
    }
    var period = node.period;
    // TODO(brianwilkerson): If the period is `null` we might want to complete
    //  the argument list after `this`.
    if (period != null &&
        offset >= period.end &&
        offset <= node.argumentList.offset) {
      _forRedirectingConstructorInvocation(constructor);
    }
  }

  @override
  void visitRegularFormalParameter(RegularFormalParameter node) {
    if (node.parent?.parent case PrimaryConstructorDeclaration primary) {
      primary.accept(this);
      return;
    }

    var name = node.name;
    var noRequired = node.requiredKeyword == null;
    bool suggestCovariant = true;
    bool suggestThis = true;
    bool suggestVoid = true;
    bool suggestDynamic = true;
    bool suggestRequired = true;
    if (name != null && node.isSingleIdentifier) {
      collector.completionLocation = 'FormalParameterList_parameter';
      keywordHelper.addFormalParameterKeywords(
        node.parentFormalParameterList,
        suggestRequired: suggestRequired && name.keyword != Keyword.REQUIRED,
        suggestCovariant: suggestCovariant && name.keyword != Keyword.COVARIANT,
        suggestVariableName: true,
      );
      suggestCovariant = false;
      suggestThis = false;
      suggestRequired = false;
      _forTypeAnnotation(
        node,
        suggestDynamic: suggestDynamic,
        suggestVoid: suggestVoid,
      );
      suggestDynamic = false;
      suggestVoid = false;

      if (name.isKeyword) {
        return;
      }
    }
    var type = node.type;
    if (type != null) {
      collector.completionLocation = 'FormalParameterList_parameter';
      if (type is NamedType) {
        if (type.importPrefix case var importPrefix?) {
          var prefixElement = importPrefix.element;
          if (prefixElement is PrefixElement) {
            if (type.name.coversOffset(offset)) {
              declarationHelper(
                mustBeType: true,
              ).addDeclarationsThroughImportPrefix(prefixElement);
            }
          }
        }
      }
      if (type.beginToken.coversOffset(offset)) {
        keywordHelper.addFormalParameterKeywords(
          node.parentFormalParameterList,
          suggestRequired: noRequired && suggestRequired,
          suggestVariableName:
              node.name == null || type.beginToken.offset == offset,
        );
        _forTypeAnnotation(node);
      } else if (type is GenericFunctionType &&
          offset < type.functionKeyword.offset &&
          type.returnType == null) {
        _forTypeAnnotation(node);
      }
    } else {
      var keyword = node.constFinalOrVarKeyword;
      if (keyword == null || offset <= keyword.end) {
        collector.completionLocation = 'FormalParameterList_parameter';
        var parent = node.parent;
        if (parent case FormalParameterList list) {
          keywordHelper.addFormalParameterKeywords(
            list,
            suggestRequired: noRequired && suggestRequired,
            suggestVariableName: name.coversOffset(offset),
            suggestCovariant: suggestCovariant,
            suggestThis: suggestThis,
          );
        }
        _forTypeAnnotation(
          node,
          suggestVoid: suggestVoid,
          suggestDynamic: suggestDynamic,
        );
      }
    }
  }

  @override
  void visitRelationalPattern(RelationalPattern node) {
    var operand = node.operand;
    if (node.operator.type == TokenType.LT &&
        operand.isSynthetic &&
        operand.beginToken.nextNonSynthetic?.type == TokenType.GT) {
      // This is most likely a type argument list before a typed literal.
      collector.completionLocation = 'TypeArgumentList_argument';
      _forTypeAnnotation(node);
    } else if (operand is SimpleIdentifier &&
        offset >= node.operator.end &&
        offset <= operand.end) {
      collector.completionLocation = 'RelationalPattern_operand';
      _forExpression(node);
    }
  }

  @override
  void visitRestPatternElement(RestPatternElement node) {
    collector.completionLocation = 'RestPatternElement_pattern';
    _forPattern(node);
  }

  @override
  void visitReturnStatement(ReturnStatement node) {
    if (offset <= node.returnKeyword.end) {
      collector.completionLocation = 'Block_statement';
      _forStatement(node);
    } else {
      collector.completionLocation = 'ReturnStatement_expression';
      _forExpression(node.expression ?? node);
    }
  }

  @override
  void visitSetOrMapLiteral(SetOrMapLiteral node) {
    var offset = this.offset;
    if (offset >= node.leftBracket.end && offset <= node.rightBracket.offset) {
      collector.completionLocation = 'SetOrMapLiteral_element';
      _forCollectionElement(node, node.elements);
    }
  }

  @override
  void visitShowCombinator(ShowCombinator node) {
    collector.completionLocation = 'ShowCombinator_shownName';
    _forCombinator(node, node.shownNames);
  }

  @override
  void visitSimpleStringLiteral(SimpleStringLiteral node) {
    if (suggestUris) {
      switch (node.parent) {
        case Configuration():
        case PartOfDirective():
        case UriBasedDirective():
          UriHelper(
            request: state.request,
            collector: collector,
            state: state,
          ).addSuggestions(node);
          return;
      }
    }

    _visitParentIfAtOrBeforeNode(node);
  }

  @override
  void visitSpreadElement(SpreadElement node) {
    collector.completionLocation = 'SpreadElement_expression';
    _forExpression(node);
  }

  @override
  void visitStringInterpolation(StringInterpolation node) {
    _visitParentIfAtOrBeforeNode(node);
  }

  @override
  void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
    var constructor = node.parent;
    if (constructor is! ConstructorDeclaration) {
      return;
    }
    collector.completionLocation = 'ConstructorDeclaration_initializer';
    if (offset <= node.superKeyword.end && node.argumentList.isFullySynthetic) {
      keywordHelper.addConstructorInitializerKeywords(constructor, node);
      return;
    }
    var period = node.period;
    // TODO(brianwilkerson): If the period is `null` we might want to complete
    //  the argument list after `super`.
    if (period != null &&
        offset >= period.end &&
        offset <= node.argumentList.offset) {
      var container = constructor.parent?.parent;
      var superType = switch (container) {
        ClassDeclaration() => container.declaredFragment?.element.supertype,
        EnumDeclaration() => container.declaredFragment?.element.supertype,
        _ => null,
      };
      if (superType != null) {
        declarationHelper(
          mustBeConstant: constructor.constKeyword != null,
        ).addConstructorNamesForType(type: superType);
      }
    }
  }

  @override
  void visitSuperFormalParameter(SuperFormalParameter node) {
    declarationHelper().addParametersFromSuperConstructor(node);
  }

  @override
  void visitSwitchCase(SwitchCase node) {
    collector.completionLocation = 'SwitchMember_statement';
    _forStatement(node);
  }

  @override
  void visitSwitchDefault(SwitchDefault node) {
    if (offset <= node.keyword.offset) {
      collector.completionLocation = 'SwitchMember_statement';
      keywordHelper.addKeyword(Keyword.CASE);
      keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':');
    } else if (offset <= node.keyword.end) {
      if (node.colon.isSynthetic) {
        keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':');
      } else {
        keywordHelper.addKeyword(Keyword.DEFAULT);
      }
    }
  }

  @override
  void visitSwitchExpression(SwitchExpression node) {
    if (offset >= node.leftParenthesis.end &&
        offset <= node.rightParenthesis.offset) {
      collector.completionLocation = 'SwitchExpression_expression';
      _forExpression(node);
    } else if (offset >= node.leftBracket.end &&
        offset <= node.rightBracket.offset) {
      collector.completionLocation = 'SwitchExpression_body';
      _forPattern(node);
    }
  }

  @override
  void visitSwitchExpressionCase(SwitchExpressionCase node) {
    if (node.arrow.isSynthetic || node.arrow.offset >= offset) {
      // The user is completing in the pattern.
      collector.completionLocation = 'SwitchExpression_body';
      _forPattern(node);
      return;
    }
    var expression = node.expression;
    var endToken = expression.endToken;
    if (endToken == expression.beginToken || endToken.isSynthetic) {
      // The user is completing in the expression.
      collector.completionLocation = 'SwitchExpressionCase_expression';
      var type =
          _computeContextType(node.expression) ?? DynamicTypeImpl.instance;
      _forExpression(
        node.expression,
        canBeBool: _canBeBool(type),
        canBeNull: _canBeNull(type),
        // TODO(FMorschel): Determine if the parameter type has a constant
        //  constructor.
        // Function tear-offs and closures cannot have the `const` keyword
        // before it
        canSuggestConst: !type.isDartCoreFunction && type is! FunctionType,
      );
    }
  }

  @override
  void visitSwitchPatternCase(SwitchPatternCase node) {
    var coveringNode = state.selection.coveringNode;

    if (offset <= node.keyword.end) {
      keywordHelper.addKeyword(Keyword.CASE);
    } else if (offset <= node.colon.offset) {
      collector.completionLocation = 'SwitchPatternCase_pattern';

      // Object pattern `Name^()`
      if (state.selection.coveringNode case NamedType type) {
        if (type.parent case ObjectPattern()) {
          collector.completionLocation = 'ObjectPattern_type';
          type.accept(this);
          return;
        }
      }

      // `case ^ y:`
      // The user want a type for incomplete DeclaredVariablePattern.
      var pattern = node.guardedPattern.pattern;
      if (pattern is ConstantPattern) {
        if (pattern.expression case SimpleIdentifier identifier) {
          if (!identifier.isSynthetic && offset < identifier.offset) {
            declarationHelper(mustBeType: true).addLexicalDeclarations(node);
            return;
          }
        }
      }

      // `case ^ _:`
      // The user want a type for incomplete WildcardPattern.
      if (pattern is WildcardPattern) {
        if (offset < pattern.name.offset) {
          declarationHelper(mustBeType: true).addLexicalDeclarations(node);
          return;
        }
      }

      // `case Name ^:`
      // The user wants the pattern variable name.
      if (pattern is ConstantPattern) {
        if (pattern.expression case TypeLiteral typeLiteral) {
          var namedType = typeLiteral.type;
          if (namedType.end < offset) {
            identifierHelper(
              includePrivateIdentifiers: false,
            ).addSuggestionsFromTypeName(namedType.name.lexeme);
            return;
          }
        }
      }

      // DeclaredVariablePattern `case Name^ y:`
      // ObjectPattern `case Name^(): `
      if (coveringNode case NamedType type) {
        switch (type.parent) {
          case DeclaredVariablePattern():
            //collector.completionLocation = 'DeclaredVariablePattern_type';
            type.accept(this);
            return;
          case ObjectPattern():
            collector.completionLocation = 'ObjectPattern_type';
            type.accept(this);
            return;
          case WildcardPattern():
            collector.completionLocation = 'WildcardPattern_type';
            type.accept(this);
            return;
        }
      }

      var previous = node.colon.previous!;
      var previousKeyword = previous.keyword;
      if (previousKeyword == null) {
        if (previous.isSynthetic || previous.coversOffset(offset)) {
          keywordHelper.addKeyword(Keyword.FINAL);
          keywordHelper.addKeyword(Keyword.VAR);
          var pattern = node.guardedPattern.pattern;
          if (pattern is ConstantPattern) {
            _forExpression(pattern.expression, mustBeNonVoid: true);
          } else {
            _forExpression(pattern, mustBeNonVoid: true);
          }
        } else {
          keywordHelper.addKeyword(Keyword.AS);
          keywordHelper.addKeyword(Keyword.WHEN);
        }
      } else if (previousKeyword == Keyword.AS) {
        keywordHelper.addKeyword(Keyword.DYNAMIC);
      } else if (previousKeyword != Keyword.WHEN) {
        keywordHelper.addKeyword(Keyword.AS);
        keywordHelper.addKeyword(Keyword.WHEN);
      }
    } else {
      collector.completionLocation = 'SwitchMember_statement';
      if (node.statements.isEmpty || offset <= node.statements.first.offset) {
        keywordHelper.addKeyword(Keyword.CASE);
        keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':');
      }
      _forStatement(node);
    }
  }

  @override
  void visitSwitchStatement(SwitchStatement node) {
    if (offset <= node.switchKeyword.end) {
      collector.completionLocation = 'Block_statement';
      _forStatement(node);
    } else if (offset >= node.leftParenthesis.end &&
        offset <= node.rightParenthesis.offset) {
      collector.completionLocation = 'SwitchStatement_expression';
      _forExpression(node, mustBeNonVoid: true);
    } else if (offset >= node.leftBracket.end &&
        offset <= node.rightBracket.offset) {
      collector.completionLocation = 'SwitchMember_statement';
      var members = node.members;
      keywordHelper.addKeyword(Keyword.CASE);
      keywordHelper.addKeywordAndText(Keyword.DEFAULT, ':');
      if (members.isNotEmpty) {
        var element = members.elementBefore(offset);
        if (element != null) {
          _forStatement(element);
        }
      }
    }
  }

  @override
  void visitSymbolLiteral(SymbolLiteral node) {
    _forExpression(node);
  }

  @override
  void visitThisExpression(ThisExpression node) {
    _forExpression(node);
  }

  @override
  void visitThrowExpression(ThrowExpression node) {
    collector.completionLocation = 'ThrowExpression_expression';
    _forExpression(node);
  }

  @override
  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
    if (_handledRecovery(node)) {
      return;
    }

    if (offset == node.offset) {
      _forCompilationUnitMemberBefore(node);
      return;
    }
    var variableDeclarationList = node.variables;
    var variables = variableDeclarationList.variables;
    if (variables.isEmpty) {
      return;
    }
    var firstVariable = variables.first;
    if (offset > firstVariable.beginToken.end) {
      if (variableDeclarationList.type == null) {
        // The name might be a type and the user might be trying to type a name
        // for the variable.
        identifierHelper(
          includePrivateIdentifiers: true,
        ).addSuggestionsFromTypeName(firstVariable.name.lexeme);
      }
      return;
    }
    if (node.externalKeyword == null) {
      keywordHelper.addKeyword(Keyword.EXTERNAL);
    }
    if (variableDeclarationList.lateKeyword == null) {
      keywordHelper.addKeyword(Keyword.LATE);
    }
    if (!variables.first.isConst) {
      keywordHelper.addKeyword(Keyword.CONST);
    }
    if (!variables.first.isFinal) {
      keywordHelper.addKeyword(Keyword.FINAL);
    }
    var parent = node.parent;
    if (parent is CompilationUnit) {
      var (:before, after: _) = parent.membersBeforeAndAfterMember(node);
      if (before == null || before is Directive) {
        collector.completionLocation = 'CompilationUnit_directive';
      } else {
        collector.completionLocation = 'CompilationUnit_declaration';
      }
    }
    declarationHelper(mustBeType: true).addLexicalDeclarations(node);
  }

  @override
  void visitTryStatement(TryStatement node) {
    if (offset <= node.tryKeyword.end) {
      collector.completionLocation = 'Block_statement';
      _forStatement(node);
    } else if (offset >= node.body.end) {
      var finallyKeyword = node.finallyKeyword;
      if (finallyKeyword == null) {
        var catchClauses = node.catchClauses;
        var lastClause = catchClauses.lastOrNull;
        if (lastClause == null) {
          keywordHelper.addTryClauseKeywords(canHaveFinally: true);
        } else {
          keywordHelper.addTryClauseKeywords(
            canHaveFinally: offset >= lastClause.end,
          );
        }
      } else if (offset < finallyKeyword.offset) {
        keywordHelper.addTryClauseKeywords(canHaveFinally: false);
      }
    }
  }

  @override
  void visitTypeArgumentList(TypeArgumentList node) {
    _forTypeAnnotation(node);
  }

  @override
  void visitTypeLiteral(TypeLiteral node) {
    _forExpression(node);
  }

  @override
  void visitTypeParameter(TypeParameter node) {
    // class A {
    //   Future<void^>
    // }
    // This is parsed as `Future<void>() {}`, where `() {}` are synthetic.
    if (node.parent case TypeParameterList typeParameters) {
      var leftParenthesis = typeParameters.rightBracket.next;
      var rightParenthesis = leftParenthesis?.next;
      if (leftParenthesis != null &&
          leftParenthesis.type == TokenType.OPEN_PAREN &&
          leftParenthesis.isSynthetic &&
          rightParenthesis != null &&
          rightParenthesis.type == TokenType.CLOSE_PAREN &&
          rightParenthesis.isSynthetic) {
        collector.completionLocation = 'TypeParameter_bound';
        _forTypeAnnotation(node, excludedNodes: {typeParameters});
        return;
      }
    }

    if (offset <= node.name.end) {
      // The cursor is in the name of the type parameter and there are no names
      // to suggest.
      return;
    }
    var extendsKeyword = node.extendsKeyword;
    if (extendsKeyword == null || offset <= extendsKeyword.end) {
      // Either there is no `extends` keyword or the cursor is in the `extends`
      // keyword, so the keyword should be suggested.
      keywordHelper.addKeyword(Keyword.EXTENDS);
    } else {
      // The cursor is after the `extends` keyword, so we should suggest valid
      // upper bounds.
      collector.completionLocation = 'TypeParameter_bound';
      _forTypeAnnotation(node, mustBeNonVoid: true);
    }
  }

  @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 preceding member even when the grandparent isn't a single
      // identifier, but want to return only if both conditions are true.
      if (_forIncompletePrecedingClassMember(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) {
      if (_handledRecovery(grandparent)) {
        return;
      }
    }

    if (offset <= node.name.end) {
      var constAdded = false;
      var container = grandparent?.parent;
      if (container is BlockClassBody) {
        container = container.parent;
      }
      var keyword = parent.keyword;
      var type = parent.type;
      if (type == null) {
        collector.completionLocation = 'VariableDeclarationList_type';
        if (keyword == null) {
          keywordHelper.addKeyword(Keyword.CONST);
          keywordHelper.addKeyword(Keyword.FINAL);
          keywordHelper.addKeyword(Keyword.VAR);
          constAdded = true;
        }

        // Complete keywords that can follow `abstract` at the top level.
        // `abstract ^` -> `base`, `class`, `final`, `interface`, `mixin`
        // `abstract b^` -> `base`
        if (grandparent is TopLevelVariableDeclaration) {
          if (grandparent.abstractKeyword != null) {
            keywordHelper.addKeyword(Keyword.BASE);
            keywordHelper.addKeyword(Keyword.CLASS);
            keywordHelper.addKeyword(Keyword.FINAL);
            keywordHelper.addKeyword(Keyword.INTERFACE);
            keywordHelper.addKeyword(Keyword.MIXIN);
          }
        }

        if (keyword == null || keyword.keyword != Keyword.VAR) {
          _forTypeAnnotation(node);
        }
      } else {
        var canBePrivate =
            grandparent is FieldDeclaration ||
            grandparent is TopLevelVariableDeclaration;
        identifierHelper(
          includePrivateIdentifiers: canBePrivate,
        ).addVariable(type);
      }
      if (grandparent is FieldDeclaration) {
        collector.completionLocation = 'FieldDeclaration_fields';
        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.firstTokenAfterCommentAndMetadata) {
          // 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.
          if (!constAdded) {
            keywordHelper.addKeyword(Keyword.CONST);
          }
          if (container is ClassDeclaration) {
            keywordHelper.addKeyword(Keyword.FACTORY);
          }
          keywordHelper.addKeyword(Keyword.GET);
          keywordHelper.addKeyword(Keyword.OPERATOR);
          keywordHelper.addKeyword(Keyword.SET);
        }
        if (grandparent.isSingleIdentifier) {
          if (container is ClassDeclaration) {
            collector.completionLocation = 'ClassDeclaration_member';
          } else if (container is EnumDeclaration) {
            collector.completionLocation = 'EnumDeclaration_member';
          } else if (container is ExtensionDeclaration) {
            collector.completionLocation = 'ExtensionDeclaration_member';
          } else if (container is MixinDeclaration) {
            collector.completionLocation = 'MixinDeclaration_member';
          }
          _suggestOverridesFor(
            element: switch (container) {
              ClassDeclaration() => container.declaredFragment?.element,
              MixinDeclaration() => container.declaredFragment?.element,
              _ => null,
            },
          );
        }
      } 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';
      _forExpression(node, mustBeNonVoid: true);
    }
  }

  @override
  void visitVariableDeclarationList(VariableDeclarationList node) {
    var keyword = node.keyword;
    var variables = node.variables;
    if (variables.isNotEmpty && offset <= variables[0].name.end) {
      var type = node.type;
      if ((type == null || type.coversOffset(offset)) &&
          keyword?.keyword != Keyword.VAR) {
        collector.completionLocation = 'VariableDeclarationList_type';
        _forTypeAnnotation(node);
      } else if (type is RecordTypeAnnotation) {
        // This might be a record pattern that happens to look like a type, in
        // which case the user might be typing `in`.
        keywordHelper.addKeyword(Keyword.IN);
      }
    }
  }

  @override
  void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
    collector.completionLocation = 'Block_statement';
    if (_forIncompletePrecedingStatement(node)) {
      return;
    }
    if (offset <= node.beginToken.end) {
      _forStatement(node);
    } else if (offset >= node.end) {
      var parent = node.parent;
      if (parent != null) {
        _forStatement(parent);
      }
    }
  }

  @override
  void visitWhenClause(WhenClause node) {
    var whenKeyword = node.whenKeyword;
    if (!whenKeyword.isSynthetic && offset > whenKeyword.end) {
      collector.completionLocation = 'WhenClause_expression';
      _forExpression(node);
    }
  }

  @override
  void visitWhileStatement(WhileStatement node) {
    if (offset <= node.whileKeyword.end) {
      collector.completionLocation = 'Block_statement';
      _forStatement(node);
    } else if (node.leftParenthesis.end <= offset &&
        offset <= node.rightParenthesis.offset) {
      if (node.condition.isSynthetic ||
          offset <= node.condition.offset ||
          offset == node.condition.end) {
        collector.completionLocation = 'WhileStatement_condition';
        _forExpression(node, mustBeNonVoid: true);
      }
    }
  }

  @override
  void visitWildcardPattern(WildcardPattern node) {
    var type = node.type;
    if (type != null) {
      if (type.coversOffset(offset)) {
        declarationHelper(mustBeType: true).addLexicalDeclarations(node);
      }
    }
  }

  @override
  void visitWithClause(WithClause node) {
    var parent = node.parent;
    var whenKeyword = node.withKeyword;
    if (offset <= whenKeyword.offset && parent is ClassDeclaration) {
      keywordHelper.addClassDeclarationKeywords(parent);
    } else if (offset <= whenKeyword.end) {
      keywordHelper.addKeyword(Keyword.WITH);
    } else {
      collector.completionLocation = 'WithClause_mixinType';
      _forTypeAnnotation(node, mustBeMixable: true);
    }
  }

  @override
  void visitYieldStatement(YieldStatement node) {
    if (offset <= node.yieldKeyword.end) {
      collector.completionLocation = 'Block_statement';
      keywordHelper.addKeyword(Keyword.YIELD);
    } else if (node.semicolon.isSynthetic || offset <= node.semicolon.end) {
      collector.completionLocation = 'YieldStatement_expression';
      _forExpression(node);
    }
  }

  /// Adds a suggestion for a closure.
  void _addClosureSuggestion(
    FunctionType parameterType, {
    required bool includeTrailingComma,
  }) {
    var includeTypes = state.includeTypes;
    collector.addSuggestion(
      ClosureSuggestion(
        functionType: parameterType,
        includeTrailingComma: includeTrailingComma,
        matcherScore: 0.0,
        includeTypes: includeTypes,
        indent: state.indent,
        endOfLine: state.endOfLine,
      ),
    );
    collector.addSuggestion(
      ClosureSuggestion(
        functionType: parameterType,
        includeTrailingComma: includeTrailingComma,
        matcherScore: 0.0,
        useBlockStatement: false,
        includeTypes: includeTypes,
        indent: state.indent,
        endOfLine: state.endOfLine,
      ),
    );
  }

  bool _canBeBool(DartType parameterType) {
    var typeProvider = state.libraryElement.typeProvider;
    var typeSystem = state.libraryElement.typeSystem;
    return typeSystem.isSubtypeOf(typeProvider.boolType, parameterType);
  }

  bool _canBeNull(DartType parameterType) {
    return parameterType.nullabilitySuffix != NullabilitySuffix.none ||
        parameterType.isDartCoreNull ||
        parameterType is DynamicType;
  }

  /// Returns the context type in which [node] is analyzed.
  DartType? _computeContextType(Expression node) {
    return state.request.featureComputer.computeContextType(
      node.parent!,
      node.offset,
    );
  }

  /// Returns the first non-synthetic token within the [node] that is at
  /// or after the [offset].
  Token? _computeDisplacedToken(AstNode node) {
    for (var token in node.allTokens) {
      if (!token.isSynthetic && offset <= token.offset) {
        return token;
      }
    }
    return null;
  }

  bool _computeMustBeAssignable(Expression node) {
    var request = state.request;
    if (node.parent case AssignmentExpression assignment) {
      if (assignment.leftHandSide == node) {
        return request.fileState.lineInfo.onSameLine(
          request.offset,
          assignment.operator.offset,
        );
      }
    }
    return false;
  }

  /// Adds the suggestions that are appropriate at the beginning of an
  /// annotation.
  void _forAnnotation(AstNode node) {
    declarationHelper(mustBeConstant: true).addLexicalDeclarations(node);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a member of a class, enum, extension, extension type, or
  /// mixin.
  void _forClassLikeMember(AstNode node) {
    switch (node) {
      case ClassDeclaration():
        _forClassMember(node);
      case EnumDeclaration():
        _forEnumMember(node);
      case ExtensionDeclaration():
        _forExtensionMember(node);
      case ExtensionTypeDeclaration():
        _forExtensionTypeMember(node);
      case MixinDeclaration():
        _forMixinMember(node);
    }
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a class member.
  void _forClassMember(ClassDeclaration node) {
    keywordHelper.addClassMemberKeywords();
    declarationHelper(mustBeType: true).addLexicalDeclarations(node);
    _suggestOverridesFor(element: node.declaredFragment?.element);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of an element in a collection [literal], with the given
  /// [elements].
  void _forCollectionElement(
    TypedLiteral literal,
    NodeList<CollectionElement> elements,
  ) {
    var mustBeStatic = literal.inStaticContext;
    var mustBeConst = literal.inConstantContext;
    // TODO(FMorschel): Consider the type of the collection to further
    //  constrain the suggestions (like `null`).
    keywordHelper.addCollectionElementKeywords(
      literal,
      elements,
      mustBeConst: mustBeConst,
      mustBeStatic: mustBeStatic,
    );
    var precedingElement = elements.elementBefore(offset);
    declarationHelper(
      mustBeStatic: mustBeStatic,
      mustBeConstant: mustBeConst,
    ).addLexicalDeclarations(precedingElement ?? literal);
  }

  /// Adds the suggestions that are appropriate when completing in the given
  /// [combinator] and the [existingNames] are in the list.
  void _forCombinator(
    Combinator combinator,
    NodeList<SimpleIdentifier> existingNames,
  ) {
    var directive = combinator.parent;
    if (directive is! NamespaceDirective) {
      return;
    }
    var library = directive.referencedLibrary;
    if (library == null) {
      return;
    }
    var coveringNode = state.selection.coveringNode;
    AstNode? excludedName;
    if (existingNames.contains(coveringNode)) {
      excludedName = coveringNode;
    }
    var excludedNames = existingNames
        .where((element) => element != excludedName)
        .map((element) => element.name)
        .toSet();
    declarationHelper(
      preferNonInvocation: true,
    ).addFromLibrary(library, excludedNames);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a top-level declaration.
  void _forCompilationUnitDeclaration(CompilationUnit unit) {
    keywordHelper.addCompilationUnitDeclarationKeywords();
    declarationHelper(mustBeType: true).addLexicalDeclarations(unit);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a member at the top-level of a compilation unit.
  void _forCompilationUnitMember(
    CompilationUnit unit,
    ({AstNode? before, AstNode? after}) surroundingMembers,
  ) {
    var before = surroundingMembers.before;
    if (before is Directive?) {
      collector.completionLocation = 'CompilationUnit_directive';
      _forDirective(unit, before);
    }
    if (surroundingMembers.after is CompilationUnitMember?) {
      collector.completionLocation ??= 'CompilationUnit_declaration';
      _forCompilationUnitDeclaration(unit);
    }
  }

  /// Adds the suggestions that are appropriate when the user is completing
  /// immediately before the given [member].
  void _forCompilationUnitMemberBefore(AstNode member) {
    if (member.parent case CompilationUnit unit) {
      _forCompilationUnitMember(unit, unit.membersBeforeAndAfterMember(member));
    }
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a constant expression. The [node] provides context to
  /// determine which keywords to include.
  void _forConstantExpression(AstNode node) {
    var inConstantContext = node is Expression && node.inConstantContext;
    keywordHelper.addConstantExpressionKeywords(
      inConstantContext: inConstantContext,
    );
    declarationHelper(
      mustBeConstant: true,
      mustBeStatic: node.inStaticContext,
    ).addLexicalDeclarations(node);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a constructor's initializer.
  void _forConstructorInitializer(
    ConstructorDeclaration constructor,
    ConstructorFieldInitializer? initializer,
  ) {
    var element = initializer?.fieldName.element;
    FieldElement? field;
    if (element is FieldElement) {
      field = element;
    }
    keywordHelper.addConstructorInitializerKeywords(constructor, initializer);
    declarationHelper().addFieldsForInitializers(constructor, field);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a directive. The [before] directive is the directive before
  /// the one being added.
  void _forDirective(CompilationUnit unit, Directive? before) {
    keywordHelper.addDirectiveKeywords(unit, before);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of an enum member.
  void _forEnumMember(EnumDeclaration node) {
    keywordHelper.addEnumMemberKeywords();
    declarationHelper(mustBeType: true).addLexicalDeclarations(node);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of an expression. The [node] provides context to determine which
  /// keywords to include.
  void _forExpression(
    AstNode node, {
    bool mustBeAssignable = false,
    bool mustBeNonVoid = false,
    bool canBeBool = true,
    bool canBeNull = true,
    bool canSuggestConst = true,
    bool preferNonInvocation = false,
    bool includeTrailingCommaAfterClosure = false,
  }) {
    var mustBeConstant =
        node is ExpressionImpl &&
        (node.constantContext(includeSelf: true) != null ||
            node.parent is FormalParameterDefaultClause);
    var mustBeStatic = node.inStaticContext;
    keywordHelper.addExpressionKeywords(
      node,
      mustBeConstant: mustBeConstant,
      mustBeStatic: mustBeStatic,
      canBeBool: canBeBool,
      canBeNull: canBeNull,
      canSuggestConst: canSuggestConst,
    );
    declarationHelper(
      mustBeAssignable: mustBeAssignable,
      mustBeConstant: mustBeConstant,
      mustBeNonVoid: mustBeNonVoid,
      mustBeStatic: mustBeStatic,
      preferNonInvocation: preferNonInvocation,
    ).addLexicalDeclarations(node);
    var type = state.contextType;
    if (type?.isDartCoreFunction ?? false) {
      type = _voidFunctionNoParameters();
    }
    if (type is FunctionType && !mustBeConstant) {
      _addClosureSuggestion(
        type,
        includeTrailingComma: includeTrailingCommaAfterClosure,
      );
    }
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of an extension member.
  void _forExtensionMember(ExtensionDeclaration node) {
    keywordHelper.addExtensionMemberKeywords(isStatic: false);
    declarationHelper(mustBeType: true).addLexicalDeclarations(node);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of an extension type member.
  void _forExtensionTypeMember(ExtensionTypeDeclaration node) {
    keywordHelper.addExtensionTypeMemberKeywords(isStatic: false);
    declarationHelper(mustBeType: true).addLexicalDeclarations(node);
  }

  /// Returns `true` if the preceding member is incomplete and no other
  /// suggestions should be offered.
  ///
  /// If the completion offset is within the first token of the given [member],
  /// then check to see whether the preceding member is incomplete. If it is,
  /// then the user might be attempting to complete the preceding member rather
  /// than attempting to prepend something to the given [member], so add the
  /// suggestions appropriate for that situation.
  bool _forIncompletePrecedingClassMember(ClassMember member) {
    if (offset <= member.beginToken.end) {
      var precedingMember = member.precedingMember;
      if (precedingMember == null) {
        return false;
      }
      // Ideally we'd visit the preceding member in order to avoid duplicating
      // code, but the offset will be past where the parser inserted synthetic
      // tokens, preventing that from working.
      switch (precedingMember) {
        case MethodDeclaration declaration:
          if (declaration.body.isFullySynthetic) {
            // TODO(brianwilkerson): Remove the following line.
            //   We set the completion location to match the old behavior, even
            //   though we aren't proposing any elements in this location and
            //   therefore don't need a completion location.
            collector.completionLocation = 'ClassDeclaration_member';
            keywordHelper.addFunctionBodyModifiers(declaration.body);
            return true;
          }
        case _:
      }
    }
    return false;
  }

  /// Returns `true` if the preceding statement is incomplete and no other
  /// suggestions should be offered.
  ///
  /// If the completion offset is within the first token of the given
  /// [statement], then check to see whether the preceding statement is
  /// incomplete. If it is, then the user might be attempting to complete the
  /// preceding statement rather than attempting to prepend something to the
  /// given [statement], so add the suggestions appropriate for that situation.
  bool _forIncompletePrecedingStatement(Statement statement) {
    if (offset <= statement.beginToken.end) {
      var precedingStatement = statement.precedingStatement;
      if (precedingStatement == null) {
        return false;
      }
      // Ideally we'd visit the preceding member in order to avoid
      // duplicating code, but the offset will be past where the parser
      // inserted synthetic tokens, preventing that from working.
      switch (precedingStatement) {
        // TODO(brianwilkerson): Add support for other kinds of declarations.
        case IfStatement declaration:
          if (declaration.elseKeyword == null) {
            keywordHelper.addKeyword(Keyword.ELSE);
            return false;
          }
        case TryStatement declaration:
          if (declaration.finallyBlock == null) {
            visitTryStatement(declaration);
            return declaration.catchClauses.isEmpty;
          }
        case _:
      }
    }
    return false;
  }

  /// Adds the suggestions that are appropriate when the [node] is
  /// referencing a member of the given [type].
  void _forMemberAccess(
    Expression node,
    DartType type, {
    bool onlySuper = false,
  }) {
    var parent = node.parent;
    // TODO(brianwilkerson): Handle the case of static member accesses.
    var mustBeAssignable = _computeMustBeAssignable(node);
    declarationHelper(
      mustBeAssignable: mustBeAssignable,
      mustBeConstant: node.inConstantContext,
      mustBeNonVoid: parent is ArgumentList,
    ).addInstanceMembersOfType(type, onlySuper: onlySuper);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a mixin member.
  void _forMixinMember(MixinDeclaration node) {
    keywordHelper.addMixinMemberKeywords();
    declarationHelper(mustBeType: true).addLexicalDeclarations(node);
    _suggestOverridesFor(element: node.declaredFragment?.element);
  }

  /// Adds the suggestions that are appropriate when the selection is in the
  /// name in a declared variable pattern
  void _forNameInDeclaredVariablePattern(DeclaredVariablePattern node) {
    var parent = node.parent;
    if (parent is GuardedPattern) {
      if (!node.name.isSynthetic) {
        keywordHelper.addKeyword(Keyword.WHEN);
        if (node.type case NamedType namedType) {
          identifierHelper(
            includePrivateIdentifiers: false,
          ).addSuggestionsFromTypeName(namedType.name.lexeme);
        }
      }
    } else if (parent is PatternField) {
      var outerPattern = parent.parent;
      if (outerPattern is DartPattern) {
        collector.completionLocation = 'PatternField_pattern';
        _forPatternFieldNameInPattern(
          outerPattern,
          isKeywordNeeded: (node.type ?? node.keyword) == null,
          isTypeNeeded: false,
        );
      }
    }
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a pattern.
  void _forPattern(AstNode node, {bool mustBeConst = true}) {
    var coveringNode = state.selection.coveringNode;

    if (node case CaseClause caseClause) {
      var pattern = caseClause.guardedPattern.pattern;
      // `if (x case ^ y)`
      // The user wants a type for incomplete DeclaredVariablePattern.
      if (pattern is ConstantPattern) {
        if (pattern.expression case SimpleIdentifier identifier) {
          if (!identifier.isSynthetic && offset < identifier.offset) {
            declarationHelper(mustBeType: true).addLexicalDeclarations(node);
            return;
          }
        }
      }
      // `if (x case ^ _)`
      // The user wants a type for incomplete WildcardPattern.
      if (pattern is WildcardPattern) {
        if (offset < pattern.name.offset) {
          declarationHelper(mustBeType: true).addLexicalDeclarations(node);
          return;
        }
      }
    }

    // `if (x case Name^) {}`
    // Might be a start of a DeclaredVariablePattern.
    // Might be a start of a ObjectPattern.
    // Might be a ConstantPattern.
    if (coveringNode case SimpleIdentifier identifier) {
      if (identifier.isSynthetic) {
        if (coveringNode.parent?.parent case PatternField _) {
          keywordHelper.addVariablePatternKeywords();
          collector.preferConstants = true;
          declarationHelper(
            mustBeConstant: mustBeConst,
            mustBeStatic: node.inStaticContext,
            objectPatternAllowed: true,
          ).addLexicalDeclarations(node);
          return;
        }
      } else if (identifier.parent is ConstantPattern) {
        keywordHelper.addPatternKeywords();
        // TODO(scheglov): Actually we need constructors, but only const.
        // And not-yet imported contributors does not work well yet.
        collector.preferConstants = true;
        declarationHelper(mustBeNonVoid: true).addLexicalDeclarations(node);
        return;
      }
    }

    // DeclaredVariablePattern `Name^ y`
    // ObjectPattern `Name^()`
    if (coveringNode case NamedType type) {
      switch (type.parent) {
        case DeclaredVariablePattern():
          collector.completionLocation = 'DeclaredVariablePattern_type';
          type.accept(this);
          return;
        case ObjectPattern():
          collector.completionLocation = 'ObjectPattern_type';
          type.accept(this);
          return;
        case WildcardPattern():
          collector.completionLocation = 'WildcardPattern_type';
          type.accept(this);
          return;
      }
    }

    keywordHelper.addPatternKeywords();
    collector.preferConstants = true;
    declarationHelper(
      mustBeConstant: mustBeConst,
      mustBeStatic: node.inStaticContext,
      objectPatternAllowed: true,
    ).addLexicalDeclarations(node);
  }

  /// Adds the suggestions that are appropriate for the name of a pattern field.
  void _forPatternFieldName(
    PatternFieldName node, {
    required bool isKeywordNeeded,
    required bool isTypeNeeded,
  }) {
    var pattern = node.parent?.parent;
    if (pattern is DartPattern) {
      _forPatternFieldNameInPattern(
        pattern,
        isKeywordNeeded: isKeywordNeeded,
        isTypeNeeded: isTypeNeeded,
      );
    }
  }

  /// Adds the suggestions that are appropriate for the name of a pattern field.
  void _forPatternFieldNameInPattern(
    DartPattern? pattern, {
    required bool isKeywordNeeded,
    required bool isTypeNeeded,
  }) {
    if (pattern is ObjectPattern) {
      declarationHelper(
        mustBeNonVoid: true,
        preferNonInvocation: true,
      ).addGetters(
        type: pattern.type.typeOrThrow,
        excludedGetters: pattern.fields.fieldNames,
        isKeywordNeeded: isKeywordNeeded,
        isTypeNeeded: isTypeNeeded,
      );
    } else if (pattern is RecordPattern) {
      declarationHelper(
        mustBeNonVoid: true,
        preferNonInvocation: true,
      ).addGetters(
        type: pattern.matchedValueTypeOrThrow,
        excludedGetters: pattern.fields.fieldNames,
        isKeywordNeeded: isKeywordNeeded,
        isTypeNeeded: isTypeNeeded,
      );
    }
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a redirecting constructor invocation.
  void _forRedirectingConstructorInvocation(
    ConstructorDeclaration constructor,
  ) {
    var container = constructor.parent?.parent;
    var thisType = switch (container) {
      ClassDeclaration() => container.declaredFragment?.element.thisType,
      EnumDeclaration() => container.declaredFragment?.element.thisType,
      ExtensionTypeDeclaration() =>
        container.declaredFragment?.element.thisType,
      _ => null,
    };
    if (thisType != null) {
      var constructorName = constructor.name?.lexeme;
      declarationHelper(
        mustBeConstant: constructor.constKeyword != null,
      ).addConstructorNamesForType(type: thisType, exclude: constructorName);
    }
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a statement. The [node] provides context to determine which
  /// keywords to include.
  void _forStatement(AstNode node) {
    _forExpression(node);
    keywordHelper.addStatementKeywords(node);
  }

  /// Adds the suggestions that are appropriate when the selection is at the
  /// beginning of a type annotation.
  void _forTypeAnnotation(
    AstNode node, {
    bool mustBeExtensible = false,
    bool mustBeImplementable = false,
    bool mustBeMixable = false,
    bool mustBeNonVoid = false,
    bool excludeTypeNames = false,
    Set<AstNode> excludedNodes = const {},
    bool isInDeclaration = false,
    bool suggestVoid = true,
    bool suggestDynamic = true,
  }) {
    if (suggestDynamic &&
        !(mustBeExtensible ||
            mustBeImplementable ||
            mustBeMixable ||
            isInDeclaration)) {
      keywordHelper.addKeyword(Keyword.DYNAMIC);
      if (suggestVoid && !mustBeNonVoid) {
        keywordHelper.addKeyword(Keyword.VOID);
      }
    }

    if (node is NamedType) {
      if (node.importPrefix case var importPrefix?) {
        var prefixElement = importPrefix.element;
        if (prefixElement is PrefixElement) {
          declarationHelper(
            mustBeExtensible: mustBeExtensible,
            mustBeImplementable: mustBeImplementable,
            mustBeMixable: mustBeMixable,
            mustBeNonVoid: mustBeNonVoid,
            excludedNodes: excludedNodes,
            excludeTypeNames: excludeTypeNames,
          ).addDeclarationsThroughImportPrefix(prefixElement);
        }
        return;
      }
    }

    declarationHelper(
      mustBeExtensible: mustBeExtensible,
      mustBeImplementable: mustBeImplementable,
      mustBeMixable: mustBeMixable,
      mustBeType: true,
      mustBeNonVoid: mustBeNonVoid,
      excludedNodes: excludedNodes,
    ).addLexicalDeclarations(node);
  }

  /// Adds 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 current scope.
    // _addTypesInScope();
  }

  /// Returns `true` if the [precedingMember] is incomplete.
  ///
  /// If it's incomplete, assume that the user is attempting to complete it and
  /// offer appropriate suggestions.
  bool _handledIncompletePrecedingUnitMember(
    CompilationUnit unit,
    AstNode precedingMember,
  ) {
    // Ideally we'd visit the preceding member in order to avoid duplicating
    // code, but in some cases the offset will be past where the parser inserted
    // synthetic tokens, preventing that from working.
    switch (precedingMember) {
      // TODO(brianwilkerson): Add support for other kinds of declarations.
      case ClassDeclaration declaration:
        if (declaration.hasNoBody) {
          // TODO(brianwilkerson): This location is bogus, but it's what OpType
          //  was computing before. Remove this when we recompute the element
          //  kind relevance table.
          collector.completionLocation = 'CompilationUnit_declaration';
          keywordHelper.addClassDeclarationKeywords(declaration);
          return true;
        }
      //   case ExtensionDeclaration declaration:
      //     if (declaration.leftBracket.isSynthetic) {
      //       // If the prior member is an unfinished extension declaration then the
      //       // user is probably finishing that.
      //       _addExtensionDeclarationKeywords(declaration);
      //       return;
      //     }
      //   }
      case ExtensionTypeDeclaration declaration:
        if (declaration.hasNoBody) {
          // TODO(brianwilkerson): This location is bogus, but it's what OpType
          //  was computing before. Remove this when we recompute the element
          //  kind relevance table.
          collector.completionLocation = 'CompilationUnit_declaration';
          visitExtensionTypeDeclaration(declaration);
          return true;
        }
      case FunctionDeclaration declaration:
        // TODO(brianwilkerson): This location is bogus, but it's what OpType
        //  was computing before. Remove this when we recompute the element
        //  kind relevance table.
        collector.completionLocation = 'CompilationUnit_declaration';
        var body = declaration.functionExpression.body;
        if (body.isEmpty) {
          keywordHelper.addFunctionBodyModifiers(body);
        }
      case ImportDirective directive:
        if (directive.semicolon.isSynthetic) {
          visitImportDirective(directive);
          return true;
        }
      //   case MixinDeclaration declaration:
      //     if (declaration.leftBracket.isSynthetic) {
      //       // If the prior member is an unfinished mixin declaration
      //       // then the user is probably finishing that.
      //       _addMixinDeclarationKeywords(declaration);
      //       return;
      //     }
      //   }
    }
    return false;
  }

  /// Returns `true` if the given [expression] is the result of recovery and
  /// suggestions have already been produced.
  ///
  /// The parser recovers from a parenthesized list in an argument list by
  /// creating either a `ParenthesizedExpression` or a `RecordLiteral`
  bool _handledPossibleClosure(Expression? expression) {
    var nextToken = expression?.endToken.nextNonSynthetic;
    if (nextToken == null || offset > nextToken.offset) {
      return false;
    }
    switch (expression) {
      case ParenthesizedExpression(:var expression):
        if (expression is SimpleIdentifier) {
          keywordHelper.addFunctionBodyModifiers(null);
          return true;
        }
      case RecordLiteral record:
        for (var field in record.fields) {
          if (field is! SimpleIdentifier) {
            return false;
          }
        }
        keywordHelper.addFunctionBodyModifiers(null);
        return true;
    }
    return false;
  }

  /// Returns `true` if the given [declaration] is the result of recovery and
  /// suggestions have already been produced.
  ///
  /// The parser recovers from a simple identifier by assuming that it's a
  /// top-level variable declaration. But a simple identifier could be the start
  /// of any kind of member, so defer to the compilation unit.
  bool _handledRecovery(TopLevelVariableDeclaration declaration) {
    var unit = declaration.parent;
    if (unit is CompilationUnit) {
      ({AstNode? before, AstNode? after})? surroundingMembers;
      if (offset <= declaration.beginToken.end) {
        surroundingMembers = unit.membersBeforeAndAfterMember(declaration);
        var before = surroundingMembers.before;
        if (before != null &&
            _handledIncompletePrecedingUnitMember(unit, before)) {
          // The preceding member is incomplete, so assume that the user is
          // completing it rather than starting a new member.
          return true;
        }
      }
      if (declaration.isSingleIdentifier &&
          offset <= declaration.beginToken.end) {
        surroundingMembers ??= unit.membersBeforeAndAfterMember(declaration);
        _forCompilationUnitMember(unit, surroundingMembers);
        return true;
      }
    }
    return false;
  }

  /// Returns the `completionLocation` for an argument in the [argumentList].
  ///
  /// The location encodes whether the argument [isNamed].
  String _locationFor(ArgumentList argumentList, {required bool isNamed}) {
    var parent = argumentList.parent;
    var context = switch (parent) {
      Annotation() => 'annotation',
      EnumConstantArguments() => 'enumConstantArguments',
      ExtensionOverride() => 'extensionOverride',
      FunctionExpressionInvocation() => 'function',
      InstanceCreationExpression() =>
        // TODO(brianwilkerson): Enable this case for better relevance.
        //        flutter.isWidgetType(parent.staticType) ? 'widgetConstructor' :
        'constructor',
      MethodInvocation() => 'method',
      RedirectingConstructorInvocation() => 'constructorRedirect',
      SuperConstructorInvocation() => 'constructorRedirect',
      _ => '',
    };
    var argumentKind = isNamed ? 'named' : 'unnamed';
    return 'ArgumentList_${context}_$argumentKind';
  }

  DartType? _resolveFutureOrType(DartType? contextType) {
    if (contextType is InterfaceType && contextType.isDartAsyncFutureOr) {
      return contextType.typeArguments.firstOrNull;
    }
    return contextType;
  }

  /// Return the element whose static members are being accessed through [target].
  Element? _staticMemberTargetElement(Expression target) {
    return switch (target) {
      Identifier() => target.element,
      TypeLiteral() => target.type.element,
      _ => null,
    };
  }

  /// Suggests overrides in the context of the given [element].
  ///
  /// If the budget has been exceeded, then the results are marked as incomplete
  /// and no suggestions are added.
  void _suggestOverridesFor({
    required InterfaceElement? element,
    bool skipAt = false,
  }) {
    if (state.budget.isEmpty) {
      // Don't suggest overrides if the time budget has already been spent.
      collector.isIncomplete = true;
      return;
    }
    if (suggestOverrides && element != null) {
      overrideHelper.computeOverridesFor(
        interfaceElement: element,
        replacementRange: SourceRange(offset, 0),
        skipAt: skipAt,
      );
    }
  }

  void _suggestRecordLiteralNamedFields({
    required DartType? contextType,
    required AstNode containerNode,
    required RecordLiteral? recordLiteral,
    required bool isNewField,
  }) {
    contextType = _resolveFutureOrType(contextType);
    RecordType recordType;
    if (contextType is RecordType) {
      recordType = contextType;
    } else {
      return;
    }

    var displaced = _computeDisplacedToken(containerNode);
    if (displaced == null) {
      return;
    }

    var includedNames = const <String>{};
    if (recordLiteral != null) {
      includedNames = recordLiteral.fields
          .whereType<RecordLiteralNamedField>()
          .map((e) => e.name.lexeme)
          .toSet();
    }

    for (var field in recordType.namedFields) {
      if (!includedNames.contains(field.name)) {
        var matcherScore = state.matcher.score(field.name);
        if (matcherScore != -1) {
          if (isNewField) {
            collector.addSuggestion(
              RecordLiteralNamedFieldSuggestion.newField(
                field: field,
                matcherScore: matcherScore,
                appendComma:
                    displaced.type != TokenType.COMMA &&
                    displaced.type != TokenType.CLOSE_PAREN,
              ),
            );
          } else {
            collector.addSuggestion(
              RecordLiteralNamedFieldSuggestion.onlyName(
                field: field,
                matcherScore: matcherScore,
              ),
            );
          }
        }
      }
    }
  }

  /// Checks for typing `@override` inside a class body, before `}`.
  ///
  /// There is no node, so this recover using tokens.
  bool _tryAnnotationAtEndOfClassBody(Declaration node) {
    var displaced = state.request.target.entity;
    if (displaced is Token && displaced.type == TokenType.CLOSE_CURLY_BRACKET) {
      var identifier = displaced.previous;
      if (identifier != null && identifier.type == TokenType.IDENTIFIER) {
        if (identifier.previous?.type == TokenType.AT) {
          collector.completionLocation = 'Annotation_name';
          _forAnnotation(node);
          _tryOverrideAnnotation(identifier, node);
          return true;
        }
      }
    }
    return false;
  }

  /// Suggests an override if the [identifier] at `@` is a start of `@override`.
  void _tryOverrideAnnotation(Token identifier, Declaration node) {
    var lexeme = identifier.lexeme;
    if (lexeme.isNotEmpty && 'override'.startsWith(lexeme)) {
      var declaredElement = node.declaredFragment?.element;
      if (declaredElement is InterfaceElement) {
        _suggestOverridesFor(element: declaredElement, skipAt: true);
      }
    }
  }

  void _visitForEachParts(ForEachParts node) {
    if (node.inKeyword.coversOffset(offset)) {
      var previous = node.findPrevious(node.inKeyword);
      if (previous is SyntheticStringToken && previous.lexeme == 'in') {
        previous = node.findPrevious(previous);
      }
      if (previous != null && previous.type == TokenType.EQ) {
        keywordHelper.addKeyword(Keyword.CONST);
        keywordHelper.addKeyword(Keyword.FALSE);
        keywordHelper.addKeyword(Keyword.NULL);
        keywordHelper.addKeyword(Keyword.TRUE);
      } else {
        keywordHelper.addKeyword(Keyword.IN);
      }
    } else if (!node.inKeyword.isSynthetic) {
      keywordHelper.addKeyword(Keyword.AWAIT);
      declarationHelper(
        mustBeStatic: node.inStaticContext,
        mustBeNonVoid: true,
      ).addLexicalDeclarations(node);
    }
  }

  /// Visits the parent of the node if the completion offset is at or before the
  /// offset of the [node].
  void _visitParentIfAtOrBeforeNode(AstNode node) {
    if (offset <= node.offset) {
      node.parent?.accept(this);
    }
  }

  FunctionTypeImpl _voidFunctionNoParameters() {
    var typeProvider = state.libraryElement.typeProvider
        .ifTypeOrNull<TypeProviderImpl>();
    return FunctionTypeImpl(
      typeParameters: const [],
      formalParameters: const [],
      returnType: typeProvider?.voidType ?? DynamicTypeImpl.instance,
      nullabilitySuffix: NullabilitySuffix.none,
    );
  }
}

extension on ClassNamePart {
  /// Whether this is part of a primary constructor declaration and contains the
  /// `const` modifier.
  bool get hasConst {
    var self = this;
    return self is PrimaryConstructorDeclaration && self.constKeyword != null;
  }
}

extension on AstNode {
  /// Whether the completion location is in a context where only static members
  /// of the enclosing type can be suggested.
  bool get inStaticContext {
    var enclosingMember = parent;
    while (enclosingMember != null) {
      if (enclosingMember is MethodDeclaration) {
        return enclosingMember.isStatic;
      } else if (enclosingMember.parent case ConstructorDeclaration(
        :var factoryKeyword,
      ) when enclosingMember is FunctionBody) {
        return factoryKeyword != null;
      } else if (enclosingMember is VariableDeclarationList &&
          enclosingMember.parent is FieldDeclaration) {
        return !enclosingMember.isLate;
      }
      enclosingMember = enclosingMember.parent;
    }
    return true;
  }

  /// Whether 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;
  }

  /// The last token in this node that is not synthetic.
  ///
  /// Returns `null` if all the tokens are synthetic.
  Token? get lastNonSynthetic {
    var current = endToken;
    var stop = beginToken.previous!;
    while (current != stop) {
      if (!current.isSynthetic) {
        return current;
      }
      current = current.previous!;
    }
    return null;
  }

  /// Returns `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),
      Block(:var statements) => statements.contains(child),
      BlockClassBody(:var members) => members.contains(child),
      BlockEnumBody(:var constants, :var members) =>
        constants.contains(child) || members.contains(child),
      CascadeExpression(:var cascadeSections) => cascadeSections.contains(
        child,
      ),
      ClassDeclaration(:var metadata) => 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),
      EnumConstantDeclaration(:var metadata) => metadata.contains(child),
      EnumDeclaration(:var metadata) => metadata.contains(child),
      ExportDirective(:var combinators, :var configurations, :var metadata) =>
        combinators.contains(child) ||
            configurations.contains(child) ||
            metadata.contains(child),
      ExtensionDeclaration(:var metadata) => metadata.contains(child),
      ExtensionTypeDeclaration(:var metadata) => 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),
      LibraryDirective(:var metadata) => metadata.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 metadata) => metadata.contains(child),
      MixinOnClause(:var superclassConstraints) =>
        superclassConstraints.contains(child),
      ObjectPattern(:var fields) => fields.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),
      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 {
  /// Whether this class declaration doesn't have a body.
  bool get hasNoBody {
    switch (body) {
      case BlockClassBody body:
        return body.leftBracket.isSynthetic && body.rightBracket.isSynthetic;
      default:
        return true;
    }
  }
}

extension on ClassMember {
  /// Returns the member before `this`, or `null` if this is the first member in
  /// the body.
  ClassMember? get precedingMember {
    var parent = this.parent?.parent;
    var members = switch (parent) {
      ClassDeclaration(:BlockClassBody body) => body.members,
      EnumDeclaration(:BlockEnumBody body) => body.members,
      ExtensionDeclaration(:BlockClassBody body) => body.members,
      ExtensionTypeDeclaration(:BlockClassBody body) => body.members,
      MixinDeclaration(:BlockClassBody body) => body.members,
      _ => null,
    };
    if (members == null) {
      return null;
    }
    var index = members.indexOf(this);
    if (index <= 0) {
      return null;
    }
    return members[index - 1];
  }
}

extension on ArgumentList {
  /// The element being invoked by the expression containing this argument list,
  /// or `null` if the element is not known.
  Element? get invokedElement {
    switch (parent) {
      case Annotation invocation:
        return invocation.element;
      case DotShorthandConstructorInvocation invocation:
        return invocation.element;
      case DotShorthandInvocation invocation:
        return invocation.memberName.element;
      case EnumConstantArguments invocation:
        var grandParent = invocation.parent;
        if (grandParent is EnumConstantDeclaration) {
          return grandParent.constructorElement;
        }
      case FunctionExpressionInvocation invocation:
        var element = invocation.element;
        if (element == null) {
          var function = invocation.function.unParenthesized;
          if (function is SimpleIdentifier) {
            return function.element;
          }
        }
        return element;
      case InstanceCreationExpression invocation:
        return invocation.constructorName.element;
      case MethodInvocation invocation:
        return invocation.methodName.element;
      case SuperConstructorInvocation invocation:
        return invocation.element;
      case RedirectingConstructorInvocation invocation:
        return invocation.element;
    }
    return null;
  }

  List<FormalParameterElement>? get invokedFormalParameters {
    var result = invokedElement?.getParameters();
    if (result != null) {
      return result;
    }

    switch (parent) {
      case FunctionExpressionInvocation invocation:
        var functionType = invocation.function.staticType;
        if (functionType is FunctionType) {
          return functionType.formalParameters;
        }
    }

    return null;
  }

  /// Returns a record whose fields indicate the number of positional arguments
  /// before the argument at the [argumentIndex], and the names of named
  /// parameters that are already in use either by this argument list or in the
  /// case of an invocation of a super constructor, already in use as super
  /// parameters.
  ({int positionalArgumentCount, Set<String> usedNames}) argumentContext(
    int argumentIndex,
  ) {
    var positionalArgumentCount = 0;
    var usedNames = <String>{};
    for (var i = 0; i < arguments.length; i++) {
      var argument = arguments[i];
      if (argument is NamedArgument) {
        usedNames.add(argument.name.lexeme);
      } else if (i < argumentIndex) {
        positionalArgumentCount++;
      }
    }

    // Add in any names used by super parameters.
    if (parent case SuperConstructorInvocation(
      parent: ConstructorDeclaration constructor,
    )) {
      var parameters = constructor.parameters.parameters;
      for (var i = 0; i < parameters.length; i++) {
        var parameter = parameters[i];
        if (!parameter.isNamed) continue;

        var formalParameter = parameter;
        if (formalParameter is SuperFormalParameter) {
          usedNames.add(formalParameter.name.lexeme);
        }
      }
    }

    return (
      positionalArgumentCount: positionalArgumentCount,
      usedNames: usedNames,
    );
  }

  /// Returns a record whose fields are the arguments in this argument list
  /// that are lexically immediately before and after the given [offset].
  ({Expression? before, Expression? after}) argumentsBeforeAndAfterOffset(
    int offset,
  ) {
    Expression? previous;
    for (var argument in arguments) {
      var expression = argument.argumentExpression;
      if (offset < argument.offset) {
        return (before: previous, after: expression);
      } else if (offset == argument.offset && offset == previous?.end) {
        return (before: previous, after: expression);
      }
      previous = expression;
    }
    return (before: previous, after: null);
  }
}

extension on CompilationUnit {
  /// Returns a record whose fields are the members in this compilation unit
  /// that are lexically immediately before and after the given [member].
  ({AstNode? before, AstNode? after}) membersBeforeAndAfterMember(
    AstNode? member,
  ) {
    var members = sortedDirectivesAndDeclarations;
    AstNode? before, after;
    if (member != null) {
      var index = members.indexOf(member);
      if (index > 0) {
        before = members[index - 1];
      }
      if (index + 1 < members.length) {
        after = members[index + 1];
      }
    }
    return (before: before, after: after);
  }

  /// Returns a record whose fields are the members in this compilation unit
  /// that are lexically immediately before and after the given [offset].
  ({AstNode? before, AstNode? after}) membersBeforeAndAfterOffset(int offset) {
    var members = sortedDirectivesAndDeclarations;
    AstNode? previous;
    for (var member in members) {
      if (offset < member.offset) {
        return (before: previous, after: member);
      }
      previous = member;
    }
    return (before: previous, after: null);
  }
}

extension on Element? {
  /// Returns the parameters associated with this element, or `null` if this
  /// element doesn't have any parameters associated with it.
  ///
  /// If this element is an executable element (method or function), then return
  /// the method / function's parameters. If this element is a variable and the
  /// variable's type is a function type, then return the parameters from the
  /// function type.
  List<FormalParameterElement>? getParameters() {
    var self = this;
    if (self is GetterElement) {
      return self.returnType.ifTypeOrNull<FunctionType>()?.formalParameters;
    } else if (self is ExecutableElement) {
      return self.formalParameters;
    } else if (self is VariableElement) {
      var type = self.type;
      if (type is FunctionType) {
        return type.formalParameters;
      }
    }
    return null;
  }
}

extension on AstNode {
  bool get isFollowedByComma {
    var nextToken = endToken.next;
    return nextToken != null &&
        nextToken.type == TokenType.COMMA &&
        !nextToken.isSynthetic;
  }
}

extension on ExpressionStatement {
  /// Whether 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 ExtensionTypeDeclaration {
  /// Whether this class declaration doesn't have a body.
  bool get hasNoBody {
    switch (body) {
      case BlockClassBody body:
        return body.leftBracket.isSynthetic && body.rightBracket.isSynthetic;
      default:
        return true;
    }
  }
}

extension on FieldDeclaration {
  /// Whether this field declaration consists of a single identifier.
  bool get isSingleIdentifier {
    var first = firstTokenAfterCommentAndMetadata;
    var last = endToken;
    return first.isKeywordOrIdentifier &&
        last.isSynthetic &&
        first.next == last;
  }
}

extension on FormalParameter {
  /// Whether this formal parameter declaration is incomplete.
  bool get isIncomplete {
    var name = this.name;
    if (name == null || name.isKeyword) {
      return true;
    }
    if (name.isSynthetic) {
      var next = name.next;
      if (next != null && next.isKeyword) {
        return true;
      }
    }
    var self = this;
    if (self.defaultClause case var defaultClause?) {
      var defaultValue = defaultClause.value;
      if (defaultValue.isSynthetic) {
        return true;
      }
    }
    return false;
  }

  /// Whether this formal parameter declaration consists of a single identifier.
  bool get isSingleIdentifier {
    var beginToken = this.beginToken;
    return beginToken == endToken && beginToken.isKeywordOrIdentifier;
  }
}

extension on GuardedPattern {
  /// Whether this pattern has, or might have, a `when` keyword.
  bool get hasWhen {
    if (whenClause != null) {
      return true;
    }
    var pattern = this.pattern;
    if (pattern is DeclaredVariablePattern) {
      if (pattern.name.lexeme == 'when') {
        var type = pattern.type;
        if (type is NamedType && type.typeArguments == null) {
          return true;
        }
      }
    }
    return false;
  }
}

extension on NodeList<PatternField> {
  /// Returns the names of the named fields in this list.
  Set<String> get fieldNames {
    return map((field) => field.effectiveName).nonNulls.toSet();
  }
}

extension on Statement {
  /// Returns the statement before `this`, or `null` if this is the first
  /// statement in the block.
  Statement? get precedingStatement {
    var 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? {
  /// Returns `true` if the receiver covers the [offset].
  bool coversOffset(int offset) {
    var self = this;
    return self != null && self.offset <= offset && self.end >= offset;
  }
}

extension on Token {
  Token? get nextNonSynthetic {
    var candidate = next;
    while (candidate != null &&
        candidate.isSynthetic &&
        candidate.next != candidate) {
      candidate = candidate.next;
    }
    return candidate;
  }
}

extension on TopLevelVariableDeclaration {
  /// Whether this top level variable declaration consists of a single
  /// identifier.
  bool get isSingleIdentifier {
    var first = beginToken;
    var next = first.next;
    var last = endToken;
    return first.isKeywordOrIdentifier &&
        last.isSynthetic &&
        (next == last || (next?.isSynthetic == true && next?.next == last));
  }
}

extension on TypeAnnotation? {
  /// Whether this type annotation consists of a single identifier.
  bool get isSingleIdentifier {
    var self = this;
    return self is NamedType &&
        self.question == null &&
        self.typeArguments == null &&
        self.importPrefix == null;
  }
}
