File: CodeModel\AbstractCodeModelService.cs
Web Access
Project: src\src\VisualStudio\Core\Impl\Microsoft.VisualStudio.LanguageServices.Implementation.csproj (Microsoft.VisualStudio.LanguageServices.Implementation)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.ExternalElements;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.InternalElements;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.Interop;
using Microsoft.VisualStudio.LanguageServices.Implementation.Interop;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel;
 
internal abstract partial class AbstractCodeModelService : ICodeModelService
{
    private readonly ConditionalWeakTable<SyntaxTree, IBidirectionalMap<SyntaxNodeKey, SyntaxNode>> _treeToNodeKeyMaps =
        new ConditionalWeakTable<SyntaxTree, IBidirectionalMap<SyntaxNodeKey, SyntaxNode>>();
 
    protected readonly ISyntaxFactsService SyntaxFactsService;
 
    private readonly EditorOptionsService _editorOptionsService;
    private readonly AbstractNodeNameGenerator _nodeNameGenerator;
    private readonly AbstractNodeLocator _nodeLocator;
    private readonly AbstractCodeModelEventCollector _eventCollector;
    private readonly IEnumerable<IRefactorNotifyService> _refactorNotifyServices;
 
    private readonly AbstractFormattingRule _lineAdjustmentFormattingRule;
    private readonly AbstractFormattingRule _endRegionFormattingRule;
    private readonly IThreadingContext _threadingContext;
 
    protected AbstractCodeModelService(
        HostLanguageServices languageServiceProvider,
        EditorOptionsService editorOptionsService,
        IEnumerable<IRefactorNotifyService> refactorNotifyServices,
        AbstractFormattingRule lineAdjustmentFormattingRule,
        AbstractFormattingRule endRegionFormattingRule,
        IThreadingContext threadingContext)
    {
        RoslynDebug.AssertNotNull(languageServiceProvider);
        RoslynDebug.AssertNotNull(editorOptionsService);
 
        this.SyntaxFactsService = languageServiceProvider.GetRequiredService<ISyntaxFactsService>();
 
        _editorOptionsService = editorOptionsService;
        _lineAdjustmentFormattingRule = lineAdjustmentFormattingRule;
        _endRegionFormattingRule = endRegionFormattingRule;
        _threadingContext = threadingContext;
        _refactorNotifyServices = refactorNotifyServices;
        _nodeNameGenerator = CreateNodeNameGenerator();
        _nodeLocator = CreateNodeLocator();
        _eventCollector = CreateCodeModelEventCollector();
    }
 
    protected string GetNewLineCharacter(SourceText text)
    {
        var textBuffer = text.Container.TryGetTextBuffer();
        var editorOptions = (textBuffer != null) ? _editorOptionsService.Factory.GetOptions(textBuffer) : _editorOptionsService.Factory.GlobalOptions;
        return editorOptions.GetNewLineCharacter();
    }
 
    protected SyntaxToken GetTokenWithoutAnnotation(SyntaxToken current, Func<SyntaxToken, SyntaxToken> nextTokenGetter)
    {
        while (current.ContainsAnnotations)
        {
            current = nextTokenGetter(current);
        }
 
        return current;
    }
 
    protected TextSpan GetEncompassingSpan(SyntaxNode root, SyntaxToken startToken, SyntaxToken endToken)
    {
        var startPosition = startToken.SpanStart;
        var endPosition = endToken.RawKind == 0 ? root.Span.End : endToken.Span.End;
 
        return TextSpan.FromBounds(startPosition, endPosition);
    }
 
    private IBidirectionalMap<SyntaxNodeKey, SyntaxNode> BuildNodeKeyMap(SyntaxTree syntaxTree)
    {
        var nameOrdinalMap = new Dictionary<string, int>();
        var nodeKeyMap = BidirectionalMap<SyntaxNodeKey, SyntaxNode>.Empty;
 
        foreach (var node in GetFlattenedMemberNodes(syntaxTree))
        {
            var name = _nodeNameGenerator.GenerateName(node);
            if (!nameOrdinalMap.TryGetValue(name, out var ordinal))
            {
                ordinal = 0;
            }
 
            nameOrdinalMap[name] = ++ordinal;
 
            var key = new SyntaxNodeKey(name, ordinal);
            nodeKeyMap = nodeKeyMap.Add(key, node);
        }
 
        return nodeKeyMap;
    }
 
    private IBidirectionalMap<SyntaxNodeKey, SyntaxNode> GetNodeKeyMap(SyntaxTree syntaxTree)
        => _treeToNodeKeyMaps.GetValue(syntaxTree, BuildNodeKeyMap);
 
    public SyntaxNodeKey GetNodeKey(SyntaxNode node)
    {
        var nodeKey = TryGetNodeKey(node);
 
        if (nodeKey.IsEmpty)
        {
            throw new ArgumentException();
        }
 
        return nodeKey;
    }
 
    public SyntaxNodeKey TryGetNodeKey(SyntaxNode node)
    {
        var nodeKeyMap = GetNodeKeyMap(node.SyntaxTree);
        if (!nodeKeyMap.TryGetKey(node, out var nodeKey))
        {
            return SyntaxNodeKey.Empty;
        }
 
        return nodeKey;
    }
 
    public SyntaxNode LookupNode(SyntaxNodeKey nodeKey, SyntaxTree syntaxTree)
    {
        var nodeKeyMap = GetNodeKeyMap(syntaxTree);
        if (!nodeKeyMap.TryGetValue(nodeKey, out var node))
        {
            throw new ArgumentException();
        }
 
        return node;
    }
 
    public bool TryLookupNode(SyntaxNodeKey nodeKey, SyntaxTree syntaxTree, [NotNullWhen(true)] out SyntaxNode? node)
    {
        var nodeKeyMap = GetNodeKeyMap(syntaxTree);
        return nodeKeyMap.TryGetValue(nodeKey, out node);
    }
 
    public abstract bool MatchesScope(SyntaxNode node, EnvDTE.vsCMElement scope);
 
    public abstract IEnumerable<SyntaxNode> GetOptionNodes(SyntaxNode parent);
    public abstract IEnumerable<SyntaxNode> GetImportNodes(SyntaxNode parent);
    public abstract IEnumerable<SyntaxNode> GetAttributeNodes(SyntaxNode parent);
    public abstract IEnumerable<SyntaxNode> GetAttributeArgumentNodes(SyntaxNode parent);
    public abstract IEnumerable<SyntaxNode> GetInheritsNodes(SyntaxNode parent);
    public abstract IEnumerable<SyntaxNode> GetImplementsNodes(SyntaxNode parent);
    public abstract IEnumerable<SyntaxNode> GetParameterNodes(SyntaxNode parent);
 
    protected IEnumerable<SyntaxNode> GetFlattenedMemberNodes(SyntaxTree syntaxTree)
        => GetMemberNodes(syntaxTree.GetRoot(), includeSelf: true, recursive: true, logicalFields: true, onlySupportedNodes: true);
 
    protected IEnumerable<SyntaxNode> GetLogicalMemberNodes(SyntaxNode container)
        => GetMemberNodes(container, includeSelf: false, recursive: false, logicalFields: true, onlySupportedNodes: false);
 
