IIncrementalGenerator 未在引用项目中生成代码

问题描述 投票:0回答:1

我有三个 C# 项目,旨在使用自定义属性来生成引用使用自定义 IIncremental Generator 的公共类的代码。我使用并扩展了 dotnet 的本指南 https://www.youtube.com/watch?v=Yf8t7GqA6zA

公共图书馆项目有以下类:

public class TestRelayCommand<T> : IRelayCommand<T>
{
    #region "Constructors..."
    public TestRelayCommand(ViewModelBase viewModel, Action<T> execute)
    {
        ViewModel = viewModel;
        _command = new(execute);
    }

    public TestRelayCommand(ViewModelBase viewModel, Action<T> execute, Predicate<T> canExecute)
    {
        ViewModel = viewModel;
        _command = new(execute, canExecute);
        _command.CanExecuteChanged += _command_CanExecuteChanged;
    }
    #endregion

    #region "Properties..."
    public ViewModelBase ViewModel { get; }
    private RelayCommand<T> _command;
    public event EventHandler? CanExecuteChanged;
    #endregion

    #region "Methods..."
    public bool CanExecute(T? parameter)
    {
        return _command.CanExecute(parameter);
    }

    public bool CanExecute(object? parameter)
    {
        return _command.CanExecute(parameter);
    }

    public void Execute(object? parameter)
    {
        //Action Started
        _command.Execute(parameter);
        //Action Ended
    }

    public void Execute(T? parameter)
    {
        //Action Started
        _command.Execute(parameter);
        //Action Ended
    }

    public void NotifyCanExecuteChanged()
    {
        _command.NotifyCanExecuteChanged();
    }
    #endregion

    #region "Events..."
    private void _command_CanExecuteChanged(object? sender, EventArgs e)
    {
        CanExecuteChanged?.Invoke(this, e);
    }
    #endregion

}

[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public class TestRelayCommandAttribute : Attribute
{
    public string? CanExecute { get; init; }
    public bool AllowConcurrentExecutions { get; init; }
    public bool FlowExceptionsToTaskScheduler { get; init; }
    public bool IncludeCancelCommand { get; init; }

}

生成器项目具有以下生成器类:

[Generator(LanguageNames.CSharp)]
public class TestRelayCommandGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        File.WriteAllText(@"C:\Users\LMatheson\Desktop\GeneratorInitialize.txt", "Started");
        if (!Debugger.IsAttached)
        {
            Debugger.Launch();
        }
        //context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());

        var provider = context.SyntaxProvider.CreateSyntaxProvider(
            predicate: static (node, _) => node is MethodDeclarationSyntax,
            transform: static (ctx, _) => (MethodDeclarationSyntax)ctx.Node
            ).Where(m => m != null);// && m.AttributeLists.SelectMany(att => att.Attributes).Any(att => att.Name.ToString() == nameof(TestRelayCommand) || att.Name.ToString() == nameof(TestRelayCommandAttribute)));

        var compilation = context.CompilationProvider.Combine(provider.Collect());

        context.RegisterSourceOutput(compilation, Execute);
        File.WriteAllText(@"C:\Users\LMatheson\Desktop\GeneratorInitialized.txt", "Done Startup");
    }

    public void Execute(SourceProductionContext context, (Compilation Left, ImmutableArray<MethodDeclarationSyntax> Right) compilation)
    {
        File.WriteAllText(@"C:\Users\LMatheson\Desktop\GeneratorExecuting.txt", "Executing");
        foreach (var method in compilation.Right)
        {
            var semanticModel = compilation.Left.GetSemanticModel(method.SyntaxTree);
            var methodSymbol = semanticModel.GetDeclaredSymbol(method) as IMethodSymbol;

            if (methodSymbol == null || !methodSymbol.GetAttributes().Any())
                continue;

            var attributeData = methodSymbol.GetAttributes().FirstOrDefault(x => x.AttributeClass?.Name == "TestRelayCommand");
            var canExecuteArg = attributeData.NamedArguments.FirstOrDefault(arg => arg.Key == nameof(TestRelayCommandAttribute.CanExecute));
            string canExecute = canExecuteArg.Value.Value != null ? canExecuteArg.Value.Value.ToString() : null;
            if (!string.IsNullOrEmpty(canExecute)) canExecute = ", " + canExecute;

            var classDeclaration = method.FirstAncestorOrSelf<ClassDeclarationSyntax>();
            if (classDeclaration == null)
                continue;

            var namespaceDeclaration = classDeclaration.FirstAncestorOrSelf<NamespaceDeclarationSyntax>();
            var namespaceName = namespaceDeclaration?.Name.ToString();
            var className = classDeclaration.Identifier.Text;
            var methodName = method.Identifier.Text;

            var parameters = string.Join(", ", methodSymbol.Parameters.Select(p => $"{p.Type}"));
            if (!string.IsNullOrEmpty(parameters)) parameters = "<" + parameters + ">";
            //var returnType = methodSymbol.ReturnType.ToString();
            //if (returnType != "void")
            //    returnType = "<" + returnType + ">";
            //else 
            //    returnType = "";
            var source = $@"
space {namespaceName}

public partial class {className}
{{
    private WPF.UI.Analyzer.{nameof(TestRelayCommand)}{parameters} _{methodName.Substring(0, 1).ToLower()}{methodName.Substring(1)}Command;
    public WPF.UI.Analyzer.{nameof(TestRelayCommand)}{parameters} {methodName}Command => _{methodName.Substring(0, 1).ToLower()}{methodName.Substring(1)}Command ??= new(this, new({methodName}){canExecute});
}}



            context.AddSource($"{className}_{methodName}_{nameof(TestRelayCommand)}.g.cs", SourceText.From(source, Encoding.UTF8));
            File.WriteAllText("C:\\Users\\LMatheson\\Desktop\\Generator.txt", source);
        }
    }
}

