File: AbstractAddImportsService.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.AddImport
{
    internal abstract class AbstractAddImportsService<TCompilationUnitSyntax, TNamespaceDeclarationSyntax, TUsingOrAliasSyntax, TExternSyntax>
        : IAddImportsService
        where TCompilationUnitSyntax : SyntaxNode
        where TNamespaceDeclarationSyntax : SyntaxNode
        where TUsingOrAliasSyntax : SyntaxNode
        where TExternSyntax : SyntaxNode
    {
        protected AbstractAddImportsService()
        {
        }
 
        protected abstract string Language { get; }
        protected abstract SyntaxNode? GetAlias(TUsingOrAliasSyntax usingOrAlias);
        protected abstract ImmutableArray<SyntaxNode> GetGlobalImports(Compilation compilation, SyntaxGenerator generator);
        protected abstract SyntaxList<TUsingOrAliasSyntax> GetUsingsAndAliases(SyntaxNode node);
        protected abstract SyntaxList<TExternSyntax> GetExterns(SyntaxNode node);
        protected abstract bool IsStaticUsing(TUsingOrAliasSyntax usingOrAlias);
 
        public AddImportPlacementOptions GetAddImportOptions(IOptionsReader configOptions, bool allowInHiddenRegions, AddImportPlacementOptions? fallbackOptions)
        {
            fallbackOptions ??= AddImportPlacementOptions.Default;
 
            return new()
            {
                PlaceSystemNamespaceFirst = configOptions.GetOption(GenerationOptions.PlaceSystemNamespaceFirst, Language, fallbackOptions.PlaceSystemNamespaceFirst),
                UsingDirectivePlacement = GetUsingDirectivePlacementCodeStyleOption(configOptions, fallbackOptions.UsingDirectivePlacement),
                AllowInHiddenRegions = allowInHiddenRegions
            };
        }
 
        public abstract CodeStyleOption2<AddImportPlacement> GetUsingDirectivePlacementCodeStyleOption(IOptionsReader configOptions, CodeStyleOption2<AddImportPlacement> fallbackValue);
 
        private bool IsSimpleUsing(TUsingOrAliasSyntax usingOrAlias) => !IsAlias(usingOrAlias) && !IsStaticUsing(usingOrAlias);
        private bool IsAlias(TUsingOrAliasSyntax usingOrAlias) => GetAlias(usingOrAlias) != null;
        private bool HasAliases(SyntaxNode node) => GetUsingsAndAliases(node).Any(IsAlias);
        private bool HasUsings(SyntaxNode node) => GetUsingsAndAliases(node).Any(IsSimpleUsing);
        private bool HasStaticUsings(SyntaxNode node) => GetUsingsAndAliases(node).Any(IsStaticUsing);
        private bool HasExterns(SyntaxNode node) => GetExterns(node).Any();
        private bool HasAnyImports(SyntaxNode node) => GetUsingsAndAliases(node).Any() || GetExterns(node).Any();
 
        public bool HasExistingImport(
            Compilation compilation,
            SyntaxNode root,
            SyntaxNode? contextLocation,
            SyntaxNode import,
            SyntaxGenerator generator)
        {
            var globalImports = GetGlobalImports(compilation, generator);
            var containers = GetAllContainers(root, contextLocation);
            return HasExistingImport(import, containers, globalImports);
        }
 
        private static ImmutableArray<SyntaxNode> GetAllContainers(SyntaxNode root, SyntaxNode? contextLocation)
        {
            contextLocation ??= root;
 
            var applicableContainer = GetFirstApplicableContainer(contextLocation);
            return applicableContainer.GetAncestorsOrThis<SyntaxNode>().ToImmutableArray();
        }
 
        private bool HasExistingImport(
            SyntaxNode import, ImmutableArray<SyntaxNode> containers, ImmutableArray<SyntaxNode> globalImports)
        {
            foreach (var node in containers)
            {
                if (GetUsingsAndAliases(node).Any(u => IsEquivalentImport(u, import)))
                {
                    return true;
                }
 
                if (GetExterns(node).Any(u => IsEquivalentImport(u, import)))
                {
                    return true;
                }
            }
 
            foreach (var node in globalImports)
            {
                if (IsEquivalentImport(node, import))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        protected abstract bool IsEquivalentImport(SyntaxNode a, SyntaxNode b);
 
        public SyntaxNode GetImportContainer(SyntaxNode root, SyntaxNode? contextLocation, SyntaxNode import, AddImportPlacementOptions options)
        {
            contextLocation ??= root;
            GetContainers(root, contextLocation, options,
                out var externContainer, out var usingContainer, out var staticUsingContainer, out var aliasContainer);
 
            switch (import)
            {
                case TExternSyntax _:
                    return externContainer;
                case TUsingOrAliasSyntax u:
                    if (IsAlias(u))
                    {
                        return aliasContainer;
                    }
 
                    if (IsStaticUsing(u))
                    {
                        return staticUsingContainer;
                    }
 
                    return usingContainer;
            }
 
            throw new InvalidOperationException();
        }
 
        public SyntaxNode AddImports(
            Compilation compilation,
            SyntaxNode root,
            SyntaxNode? contextLocation,
            IEnumerable<SyntaxNode> newImports,
            SyntaxGenerator generator,
            AddImportPlacementOptions options,
            CancellationToken cancellationToken)
        {
            contextLocation ??= root;
 
            var globalImports = GetGlobalImports(compilation, generator);
            var containers = GetAllContainers(root, contextLocation);
            var filteredImports = newImports.Where(i => !HasExistingImport(i, containers, globalImports)).ToArray();
 
            var externAliases = filteredImports.OfType<TExternSyntax>().ToArray();
            var usingDirectives = filteredImports.OfType<TUsingOrAliasSyntax>().Where(IsSimpleUsing).ToArray();
            var staticUsingDirectives = filteredImports.OfType<TUsingOrAliasSyntax>().Where(IsStaticUsing).ToArray();
            var aliasDirectives = filteredImports.OfType<TUsingOrAliasSyntax>().Where(IsAlias).ToArray();
 
            GetContainers(root, contextLocation, options,
                out var externContainer, out var usingContainer, out var aliasContainer, out var staticUsingContainer);
 
            var newRoot = Rewrite(
                externAliases, usingDirectives, staticUsingDirectives, aliasDirectives,
                externContainer, usingContainer, staticUsingContainer, aliasContainer,
                options, root, cancellationToken);
 
            return newRoot;
        }
 
        protected abstract SyntaxNode Rewrite(
            TExternSyntax[] externAliases, TUsingOrAliasSyntax[] usingDirectives, TUsingOrAliasSyntax[] staticUsingDirectives, TUsingOrAliasSyntax[] aliasDirectives,
            SyntaxNode externContainer, SyntaxNode usingContainer, SyntaxNode staticUsingContainer, SyntaxNode aliasContainer,
            AddImportPlacementOptions options, SyntaxNode root, CancellationToken cancellationToken);
 
        private void GetContainers(SyntaxNode root, SyntaxNode contextLocation, AddImportPlacementOptions options, out SyntaxNode externContainer, out SyntaxNode usingContainer, out SyntaxNode staticUsingContainer, out SyntaxNode aliasContainer)
        {
            var applicableContainer = GetFirstApplicableContainer(contextLocation);
            var contextSpine = applicableContainer.GetAncestorsOrThis<SyntaxNode>().ToImmutableArray();
 
            // The node we'll add to if we can't find a specific namespace with imports of 
            // the type we're trying to add.  This will be the closest namespace with any
            // imports in it
            var fallbackNode = contextSpine.FirstOrDefault(HasAnyImports);
 
            // If there aren't any existing imports then make sure we honour the inside namespace preference
            // for using directings if it's set
            if (fallbackNode is null && options.PlaceImportsInsideNamespaces)
                fallbackNode = contextSpine.OfType<TNamespaceDeclarationSyntax>().FirstOrDefault();
 
            // If all else fails use the root
            fallbackNode ??= root;
 
            // The specific container to add each type of import to.  We look for a container
            // that already has an import of the same type as the node we want to add to.
            // If we can find one, we add to that container.  If not, we call back to the 
            // innermost node with any imports.
            externContainer = contextSpine.FirstOrDefault(HasExterns) ?? fallbackNode;
            usingContainer = contextSpine.FirstOrDefault(HasUsings) ?? fallbackNode;
            staticUsingContainer = contextSpine.FirstOrDefault(HasStaticUsings) ?? fallbackNode;
            aliasContainer = contextSpine.FirstOrDefault(HasAliases) ?? fallbackNode;
        }
 
        private static SyntaxNode? GetFirstApplicableContainer(SyntaxNode contextNode)
        {
            var usingDirective = contextNode.GetAncestor<TUsingOrAliasSyntax>();
 
            var node = usingDirective != null ? usingDirective.Parent! : contextNode;
            return node.GetAncestor<TNamespaceDeclarationSyntax>() ??
                   (SyntaxNode?)node.GetAncestorOrThis<TCompilationUnitSyntax>();
        }
    }
}