    public IEnumerable<SyntaxNode> GetLogicalSupportedMemberNodes(SyntaxNode container)
        => GetMemberNodes(container, includeSelf: false, recursive: false, logicalFields: true, onlySupportedNodes: true);
 
    /// <summary>
    /// Retrieves the members of a specified <paramref name="container"/> node. The members that are
    /// returned can be controlled by passing various parameters.
    /// </summary>
    /// <param name="container">The <see cref="SyntaxNode"/> from which to retrieve members.</param>
    /// <param name="includeSelf">If true, the container is returned as well.</param>
    /// <param name="recursive">If true, members are recursed to return descendant members as well
    /// as immediate children. For example, a namespace would return the namespaces and types within.
    /// However, if <paramref name="recursive"/> is true, members with the namespaces and types would
    /// also be returned.</param>
    /// <param name="logicalFields">If true, field declarations are broken into their respective declarators.
    /// For example, the field "int x, y" would return two declarators, one for x and one for y in place
    /// of the field.</param>
    /// <param name="onlySupportedNodes">If true, only members supported by Code Model are returned.</param>
    public abstract IEnumerable<SyntaxNode> GetMemberNodes(SyntaxNode container, bool includeSelf, bool recursive, bool logicalFields, bool onlySupportedNodes);
 
    public abstract string Language { get; }
    public abstract string AssemblyAttributeString { get; }
 
    public EnvDTE.CodeElement CreateExternalCodeElement(CodeModelState state, ProjectId projectId, ISymbol symbol)
    {
        switch (symbol.Kind)
        {
            case SymbolKind.Event:
                return (EnvDTE.CodeElement)ExternalCodeEvent.Create(state, projectId, (IEventSymbol)symbol);
            case SymbolKind.Field:
                return (EnvDTE.CodeElement)ExternalCodeVariable.Create(state, projectId, (IFieldSymbol)symbol);
            case SymbolKind.Method:
                return (EnvDTE.CodeElement)ExternalCodeFunction.Create(state, projectId, (IMethodSymbol)symbol);
            case SymbolKind.Namespace:
                return (EnvDTE.CodeElement)ExternalCodeNamespace.Create(state, projectId, (INamespaceSymbol)symbol);
            case SymbolKind.NamedType:
                var namedType = (INamedTypeSymbol)symbol;
                switch (namedType.TypeKind)
                {
                    case TypeKind.Class:
                    case TypeKind.Module:
                        return (EnvDTE.CodeElement)ExternalCodeClass.Create(state, projectId, namedType);
                    case TypeKind.Delegate:
                        return (EnvDTE.CodeElement)ExternalCodeDelegate.Create(state, projectId, namedType);
                    case TypeKind.Enum:
                        return (EnvDTE.CodeElement)ExternalCodeEnum.Create(state, projectId, namedType);
                    case TypeKind.Interface:
                        return (EnvDTE.CodeElement)ExternalCodeInterface.Create(state, projectId, namedType);
                    case TypeKind.Struct:
                        return (EnvDTE.CodeElement)ExternalCodeStruct.Create(state, projectId, namedType);
                    default:
                        throw Exceptions.ThrowEFail();
                }
 
            case SymbolKind.Property:
                var propertySymbol = (IPropertySymbol)symbol;
                return propertySymbol.IsWithEvents
                    ? (EnvDTE.CodeElement)ExternalCodeVariable.Create(state, projectId, propertySymbol)
                    : (EnvDTE.CodeElement)ExternalCodeProperty.Create(state, projectId, (IPropertySymbol)symbol);
            default:
                throw Exceptions.ThrowEFail();
        }
    }
 
    /// <summary>
    /// Do not use this method directly! Instead, go through <see cref="FileCodeModel.GetOrCreateCodeElement{T}(SyntaxNode)"/>
    /// </summary>
    public abstract EnvDTE.CodeElement CreateInternalCodeElement(
        CodeModelState state,
        FileCodeModel fileCodeModel,
        SyntaxNode node);
 
    public EnvDTE.CodeElement CreateCodeType(CodeModelState state, ProjectId projectId, ITypeSymbol typeSymbol)
    {
        if (typeSymbol.TypeKind is TypeKind.Pointer or
            TypeKind.TypeParameter or
            TypeKind.Submission)
        {
            throw Exceptions.ThrowEFail();
        }
 
        if (typeSymbol.TypeKind is TypeKind.Error or
            TypeKind.Unknown)
        {
            return ExternalCodeUnknown.Create(state, projectId, typeSymbol);
        }
 
        var project = state.Workspace.CurrentSolution.GetProject(projectId);
        if (project == null)
        {
            throw Exceptions.ThrowEFail();
        }
 
        if (typeSymbol.TypeKind == TypeKind.Dynamic)
        {
            var obj = project.GetRequiredCompilationAsync(CancellationToken.None).Result.GetSpecialType(SpecialType.System_Object);
            return (EnvDTE.CodeElement)ExternalCodeClass.Create(state, projectId, obj);
        }
 
        if (TryGetElementFromSource(state, project, typeSymbol, out var element))
        {
            return element;
        }
 
        var elementKind = GetElementKind(typeSymbol);
        switch (elementKind)
        {
            case EnvDTE.vsCMElement.vsCMElementClass:
            case EnvDTE.vsCMElement.vsCMElementModule:
                return (EnvDTE.CodeElement)ExternalCodeClass.Create(state, projectId, typeSymbol);
            case EnvDTE.vsCMElement.vsCMElementInterface:
                return (EnvDTE.CodeElement)ExternalCodeInterface.Create(state, projectId, typeSymbol);
            case EnvDTE.vsCMElement.vsCMElementDelegate:
                return (EnvDTE.CodeElement)ExternalCodeDelegate.Create(state, projectId, typeSymbol);
            case EnvDTE.vsCMElement.vsCMElementEnum:
                return (EnvDTE.CodeElement)ExternalCodeEnum.Create(state, projectId, typeSymbol);
            case EnvDTE.vsCMElement.vsCMElementStruct:
                return (EnvDTE.CodeElement)ExternalCodeStruct.Create(state, projectId, typeSymbol);
            default:
                Debug.Fail("Unsupported element kind: " + elementKind);
                throw Exceptions.ThrowEInvalidArg();
        }
    }
 
    public abstract EnvDTE.CodeTypeRef CreateCodeTypeRef(CodeModelState state, ProjectId projectId, object type);
 
    public abstract EnvDTE.vsCMTypeRef GetTypeKindForCodeTypeRef(ITypeSymbol typeSymbol);
    public abstract string GetAsFullNameForCodeTypeRef(ITypeSymbol typeSymbol);
    public abstract string GetAsStringForCodeTypeRef(ITypeSymbol typeSymbol);
 
    public abstract bool IsParameterNode(SyntaxNode node);
    public abstract bool IsAttributeNode(SyntaxNode node);
    public abstract bool IsAttributeArgumentNode(SyntaxNode node);
    public abstract bool IsOptionNode(SyntaxNode node);
    public abstract bool IsImportNode(SyntaxNode node);
 
