【问题标题】:How to remove SyntaxToken when using a Code Fix Provider使用代码修复提供程序时如何删除 SyntaxToken
【发布时间】:2015-08-12 03:51:50
【问题描述】:

我正在开发一个旨在格式化代码的代码库。在某些情况下,我必须删除语法标记,例如参数列表中的关键字“params”,如果我找到一个并且它是多余的,或者删除一些不需要的大括号(“{”“}”)。

问题是,从语法树中删除语法标记是如此困难和不安。我尝试了许多不同的解决方案,您可以在下面找到这些解决方案,但对于我的生活,我无法成功。我想要一个适用于 U 可能遇到的任何语法标记并且我可能需要从语法树中删除的解决方案。

在以下情况下,我构建了一个当前可以正常工作的分析器,但代码修复提供程序并不能很好地工作......(这是冗余 params 关键字使用的情况)。

//更新 - 我提供了分析器/代码修复提供程序/NUnit 测试的完整代码

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Formatting;

namespace RefactoringEssentials.CSharp.Diagnostics
{
    [ExportCodeFixProvider(LanguageNames.CSharp), System.Composition.Shared]
    public class RedundantParamsCodeFixProvider : CodeFixProvider
    {
        public override ImmutableArray<string> FixableDiagnosticIds
        {
            get
            {
                return ImmutableArray.Create(CSharpDiagnosticIDs.RedundantParamsAnalyzerID);
            }
        }

        public override FixAllProvider GetFixAllProvider()
        {
            return WellKnownFixAllProviders.BatchFixer;
        }

        public async override Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var document = context.Document;
            var cancellationToken = context.CancellationToken;
            var span = context.Span;
            var diagnostics = context.Diagnostics;
            var root = await document.GetSyntaxRootAsync(cancellationToken);
            var diagnostic = diagnostics.First();
            var node = root.FindNode(context.Span) as ParameterSyntax;
            if (node == null)
                return;

            if (!node.Modifiers.Any(x => x.IsKind(SyntaxKind.ParamsKeyword)))
                return;

            var oldParameterNode = node;
            var paramList = node.Parent as ParameterListSyntax;
            if (paramList == null)
                return;

            //var newRoot = root.ReplaceNode(
            //                            oldParameterNode.Parent as ParameterListSyntax,
            //                            paramList.WithParameters
            //                            (SyntaxFactory.SeparatedList(paramList.Parameters.ToArray()))
            //                            .WithLeadingTrivia(node.GetLeadingTrivia())
            //                            .WithTrailingTrivia(node.GetTrailingTrivia()))
            //                            .WithAdditionalAnnotations(Formatter.Annotation);

            //var paramsKeyword = (node.Modifiers.FirstOrDefault(x => x.IsKind(SyntaxKind.ParamsKeyword)));
            //var indexParams = node.Modifiers.IndexOf(paramsKeyword);
            //var syntaxListWithoutParams = node.Modifiers.RemoveAt(indexParams);
            //node.ReplaceToken(paramsKeyword, syntaxListWithoutParams.AsEnumerable());
            context.RegisterCodeFix(CodeActionFactory.Create(node.Span, diagnostic.Severity, "Remove 'params' modifier", token =>
            {
              var newNode = SyntaxFactory.Parameter(node.AttributeLists,node.Modifiers.Remove(SyntaxFactory.Token(SyntaxKind.ParamsKeyword)),node.Type,node.Identifier,node.Default);
             var newRoot = root.ReplaceNode(node, newNode);
             return Task.FromResult(document.WithSyntaxRoot(newRoot));
            }), diagnostic);
            //context.RegisterCodeFix(CodeActionFactory.Create(node.SKCpan, diagnostic.Severity, , document.WithSyntaxRoot(newRoot)), diagnostic);
        }
    }
}

这是我的情况的用例。

using System;
using NUnit.Framework;
using RefactoringEssentials.CSharp.Diagnostics;

