测试源生成器

Testing source generator

我正在尝试测试源代码生成器。 发电机:

[Generator]
public class CustomGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context) { }

    public void Execute(GeneratorExecutionContext context)
    {
        //...
    }
}

测试代码:

              Compilation inputCompilation = CreateCompilation(@"
namespace MyCode
{
    public class Program
    {
        public static void Main(string[] args)
        {
        }
    } 
}
");   
            var generator = new CustomGenerator();
             
            var driver = CSharpGeneratorDriver.Create(generator);
             
            driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics);

            static Compilation CreateCompilation(string source)
                => CSharpCompilation.Create("compilation",
                    new[] { CSharpSyntaxTree.ParseText(source) },
                    new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
                    new CSharpCompilationOptions(OutputKind.ConsoleApplication));

问题是 context.SyntaxReceiver 总是为空,如何解决?

让我们检查 GeneratorExecutionContext.SyntaxReceiver 属性:

的定义
namespace Microsoft.CodeAnalysis
{
    public readonly struct GeneratorExecutionContext
    {
        // Summary:
        //     If the generator registered an Microsoft.CodeAnalysis.ISyntaxReceiver during
        //     initialization, this will be the instance created for this generation pass.
        public ISyntaxReceiver? SyntaxReceiver { get; }
    }
}

我们看到 GeneratorExecutionContext.SyntaxReceiver 属性 被声明为 nullable,这意味着在某些情况下它确实应该为 null。 文档注释显示我们首先必须注册我们的 ISyntaxReceiver in ISourceGenerator.Initialize.

作为canton7 has pointed out, we first need to register it via the GeneratorInitializationContext.RegisterForSyntaxNotifications方法:

[Generator]
public class CustomGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        context.RegisterForSyntaxNotifications(static () => new CustomReceiver());
    }

    public void Execute(GeneratorExecutionContext context)
    {
        Debug.Assert(context.SyntaxReceiver is not null, $"No {nameof(ISyntaxReceiver)} registerd via {nameof(GeneratorInitializationContext.RegisterForSyntaxNotifications)}.");

        if (context.SyntaxReceiver is not CustomReceiver receiver)
        {
            return;
        }

        Debug.Assert(receiver is not null);

        //...
    }
}

internal sealed class CustomReceiver : ISyntaxReceiver
{
    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
    {
    }
}

记住要避免在 ISyntaxReceiver 中进行昂贵的操作,因为它不像 Generator 本身那样支持 取消