    public ISymbol? ResolveSymbol(Microsoft.CodeAnalysis.Workspace workspace, ProjectId projectId, SymbolKey symbolId)
    {
        var project = workspace.CurrentSolution.GetProject(projectId);
 
        if (project == null)
        {
            throw Exceptions.ThrowEFail();
        }
 
        return symbolId.Resolve(project.GetRequiredCompilationAsync(CancellationToken.None).Result).Symbol;
    }
 
    protected EnvDTE.CodeFunction CreateInternalCodeAccessorFunction(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node)
    {
        var parentNode = node
            .Ancestors()
            .FirstOrDefault(n => TryGetNodeKey(n) != SyntaxNodeKey.Empty);
 
        if (parentNode == null)
        {
            throw new InvalidOperationException();
        }
 
        var parent = fileCodeModel.GetOrCreateCodeElement<EnvDTE.CodeElement>(parentNode);
        var parentObj = ComAggregate.GetManagedObject<AbstractCodeMember>(parent);
        var accessorKind = GetAccessorKind(node);
 
        return CodeAccessorFunction.Create(state, parentObj, accessorKind);
    }
 
    protected EnvDTE.CodeAttribute CreateInternalCodeAttribute(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node)
    {
        var parentNode = GetEffectiveParentForAttribute(node);
 
        AbstractCodeElement? parentObject;
 
        if (IsParameterNode(parentNode))
        {
            var parentElement = fileCodeModel.GetOrCreateCodeElement<EnvDTE.CodeElement>(parentNode);
            parentObject = ComAggregate.GetManagedObject<AbstractCodeElement>(parentElement);
        }
        else
        {
            var nodeKey = parentNode.AncestorsAndSelf()
                                .Select(n => TryGetNodeKey(n))
                                .FirstOrDefault(nk => nk != SyntaxNodeKey.Empty);
 
            if (nodeKey == SyntaxNodeKey.Empty)
            {
                // This is an assembly-level attribute.
                parentNode = fileCodeModel.GetSyntaxRoot();
 
                parentObject = null;
            }
            else
            {
                parentNode = fileCodeModel.LookupNode(nodeKey);
 
                var parentElement = fileCodeModel.GetOrCreateCodeElement<EnvDTE.CodeElement>(parentNode);
                parentObject = ComAggregate.GetManagedObject<AbstractCodeElement>(parentElement);
            }
        }
 
        GetAttributeNameAndOrdinal(parentNode, node, out var name, out var ordinal);
 
        return CodeAttribute.Create(state, fileCodeModel, parentObject, name, ordinal);
    }
 
    protected EnvDTE80.CodeImport CreateInternalCodeImport(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node)
    {
        GetImportParentAndName(node, out var parentNode, out var name);
 
        AbstractCodeElement? parentObj = null;
        if (parentNode != null)
        {
            var parent = fileCodeModel.GetOrCreateCodeElement<EnvDTE.CodeElement>(parentNode);
            parentObj = ComAggregate.GetManagedObject<AbstractCodeElement>(parent);
        }
 
        return CodeImport.Create(state, fileCodeModel, parentObj, name);
    }
 
    protected EnvDTE.CodeParameter CreateInternalCodeParameter(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node)
    {
        var parentNode = node
            .Ancestors()
            .FirstOrDefault(n => TryGetNodeKey(n) != SyntaxNodeKey.Empty);
 
        if (parentNode == null)
        {
            throw new InvalidOperationException();
        }
 
        var name = GetParameterName(node);
 
        var parent = fileCodeModel.GetOrCreateCodeElement<EnvDTE.CodeElement>(parentNode);
        var parentObj = ComAggregate.GetManagedObject<AbstractCodeMember>(parent);
 
        return CodeParameter.Create(state, parentObj, name);
    }
 
    protected EnvDTE80.CodeElement2 CreateInternalCodeOptionStatement(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node)
    {
        Contract.ThrowIfNull(node.Parent);
        GetOptionNameAndOrdinal(node.Parent, node, out var name, out var ordinal);
 
        return CodeOptionsStatement.Create(state, fileCodeModel, name, ordinal);
    }
 
    protected EnvDTE80.CodeElement2 CreateInternalCodeInheritsStatement(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node)
    {
        var parentNode = node
            .Ancestors()
            .FirstOrDefault(n => TryGetNodeKey(n) != SyntaxNodeKey.Empty);
 
        if (parentNode == null)
        {
            throw new InvalidOperationException();
        }
 
        GetInheritsNamespaceAndOrdinal(parentNode, node, out var namespaceName, out var ordinal);
 
        var parent = fileCodeModel.GetOrCreateCodeElement<EnvDTE.CodeElement>(parentNode);
        var parentObj = ComAggregate.GetManagedObject<AbstractCodeMember>(parent);
 
        return CodeInheritsStatement.Create(state, parentObj, namespaceName, ordinal);
    }
 
    protected EnvDTE80.CodeElement2 CreateInternalCodeImplementsStatement(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node)
    {
        var parentNode = node
            .Ancestors()
            .FirstOrDefault(n => TryGetNodeKey(n) != SyntaxNodeKey.Empty);
 
        if (parentNode == null)
        {
            throw new InvalidOperationException();
        }
 
        GetImplementsNamespaceAndOrdinal(parentNode, node, out var namespaceName, out var ordinal);
 
        var parent = fileCodeModel.GetOrCreateCodeElement<EnvDTE.CodeElement>(parentNode);
        var parentObj = ComAggregate.GetManagedObject<AbstractCodeMember>(parent);
 
        return CodeImplementsStatement.Create(state, parentObj, namespaceName, ordinal);
    }
 
    protected EnvDTE80.CodeAttributeArgument CreateInternalCodeAttributeArgument(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node)
    {
        GetAttributeArgumentParentAndIndex(node, out var attributeNode, out var index);
 
        var codeAttribute = CreateInternalCodeAttribute(state, fileCodeModel, attributeNode);
        var codeAttributeObj = ComAggregate.GetManagedObject<CodeAttribute>(codeAttribute);
 
        return CodeAttributeArgument.Create(state, codeAttributeObj, index);
    }
 
    public abstract EnvDTE.CodeElement CreateUnknownCodeElement(CodeModelState state, FileCodeModel fileCodeModel, SyntaxNode node);
    public abstract EnvDTE.CodeElement CreateUnknownRootNamespaceCodeElement(CodeModelState state, FileCodeModel fileCodeModel);
 
    [return: NotNullIfNotNull(nameof(name))]
    public abstract string? GetUnescapedName(string? name);
 
    public abstract string GetName(SyntaxNode node);
    public abstract SyntaxNode GetNodeWithName(SyntaxNode node);
    public abstract SyntaxNode SetName(SyntaxNode node, string name);
 
    public abstract string GetFullName(SyntaxNode node, SemanticModel semanticModel);
 
    public abstract string GetFullyQualifiedName(string name, int position, SemanticModel semanticModel);
 