namespace RefactoringEssentials.Tests.CSharp.Diagnostics
{
    [TestFixture]
    public class RedundantParamsTests : CSharpDiagnosticTestBase
    {
        [Test]
        public void TestBasicCase()
        {
            Analyze<RedundantParamsAnalyzer>(@"class FooBar
{
    public virtual void Foo(string fmt, object[] args)
    {
    }
}

class FooBar2 : FooBar
{
    public override void Foo(string fmt, $params object[] args$)
    {
        System.Console.WriteLine(fmt, args);
    }
}", @"class FooBar
{
    public virtual void Foo(string fmt, object[] args)
    {
    }
}

class FooBar2 : FooBar
{
    public override void Foo(string fmt, object[] args)
    {
        System.Console.WriteLine(fmt, args);
    }
}");
        }

        [Test]
        public void TestValidCase()
        {
            Analyze<RedundantParamsAnalyzer>(@"class FooBar
{
    public virtual void Foo(string fmt, object[] args)
    {
    }
}

class FooBar2 : FooBar
{
    public override void Foo(string fmt, object[] args)
    {
        System.Console.WriteLine(fmt, args);
    }
}");
        }

        [Test]
        public void ValideParamsUsageTests()
        {
            Analyze<RedundantParamsAnalyzer>(@"class FooBar
{
    public virtual void Foo(string fmt, params object[] args)
    {
    }
}

class FooBar2 : FooBar
{
    public override void Foo(string fmt, params object[] args)
    {
        System.Console.WriteLine(fmt, args);
    }
}");
        }

        [Test]
        public void TestDisable()
        {
            Analyze<RedundantParamsAnalyzer>(@"class FooBar
{
    public virtual void Foo(string fmt, object[] args)
    {
    }
}

class FooBar2 : FooBar
{
    // ReSharper disable once RedundantParams
    public override void Foo(string fmt, params object[] args)
    {
        System.Console.WriteLine(fmt, args);
    }
}");
        }
    }
}

对于那些可能对我如何确定参数是多余的感兴趣的人,以下是逻辑:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Linq;

namespace RefactoringEssentials.CSharp.Diagnostics
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class RedundantParamsAnalyzer : DiagnosticAnalyzer
    {
        private static readonly DiagnosticDescriptor descriptor = new DiagnosticDescriptor(
            CSharpDiagnosticIDs.RedundantParamsAnalyzerID,
            GettextCatalog.GetString("'params' is ignored on overrides"),
            GettextCatalog.GetString("'params' is always ignored in overrides"),
            DiagnosticAnalyzerCategories.RedundanciesInDeclarations,
            DiagnosticSeverity.Warning,
            isEnabledByDefault: true,
            helpLinkUri: HelpLink.CreateFor(CSharpDiagnosticIDs.RedundantParamsAnalyzerID),
            customTags: DiagnosticCustomTags.Unnecessary
        );

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(descriptor);

        public override void Initialize(AnalysisContext context)
        {
            context.RegisterSyntaxNodeAction(
                (nodeContext) =>
                {
                    Diagnostic diagnostic;
                    if (TryGetParamsDiagnostic(nodeContext, out diagnostic))
                    {
                        nodeContext.ReportDiagnostic(diagnostic);
                    }
                },
                SyntaxKind.ParameterList
            );
        }

        //I think it's a better decision to head in this direction instead of MethodDeclaration.
        private static bool TryGetParamsDiagnostic(SyntaxNodeAnalysisContext nodeContext, out Diagnostic diagnostic)
        {
            diagnostic = default(Diagnostic);
            if (nodeContext.IsFromGeneratedCode())
                return false;

            var paramList = nodeContext.Node as ParameterListSyntax;
            var declaration = paramList?.Parent as MethodDeclarationSyntax;

            if (declaration == null)
                return false;

            if (declaration.Modifiers.Count == 0 || !declaration.Modifiers.Any(SyntaxKind.OverrideKeyword))
                return false;

            var lastParam = declaration.ParameterList.Parameters.LastOrDefault();
            SyntaxToken? paramsModifierToken = null;
            if (lastParam == null)
                return false;

            foreach (var x in lastParam.Modifiers)
            {
                if (x.IsKind(SyntaxKind.ParamsKeyword))
                {
                    paramsModifierToken = x;
                    break;
                }
            }

            if (!paramsModifierToken.HasValue ||
                !paramsModifierToken.Value.IsKind(SyntaxKind.ParamsKeyword))
                return false;

            diagnostic = Diagnostic.Create(descriptor, lastParam.GetLocation());
            return true;
        }
}

【问题讨论】:

  • 我很好奇您是如何确定params 关键字是多余的?
  • 请查看更新,我已经添加了如何完成它。如果您对此有任何疑问,我想澄清一下。我正在使用 NRefactory 的旧代码库并将其移植到 Roslyn 平台。代码分析中的逻辑已经存在,我只需要更新它。此外,代码修复提供程序部分由我负责。 @大卫G
  • 您能提供给我们分析器/codefix/tests 的完整代码吗?玩起来更容易,而不必自己重新创建。
  • 所以 Roslyn 会让您使用 ReplaceNode 或 Remove 节点破解树,但您必须跟踪所有细节?玉。看看是否找不到(非 Roslyn)源到源转换系统;然后,您可以编写一个 s-to-s 转换,例如 rule remove_params(v: visiblity, r: type, n: identifier, a1: arg, a2:arg): " \v \r \n(\a1 , 参数 \a2)" --> " \v \r \n(\a1, \a2)" 。这意味着您不必进行所有低级节点黑客攻击。
  • @IraBaxter 必须是罗斯林。我有点理解你的观点,但从来没有使用过这样的东西,我不能完全理解你的建议。我正在开发一个想要使用 Roslyn 平台更新其代码库的库。

标签: c# abstract-syntax-tree roslyn


【解决方案1】:

在这种情况下,您不想删除整个节点,而只想删除 params 修饰符。由于SyntaxNodes 是不可变的,您需要使用WithModifiers 方法创建一个具有适当修饰符的新节点:

public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{

    var diagnostic = context.Diagnostics.First();
    context.RegisterCodeFix(CodeAction.Create("Remove 'params' modifier", async token =>
    {

        var document = context.Document;
        var root = await document.GetSyntaxRootAsync(token);

        var fullParameterNode = root.FindNode(diagnostic.Location.SourceSpan, false) as ParameterSyntax;

        // Keep all modifiers except the params
        var newModifiers = fullParameterNode.Modifiers.Where(m => !m.IsKind(SyntaxKind.ParamsKeyword));
        var syntaxModifiers = SyntaxTokenList.Create(new SyntaxToken());
        syntaxModifiers.AddRange(newModifiers);

        var updatedParameterNode = fullParameterNode.WithModifiers(syntaxModifiers);

        var newDoc = document.WithSyntaxRoot(root.ReplaceNode(fullParameterNode, updatedParameterNode));
        return newDoc;
    }, "KEY"), diagnostic);
}

至于语法的通用删除选项,我不知道有一个易于使用。 Node.ReplaceSyntax 是一个相当复杂的方法,我通常发现使用ReplaceNode 或RemoveNode 方法更容易。

【讨论】:

  • 先生,您是国王 :-) 我花了很长时间才想出这三个解决方案 :O 非常感谢! :-D
  • 如果问的不是太多,如果您有 ObjectCreationExpressionSyntax 并且“()”标记在那里是多余的,那么您将如何创建代码修复,所以应该消除它们?从 var x = new TestClass() { Prop = 1 }; To var x = new TestClass { Prop = 1 }; @JohnKoerner
  • 我必须玩弄它才能让它工作。这里没有魔法。我通常只是对注册的内容进行最佳猜测,然后设置断点并检查我拥有的节点的状态。在那一点上,我确定我可以操纵什么来获得我想要的结果。
  • @KevinAvignon 看看这篇博文,看看它是否回答了您的问题,如果没有,请提出一个新问题,我会尝试回答:johnkoerner.com/csharp/…
  • 我可能还有一个问题要问你:stackoverflow.com/questions/31972748/… 谢谢!
猜你喜欢
  • 2017-03-15
  • 1970-01-01
  • 2021-12-04
  • 1970-01-01
  • 2021-03-20
  • 2020-06-28
  • 1970-01-01
  • 2011-09-24
  • 2018-12-04
相关资源
最近更新 更多