用Roslyn转换流畅的表达链

时间:2016-12-28 01:43:53

标签: c# visual-studio roslyn

我有以下示例类:

// 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类的代码并将FluentSample.Build()替换为Fluent.FluentSample.Build().WithOption("addnewline")。为了做到这一点,我需要确保它真正调用该类(而不是另一个具有相同名称的类),这需要一些符号绑定。

在Syntax Visualizer窗口中,我发现它基本上是InvocationExpressionSyntax节点,因此我从VisitInvocationExpression覆盖CSharpSyntaxRewriter:< / p>

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 s。如何检查这是否真的是对Fluent.FluentSample的调用并应用转换?

原始代码几乎与此类似,因此语句将始终流畅。

1 个答案:

答案 0 :(得分:1)

这应该让你开始:

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"))))));

    }
}