File: CodeActions\Razor\RemoveUnnecessaryDirectivesCodeActionProvider.cs
Web Access
Project: src\src\Razor\src\Razor\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj (Microsoft.CodeAnalysis.Razor.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.AspNetCore.Razor.Threading;
using Microsoft.CodeAnalysis.Razor.CodeActions.Models;
using Microsoft.CodeAnalysis.Razor.CodeActions.Razor;
using Microsoft.CodeAnalysis.Razor.Diagnostics;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Razor.CodeActions;
 
internal class RemoveUnnecessaryDirectivesCodeActionProvider : IRazorCodeActionProvider
{
    public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken)
    {
        // We can only provide this code action if diagnostics has ran and filled in our cache with the info we need
        if (!UnusedDirectiveCache.TryGet(context.CodeDocument, out var unusedDirectiveSpans) || unusedDirectiveSpans.Length == 0)
        {
            return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
        }
 
        if (!context.CodeDocument.TryGetTagHelperRewrittenSyntaxTree(out var tree))
        {
            return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
        }
 
        // Trigger if the selection start or end is inside any single line directive
        var root = tree.Root;
        var startToken = root.FindToken(context.StartAbsoluteIndex);
        var endToken = context.StartAbsoluteIndex != context.EndAbsoluteIndex
            ? root.FindToken(context.EndAbsoluteIndex)
            : startToken;
 
        var startDirective = startToken.Parent?.FirstAncestorOrSelf<BaseRazorDirectiveSyntax>();
        var endDirective = endToken.Parent?.FirstAncestorOrSelf<BaseRazorDirectiveSyntax>();
 
        if (!ShouldOffer(startDirective) && !ShouldOffer(endDirective))
        {
            return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
        }
 
        var data = new RemoveUnnecessaryDirectivesCodeActionParams
        {
            UnusedDirectiveSpans = Array.ConvertAll(unusedDirectiveSpans, static s => s.ToRazorTextSpan())
        };
 
        var resolutionParams = new RazorCodeActionResolutionParams()
        {
            TextDocument = context.Request.TextDocument,
            Action = LanguageServerConstants.CodeActions.RemoveUnnecessaryDirectives,
            Language = RazorLanguageKind.Razor,
            DelegatedDocumentUri = context.DelegatedDocumentUri,
            Data = data,
        };
 
        var action = RazorCodeActionFactory.CreateRemoveUnnecessaryDirectives(resolutionParams);
        return Task.FromResult<ImmutableArray<RazorVSInternalCodeAction>>([action]);
    }
 
    private static bool ShouldOffer(BaseRazorDirectiveSyntax? directive)
    {
        if (directive is null)
        {
            return false;
        }
 
        if (directive is RazorUsingDirectiveSyntax)
        {
            return true;
        }
 
        // We offer for any single line directive on the assumption that the user has a block of directives
        // at the top of their file that they want to clear up.
        return directive.DirectiveBody.Keyword.GetContent() == SyntaxConstants.CSharp.AddTagHelperKeyword;
    }
}