    public void Rename(ISymbol symbol, string newName, Workspace workspace, ProjectCodeModelFactory projectCodeModelFactory)
    {
        // Save the node keys.
        var nodeKeyValidation = new NodeKeyValidation(projectCodeModelFactory);
 
        // Rename symbol.
        var oldSolution = workspace.CurrentSolution;
 
        // RenameSymbolAsync may be implemented using OOP, which has known cases for requiring the UI thread to do work. Use JTF
        // to keep the rename action from deadlocking.
        var newSolution = _threadingContext.JoinableTaskFactory.Run(() => Renamer.RenameSymbolAsync(oldSolution, symbol, new SymbolRenameOptions(), newName));
        var changedDocuments = newSolution.GetChangedDocuments(oldSolution);
 
        // Notify third parties of the coming rename operation and let exceptions propagate out
        _refactorNotifyServices.TryOnBeforeGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure: true);
 
        // Update the workspace.
        if (!workspace.TryApplyChanges(newSolution))
        {
            throw Exceptions.ThrowEFail();
        }
 
        // Notify third parties of the completed rename operation and let exceptions propagate out
        _refactorNotifyServices.TryOnAfterGlobalSymbolRenamed(workspace, changedDocuments, symbol, newName, throwOnFailure: true);
 
        RenameTrackingDismisser.DismissRenameTracking(workspace, changedDocuments);
 
        // Update the node keys.
        nodeKeyValidation.RestoreKeys();
    }
 
    public abstract bool IsValidExternalSymbol(ISymbol symbol);
    public abstract string GetExternalSymbolName(ISymbol symbol);
    public abstract string GetExternalSymbolFullName(ISymbol symbol);
 
    public VirtualTreePoint? GetStartPoint(SyntaxNode node, LineFormattingOptions options, EnvDTE.vsCMPart? part)
        => _nodeLocator.GetStartPoint(node, options, part);
 
    public VirtualTreePoint? GetEndPoint(SyntaxNode node, LineFormattingOptions options, EnvDTE.vsCMPart? part)
        => _nodeLocator.GetEndPoint(node, options, part);
 
    public abstract EnvDTE.vsCMAccess GetAccess(ISymbol symbol);
    public abstract EnvDTE.vsCMAccess GetAccess(SyntaxNode node);
#nullable disable
    public abstract SyntaxNode GetNodeWithModifiers(SyntaxNode node);
    public abstract SyntaxNode GetNodeWithType(SyntaxNode node);