使用公共属性的最终项目引用生成器项目,如下所示:

<ProjectReference Include="..\WPF.UI.Analyzer\WPF.UI.Analyzer.csproj">
 <OutputItemType>Analyzer</OutputItemType>
 <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>

并且包含具有属性的此类:

public partial class UserRoleListViewModel
{
    [TestRelayCommand]
    public override async void SelectedViewModelChanging(object args)
    {
        Debug.Print("Testing");
    }
}

生成器应该创建一个与方法的父级匹配的分部类,具有 TestRelayCommand 类型的字段和属性,但无论我是否重建解决方案或通过添加具有相同属性的新方法或调整来调整代码,都永远不会生成代码现有的属性。源生成器在引用项目中生成源所需的内容缺少什么。

这是我在 StackOverflow 上的第一个问题,因此如果需要更多信息,请告诉我。

我尝试了以下更改,但没有结果:

将生成器从 ISourceGenerator 切换到 IIncrementalGenerator 并重建它生成编译的方式。 尝试在生成器进程的不同步骤添加输出文件,以查看文件是否已创建。 将以下属性添加到 .csproj 文件中,输出文件夹是使用其他引用的分析器创建的,但没有来自我的自定义分析器:

<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>C:\Users\Lachlan\Desktop\Generated</CompilerGeneratedFilesOutputPath>

使用与包含属性方法的类文件匹配的文本创建测试编译,此过程在测试环境中输出预期响应,但不会影响分析器生成的输出:

[Generator(LanguageNames.CSharp)]
public class TestRelayCommandGenerator : IIncrementalGenerator
{
    private static Compilation CreateCompilation(string source)
       => CSharpCompilation.Create("compilation",
           new[] { CSharpSyntaxTree.ParseText(source) },
           new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
           new CSharpCompilationOptions(OutputKind.ConsoleApplication));

    public static void Test()
    {
        Compilation inputCompilation = CreateCompilation(@"
using WPF.UI.Analyzer;

namespace WPF.UI.UserManager.ViewModels
{

    public partial class UserRoleListViewModel
    {
        
        [TestRelayCommand]
        public override async void SelectedViewModelChanging(object args)
        {
            Debug.Print(""Testing"");
            }
        }
    }
}");
    TestRelayCommandGenerator generator = new();

    // Create the driver that will control the generation, passing in our generator
    GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);

    // Run the generation pass
    // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls)
    driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics);

    // Or we can look at the results directly:
    GeneratorDriverRunResult runResult = driver.GetRunResult();
}
...

该项目需要使用.Net Standard 2.0作为框架来构建。任何较新的框架都会使任何生成器无法运行。

切换现有的 .Net 8 项目未能使分析器运行,因此我创建了一个新项目并遵循 Andrew Lock 的指南(此处找到:https://andrewlock.net/creating-a-source-generator-part- 1-creating-an-incremental-source-generator/) 来格式化 .csproj 文件,重新添加我之前项目中的生成器,并将这个新项目作为引用包含在包含它查找的属性的项目中。经过这些步骤后,它就能够分析项目并输出预期的代码。

c# roslyn sourcegenerators
1个回答
0
投票

该项目需要使用.Net Standard 2.0作为框架来构建。任何较新的框架都会使任何生成器无法运行。

切换现有的 .Net 8 项目未能使分析器运行,因此我创建了一个新项目并遵循 Andrew Lock 的指南(此处找到:https://andrewlock.net/creating-a-source-generator-part- 1-creating-an-incremental-source-generator/) 来格式化 .csproj 文件,重新添加我之前项目中的生成器,并将这个新项目作为引用包含在包含它查找的属性的项目中。经过这些步骤后,它就能够分析项目并输出预期的代码。

© www.soinside.com 2019 - 2024. All rights reserved.