| // 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 or starts after the requested 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 visitComment(Comment node) { | |
| super.visitComment(node); | |
| // Handle code blocks inside documentation comments. | |
| if (computer._computeSemanticTokens) { | |
| for (var code in node.codeBlocks) { | |
| for (var line in code.lines) { | |
| computer._addRegion( | |
| line.offset, | |
| line.length, | |
| HighlightRegionType.COMMENT_DOCUMENTATION, | |
| additionalSemanticTokenModifiers: { | |
| CustomSemanticTokenModifiers.source, | |
| }, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| @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); | |
| } |