You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用CSharpSyntaxRewriter在Roslyn中实现初始化器符号全限定?

问题描述

我在编写Roslyn源生成器时,需要获取字段的VariableDeclaratorSyntax.Initializer初始化器并输出到生成文件,要求将初始化器内所有符号转为全限定名(无需依赖using指令)。例如:

原代码:

using MyNamespace.SomeStuff;
...
partial class Bar
{
    [Convert]
    Foo _fooField = Foo.Baz;
}

期望生成代码:

partial class Bar
{
    MyNamespace.SomeStuff.Foo _fooField = MyNamespace.SomeStuff.Foo.Baz;
}

我能通过IFieldSymbol轻松拿到字段类型的全限定名,但不知道怎么处理初始化器的全限定化——初始化器可以是任意内容,比如嵌套构造函数等。我试过自定义CSharpSyntaxRewriter,重写VisitMemberAccessExpression方法:

public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
{
    var symbol = SemanticModel.GetSymbolInfo(node);
    if (symbol.Symbol is not null)
        return node.WithName(SyntaxFactory.IdentifierName(symbol.Symbol.ToString()));
    else return base.VisitMemberAccessExpression(node);
}

但得到的结果是Foo.MyNamespace.SomeStuff.Foo.Baz,不是正确的全限定名。我知道应该用WithExpression而非WithName,但不确定怎么处理任意类型的表达式。求解决方案或者更好的实现方式?

附完整上下文代码:

[Generator]
public class StyleSourceGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 查找所有标记了[Convert]的字段
        var fields = context.SyntaxProvider.ForAttributeWithMetadataName(typeof(ConvertAttribute).FullName,
            (node, token) =>
            {
                return node is VariableDeclaratorSyntax;
            },
            (ctx, token) =>
            {
                var syntax = (VariableDeclaratorSyntax)ctx.TargetNode;
                syntax.Initializer.Value.ToString(); // 这里会打印Foo.Baz,但我需要MyNamespace.SomeStuff.Foo.Baz
            });
    }
}
解决方案

核心是遍历初始化器语法树,把每个符号节点替换为对应的全限定名语法节点,而非只修改成员访问的局部。需要改进CSharpSyntaxRewriter,处理不同类型的表达式节点,同时借助语义模型获取正确的符号全限定名。

1. 实现完整的符号替换重写器

创建重写器,不仅处理成员访问表达式,还要处理标识符名称(比如单独的类型名、常量名),确保所有符号都转为全限定形式:

public class FullyQualifiedNameRewriter : CSharpSyntaxRewriter
{
    private readonly SemanticModel _semanticModel;
    private readonly CancellationToken _cancellationToken;

    public FullyQualifiedNameRewriter(SemanticModel semanticModel, CancellationToken cancellationToken)
    {
        _semanticModel = semanticModel;
        _cancellationToken = cancellationToken;
    }

    // 处理单独的标识符(比如Foo)
    public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
    {
        var symbolInfo = _semanticModel.GetSymbolInfo(node, _cancellationToken);
        if (symbolInfo.Symbol is ISymbol symbol && !symbol.IsImplicitlyDeclared)
        {
            return BuildFullyQualifiedName(symbol);
        }
        return base.VisitIdentifierName(node);
    }

    // 处理成员访问表达式(比如Foo.Baz)
    public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
    {
        // 先递归处理表达式部分,确保嵌套符号也被替换
        var rewrittenExpr = Visit(node.Expression) as ExpressionSyntax;
        if (rewrittenExpr == null)
        {
            return base.VisitMemberAccessExpression(node);
        }

        var symbolInfo = _semanticModel.GetSymbolInfo(node, _cancellationToken);
        if (symbolInfo.Symbol is ISymbol symbol && !symbol.IsImplicitlyDeclared)
        {
            // 直接生成全限定名节点,不用保留原有成员访问结构
            return BuildFullyQualifiedName(symbol);
        }

        return node.WithExpression(rewrittenExpr);
    }

    // 辅助方法:从ISymbol生成全限定名语法节点
    private NameSyntax BuildFullyQualifiedName(ISymbol symbol)
    {
        var nameParts = symbol.ToDisplayParts(SymbolDisplayFormat.FullyQualifiedFormat)
            .Where(part => part.Kind != SymbolDisplayPartKind.Punctuation)
            .Select(part => SyntaxFactory.IdentifierName(part.Text));

        return nameParts.Aggregate<IdentifierNameSyntax, NameSyntax>(
            (current, next) => SyntaxFactory.QualifiedName(current, next)
        );
    }
}

2. 在生成器中使用重写器

在SyntaxProvider的回调里,获取语义模型,初始化重写器后处理初始化器表达式:

var fields = context.SyntaxProvider.ForAttributeWithMetadataName(
    typeof(ConvertAttribute).FullName,
    (node, token) => node is VariableDeclaratorSyntax,
    (ctx, token) =>
    {
        var declarator = (VariableDeclaratorSyntax)ctx.TargetNode;
        var semanticModel = ctx.SemanticModel;
        
        ExpressionSyntax? rewrittenInitializer = null;
        if (declarator.Initializer?.Value != null)
        {
            var rewriter = new FullyQualifiedNameRewriter(semanticModel, token);
            rewrittenInitializer = rewriter.Visit(declarator.Initializer.Value) as ExpressionSyntax;
        }

        // 获取字段类型的全限定名
        var fieldSymbol = semanticModel.GetDeclaredSymbol(declarator, token) as IFieldSymbol;
        var fullyQualifiedType = fieldSymbol?.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

        // 组装生成的字段声明示例
        if (fullyQualifiedType != null && rewrittenInitializer != null)
        {
            var fieldDeclaration = SyntaxFactory.FieldDeclaration(
                SyntaxFactory.VariableDeclaration(
                    SyntaxFactory.ParseTypeName(fullyQualifiedType)
                ).WithVariables(
                    SyntaxFactory.SingletonSeparatedList(
                        declarator.WithInitializer(
                            SyntaxFactory.EqualsValueClause(rewrittenInitializer)
                        )
                    )
                )
            ).WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PartialKeyword)));
            
            // 后续可将fieldDeclaration整合到生成的语法树中
        }

        return declarator;
    });

关键细节

  • SymbolDisplayFormat.FullyQualifiedFormat:Roslyn内置的全限定名格式,能正确生成包含命名空间的完整名称,避免手动拼接出错。
  • 递归处理:重写器会先处理成员访问的左半部分(比如Foo.Baz中的Foo),确保嵌套符号也被转为全限定名。
  • 扩展支持:如果需要处理构造函数调用(new Foo()),可以添加VisitObjectCreationExpression方法,同样通过语义模型获取类型符号后替换为全限定名。

内容的提问来源于stack exchange,提问作者Aaa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 03:17:09