如何在初始化后步骤的源代码生成中使用项目的命名空间?

How to get consuming project's namespace at source generation postinitialization step?

提问人:TRex 提问时间:8/16/2023 最后编辑:Jason AllerTRex 更新时间:8/23/2023 访问量:70

问:

我正在构建我的增量源生成器项目来探索这个新工具。

我即将完善它,因为它现在有效。

我想改进的一件事是更改使用项目使用的自动生成的标记属性的命名空间。

目前,命名空间在要生成的代码的字符串表示形式中进行硬编码。

例如:

public void Initialize(IncrementalGeneratorInitializationContext context)
{            
    context.RegisterPostInitializationOutput
        (@context =>
        {
            @context.AddSource("SettableNTimesProperty.g.cs", SourcesAsString.GetSettableNTimesCode());
            @context.AddSource("SetNTimesAttribute.g.cs", SourcesAsString.GetSetNTimesAttributeCode());
        });

    var pipeline = context.SyntaxProvider    
        .CreateSyntaxProvider(SourcesAsString.SyntacticPredicate, SourcesAsString.SemanticTransform)
        .Where(found => found is not null && found.HasValue)
        .Collect()
        .Select(SourcesAsString.TransformType)
        ;

    context.RegisterSourceOutput(pipeline, SourcesAsString.Execute);
}

public static string GetSetNTimesAttributeCode()
{
    return
        @"
    // <auto-generated/>

    namespace SetOnceProperties.Sources.Utilities.Attributes
    {
        [AttributeUsage(AttributeTargets.Property)]
        internal class SetNTimesAttribute : Attribute
        {
            public int MaximumSettable { get; }

            public SetNTimesAttribute(int maximumSettable = 1)
            {
                MaximumSettable = maximumSettable;
            }
        }

        [AttributeUsage(AttributeTargets.Property, Inherited = false)]
        internal sealed class SetOnceAttribute : SetNTimesAttribute
        { }
    }";
}

我知道如何在使用参数执行“管道”步骤时获取命名空间。xxxDeclarationSyntax

但是对于该步骤,我只能访问参数,我不知道如何从中检索使用项目命名空间。RegisterPostInitializationOutputIncrementalGeneratorInitializationContext

感谢您在这方面的任何帮助。

C# 源生成器

评论


答:

0赞 Swan 8/23/2023 #1

不幸的是,简短的回答是这是不可能的。该方法在设计上不接收有关编译的输入。GeneratePostInitializationOutput

然而,并不是所有的希望都消失了。我碰巧遇到了类似的问题,我希望在正确的命名空间中生成某些简单的标记类和属性,但在生成器的其余部分的语义分析期间也可用。

我解决这个问题的方式绝对感觉像是一个黑客,所以请谨慎使用,但如下:

您可以使用 创建标记的源输出,因此您具有对编译的完全访问权限以获取命名空间。 然后,在新的 . 然后,生成器的其余部分应使用该提供程序。RegisterSourceOutputIncrementalValueProvider

public void Initialize(IncrementalGeneratorInitializationContext context) {
    context.RegisterSourceOutput(context.CompilationProvider, SourceAsString.Execute);

    IncrementalValueProvider<Compilation> compilationProvider = context.CompilationProvider.Select(
        (compilation, cancelToken) => {
            string source = SourceAsString.Execute(compilation);
            SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, (CSharpParseOptions)compilation.SyntaxTrees.First().Options);
            return compilation.AddSyntaxTrees(syntaxTree);
        }
    );
}

实际上,您只是生成了两次属性,一次作为实际的源输出,一次作为编译提供程序中的附加语法树。

需要注意的一点是,这是专门作用于 CompilationProvider,而不是 SyntaxProvider,因为这是我使用它的用例。 您可以对语法提供程序执行非常相似的操作,但这留给读者作为练习。

我再说一遍,谨慎使用。我昨天真的想出了这个解决方法。我不知道这是否会在某个时候中断,或者对性能有什么影响。