// Copyright (c) 2014, 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.

// Many functions here are mostly camelcase, with an occasional underscore to
// separate phrases.
// ignore_for_file: non_constant_identifier_names

import 'dart:math' as math;

import 'package:_fe_analyzer_shared/src/parser/quote.dart'
    show analyzeQuote, Quote, firstQuoteLength, lastQuoteLength;
import 'package:_fe_analyzer_shared/src/scanner/characters.dart' as char;
import 'package:analysis_server/lsp_protocol/protocol.dart'
    show SemanticTokenTypes, SemanticTokenModifiers;
import 'package:analysis_server/src/lsp/constants.dart'
    show CustomSemanticTokenModifiers, CustomSemanticTokenTypes;
import 'package:analysis_server/src/lsp/semantic_tokens/encoder.dart'
    show SemanticTokenInfo;
import 'package:analysis_server/src/lsp/semantic_tokens/mapping.dart'
    show highlightRegionTokenModifiers, highlightRegionTokenTypes;
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/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/element/extensions.dart';
import 'package:analyzer/src/utilities/extensions/ast.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
import 'package:collection/collection.dart';

/// A computer for [HighlightRegion]s and LSP [SemanticTokenInfo] in a Dart [CompilationUnit].
class DartUnitHighlightsComputer {
  final CompilationUnit _unit;
  final SourceRange? range;

  final _regions = <HighlightRegion>[];
  final _semanticTokens = <SemanticTokenInfo>[];
  bool _computeRegions = false;
  bool _computeSemanticTokens = false;

  /// Creates a computer for [HighlightRegion]s and LSP [SemanticTokenInfo] in a
  /// Dart [CompilationUnit].
  ///
  /// If [range] is supplied, tokens outside of this range will not be included
  /// in results.
  new(this._unit, {this.range});

  /// Returns the computed highlight regions, not `null`.
  List<HighlightRegion> compute() {
    _reset();
    _computeRegions = true;
    _unit.accept(_DartUnitHighlightsComputerVisitor(this));
    _addCommentRanges();
    return _regions;
  }

  /// Returns the computed semantic tokens, not `null`.
  List<SemanticTokenInfo> computeSemanticTokens() {
    _reset();
    _computeSemanticTokens = true;
    _unit.accept(_DartUnitHighlightsComputerVisitor(this));
    _addCommentRanges();
    return _semanticTokens;
  }

  void _addCommentRanges() {
    Token? token = _unit.beginToken;
    while (token != null) {
      Token? commentToken = token.precedingComments;
      while (commentToken != null) {
        HighlightRegionType? highlightType;
        if (commentToken.type == TokenType.MULTI_LINE_COMMENT) {
          if (commentToken.lexeme.startsWith('/**')) {
            highlightType = HighlightRegionType.COMMENT_DOCUMENTATION;
          } else {
            highlightType = HighlightRegionType.COMMENT_BLOCK;
          }
        }
        if (commentToken.type == TokenType.SINGLE_LINE_COMMENT) {
          if (commentToken.lexeme.startsWith('///')) {
            highlightType = HighlightRegionType.COMMENT_DOCUMENTATION;
          } else {
            highlightType = HighlightRegionType.COMMENT_END_OF_LINE;
          }
        }
        if (highlightType != null) {
          _addRegion_token(commentToken, highlightType);
        }
        commentToken = commentToken.next;
      }
      if (token.isEof) {
        // Only exit the loop *after* processing the EOF token as it may
        // have preceding comments.
        break;
      }
      token = token.next;
    }
  }

