使用 Roslyn 转换流式表达式链

Transforming chain of fluent expressions with Roslyn

我有以下示例 classes:

// Sample fluent style class
namespace Fluent
{
    public class FluentSample
    {
        private readonly IList<string> _options;

        public FluentSample()
        {
            _options = new List<string>();
        }

        public static FluentSample Build()
        {
            return new FluentSample();
        }

        public FluentSample WithOption(string option)
        {
            _options.Add(option);
            return this;
        }
    }
}

// Sample class that uses the one above
public class FooSample
{
    public void Build()
    {
        FluentSample.Build()
            .WithOption("uppercase")
            .WithOption("trim")
            .WithOption("concat");
    }
}

假设我想转换使用 FluentSample class 的代码并将 FluentSample.Build() 替换为 Fluent.FluentSample.Build().WithOption("addnewline")。为了做到这一点,我需要确保它真的在调用 class(而不是另一个同名的),这需要一些符号绑定。

从语法可视化工具 window 中,我发现它基本上是一个 InvocationExpressionSyntax 节点,所以我从 CSharpSyntaxRewriter 覆盖 VisitInvocationExpression:

public class Rewritter : CSharpSyntaxRewriter
{
    private readonly SemanticModel SemanticModel;

    public Rewritter(SemanticModel semanticModel)
    {
        SemanticModel = semanticModel;
    }

    public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
    {
        var symbol = SemanticModel.GetSymbolInfo(node).Symbol;
    }
}

但是,我得到的符号是许多 WithOption 调用。语法分析表明,它包含的表达式链只会导致越来越多的 WithOption。如何检查这是否真的是对 Fluent.FluentSample 的调用并应用转换?

原来的代码几乎就是这样,所以语句总是流畅的。

这应该让你开始:

public class Rewritter : CSharpSyntaxRewriter
{
    private readonly SemanticModel SemanticModel;

    public Rewritter(SemanticModel semanticModel)
    {
        SemanticModel = semanticModel;
    }

    public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
    {
        var symbol = SemanticModel.GetSymbolInfo(node).Symbol as IMethodSymbol;

        // symbol could be null, e.g. when invoking a delegate
        if (symbol == null)
        {
            return base.VisitInvocationExpression(node);
        }

        // symbol must be called Build and have 0 parameters
        if (symbol.Name != "Build" ||
            symbol.Parameters.Length != 0)
        {
            return base.VisitInvocationExpression(node);
        }

        // TODO you might want to check that the parent is not an invocation of .WithOption("addnewline") already

        // symbol must be a method on the type "Fluent.FluentSample"
        var type = symbol.ContainingType;

        if (type.Name != "FluentSample" || type.ContainingSymbol.Name != "Fluent")
        {
            return base.VisitInvocationExpression(node);
        }

        // TODO you may want to add a check that the containing symbol is a namespace, and that its containing namespace is the global namespace

        // we have the right one, so return the syntax we want
        return
            SyntaxFactory.InvocationExpression(
                SyntaxFactory.MemberAccessExpression(
                    SyntaxKind.SimpleMemberAccessExpression,
                    node,
                    SyntaxFactory.IdentifierName("WithOption")),
                SyntaxFactory.ArgumentList(
                    SyntaxFactory.SingletonSeparatedList(
                        SyntaxFactory.Argument(
                            SyntaxFactory.LiteralExpression(
                                SyntaxKind.StringLiteralExpression,
                                SyntaxFactory.Literal("addnewline"))))));

    }
}