#nullable restore
    public abstract SyntaxNode GetNodeWithInitializer(SyntaxNode node);
    public abstract SyntaxNode SetAccess(SyntaxNode node, EnvDTE.vsCMAccess access);
 
    public abstract EnvDTE.vsCMElement GetElementKind(SyntaxNode node);
 
    protected EnvDTE.vsCMElement GetElementKind(ITypeSymbol typeSymbol)
    {
        switch (typeSymbol.TypeKind)
        {
            case TypeKind.Array:
            case TypeKind.Class:
                return EnvDTE.vsCMElement.vsCMElementClass;
 
            case TypeKind.Interface:
                return EnvDTE.vsCMElement.vsCMElementInterface;
 
            case TypeKind.Struct:
                return EnvDTE.vsCMElement.vsCMElementStruct;
 
            case TypeKind.Enum:
                return EnvDTE.vsCMElement.vsCMElementEnum;
 
            case TypeKind.Delegate:
                return EnvDTE.vsCMElement.vsCMElementDelegate;
 
            case TypeKind.Module:
                return EnvDTE.vsCMElement.vsCMElementModule;
 
            default:
                Debug.Fail("Unexpected TypeKind: " + typeSymbol.TypeKind);
                throw Exceptions.ThrowEInvalidArg();
        }
    }
 
    protected bool TryGetElementFromSource(
        CodeModelState state, Project project, ITypeSymbol typeSymbol, [NotNullWhen(true)] out EnvDTE.CodeElement? element)
    {
        element = null;
 
        if (!typeSymbol.IsDefinition)
        {
            return false;
        }
 
        // Here's the strategy for determine what source file we'd try to return an element from.
        //     1. Prefer source files that we don't heuristically flag as generated code.
        //     2. If all of the source files are generated code, pick the first one.
 
        Tuple<DocumentId, Location>? generatedCode = null;
 
        DocumentId? chosenDocumentId = null;
        Location? chosenLocation = null;
 
        foreach (var location in typeSymbol.Locations)
        {
            if (location.IsInSource)
            {
                var document = project.GetDocument(location.SourceTree);
                if (document is null)
                    continue;
 
                if (!document.IsGeneratedCode(CancellationToken.None))
                {
                    chosenLocation = location;
                    chosenDocumentId = document.Id;
                    break;
                }
                else
                {
                    generatedCode ??= Tuple.Create(document.Id, location);
                }
            }
        }
 
        if (chosenDocumentId == null && generatedCode != null)
        {
            chosenDocumentId = generatedCode.Item1;
            chosenLocation = generatedCode.Item2;
        }
 
        if (chosenDocumentId != null)
        {
            var fileCodeModel = state.Workspace.GetFileCodeModel(chosenDocumentId);
            if (fileCodeModel != null)
            {
                var underlyingFileCodeModel = ComAggregate.GetManagedObject<FileCodeModel>(fileCodeModel);
                element = underlyingFileCodeModel.CodeElementFromPosition(chosenLocation!.SourceSpan.Start, GetElementKind(typeSymbol));
                return element != null;
            }
        }
 
        return false;
    }
 
    public abstract bool IsExpressionBodiedProperty(SyntaxNode node);
    public abstract bool IsAccessorNode(SyntaxNode node);
    public abstract MethodKind GetAccessorKind(SyntaxNode node);
 
    public abstract bool TryGetAccessorNode(SyntaxNode parentNode, MethodKind kind, [NotNullWhen(true)] out SyntaxNode? accessorNode);
    public abstract bool TryGetAutoPropertyExpressionBody(SyntaxNode parentNode, [NotNullWhen(true)] out SyntaxNode? accessorNode);
    public abstract bool TryGetParameterNode(SyntaxNode parentNode, string name, [NotNullWhen(true)] out SyntaxNode? parameterNode);
    public abstract bool TryGetImportNode(SyntaxNode parentNode, string dottedName, [NotNullWhen(true)] out SyntaxNode? importNode);
    public abstract bool TryGetOptionNode(SyntaxNode parentNode, string name, int ordinal, [NotNullWhen(true)] out SyntaxNode? optionNode);
    public abstract bool TryGetInheritsNode(SyntaxNode parentNode, string name, int ordinal, [NotNullWhen(true)] out SyntaxNode? inheritsNode);
    public abstract bool TryGetImplementsNode(SyntaxNode parentNode, string name, int ordinal, [NotNullWhen(true)] out SyntaxNode? implementsNode);
    public abstract bool TryGetAttributeNode(SyntaxNode parentNode, string name, int ordinal, [NotNullWhen(true)] out SyntaxNode? attributeNode);
    public abstract bool TryGetAttributeArgumentNode(SyntaxNode attributeNode, int index, [NotNullWhen(true)] out SyntaxNode? attributeArgumentNode);
 
    public abstract void GetOptionNameAndOrdinal(SyntaxNode parentNode, SyntaxNode optionNode, out string name, out int ordinal);
    public abstract void GetInheritsNamespaceAndOrdinal(SyntaxNode inheritsNode, SyntaxNode optionNode, out string namespaceName, out int ordinal);
    public abstract void GetImplementsNamespaceAndOrdinal(SyntaxNode implementsNode, SyntaxNode optionNode, out string namespaceName, out int ordinal);
 
    public abstract void GetAttributeNameAndOrdinal(SyntaxNode parentNode, SyntaxNode attributeNode, out string name, out int ordinal);
    public abstract SyntaxNode GetAttributeTargetNode(SyntaxNode attributeNode);
    public abstract string GetAttributeTarget(SyntaxNode attributeNode);
    public abstract string GetAttributeValue(SyntaxNode attributeNode);
    public abstract SyntaxNode SetAttributeTarget(SyntaxNode attributeNode, string value);
    public abstract SyntaxNode SetAttributeValue(SyntaxNode attributeNode, string value);
    public abstract SyntaxNode GetNodeWithAttributes(SyntaxNode node);
    public abstract SyntaxNode GetEffectiveParentForAttribute(SyntaxNode node);
    public abstract SyntaxNode CreateAttributeNode(string name, string value, string? target = null);
 
    public abstract void GetAttributeArgumentParentAndIndex(SyntaxNode attributeArgumentNode, out SyntaxNode attributeNode, out int index);
    public abstract SyntaxNode CreateAttributeArgumentNode(string name, string value);
 
    public abstract string GetAttributeArgumentValue(SyntaxNode attributeArgumentNode);
 
    public abstract string GetImportAlias(SyntaxNode node);
    public abstract string GetImportNamespaceOrType(SyntaxNode node);
    public abstract void GetImportParentAndName(SyntaxNode importNode, out SyntaxNode? namespaceNode, out string name);
    public abstract SyntaxNode CreateImportNode(string name, string? alias = null);
 
    public abstract string GetParameterName(SyntaxNode node);
 
    public virtual string GetParameterFullName(SyntaxNode node)
        => GetParameterName(node);
 
    public abstract EnvDTE80.vsCMParameterKind GetParameterKind(SyntaxNode node);
    public abstract SyntaxNode SetParameterKind(SyntaxNode node, EnvDTE80.vsCMParameterKind kind);
    public abstract EnvDTE80.vsCMParameterKind UpdateParameterKind(EnvDTE80.vsCMParameterKind parameterKind, PARAMETER_PASSING_MODE passingMode);
 
    public abstract SyntaxNode CreateParameterNode(string name, string type);
 
    public abstract EnvDTE.vsCMFunction ValidateFunctionKind(SyntaxNode containerNode, EnvDTE.vsCMFunction kind, string name);
 
    public abstract bool SupportsEventThrower { get; }
 
    public abstract bool GetCanOverride(SyntaxNode memberNode);
    public abstract SyntaxNode SetCanOverride(SyntaxNode memberNode, bool value);
 
    public abstract EnvDTE80.vsCMClassKind GetClassKind(SyntaxNode typeNode, INamedTypeSymbol typeSymbol);
    public abstract SyntaxNode SetClassKind(SyntaxNode typeNode, EnvDTE80.vsCMClassKind kind);
 
    public abstract string GetComment(SyntaxNode node);
    public abstract SyntaxNode SetComment(SyntaxNode node, string value);
 
    public abstract EnvDTE80.vsCMConstKind GetConstKind(SyntaxNode variableNode);
    public abstract SyntaxNode SetConstKind(SyntaxNode variableNode, EnvDTE80.vsCMConstKind kind);
 
    public abstract EnvDTE80.vsCMDataTypeKind GetDataTypeKind(SyntaxNode typeNode, INamedTypeSymbol symbol);
    public abstract SyntaxNode SetDataTypeKind(SyntaxNode typeNode, EnvDTE80.vsCMDataTypeKind kind);
 
    public abstract string GetDocComment(SyntaxNode node);
    public abstract SyntaxNode SetDocComment(SyntaxNode node, string value);
 
    public abstract EnvDTE.vsCMFunction GetFunctionKind(IMethodSymbol symbol);
 
    public abstract EnvDTE80.vsCMInheritanceKind GetInheritanceKind(SyntaxNode typeNode, INamedTypeSymbol typeSymbol);
    public abstract SyntaxNode SetInheritanceKind(SyntaxNode typeNode, EnvDTE80.vsCMInheritanceKind kind);
 
    public abstract bool GetIsAbstract(SyntaxNode memberNode, ISymbol symbol);
    public abstract SyntaxNode SetIsAbstract(SyntaxNode memberNode, bool value);
 
    public abstract bool GetIsConstant(SyntaxNode variableNode);
    public abstract SyntaxNode SetIsConstant(SyntaxNode variableNode, bool value);
 
    public abstract bool GetIsDefault(SyntaxNode propertyNode);
    public abstract SyntaxNode SetIsDefault(SyntaxNode propertyNode, bool value);
 
    public abstract bool GetIsGeneric(SyntaxNode memberNode);
 
    public abstract bool GetIsPropertyStyleEvent(SyntaxNode eventNode);
 
    public abstract bool GetIsShared(SyntaxNode memberNode, ISymbol symbol);
    public abstract SyntaxNode SetIsShared(SyntaxNode memberNode, bool value);
 
    public abstract bool GetMustImplement(SyntaxNode memberNode);
    public abstract SyntaxNode SetMustImplement(SyntaxNode memberNode, bool value);
 
    public abstract EnvDTE80.vsCMOverrideKind GetOverrideKind(SyntaxNode memberNode);
    public abstract SyntaxNode SetOverrideKind(SyntaxNode memberNode, EnvDTE80.vsCMOverrideKind kind);
 
    public abstract EnvDTE80.vsCMPropertyKind GetReadWrite(SyntaxNode memberNode);
 
    public abstract SyntaxNode SetType(SyntaxNode node, ITypeSymbol? typeSymbol);
 
    public abstract Document Delete(Document document, SyntaxNode node);
 
    public abstract string GetMethodXml(SyntaxNode node, SemanticModel semanticModel);
 
    public abstract string? GetInitExpression(SyntaxNode node);
    public abstract SyntaxNode AddInitExpression(SyntaxNode node, string value);
 
    public abstract CodeGenerationDestination GetDestination(SyntaxNode containerNode);
 
    protected abstract Accessibility GetDefaultAccessibility(SymbolKind targetSymbolKind, CodeGenerationDestination destination);
 
    public Accessibility GetAccessibility(EnvDTE.vsCMAccess access, SymbolKind targetSymbolKind, CodeGenerationDestination destination = CodeGenerationDestination.Unspecified)
    {
        // Note: Some EnvDTE.vsCMAccess members aren't "bitwise-mutually-exclusive"
        // Specifically, vsCMAccessProjectOrProtected (12) is a combination of vsCMAccessProject (4) and vsCMAccessProtected (8)
        // We therefore check for this first.
 
        if ((access & EnvDTE.vsCMAccess.vsCMAccessProjectOrProtected) == EnvDTE.vsCMAccess.vsCMAccessProjectOrProtected)
        {
            return Accessibility.ProtectedOrInternal;
        }
        else if ((access & EnvDTE.vsCMAccess.vsCMAccessPrivate) != 0)
        {
            return Accessibility.Private;
        }
        else if ((access & EnvDTE.vsCMAccess.vsCMAccessProject) != 0)
        {
            return Accessibility.Internal;
        }
        else if ((access & EnvDTE.vsCMAccess.vsCMAccessProtected) != 0)
        {
            return Accessibility.Protected;
        }
        else if ((access & EnvDTE.vsCMAccess.vsCMAccessPublic) != 0)
        {
            return Accessibility.Public;
        }
        else if ((access & EnvDTE.vsCMAccess.vsCMAccessDefault) != 0)
        {
            return GetDefaultAccessibility(targetSymbolKind, destination);
        }
        else
        {
            throw new ArgumentException(ServicesVSResources.Invalid_access, nameof(access));
        }
    }
 
    public bool GetWithEvents(EnvDTE.vsCMAccess access)
        => (access & EnvDTE.vsCMAccess.vsCMAccessWithEvents) != 0;
 
    // TODO(DustinCa): Verify this list against VB
    protected SpecialType GetSpecialType(EnvDTE.vsCMTypeRef type)
        => type switch
        {
            EnvDTE.vsCMTypeRef.vsCMTypeRefBool => SpecialType.System_Boolean,
            EnvDTE.vsCMTypeRef.vsCMTypeRefByte => SpecialType.System_Byte,
            EnvDTE.vsCMTypeRef.vsCMTypeRefChar => SpecialType.System_Char,
            EnvDTE.vsCMTypeRef.vsCMTypeRefDecimal => SpecialType.System_Decimal,
            EnvDTE.vsCMTypeRef.vsCMTypeRefDouble => SpecialType.System_Double,
            EnvDTE.vsCMTypeRef.vsCMTypeRefFloat => SpecialType.System_Single,
            EnvDTE.vsCMTypeRef.vsCMTypeRefInt => SpecialType.System_Int32,
            EnvDTE.vsCMTypeRef.vsCMTypeRefLong => SpecialType.System_Int64,
            EnvDTE.vsCMTypeRef.vsCMTypeRefObject => SpecialType.System_Object,
            EnvDTE.vsCMTypeRef.vsCMTypeRefShort => SpecialType.System_Int16,
            EnvDTE.vsCMTypeRef.vsCMTypeRefString => SpecialType.System_String,
            EnvDTE.vsCMTypeRef.vsCMTypeRefVoid => SpecialType.System_Void,
            _ => throw new ArgumentException(),
        };
 
    private ITypeSymbol GetSpecialType(EnvDTE.vsCMTypeRef type, Compilation compilation)
        => compilation.GetSpecialType(GetSpecialType(type));
 
    protected abstract ITypeSymbol? GetTypeSymbolFromPartialName(string partialName, SemanticModel semanticModel, int position);
    public abstract ITypeSymbol? GetTypeSymbolFromFullName(string fullName, Compilation compilation);
 
    public ITypeSymbol GetTypeSymbol(object type, SemanticModel semanticModel, int position)
    {
        ITypeSymbol? typeSymbol;
        if (type is EnvDTE.CodeTypeRef)
        {
            typeSymbol = GetTypeSymbolFromPartialName(((EnvDTE.CodeTypeRef)type).AsString, semanticModel, position);
 
            // This could return null if there was a parse error, but given we produced the name in the first place it should be OK
            Contract.ThrowIfNull(typeSymbol);
            return typeSymbol;
        }
        else if (type is EnvDTE.CodeType)
        {
            typeSymbol = GetTypeSymbolFromFullName(((EnvDTE.CodeType)type).FullName, semanticModel.Compilation);
 
            // This could return null if there was a parse error, but given we produced the name in the first place it should be OK
            Contract.ThrowIfNull(typeSymbol);
            return typeSymbol;
        }
 
        if (type is EnvDTE.vsCMTypeRef or int)
        {
            typeSymbol = GetSpecialType((EnvDTE.vsCMTypeRef)type, semanticModel.Compilation);
        }
        else if (type is string s)
        {
            typeSymbol = GetTypeSymbolFromPartialName(s, semanticModel, position);
        }
        else
        {
            throw new InvalidOperationException();
        }
 
        if (typeSymbol == null)
        {
            throw new ArgumentException();
        }
 
        return typeSymbol;
    }
 
    public abstract SyntaxNode CreateReturnDefaultValueStatement(ITypeSymbol type);
 
    protected abstract int GetAttributeIndexInContainer(
        SyntaxNode containerNode,
        Func<SyntaxNode, bool> predicate);
 
    /// <summary>
    /// The position argument is a VARIANT which may be an EnvDTE.CodeElement, an int or a string
    /// representing the name of a member. This function translates the argument and returns the
    /// 1-based position of the specified attribute.
    /// </summary>
    public int PositionVariantToAttributeInsertionIndex(object position, SyntaxNode containerNode, FileCodeModel fileCodeModel)
    {
        return PositionVariantToInsertionIndex(
            position,
            containerNode,
            fileCodeModel,
            GetAttributeIndexInContainer,
            GetAttributeNodes);
    }
 
    protected abstract int GetAttributeArgumentIndexInContainer(
        SyntaxNode containerNode,
        Func<SyntaxNode, bool> predicate);
 
    public int PositionVariantToAttributeArgumentInsertionIndex(object position, SyntaxNode containerNode, FileCodeModel fileCodeModel)
    {
        return PositionVariantToInsertionIndex(
            position,
            containerNode,
            fileCodeModel,
            GetAttributeArgumentIndexInContainer,
            GetAttributeArgumentNodes);
    }
 
    protected abstract int GetImportIndexInContainer(
        SyntaxNode containerNode,
        Func<SyntaxNode, bool> predicate);
 
    public int PositionVariantToImportInsertionIndex(object position, SyntaxNode containerNode, FileCodeModel fileCodeModel)
    {
        return PositionVariantToInsertionIndex(
            position,
            containerNode,
            fileCodeModel,
            GetImportIndexInContainer,
            GetImportNodes);
    }
 
    protected abstract int GetParameterIndexInContainer(
        SyntaxNode containerNode,
        Func<SyntaxNode, bool> predicate);
 
    public int PositionVariantToParameterInsertionIndex(object position, SyntaxNode containerNode, FileCodeModel fileCodeModel)
    {
        return PositionVariantToInsertionIndex(
            position,
            containerNode,
            fileCodeModel,
            GetParameterIndexInContainer,
            GetParameterNodes);
    }
 
    /// <summary>
    /// Finds the index of the first child within the container for which <paramref name="predicate"/> returns true.
    /// Note that the result is a 1-based as that is what code model expects. Returns -1 if no match is found.
    /// </summary>
    protected abstract int GetMemberIndexInContainer(
        SyntaxNode containerNode,
        Func<SyntaxNode, bool> predicate);
 
    /// <summary>
    /// The position argument is a VARIANT which may be an EnvDTE.CodeElement, an int or a string
    /// representing the name of a member. This function translates the argument and returns the
    /// 1-based position of the specified member.
    /// </summary>
    public int PositionVariantToMemberInsertionIndex(object position, SyntaxNode containerNode, FileCodeModel fileCodeModel)
    {
        return PositionVariantToInsertionIndex(
            position,
            containerNode,
            fileCodeModel,
            GetMemberIndexInContainer,
            n => GetMemberNodes(n, includeSelf: false, recursive: false, logicalFields: false, onlySupportedNodes: false));
    }
 
    private int PositionVariantToInsertionIndex(
        object position,
        SyntaxNode containerNode,
        FileCodeModel fileCodeModel,
        Func<SyntaxNode, Func<SyntaxNode, bool>, int> getIndexInContainer,
        Func<SyntaxNode, IEnumerable<SyntaxNode>> getChildNodes)
    {
        int result;
 
        if (position is int i)
        {
            result = i;
        }
        else if (position is EnvDTE.CodeElement)
        {
            var codeElement = ComAggregate.TryGetManagedObject<AbstractCodeElement>(position);
            if (codeElement == null || codeElement.FileCodeModel != fileCodeModel)
            {
                throw Exceptions.ThrowEInvalidArg();
            }
 
            var positionNode = codeElement.LookupNode();
            if (positionNode == null)
            {
                throw Exceptions.ThrowEFail();
            }
 
            result = getIndexInContainer(containerNode, child => child == positionNode);
        }
        else if (position is string name)
        {
            result = getIndexInContainer(containerNode, child => GetName(child) == name);
        }
        else if (position == null || position == Type.Missing)
        {
            result = 0;
        }
        else
        {
            // Nothing we can handle...
            throw Exceptions.ThrowEInvalidArg();
        }
 
        // -1 means to insert at the end, so we'll return the last child.
        return result == -1
            ? getChildNodes(containerNode).ToArray().Length
            : result;
    }
 
    protected abstract SyntaxNode GetFieldFromVariableNode(SyntaxNode variableNode);
    protected abstract SyntaxNode GetVariableFromFieldNode(SyntaxNode fieldNode);
    protected abstract SyntaxNode GetAttributeFromAttributeDeclarationNode(SyntaxNode attributeDeclarationNode);
 
    private int GetMemberInsertionIndex(SyntaxNode container, int insertionIndex)
    {
        var childNodes = GetLogicalMemberNodes(container).ToArray();
 
        // Note: childIndexToInsertAfter is 1-based but can be 0, meaning insert before any other members.
        // If it isn't 0, it means to insert the member node *after* the node at the 1-based index.
        Debug.Assert(insertionIndex >= 0 && insertionIndex <= childNodes.Length);
 
        if (insertionIndex == 0)
        {
            return 0;
        }
        else
        {
            var nodeAtIndex = GetFieldFromVariableNode(childNodes[insertionIndex - 1]);
            return GetMemberNodes(container, includeSelf: false, recursive: false, logicalFields: false, onlySupportedNodes: false).ToList().IndexOf(nodeAtIndex) + 1;
        }
    }
 
    private static int GetAttributeArgumentInsertionIndex(int insertionIndex)
        => insertionIndex;
 
    private static int GetAttributeInsertionIndex(int insertionIndex)
        => insertionIndex;
 
    private static int GetImportInsertionIndex(int insertionIndex)
        => insertionIndex;
 
    private static int GetParameterInsertionIndex(int insertionIndex)
        => insertionIndex;
 
    protected abstract bool IsCodeModelNode(SyntaxNode node);
 
    protected abstract TextSpan GetSpanToFormat(SyntaxNode root, TextSpan span);
 
    protected abstract SyntaxNode InsertMemberNodeIntoContainer(int index, SyntaxNode member, SyntaxNode container);
    protected abstract SyntaxNode InsertAttributeArgumentIntoContainer(int index, SyntaxNode attributeArgument, SyntaxNode container);
    protected abstract SyntaxNode InsertAttributeListIntoContainer(int index, SyntaxNode attribute, SyntaxNode container);
    protected abstract SyntaxNode InsertImportIntoContainer(int index, SyntaxNode import, SyntaxNode container);
    protected abstract SyntaxNode InsertParameterIntoContainer(int index, SyntaxNode parameter, SyntaxNode container);
 
    private Document FormatAnnotatedNode(Document document, SyntaxAnnotation annotation, IEnumerable<AbstractFormattingRule>? additionalRules, CancellationToken cancellationToken)
    {
        var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken);
        var annotatedNode = root.GetAnnotatedNodesAndTokens(annotation).Single().AsNode();
        Contract.ThrowIfNull(annotatedNode);
        var formattingSpan = GetSpanToFormat(root, annotatedNode.FullSpan);
 
        var formattingRules = Formatter.GetDefaultFormattingRules(document);
        if (additionalRules != null)
        {
            formattingRules = [.. additionalRules, .. formattingRules];
        }
 
        return _threadingContext.JoinableTaskFactory.Run(async () =>
        {
            var options = await document.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false);
 
            return await Formatter.FormatAsync(
                document,
                [formattingSpan],
                options,
                formattingRules,
                cancellationToken).ConfigureAwait(false);
        });
    }
 
    private SyntaxNode InsertNode(
        Document document,
        bool batchMode,
        int insertionIndex,
        SyntaxNode containerNode,
        SyntaxNode node,
        Func<int, SyntaxNode, SyntaxNode, SyntaxNode> insertNodeIntoContainer,
        CancellationToken cancellationToken,
        out Document newDocument)
    {
        var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken);
 
        // Annotate the member we're inserting so we can get back to it.
        var annotation = new SyntaxAnnotation();
 
        var gen = SyntaxGenerator.GetGenerator(document);
        node = node.WithAdditionalAnnotations(annotation);
 
        if (gen.GetDeclarationKind(node) != DeclarationKind.NamespaceImport)
        {
            // REVIEW: how simplifier ever worked for code model? nobody added simplifier.Annotation before?
            node = node.WithAdditionalAnnotations(Simplifier.Annotation);
        }
 
        var newContainerNode = insertNodeIntoContainer(insertionIndex, node, containerNode);
        var newRoot = root.ReplaceNode(containerNode, newContainerNode);
 
        Contract.ThrowIfTrue(object.ReferenceEquals(root, newRoot), $"We failed to insert the node into the tree; this might be if {nameof(containerNode)} came from a different snapshot.");
 
        document = document.WithSyntaxRoot(newRoot);
 
        if (!batchMode)
        {
            document = _threadingContext.JoinableTaskFactory.Run(async () =>
            {
                var simplifierOptions = await document.GetSimplifierOptionsAsync(cancellationToken).ConfigureAwait(false);
                return await Simplifier.ReduceAsync(document, annotation, simplifierOptions, cancellationToken).ConfigureAwait(false);
            });
        }
 
        document = FormatAnnotatedNode(document, annotation, new[] { _lineAdjustmentFormattingRule, _endRegionFormattingRule }, cancellationToken);
 
        // out param
        newDocument = document;
 
        // new node
        return document
            .GetRequiredSyntaxRootSynchronously(cancellationToken)
            .GetAnnotatedNodesAndTokens(annotation)
            .Single()
            .AsNode()!;
    }
 
    /// <summary>
    /// Override to determine whether <param name="newNode"/> adds a method body to <param name="node"/>.
    /// This is used to determine whether a blank line should be added inside the body when formatting.
    /// </summary>
    protected abstract bool AddBlankLineToMethodBody(SyntaxNode node, SyntaxNode newNode);
 
    public Document UpdateNode(
        Document document,
        SyntaxNode node,
        SyntaxNode newNode,
        CancellationToken cancellationToken)
    {
        // Annotate the member we're inserting so we can get back to it.
        var annotation = new SyntaxAnnotation();
 
        // REVIEW: how simplifier ever worked for code model? nobody added simplifier.Annotation before?
        var annotatedNode = newNode.WithAdditionalAnnotations(annotation, Simplifier.Annotation);
 
        var oldRoot = document.GetRequiredSyntaxRootSynchronously(cancellationToken);
        var newRoot = oldRoot.ReplaceNode(node, annotatedNode);
 
        document = document.WithSyntaxRoot(newRoot);
 
        var additionalRules = AddBlankLineToMethodBody(node, newNode)
            ? SpecializedCollections.SingletonEnumerable(_lineAdjustmentFormattingRule)
            : null;
 
        document = FormatAnnotatedNode(document, annotation, additionalRules, cancellationToken);
 
        return document;
    }
 
    public SyntaxNode InsertAttribute(
        Document document,
        bool batchMode,
        int insertionIndex,
        SyntaxNode containerNode,
        SyntaxNode attributeNode,
        CancellationToken cancellationToken,
        out Document newDocument)
    {
        var finalNode = InsertNode(
            document,
            batchMode,
            GetAttributeInsertionIndex(insertionIndex),
            containerNode,
            attributeNode,
            InsertAttributeListIntoContainer,
            cancellationToken,
            out newDocument);
 
        return GetAttributeFromAttributeDeclarationNode(finalNode);
    }
 
    public SyntaxNode InsertAttributeArgument(
        Document document,
        bool batchMode,
        int insertionIndex,
        SyntaxNode containerNode,
        SyntaxNode attributeArgumentNode,
        CancellationToken cancellationToken,
        out Document newDocument)
    {
        var finalNode = InsertNode(
            document,
            batchMode,
            GetAttributeArgumentInsertionIndex(insertionIndex),
            containerNode,
            attributeArgumentNode,
            InsertAttributeArgumentIntoContainer,
            cancellationToken,
            out newDocument);
 
        return finalNode;
    }
 
    public SyntaxNode InsertImport(
        Document document,
        bool batchMode,
        int insertionIndex,
        SyntaxNode containerNode,
        SyntaxNode importNode,
        CancellationToken cancellationToken,
        out Document newDocument)
    {
        var finalNode = InsertNode(
            document,
            batchMode,
            GetImportInsertionIndex(insertionIndex),
            containerNode,
            importNode,
            InsertImportIntoContainer,
            cancellationToken,
            out newDocument);
 
        return finalNode;
    }
 
    public SyntaxNode InsertParameter(
        Document document,
        bool batchMode,
        int insertionIndex,
        SyntaxNode containerNode,
        SyntaxNode parameterNode,
        CancellationToken cancellationToken,
        out Document newDocument)
    {
        var finalNode = InsertNode(
            document,
            batchMode,
            GetParameterInsertionIndex(insertionIndex),
            containerNode,
            parameterNode,
            InsertParameterIntoContainer,
            cancellationToken,
            out newDocument);
 
        return finalNode;
    }
 
    public SyntaxNode InsertMember(
        Document document,
        bool batchMode,
        int insertionIndex,
        SyntaxNode containerNode,
        SyntaxNode memberNode,
        CancellationToken cancellationToken,
        out Document newDocument)
    {
        var finalNode = InsertNode(
            document,
            batchMode,
            GetMemberInsertionIndex(containerNode, insertionIndex),
            containerNode,
            memberNode,
            InsertMemberNodeIntoContainer,
            cancellationToken,
            out newDocument);
 
        return GetVariableFromFieldNode(finalNode);
    }
 
    public Queue<CodeModelEvent> CollectCodeModelEvents(SyntaxTree oldTree, SyntaxTree newTree)
        => _eventCollector.Collect(oldTree, newTree);
 
    public abstract bool IsNamespace(SyntaxNode node);
    public abstract bool IsType(SyntaxNode node);
 
    public virtual IList<string> GetHandledEventNames(SyntaxNode method, SemanticModel semanticModel)
    {
        // descendants may override (particularly VB).
 
        return SpecializedCollections.EmptyList<string>();
    }
 
    public virtual bool HandlesEvent(string eventName, SyntaxNode method, SemanticModel semanticModel)
    {
        // descendants may override (particularly VB).
 
        return false;
    }
 
    public virtual Document AddHandlesClause(Document document, string eventName, SyntaxNode method, CancellationToken cancellationToken)
    {
        // descendants may override (particularly VB).
 
        return document;
    }
 
    public virtual Document RemoveHandlesClause(Document document, string eventName, SyntaxNode method, CancellationToken cancellationToken)
    {
        // descendants may override (particularly VB).
 
        return document;
    }
 
    public abstract string[] GetFunctionExtenderNames();
    public abstract object GetFunctionExtender(string name, SyntaxNode node, ISymbol symbol);
    public abstract string[] GetPropertyExtenderNames();
    public abstract object GetPropertyExtender(string name, SyntaxNode node, ISymbol symbol);
    public abstract string[] GetExternalTypeExtenderNames();
    public abstract object GetExternalTypeExtender(string name, string externalLocation);
    public abstract string[] GetTypeExtenderNames();
    public abstract object GetTypeExtender(string name, AbstractCodeType codeType);
 
    public abstract bool IsValidBaseType(SyntaxNode node, ITypeSymbol typeSymbol);
    public abstract SyntaxNode AddBase(SyntaxNode node, ITypeSymbol typeSymbol, SemanticModel semanticModel, int? position);
    public abstract SyntaxNode RemoveBase(SyntaxNode node, ITypeSymbol typeSymbol, SemanticModel semanticModel);
 
    public abstract bool IsValidInterfaceType(SyntaxNode node, ITypeSymbol typeSymbol);
    public abstract SyntaxNode AddImplementedInterface(SyntaxNode node, ITypeSymbol typeSymbol, SemanticModel semanticModel, int? position);
    public abstract SyntaxNode RemoveImplementedInterface(SyntaxNode node, ITypeSymbol typeSymbol, SemanticModel semanticModel);
 
    public abstract string GetPrototype(SyntaxNode node, ISymbol symbol, PrototypeFlags flags);
 
    public virtual void AttachFormatTrackingToBuffer(ITextBuffer buffer)
    {
        // can be override by languages if needed
    }
 
    public virtual void DetachFormatTrackingToBuffer(ITextBuffer buffer)
    {
        // can be override by languages if needed
    }
 
    public virtual void EnsureBufferFormatted(ITextBuffer buffer)
    {
        // can be override by languages if needed
    }
}