  void _addIdentifierRegion({
    required AstNode parent,
    required Token nameToken,
    required Element? element,
  }) {
    if (_addIdentifierRegion_keyword(nameToken)) {
      return;
    }
    if (_addIdentifierRegion_class(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_extension(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_constructor(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_getterSetterDeclaration(
      parent,
      nameToken,
      element,
    )) {
      return;
    }
    if (_addIdentifierRegion_field(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_function(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_importPrefix(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_label(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_localVariable(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_method(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_parameter(parent, nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_typeAlias(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_typeParameter(nameToken, element)) {
      return;
    }
    if (_addIdentifierRegion_unresolvedInstanceMemberReference(
      parent,
      nameToken,
      element,
    )) {
      return;
    }
    _addRegion_token(nameToken, HighlightRegionType.IDENTIFIER_DEFAULT);
  }

  void _addIdentifierRegion_annotation(Annotation node) {
    var arguments = node.arguments;
    if (arguments == null) {
      _addRegion_node(node, HighlightRegionType.ANNOTATION);
    } else {
      _addRegion_nodeStart_tokenEnd(
        node,
        arguments.beginToken,
        HighlightRegionType.ANNOTATION,
      );
      _addRegion_token(arguments.endToken, HighlightRegionType.ANNOTATION);
    }
  }

  bool _addIdentifierRegion_class(
    AstNode parent,
    Token nameToken,
    Element? element,
  ) {
    if (element is! InterfaceElement) {
      return false;
    }
    // prepare type
    HighlightRegionType type;
    SemanticTokenTypes? semanticType;
    Set<SemanticTokenModifiers>? semanticModifiers;
    var grandParent = parent.parent;
    if (parent is NamedType &&
        grandParent is ConstructorName &&
        grandParent.parent is InstanceCreationExpression) {
      // new [!Class!]()
      // new [!Class!].named()
      type = HighlightRegionType.CONSTRUCTOR;
      semanticType = SemanticTokenTypes.class_;
      semanticModifiers = {CustomSemanticTokenModifiers.constructor};
    } else if (element is EnumElement) {
      type = HighlightRegionType.ENUM;
    } else if (element is ExtensionTypeElement) {
      type = HighlightRegionType.EXTENSION_TYPE;
    } else if (element is MixinElement) {
      type = HighlightRegionType.MIXIN;
    } else {
      type = HighlightRegionType.CLASS;
      if (parent is ConstructorDeclaration) {
        semanticModifiers = {
          CustomSemanticTokenModifiers.constructor,
          SemanticTokenModifiers.declaration,
        };
      }
    }

    // add region
    return _addRegion_token(
      nameToken,
      type,
      semanticTokenType: semanticType,
      semanticTokenModifiers: semanticModifiers,
      additionalSemanticTokenModifiers: _isAnnotationIdentifier(parent)
          ? {CustomSemanticTokenModifiers.annotation}
          : null,
    );
  }

  bool _addIdentifierRegion_constructor(
    AstNode parent,
    Token nameToken,
    Element? element,
  ) {
    if (element is! ConstructorElement) {
      return false;
    }
    return _addRegion_token(
      nameToken,
      HighlightRegionType.CONSTRUCTOR,
      // For semantic tokens, constructor names are coloured like methods but
      // have a modifier applied.
      semanticTokenType: SemanticTokenTypes.method,
      semanticTokenModifiers: {
        CustomSemanticTokenModifiers.constructor,
        if (_isAnnotationIdentifier(parent))
          CustomSemanticTokenModifiers.annotation,
      },
    );
  }

  bool _addIdentifierRegion_extension(Token nameToken, Element? element) {
    if (element is! ExtensionElement) {
      return false;
    }

    return _addRegion_token(nameToken, HighlightRegionType.EXTENSION);
  }

  bool _addIdentifierRegion_field(
    AstNode parent,
    Token nameToken,
    Element? element,
  ) {
    // Compute the type of the identifier.
    HighlightRegionType? type;
    if (element is FieldElement) {
      if (element.isEnumConstant) {
        type = HighlightRegionType.ENUM_CONSTANT;
      } else if (element.isStatic) {
        type = HighlightRegionType.STATIC_FIELD_DECLARATION;
      } else {
        type = HighlightRegionType.INSTANCE_FIELD_REFERENCE;
      }
    } else if (element is TopLevelVariableElement) {
      type = HighlightRegionType.TOP_LEVEL_VARIABLE_DECLARATION;
    } else if (element is GetterElement) {
      var accessor = element;
      var variable = accessor.variable;
      if (variable is TopLevelVariableElement) {
        type = HighlightRegionType.TOP_LEVEL_GETTER_REFERENCE;
      } else if (variable is FieldElement && variable.isEnumConstant) {
        type = HighlightRegionType.ENUM_CONSTANT;
      } else if (accessor.isStatic) {
        type = HighlightRegionType.STATIC_GETTER_REFERENCE;
      } else {
        type = HighlightRegionType.INSTANCE_GETTER_REFERENCE;
      }
    } else if (element is SetterElement) {
      var accessor = element;
      var variable = accessor.variable;
      if (variable is TopLevelVariableElement) {
        type = HighlightRegionType.TOP_LEVEL_SETTER_REFERENCE;
      } else if (variable is FieldElement && variable.isEnumConstant) {
        type = HighlightRegionType.ENUM_CONSTANT;
      } else if (accessor.isStatic) {
        type = HighlightRegionType.STATIC_SETTER_REFERENCE;
      } else {
        type = HighlightRegionType.INSTANCE_SETTER_REFERENCE;
      }
    } else if (element == null) {
      DartType? staticType;
      if (parent is PropertyAccess && nameToken == parent.propertyName.token) {
        staticType = parent.realTarget.staticType;
      } else if (parent.enclosingInstanceElement case ExtensionElement(
        :var extendedType,
      ) when parent is! PrefixedIdentifier) {
        staticType = extendedType;
      }
      // Handle tokens that are references to record fields.
      if (staticType is RecordType) {
        type = staticType.fieldByName(nameToken.lexeme) != null
            ? HighlightRegionType.INSTANCE_GETTER_REFERENCE
            : HighlightRegionType.UNRESOLVED_INSTANCE_MEMBER_REFERENCE;
      }
    }
    // Add the highlight region.
    if (type != null) {
      return _addRegion_token(
        nameToken,
        type,
        additionalSemanticTokenModifiers: _isAnnotationIdentifier(parent)
            ? {CustomSemanticTokenModifiers.annotation}
            : null,
      );
    }
    return false;
  }

  bool _addIdentifierRegion_function(
    AstNode parent,
    Token nameToken,
    Element? element,
  ) {
    if (element is! TopLevelFunctionElement &&
        element is! LocalFunctionElement) {
      return false;
    }
    var isInvocation =
        parent is MethodInvocation && parent.methodName.token == nameToken;
    HighlightRegionType type;
    var isTopLevel = element is TopLevelFunctionElement;
    type = isTopLevel
        ? isInvocation
              ? HighlightRegionType.TOP_LEVEL_FUNCTION_REFERENCE
              : HighlightRegionType.TOP_LEVEL_FUNCTION_TEAR_OFF
        : isInvocation
        ? HighlightRegionType.LOCAL_FUNCTION_REFERENCE
        : HighlightRegionType.LOCAL_FUNCTION_TEAR_OFF;
    return _addRegion_token(nameToken, type);
  }

  bool _addIdentifierRegion_getterSetterDeclaration(
    AstNode parent,
    Token nameToken,
    Element? element,
  ) {
    // should be declaration
    if (!(parent is MethodDeclaration || parent is FunctionDeclaration)) {
      return false;
    }
    var isTopLevel = parent.parent is CompilationUnit;
    HighlightRegionType type;
    if (element is GetterElement) {
      if (isTopLevel) {
        type = HighlightRegionType.TOP_LEVEL_GETTER_DECLARATION;
      } else if (element.isStatic) {
        type = HighlightRegionType.STATIC_GETTER_DECLARATION;
      } else {
        type = HighlightRegionType.INSTANCE_GETTER_DECLARATION;
      }
    } else if (element is SetterElement) {
      if (isTopLevel) {
        type = HighlightRegionType.TOP_LEVEL_SETTER_DECLARATION;
      } else if (element.isStatic) {
        type = HighlightRegionType.STATIC_SETTER_DECLARATION;
      } else {
        type = HighlightRegionType.INSTANCE_SETTER_DECLARATION;
      }
    } else {
      // should be property accessor
      return false;
    }
    return _addRegion_token(nameToken, type);
  }

  bool _addIdentifierRegion_importPrefix(Token nameToken, Element? element) {
    if (element is! PrefixElement) {
      return false;
    }
    return _addRegion_token(nameToken, HighlightRegionType.IMPORT_PREFIX);
  }

  bool _addIdentifierRegion_keyword(Token nameToken) {
    var name = nameToken.lexeme;
    if (name == 'void') {
      return _addRegion_token(
        nameToken,
        HighlightRegionType.KEYWORD,
        semanticTokenModifiers: {CustomSemanticTokenModifiers.void_},
      );
    }
    return false;
  }

  bool _addIdentifierRegion_label(
    AstNode parent,
    Token nameToken,
    Element? element,
  ) {
    if (element is! LabelElement) {
      return false;
    }
    return _addRegion_token(
      nameToken,
      HighlightRegionType.LABEL,
      semanticTokenModifiers: parent is! BreakStatement
          ? {SemanticTokenModifiers.declaration}
          : null,
    );
  }

  bool _addIdentifierRegion_localVariable(Token nameToken, Element? element) {
    if (element is! LocalVariableElement) {
      return false;
    }
    // OK
    var type = element.type is DynamicType
        ? HighlightRegionType.DYNAMIC_LOCAL_VARIABLE_REFERENCE
        : HighlightRegionType.LOCAL_VARIABLE_REFERENCE;
    return _addRegion_token(nameToken, type);
  }

  bool _addIdentifierRegion_method(
    AstNode parent,
    Token nameToken,
    Element? element,
  ) {
    var isInvocation =
        (parent is MethodInvocation && parent.methodName.token == nameToken) ||
        (parent is DotShorthandInvocation &&
            parent.memberName.token == nameToken);

    // Handle the `call` method on functions.
    if (_isCallMethod(parent, nameToken)) {
      return _addRegion_token(
        nameToken,
        isInvocation
            ? HighlightRegionType.INSTANCE_METHOD_REFERENCE
            : HighlightRegionType.INSTANCE_METHOD_TEAR_OFF,
      );
    }

    if (element is! MethodElement) {
      return false;
    }
    var isStatic = element.isStatic;
    // OK
    HighlightRegionType type;
    if (isStatic) {
      type = isInvocation
          ? HighlightRegionType.STATIC_METHOD_REFERENCE
          : HighlightRegionType.STATIC_METHOD_TEAR_OFF;
    } else {
      type = isInvocation
          ? HighlightRegionType.INSTANCE_METHOD_REFERENCE
          : HighlightRegionType.INSTANCE_METHOD_TEAR_OFF;
    }
    return _addRegion_token(nameToken, type);
  }

  bool _addIdentifierRegion_parameter(
    AstNode parent,
    Token nameToken,
    Element? element,
  ) {
    if (element is! FormalParameterElement) {
      return false;
    }
    var type = element.type is DynamicType
        ? HighlightRegionType.DYNAMIC_PARAMETER_REFERENCE
        : HighlightRegionType.PARAMETER_REFERENCE;
    var modifiers = parent is Label
        ? {CustomSemanticTokenModifiers.label}
        : null;
    return _addRegion_token(nameToken, type, semanticTokenModifiers: modifiers);
  }

  bool _addIdentifierRegion_typeAlias(Token nameToken, Element? element) {
    if (element is TypeAliasElement) {
      var type = element.aliasedType is FunctionType
          ? HighlightRegionType.FUNCTION_TYPE_ALIAS
          : HighlightRegionType.TYPE_ALIAS;
      return _addRegion_token(nameToken, type);
    }
    return false;
  }

  bool _addIdentifierRegion_typeParameter(Token nameToken, Element? element) {
    if (element is! TypeParameterElement) {
      return false;
    }
    return _addRegion_token(
      nameToken,
      HighlightRegionType.TYPE_PARAMETER,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(element),
    );
  }

  bool _addIdentifierRegion_unresolvedInstanceMemberReference(
    AstNode parent,
    Token nameToken,
    Element? element,
  ) {
    // unresolved
    if (element != null) {
      return false;
    }
    // invoke / get / set
    var decorate = false;
    if (parent is MethodInvocation) {
      var target = parent.realTarget;
      if (parent.methodName.token == nameToken &&
          target != null &&
          _isDynamicExpression(target)) {
        decorate = true;
      }
    } else {
      if (parent is PrefixedIdentifier) {
        decorate = parent.identifier.token == nameToken;
      } else if (parent is PropertyAccess) {
        decorate = parent.propertyName.token == nameToken;
      }
    }
    if (decorate) {
      _addRegion_token(
        nameToken,
        HighlightRegionType.UNRESOLVED_INSTANCE_MEMBER_REFERENCE,
      );
      return true;
    }
    return false;
  }

  /// Returns a set of additional semantic token modifiers that apply to
  /// [element].
  Set<SemanticTokenModifiers>? _additionalModifiersForElement(
    Element? element,
  ) {
    return (element?.isWildcardVariable ?? false)
        ? {CustomSemanticTokenModifiers.wildcard}
        : null;
  }

  /// Adds a highlight region/semantic token for the given [offset]/[length].
  ///
  /// If [semanticTokenType] or [semanticTokenModifiers] are not provided, the
  /// values from the default LSP mapping for [type] (also used for plugins)
  /// will be used instead. If [additionalSemanticTokenModifiers] are provided,
  /// they will always be added in addition to [semanticTokenModifiers]/the
  /// defaults.
  ///
  /// If the computer has a [range] set, tokens that fall outside of that range
  /// will not be recorded.
  void _addRegion(
    int offset,
    int length,
    HighlightRegionType type, {
    SemanticTokenTypes? semanticTokenType,
    Set<SemanticTokenModifiers>? semanticTokenModifiers,
    Set<SemanticTokenModifiers>? additionalSemanticTokenModifiers,
  }) {
    var range = this.range;
    if (range != null) {
      var end = offset + length;
      // Skip token if it ends before the range of starts after the range.
      if (end < range.offset || offset > range.end) {
        return;
      }
    }
    if (_computeRegions) {
      _regions.add(HighlightRegion(type, offset, length));
    }
    if (_computeSemanticTokens) {
      // Use default mappings if an overridden type/modifiers were not supplied.
      semanticTokenType ??= highlightRegionTokenTypes[type];
      semanticTokenModifiers ??= highlightRegionTokenModifiers[type];

      if (additionalSemanticTokenModifiers != null &&
          additionalSemanticTokenModifiers.isNotEmpty) {
        semanticTokenModifiers = {
          ...?semanticTokenModifiers,
          ...additionalSemanticTokenModifiers,
        };
      }
      if (semanticTokenType != null) {
        _semanticTokens.add(
          SemanticTokenInfo(
            offset,
            length,
            semanticTokenType,
            semanticTokenModifiers,
          ),
        );
      }
    }
  }

  bool _addRegion_node(
    AstNode node,
    HighlightRegionType type, {
    SemanticTokenTypes? semanticTokenType,
    Set<SemanticTokenModifiers>? semanticTokenModifiers,
  }) {
    var offset = node.offset;
    var length = node.length;
    _addRegion(
      offset,
      length,
      type,
      semanticTokenType: semanticTokenType,
      semanticTokenModifiers: semanticTokenModifiers,
    );
    return true;
  }

  void _addRegion_nodeStart_tokenEnd(
    AstNode a,
    Token b,
    HighlightRegionType type,
  ) {
    var offset = a.offset;
    var end = b.end;
    _addRegion(offset, end - offset, type);
  }

  bool _addRegion_token(
    Token? token,
    HighlightRegionType type, {
    SemanticTokenTypes? semanticTokenType,
    Set<SemanticTokenModifiers>? semanticTokenModifiers,
    Set<SemanticTokenModifiers>? additionalSemanticTokenModifiers,
  }) {
    if (token != null) {
      var offset = token.offset;
      var length = token.length;
      _addRegion(
        offset,
        length,
        type,
        semanticTokenType: semanticTokenType,
        semanticTokenModifiers: semanticTokenModifiers,
        additionalSemanticTokenModifiers: additionalSemanticTokenModifiers,
      );
    }
    return true;
  }

  void _addRegion_tokenStart_tokenEnd(
    Token a,
    Token b,
    HighlightRegionType type,
  ) {
    var offset = a.offset;
    var end = b.end;
    _addRegion(offset, end - offset, type);
  }

  /// Checks whether [parent] is the identifier part of an annotation.
  bool _isAnnotationIdentifier(AstNode? parent) {
    if (parent is Annotation) {
      return true;
    } else if (parent is PrefixedIdentifier && parent.parent is Annotation) {
      return true;
    } else {
      return false;
    }
  }

  /// Returns whether [nameToken] is a reference to the `call` method on
  /// a function.
  bool _isCallMethod(AstNode parent, Token nameToken) {
    late bool enclosingInstanceFunction =
        switch (parent.enclosingInstanceElement) {
          ExtensionElement(:var extendedType) => extendedType.isFunction,
          _ => false,
        };
    Expression? expression() => switch (parent) {
      ExpressionFunctionBody(:var expression) ||
      ExpressionStatement(:var expression) => expression,
      ReturnStatement(:var expression) => expression,
      ArgumentList(:var arguments) =>
        arguments
            .map((argument) => argument.argumentExpression)
            .firstWhereOrNull((argument) {
              return argument is SimpleIdentifier &&
                  argument.token == nameToken;
            }),
      AssignmentExpression(:var rightHandSide) => rightHandSide,
      VariableDeclaration(:var initializer) => initializer,
      _ => null,
    };
    return // Invocation
    (parent is MethodInvocation &&
            parent.methodName.token == nameToken &&
            parent.methodName.name == MethodElement.CALL_METHOD_NAME &&
            ((parent.realTarget?.staticType).isFunction ||
                enclosingInstanceFunction)) ||
        // Tearoff
        (parent is PrefixedIdentifier &&
            parent.identifier.token == nameToken &&
            parent.identifier.name == MethodElement.CALL_METHOD_NAME &&
            parent.prefix.staticType.isFunction) ||
        // Property access
        (parent is PropertyAccess &&
            parent.propertyName.token == nameToken &&
            parent.propertyName.name == MethodElement.CALL_METHOD_NAME &&
            parent.realTarget.staticType.isFunction) ||
        // Special cases for extension methods
        (expression() is SimpleIdentifier &&
            nameToken.lexeme == MethodElement.CALL_METHOD_NAME &&
            enclosingInstanceFunction);
  }

  void _reset() {
    _computeRegions = false;
    _computeSemanticTokens = false;
    _regions.clear();
    _semanticTokens.clear();
  }

  static bool _isDynamicExpression(Expression e) {
    var type = e.staticType;
    return type != null && type is DynamicType || type is InvalidType;
  }
}

/// An AST visitor for [DartUnitHighlightsComputer].
class _DartUnitHighlightsComputerVisitor extends RecursiveAstVisitor<void> {
  final DartUnitHighlightsComputer computer;

  new(this.computer);

  @override
  void visitAnnotation(Annotation node) {
    computer._addIdentifierRegion_annotation(node);
    super.visitAnnotation(node);
  }

  @override
  void visitAsExpression(AsExpression node) {
    computer._addRegion_token(node.asOperator, HighlightRegionType.KEYWORD);
    super.visitAsExpression(node);
  }

  @override
  void visitAssertStatement(AssertStatement node) {
    computer._addRegion_token(
      node.assertKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitAssertStatement(node);
  }

  @override
  void visitAssignedVariablePattern(AssignedVariablePattern node) {
    computer._addRegion_token(
      node.name,
      HighlightRegionType.LOCAL_VARIABLE_REFERENCE,
    );
    super.visitAssignedVariablePattern(node);
  }

  @override
  void visitAwaitExpression(AwaitExpression node) {
    computer._addRegion_token(
      node.awaitKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitAwaitExpression(node);
  }

  @override
  void visitBlockFunctionBody(BlockFunctionBody node) {
    _addRegions_functionBody(node);
    super.visitBlockFunctionBody(node);
  }

  @override
  void visitBooleanLiteral(BooleanLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.KEYWORD);
    computer._addRegion_node(node, HighlightRegionType.LITERAL_BOOLEAN);
    super.visitBooleanLiteral(node);
  }

  @override
  void visitBreakStatement(BreakStatement node) {
    computer._addRegion_token(
      node.breakKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitBreakStatement(node);
  }

  @override
  void visitCaseClause(CaseClause node) {
    computer._addRegion_token(
      node.caseKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitCaseClause(node);
  }

  @override
  void visitCastPattern(CastPattern node) {
    computer._addRegion_token(node.asToken, HighlightRegionType.KEYWORD);
    super.visitCastPattern(node);
  }

  @override
  void visitCatchClause(CatchClause node) {
    computer._addRegion_token(
      node.catchKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.onKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );

    var exceptionParameter = node.exceptionParameter;
    if (exceptionParameter != null) {
      computer._addRegion_token(
        exceptionParameter.name,
        HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
        additionalSemanticTokenModifiers: _additionalModifiersForElement(
          exceptionParameter.declaredFragment?.element,
        ),
      );
    }
    var stackTraceParameter = node.stackTraceParameter;
    if (stackTraceParameter != null) {
      computer._addRegion_token(
        stackTraceParameter.name,
        HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
        additionalSemanticTokenModifiers: _additionalModifiersForElement(
          stackTraceParameter.declaredFragment?.element,
        ),
      );
    }

    super.visitCatchClause(node);
  }

  @override
  void visitClassDeclaration(ClassDeclaration node) {
    computer._addRegion_token(node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.abstractKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(node.sealedKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.baseKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.interfaceKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(node.finalKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.mixinKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.classKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.namePart.typeName,
      HighlightRegionType.CLASS,
      semanticTokenModifiers: {SemanticTokenModifiers.declaration},
    );
    super.visitClassDeclaration(node);
  }

  @override
  void visitClassTypeAlias(ClassTypeAlias node) {
    // TODO(brianwilkerson): Update the interface to expose the token.
    // computer._addRegion_token(
    //     node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.abstractKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(node.sealedKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.baseKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.interfaceKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(node.finalKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.mixinKeyword, HighlightRegionType.KEYWORD);
    super.visitClassTypeAlias(node);
  }

  @override
  void visitConstantPattern(ConstantPattern node) {
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);
    super.visitConstantPattern(node);
  }

  @override
  void visitConstructorDeclaration(ConstructorDeclaration node) {
    computer._addRegion_token(node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(
      node.factoryKeyword,
      HighlightRegionType.KEYWORD,
      // We mark the factory keyword as the declaration when there is no
      // type name (otherwise the type name is the declaration). This will allow
      // the client/user to customise these differently if they wish, like they
      // can with methods / constructors.
      semanticTokenModifiers: node.typeName != null
          ? null
          : {
              CustomSemanticTokenModifiers.constructor,
              SemanticTokenModifiers.declaration,
            },
    );
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.newKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {
        CustomSemanticTokenModifiers.constructor,
        SemanticTokenModifiers.declaration,
      },
    );
    computer._addRegion_token(
      node.name,
      HighlightRegionType.CONSTRUCTOR,
      semanticTokenType: SemanticTokenTypes.method,
      semanticTokenModifiers: {
        CustomSemanticTokenModifiers.constructor,
        SemanticTokenModifiers.declaration,
      },
    );
    super.visitConstructorDeclaration(node);
  }

  @override
  void visitConstructorReference(ConstructorReference node) {
    var constructorName = node.constructorName;
    constructorName.type.accept(this);

    // We have a `ConstructorReference` only when it is resolved.
    // TODO(scheglov): The `ConstructorName` in a tear-off always has a name,
    //  but this is not expressed via types.
    computer._addRegion_node(
      constructorName.name!,
      HighlightRegionType.CONSTRUCTOR_TEAR_OFF,
    );
  }

  @override
  void visitConstructorSelector(ConstructorSelector node) {
    computer._addRegion_node(node.name, HighlightRegionType.CONSTRUCTOR);
  }

  @override
  void visitContinueStatement(ContinueStatement node) {
    computer._addRegion_token(
      node.continueKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitContinueStatement(node);
  }

  @override
  void visitDeclaredIdentifier(DeclaredIdentifier node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);

    computer._addRegion_token(
      node.name,
      HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(
        node.declaredFragment?.element,
      ),
    );

    super.visitDeclaredIdentifier(node);
  }

  @override
  void visitDeclaredVariablePattern(DeclaredVariablePattern node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.name,
      HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
    );
    super.visitDeclaredVariablePattern(node);
  }

  @override
  void visitDoStatement(DoStatement node) {
    computer._addRegion_token(
      node.doKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.whileKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitDoStatement(node);
  }

  @override
  void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
    var element = node.propertyName.element;
    if (element is ConstructorElement) {
      computer._addRegion_node(
        node.propertyName,
        HighlightRegionType.CONSTRUCTOR_TEAR_OFF,
      );
    } else {
      computer._addIdentifierRegion(
        parent: node,
        nameToken: node.propertyName.token,
        element: element,
      );
    }
  }

  @override
  void visitDottedName(DottedName node) {
    if (node.parent is Configuration) {
      for (var token in node.tokens) {
        if (token.type != TokenType.PERIOD) {
          computer._addRegion_token(
            token,
            HighlightRegionType.IDENTIFIER_DEFAULT,
          );
        }
      }
    } else {
      computer._addRegion_node(node, HighlightRegionType.LIBRARY_NAME);
    }
    super.visitDottedName(node);
  }

  @override
  void visitDoubleLiteral(DoubleLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_DOUBLE);
    super.visitDoubleLiteral(node);
  }

  @override
  void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
    computer._addRegion_token(node.name, HighlightRegionType.ENUM_CONSTANT);
    node.visitChildren(this);
  }

  @override
  void visitEnumDeclaration(EnumDeclaration node) {
    // TODO(brianwilkerson): Uncomment the following lines when the token is
    //  supported.
    // computer._addRegion_token(
    //     node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.enumKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.namePart.typeName, HighlightRegionType.ENUM);
    super.visitEnumDeclaration(node);
  }

  @override
  void visitExportDirective(ExportDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_token(node.exportKeyword, HighlightRegionType.KEYWORD);
    _addRegions_configurations(node.configurations);
    super.visitExportDirective(node);
  }

  @override
  void visitExpressionFunctionBody(ExpressionFunctionBody node) {
    _addRegions_functionBody(node);
    super.visitExpressionFunctionBody(node);
  }

  @override
  void visitExtendsClause(ExtendsClause node) {
    computer._addRegion_token(node.extendsKeyword, HighlightRegionType.KEYWORD);
    super.visitExtendsClause(node);
  }

  @override
  void visitExtensionDeclaration(ExtensionDeclaration node) {
    computer._addRegion_token(node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.extensionKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(node.name, HighlightRegionType.EXTENSION);
    super.visitExtensionDeclaration(node);
  }

  @override
  void visitExtensionOnClause(ExtensionOnClause node) {
    computer._addRegion_token(node.onKeyword, HighlightRegionType.KEYWORD);
    super.visitExtensionOnClause(node);
  }

  @override
  void visitExtensionOverride(ExtensionOverride node) {
    computer._addRegion_token(node.name, HighlightRegionType.EXTENSION);

    super.visitExtensionOverride(node);
  }

  @override
  void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
    computer._addRegion_token(
      node.extensionKeyword,
      HighlightRegionType.KEYWORD,
    );

    computer._addRegion_token(node.typeKeyword, HighlightRegionType.KEYWORD);

    computer._addRegion_token(
      node.namePart.typeName,
      HighlightRegionType.EXTENSION_TYPE,
      semanticTokenModifiers: {SemanticTokenModifiers.declaration},
    );

    super.visitExtensionTypeDeclaration(node);
  }

  @override
  void visitFieldDeclaration(FieldDeclaration node) {
    // TODO(brianwilkerson): Update the interface to expose the token.
    // computer._addRegion_token(
    //     node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.abstractKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(node.staticKeyword, HighlightRegionType.KEYWORD);

    super.visitFieldDeclaration(node);
  }

  @override
  void visitFieldFormalParameter(FieldFormalParameter node) {
    computer._addRegion_token(
      node.requiredKeyword,
      HighlightRegionType.KEYWORD,
    );

    computer._addRegion_token(
      node.constFinalOrVarKeyword,
      HighlightRegionType.KEYWORD,
    );

    computer._addRegion_token(node.thisKeyword, HighlightRegionType.KEYWORD);

    computer._addRegion_token(
      node.name,
      HighlightRegionType.INSTANCE_FIELD_REFERENCE,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(
        node.declaredFragment?.element,
      ),
    );

    super.visitFieldFormalParameter(node);
  }

  @override
  void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
    computer._addRegion_token(
      node.inKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitForEachPartsWithDeclaration(node);
  }

  @override
  void visitForEachPartsWithIdentifier(ForEachPartsWithIdentifier node) {
    computer._addRegion_token(
      node.inKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitForEachPartsWithIdentifier(node);
  }

  @override
  void visitForEachPartsWithPattern(ForEachPartsWithPattern node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    super.visitForEachPartsWithPattern(node);
  }

  @override
  void visitForElement(ForElement node) {
    computer._addRegion_token(
      node.awaitKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.forKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitForElement(node);
  }

  @override
  void visitForStatement(ForStatement node) {
    computer._addRegion_token(
      node.awaitKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.forKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitForStatement(node);
  }

  @override
  void visitFunctionDeclaration(FunctionDeclaration node) {
    computer._addRegion_token(node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(
      node.propertyKeyword,
      HighlightRegionType.KEYWORD,
    );

    HighlightRegionType nameType;
    if (node.isGetter) {
      nameType = HighlightRegionType.TOP_LEVEL_GETTER_DECLARATION;
    } else if (node.isSetter) {
      nameType = HighlightRegionType.TOP_LEVEL_SETTER_DECLARATION;
    } else if (node.parent is CompilationUnit) {
      nameType = HighlightRegionType.TOP_LEVEL_FUNCTION_DECLARATION;
    } else {
      nameType = HighlightRegionType.LOCAL_FUNCTION_DECLARATION;
    }
    computer._addRegion_token(node.name, nameType);

    super.visitFunctionDeclaration(node);
  }

  @override
  void visitFunctionTypeAlias(FunctionTypeAlias node) {
    computer._addRegion_token(node.typedefKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.name,
      HighlightRegionType.FUNCTION_TYPE_ALIAS,
    );
    super.visitFunctionTypeAlias(node);
  }

  @override
  void visitGenericFunctionType(GenericFunctionType node) {
    computer._addRegion_token(
      node.functionKeyword,
      HighlightRegionType.KEYWORD,
    );
    super.visitGenericFunctionType(node);
  }

  @override
  void visitGenericTypeAlias(GenericTypeAlias node) {
    computer._addRegion_token(node.typedefKeyword, HighlightRegionType.KEYWORD);

    HighlightRegionType nameType;
    if (node.functionType != null) {
      nameType = HighlightRegionType.FUNCTION_TYPE_ALIAS;
    } else {
      nameType = HighlightRegionType.TYPE_ALIAS;
    }
    computer._addRegion_token(node.name, nameType);

    super.visitGenericTypeAlias(node);
  }

  @override
  void visitHideCombinator(HideCombinator node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    super.visitHideCombinator(node);
  }

  @override
  void visitIfElement(IfElement node) {
    computer._addRegion_token(
      node.ifKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.elseKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitIfElement(node);
  }

  @override
  void visitIfStatement(IfStatement node) {
    computer._addRegion_token(
      node.ifKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.elseKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitIfStatement(node);
  }

  @override
  void visitImplementsClause(ImplementsClause node) {
    computer._addRegion_token(
      node.implementsKeyword,
      HighlightRegionType.KEYWORD,
    );
    super.visitImplementsClause(node);
  }

  @override
  void visitImportDirective(ImportDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_token(node.importKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.deferredKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(node.asKeyword, HighlightRegionType.KEYWORD);
    _addRegions_configurations(node.configurations);
    super.visitImportDirective(node);
  }

  @override
  void visitImportPrefixReference(ImportPrefixReference node) {
    computer._addRegion_token(node.name, HighlightRegionType.IMPORT_PREFIX);
  }

  @override
  void visitInstanceCreationExpression(InstanceCreationExpression node) {
    if (node.keyword != null) {
      computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    }
    super.visitInstanceCreationExpression(node);
  }

  @override
  void visitIntegerLiteral(IntegerLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_INTEGER);
    super.visitIntegerLiteral(node);
  }

  @override
  void visitInterpolationExpression(InterpolationExpression node) {
    if (computer._computeSemanticTokens) {
      // Interpolation expressions may include uncolored code, but clients may
      // be providing their own basic coloring for strings that would leak
      // into those uncolored parts so we mark them up to allow the client to
      // reset the coloring if required.
      //
      // Using the String token type with a modifier would work for VS Code but
      // would cause other editors that don't know about the modifier (and also
      // do not have their own local coloring) to color the tokens as a string,
      // which is exactly what we'd like to avoid).

      computer._addRegion_node(
        node,
        // The HighlightRegionType here is not used because of the
        // computer._computeSemanticTokens check above.
        HighlightRegionType.LITERAL_STRING,
        semanticTokenType: CustomSemanticTokenTypes.source,
        semanticTokenModifiers: {CustomSemanticTokenModifiers.interpolation},
      );
    }
    super.visitInterpolationExpression(node);
  }

  @override
  void visitInterpolationString(InterpolationString node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_STRING);
    if (computer._computeSemanticTokens) {
      var string = node.contents.lexeme;
      var parent = node.parent as StringInterpolation;
      _addRegions_stringEscapes(
        string,
        parent.quote,
        node.offset,
        0,
        string.length,
      );
    }
    super.visitInterpolationString(node);
  }

  @override
  void visitIsExpression(IsExpression node) {
    computer._addRegion_token(node.isOperator, HighlightRegionType.KEYWORD);
    super.visitIsExpression(node);
  }

  @override
  void visitLabel(Label node) {
    computer._addRegion_token(
      node.name,
      HighlightRegionType.LABEL,
      semanticTokenModifiers: {SemanticTokenModifiers.declaration},
    );
    super.visitLabel(node);
  }

  @override
  void visitLabelReference(LabelReference node) {
    computer._addRegion_token(node.name, HighlightRegionType.LABEL);
    super.visitLabelReference(node);
  }

  @override
  void visitLibraryDirective(LibraryDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_token(node.libraryKeyword, HighlightRegionType.KEYWORD);
    super.visitLibraryDirective(node);
  }

  @override
  void visitListLiteral(ListLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_LIST);
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);
    super.visitListLiteral(node);
  }

  @override
  void visitMethodDeclaration(MethodDeclaration node) {
    computer._addRegion_token(node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(
      node.modifierKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(
      node.operatorKeyword,
      HighlightRegionType.KEYWORD,
    );
    computer._addRegion_token(
      node.propertyKeyword,
      HighlightRegionType.KEYWORD,
    );

    HighlightRegionType nameType;
    if (node.isGetter) {
      nameType = node.isStatic
          ? HighlightRegionType.STATIC_GETTER_DECLARATION
          : HighlightRegionType.INSTANCE_GETTER_DECLARATION;
    } else if (node.isSetter) {
      nameType = node.isStatic
          ? HighlightRegionType.STATIC_SETTER_DECLARATION
          : HighlightRegionType.INSTANCE_SETTER_DECLARATION;
    } else {
      nameType = node.isStatic
          ? HighlightRegionType.STATIC_METHOD_DECLARATION
          : HighlightRegionType.INSTANCE_METHOD_DECLARATION;
    }
    computer._addRegion_token(node.name, nameType);

    super.visitMethodDeclaration(node);
  }

  @override
  void visitMixinDeclaration(MixinDeclaration node) {
    computer._addRegion_token(node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.baseKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.mixinKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.name, HighlightRegionType.MIXIN);
    super.visitMixinDeclaration(node);
  }

  @override
  void visitMixinOnClause(MixinOnClause node) {
    computer._addRegion_token(node.onKeyword, HighlightRegionType.KEYWORD);
    super.visitMixinOnClause(node);
  }

  @override
  void visitNamedArgument(NamedArgument node) {
    var parameter = node.correspondingParameter;
    if (parameter != null) {
      var type = parameter.type is DynamicType
          ? HighlightRegionType.DYNAMIC_PARAMETER_REFERENCE
          : HighlightRegionType.PARAMETER_REFERENCE;
      computer._addRegion_token(
        node.name,
        type,
        semanticTokenModifiers: {CustomSemanticTokenModifiers.label},
      );
    } else {
      computer._addIdentifierRegion(
        parent: node,
        nameToken: node.name,
        element: null,
      );
    }
    node.argumentExpression.accept(this);
  }

  @override
  void visitNamedType(NamedType node) {
    if (node.importPrefix case var importPrefix?) {
      computer._addRegion_token(
        importPrefix.name,
        HighlightRegionType.IMPORT_PREFIX,
      );
    }

    var type = node.type;
    if (type != null) {
      var isDynamic = type is DynamicType && node.name.lexeme == 'dynamic';
      var isNever = type is NeverType;
      if (isDynamic || isNever) {
        computer._addRegion_token(
          node.name,
          isDynamic
              ? HighlightRegionType.TYPE_NAME_DYNAMIC
              : HighlightRegionType.CLASS,
          semanticTokenType: SemanticTokenTypes.type,
        );
        return;
      }
    }

    computer._addIdentifierRegion(
      parent: node,
      nameToken: node.name,
      element: node.element,
    );

    node.typeArguments?.accept(this);
  }

  @override
  void visitNativeClause(NativeClause node) {
    computer._addRegion_token(node.nativeKeyword, HighlightRegionType.KEYWORD);
    super.visitNativeClause(node);
  }

  @override
  void visitNativeFunctionBody(NativeFunctionBody node) {
    computer._addRegion_token(node.nativeKeyword, HighlightRegionType.KEYWORD);
    super.visitNativeFunctionBody(node);
  }

  @override
  void visitNullLiteral(NullLiteral node) {
    computer._addRegion_token(node.literal, HighlightRegionType.KEYWORD);
    super.visitNullLiteral(node);
  }

  @override
  void visitPartDirective(PartDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_token(node.partKeyword, HighlightRegionType.KEYWORD);
    super.visitPartDirective(node);
  }

  @override
  void visitPartOfDirective(PartOfDirective node) {
    computer._addRegion_node(node, HighlightRegionType.DIRECTIVE);
    computer._addRegion_tokenStart_tokenEnd(
      node.partKeyword,
      node.ofKeyword,
      HighlightRegionType.KEYWORD,
    );
    super.visitPartOfDirective(node);
  }

  @override
  void visitPatternField(PatternField node) {
    var name = node.name?.name;
    if (name != null) {
      // Patterns can be method tear-offs as well as getters:
      // https://github.com/dart-lang/sdk/issues/59976#issuecomment-2613558317
      var type = switch (node.element) {
        MethodElement() => HighlightRegionType.INSTANCE_METHOD_TEAR_OFF,
        _ => HighlightRegionType.INSTANCE_GETTER_REFERENCE,
      };

      computer._addRegion_token(name, type);
    }
    super.visitPatternField(node);
  }

  @override
  void visitPatternVariableDeclaration(PatternVariableDeclaration node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    super.visitPatternVariableDeclaration(node);
  }

  @override
  void visitPrimaryConstructorBody(PrimaryConstructorBody node) {
    computer._addRegion_token(node.thisKeyword, HighlightRegionType.KEYWORD);
    super.visitPrimaryConstructorBody(node);
  }

  @override
  void visitPrimaryConstructorDeclaration(PrimaryConstructorDeclaration node) {
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);
    super.visitPrimaryConstructorDeclaration(node);
  }

  @override
  void visitPrimaryConstructorName(PrimaryConstructorName node) {
    computer._addRegion_token(node.name, HighlightRegionType.CONSTRUCTOR);
    super.visitPrimaryConstructorName(node);
  }

  @override
  void visitRecordLiteral(RecordLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_RECORD);
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);

    for (var field in node.fields) {
      if (field is RecordLiteralNamedField) {
        computer._addRegion_token(
          field.name,
          HighlightRegionType.PARAMETER_REFERENCE,
        );
        field.fieldExpression.accept(this);
      } else {
        field.accept(this);
      }
    }
  }

  @override
  void visitRecordTypeAnnotation(RecordTypeAnnotation node) {
    for (var field in node.fields) {
      computer._addRegion_token(field.name, HighlightRegionType.FIELD);
    }

    super.visitRecordTypeAnnotation(node);
  }

  @override
  void visitRegularFormalParameter(RegularFormalParameter node) {
    computer._addRegion_token(
      node.requiredKeyword,
      HighlightRegionType.KEYWORD,
    );

    computer._addRegion_token(
      node.covariantKeyword,
      HighlightRegionType.KEYWORD,
    );

    computer._addRegion_token(
      node.constFinalOrVarKeyword,
      HighlightRegionType.KEYWORD,
    );

    var declaredElement = node.declaredFragment!.element;
    computer._addRegion_token(
      node.name,
      declaredElement.type is DynamicType
          ? HighlightRegionType.DYNAMIC_PARAMETER_DECLARATION
          : HighlightRegionType.PARAMETER_DECLARATION,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(
        declaredElement,
      ),
    );

    super.visitRegularFormalParameter(node);
  }

  @override
  void visitRethrowExpression(RethrowExpression node) {
    computer._addRegion_token(
      node.rethrowKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitRethrowExpression(node);
  }

  @override
  void visitReturnStatement(ReturnStatement node) {
    computer._addRegion_token(
      node.returnKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitReturnStatement(node);
  }

  @override
  void visitSetOrMapLiteral(SetOrMapLiteral node) {
    if (node.isMap) {
      computer._addRegion_node(node, HighlightRegionType.LITERAL_MAP);
      // TODO(brianwilkerson): Add a highlight region for set literals. This
      //  would be a breaking change, but would be consistent with list and map
      //  literals.
      //    } else if (node.isSet) {
      //    computer._addRegion_node(node, HighlightRegionType.LITERAL_SET);
    }
    computer._addRegion_token(node.constKeyword, HighlightRegionType.KEYWORD);
    super.visitSetOrMapLiteral(node);
  }

  @override
  void visitShowCombinator(ShowCombinator node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    super.visitShowCombinator(node);
  }

  @override
  void visitSimpleIdentifier(SimpleIdentifier node) {
    var parent = node.parent;
    if (parent != null) {
      computer._addIdentifierRegion(
        parent: parent,
        nameToken: node.token,
        element: node.writeOrReadElement,
      );
    }
    super.visitSimpleIdentifier(node);
  }

  @override
  void visitSimpleStringLiteral(SimpleStringLiteral node) {
    computer._addRegion_node(node, HighlightRegionType.LITERAL_STRING);
    if (computer._computeSemanticTokens) {
      var string = node.literal.lexeme;
      var quote = analyzeQuote(string);
      var startIndex = firstQuoteLength(string, quote);
      var endIndex = string.length - lastQuoteLength(quote);
      _addRegions_stringEscapes(
        string,
        quote,
        node.offset,
        startIndex,
        endIndex,
      );
    }
    super.visitSimpleStringLiteral(node);
  }

  @override
  void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
    computer._addRegion_token(node.superKeyword, HighlightRegionType.KEYWORD);
    super.visitSuperConstructorInvocation(node);
  }

  @override
  void visitSuperExpression(SuperExpression node) {
    computer._addRegion_token(node.superKeyword, HighlightRegionType.KEYWORD);
    super.visitSuperExpression(node);
  }

  @override
  void visitSuperFormalParameter(SuperFormalParameter node) {
    computer._addRegion_token(
      node.requiredKeyword,
      HighlightRegionType.KEYWORD,
    );

    computer._addRegion_token(
      node.constFinalOrVarKeyword,
      HighlightRegionType.KEYWORD,
    );

    computer._addRegion_token(node.superKeyword, HighlightRegionType.KEYWORD);

    computer._addRegion_token(
      node.name,
      node.declaredFragment?.element.type is DynamicType
          ? HighlightRegionType.DYNAMIC_PARAMETER_DECLARATION
          : HighlightRegionType.PARAMETER_DECLARATION,
    );

    super.visitSuperFormalParameter(node);
  }

  @override
  void visitSwitchCase(SwitchCase node) {
    computer._addRegion_token(
      node.keyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitSwitchCase(node);
  }

  @override
  void visitSwitchDefault(SwitchDefault node) {
    computer._addRegion_token(
      node.keyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitSwitchDefault(node);
  }

  @override
  void visitSwitchExpression(SwitchExpression node) {
    computer._addRegion_token(node.switchKeyword, HighlightRegionType.KEYWORD);
    super.visitSwitchExpression(node);
  }

  @override
  void visitSwitchPatternCase(SwitchPatternCase node) {
    computer._addRegion_token(
      node.keyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitSwitchPatternCase(node);
  }

  @override
  void visitSwitchStatement(SwitchStatement node) {
    computer._addRegion_token(
      node.switchKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitSwitchStatement(node);
  }

  @override
  void visitThisExpression(ThisExpression node) {
    computer._addRegion_token(node.thisKeyword, HighlightRegionType.KEYWORD);
    super.visitThisExpression(node);
  }

  @override
  void visitThrowExpression(ThrowExpression node) {
    computer._addRegion_token(
      node.throwKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitThrowExpression(node);
  }

  @override
  void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
    computer._addRegion_token(node.augmentKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(
      node.externalKeyword,
      HighlightRegionType.KEYWORD,
    );

    super.visitTopLevelVariableDeclaration(node);
  }

  @override
  void visitTryStatement(TryStatement node) {
    computer._addRegion_token(
      node.tryKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    computer._addRegion_token(
      node.finallyKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitTryStatement(node);
  }

  @override
  void visitTypeParameter(TypeParameter node) {
    computer._addRegion_token(
      node.name,
      HighlightRegionType.TYPE_PARAMETER,
      additionalSemanticTokenModifiers: _additionalModifiersForElement(
        node.declaredFragment?.element,
      ),
    );
    computer._addRegion_token(node.extendsKeyword, HighlightRegionType.KEYWORD);
    super.visitTypeParameter(node);
  }

  @override
  void visitVariableDeclaration(VariableDeclaration node) {
    var element = node.declaredFragment?.element;
    if (element is FieldElement) {
      computer._addRegion_token(
        node.name,
        element.isStatic
            ? HighlightRegionType.STATIC_FIELD_DECLARATION
            : HighlightRegionType.INSTANCE_FIELD_DECLARATION,
      );
    } else if (element is LocalVariableElement) {
      computer._addRegion_token(
        node.name,
        element.type is DynamicType
            ? HighlightRegionType.DYNAMIC_LOCAL_VARIABLE_DECLARATION
            : HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
        additionalSemanticTokenModifiers: _additionalModifiersForElement(
          element,
        ),
      );
    } else if (element is TopLevelVariableElement) {
      computer._addRegion_token(
        node.name,
        HighlightRegionType.TOP_LEVEL_VARIABLE_DECLARATION,
      );
    }

    super.visitVariableDeclaration(node);
  }

  @override
  void visitVariableDeclarationList(VariableDeclarationList node) {
    computer._addRegion_token(node.lateKeyword, HighlightRegionType.KEYWORD);
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);
    super.visitVariableDeclarationList(node);
  }

  @override
  void visitWhenClause(WhenClause node) {
    computer._addRegion_token(
      node.whenKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitWhenClause(node);
  }

  @override
  void visitWhileStatement(WhileStatement node) {
    computer._addRegion_token(
      node.whileKeyword,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitWhileStatement(node);
  }

  @override
  void visitWildcardPattern(WildcardPattern node) {
    computer._addRegion_token(node.keyword, HighlightRegionType.KEYWORD);

    // For consistency, wildcard patterns are treated like wildcard variable
    // declarations.
    // https://github.com/dart-lang/sdk/issues/56567
    computer._addRegion_token(
      node.name,
      HighlightRegionType.LOCAL_VARIABLE_DECLARATION,
      additionalSemanticTokenModifiers: {CustomSemanticTokenModifiers.wildcard},
    );

    super.visitWildcardPattern(node);
  }

  @override
  void visitWithClause(WithClause node) {
    computer._addRegion_token(node.withKeyword, HighlightRegionType.KEYWORD);
    super.visitWithClause(node);
  }

  @override
  void visitYieldStatement(YieldStatement node) {
    var keyword = node.yieldKeyword;
    var star = node.star;
    var offset = keyword.offset;
    var end = star != null ? star.end : keyword.end;
    computer._addRegion(
      offset,
      end - offset,
      HighlightRegionType.KEYWORD,
      semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
    );
    super.visitYieldStatement(node);
  }

  /// Returns a set of additional semantic token modifiers that apply to
  /// [element].
  Set<SemanticTokenModifiers>? _additionalModifiersForElement(
    Element? element,
  ) {
    return computer._additionalModifiersForElement(element);
  }

  void _addRegions_configurations(List<Configuration> configurations) {
    for (var configuration in configurations) {
      computer._addRegion_token(
        configuration.ifKeyword,
        HighlightRegionType.KEYWORD,
        semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
      );
    }
  }

  void _addRegions_functionBody(FunctionBody node) {
    var keyword = node.keyword;
    if (keyword != null) {
      var star = node.star;
      var offset = keyword.offset;
      var end = star != null ? star.end : keyword.end;
      computer._addRegion(
        offset,
        end - offset,
        HighlightRegionType.KEYWORD,
        semanticTokenModifiers: {CustomSemanticTokenModifiers.control},
      );
    }
  }

  void _addRegions_stringEscapes(
    String string,
    Quote quote,
    int nodeOffset,
    int startIndex,
    int endIndex,
  ) {
    switch (quote) {
      case Quote.RawSingle ||
          Quote.RawDouble ||
          Quote.RawMultiLineSingle ||
          Quote.RawMultiLineDouble:
        // Raw strings don't have escape characters.
        break;
      case Quote.Single ||
          Quote.Double ||
          Quote.MultiLineSingle ||
          Quote.MultiLineDouble:
        _findEscapes(
          string,
          startIndex: startIndex,
          endIndex: endIndex,
          listener: (offset, end) {
            var length = end - offset;
            computer._addRegion(
              nodeOffset + offset,
              length,
              HighlightRegionType.VALID_STRING_ESCAPE,
            );
          },
        );
    }
  }

  /// Finds escaped regions within a string between [startIndex] and [endIndex],
  /// calling [listener] for each found region.
  void _findEscapes(
    String string, {
    required int startIndex,
    required int endIndex,
    required void Function(int offset, int end) listener,
  }) {
    var codeUnits = string.codeUnits;
    var length = string.length;

    bool isBackslash(int i) => i <= length && codeUnits[i] == char.$BACKSLASH;
    bool isHexEscape(int i) => i <= length && codeUnits[i] == char.$x;
    bool isUnicodeHexEscape(int i) => i <= length && codeUnits[i] == char.$u;
    bool isOpenBrace(int i) =>
        i <= length && codeUnits[i] == char.$OPEN_CURLY_BRACKET;
    bool isCloseBrace(int i) =>
        i <= length && codeUnits[i] == char.$CLOSE_CURLY_BRACKET;
    int? numHexDigits(int i, {required int min, required int max}) {
      var numHexDigits = 0;
      for (var j = i; j < math.min(i + max, length); j++) {
        if (!char.isHexDigit(codeUnits[j])) {
          break;
        }
        numHexDigits++;
      }
      return numHexDigits >= min ? numHexDigits : null;
    }

    for (var i = startIndex; i < endIndex;) {
      if (isBackslash(i)) {
        var backslashOffset = i++;
        // All escaped characters are a single character except for:
        // `\uXXXX` or `\u{XX?X?X?X?X?}` for Unicode hex escape.
        // `\xXX` for hex escape.
        if (isHexEscape(i)) {
          // Expect exactly 2 hex digits.
          var numDigits = numHexDigits(i + 1, min: 2, max: 2);
          if (numDigits != null) {
            i += 1 + numDigits;
            listener(backslashOffset, i);
          }
        } else if (isUnicodeHexEscape(i) && isOpenBrace(i + 1)) {
          // Expect 1-6 hex digits followed by '}'.
          var numDigits = numHexDigits(i + 2, min: 1, max: 6);
          if (numDigits != null && isCloseBrace(i + 2 + numDigits)) {
            i += 2 + numDigits + 1;
            listener(backslashOffset, i);
          }
        } else if (isUnicodeHexEscape(i)) {
          // Expect exactly 4 hex digits.
          var numDigits = numHexDigits(i + 1, min: 4, max: 4);
          if (numDigits != null) {
            i += 1 + numDigits;
            listener(backslashOffset, i);
          }
        } else {
          i++;
          // Single-character escape.
          listener(backslashOffset, i);
        }
      } else {
        i++;
      }
    }
  }
}

extension on StringInterpolation {
  Quote get quote => switch ((isRaw, isMultiline, isSingleQuoted)) {
    (true, true, true) => Quote.RawMultiLineSingle,
    (true, true, false) => Quote.RawMultiLineDouble,
    (true, false, true) => Quote.RawSingle,
    (true, false, false) => Quote.RawDouble,
    (false, true, true) => Quote.MultiLineSingle,
    (false, true, false) => Quote.MultiLineDouble,
    (false, false, true) => Quote.Single,
    (false, false, false) => Quote.Double,
  };
}

extension on DartType? {
  bool get isFunction =>
      this is FunctionType || (this?.isDartCoreFunction ?? false);
}
