O paradigma 'partial' do SourceGenerator e testes

O modificador 'partial' permite dividir uma classe ou método em vários arquivos, facilitando a geração de código.

Ele é ideal para combinar com atributos personalizados, ajudando a localizar exatamente onde o código será gerado.

Pode ser visto como uma forma estática de injeção de dependências, automatizando a adição de código com base em regras.

Reduz a necessidade de código repetitivo e melhorает o desempenho ao eliminar o uso de reflexão.

O projeto GenerateCore implementa este paradigma para tornar a geração de código mais eficinete e manutenível.

  1. Aplicações no framework

Em regex, serialização e log, o SourceGenerator trabalha em conjunto com 'partial' para criar código dinâmico.

Exemplo de regex 'partial'


partial class Program
{
   [GeradoRegex("abc|def", RegexOptions.IgnoreCase)]
   private static partial Regex AbcOuDefGeradoRegex();
}
       

Resultado da geração de regex


partial class Program
{
   [global::System.CodeDom.Compiler.GeradoCodigoAttribute("System.Text.RegularExpressions.Gerador", "8.0.14.7010")]
   private static partial global::System.Text.RegularExpressions.Regex AbcOuDefGeradoRegex()
       => global::System.Text.RegularExpressions.Gerado.AbcOuDefGeradoRegex_0.Instancia;
}
       
  1. Elementos do paradigma 'partial'

1. Atributos

Atributos são usados para marcar locais onde o código será gerado automaticamente.

Eles devem ser nomeados de forma consistente com o gerador de código correspondente.

Pode transmitir parâmetros para o gerador, que os utiliza para ajustar a saída.

2. Filtros

Interface ISyntaxFilter é usada para filtrar nós sintáticos.


interface ISyntaxFilter
{
   bool Correspondencia(NóSintático nó, CancellationToken cancelamento);
}
class FiltroSintático(bool éParcial, params SyntaxKind[] tipos)
   : ISyntaxFilter;
       

3. Fonte do Gerador

IGeneratorSource define como o código será gerado e nomeado.


interface IGeneratorSource
{
   string NomeArquivoGerado { get; }
   GeradorSintático Gerar();
}
       

4. Transformação

Transformação converte contexto de atributos em fonte de geração.


interface IGeneratorTransform<tsource>
{
   TSource? Transformar(ContextoSyntaxGeneratorAtributo contexto, CancellationToken cancelamento);
}
       </tsource>

5. Executor

IGeneratorExecutor é responsável por executar a geração de código.


interface IGeneratorExecutor<tsource>
{
   void Executar(ContextoProduçãoFonte contexto, TSource fonte);
}
       </tsource>

6. Gerador de Valores

ValuesGenerator ajuda a desenvolver geradores de código simplificando a lógica de negócio.


class GeradorValores<tsource>(
   string nomeAtributo, 
   ISyntaxFilter filtro, 
   ISyntaxTransform<tsource> transformação, 
   ISyntaxExecutor<tsource> executor);
       </tsource></tsource></tsource>
  1. Implementando um gerador com ValuesGenerator

1. Código do gerador SaudacaoGenerator


class SaudacaoGenerator()
   : ValuesGenerator<saudacaosource>(
   "GenerateCoreTests.Saudacao.SaudacaoGeneratorAttribute",
   new FiltroSintático(true, SyntaxKind.DeclaraçãoClasse),
   new SaudacaoTransformação(),
   new ExecutorGerador<saudacaosource>())
{
}
       </saudacaosource></saudacaosource>

2. Atributo SaudacaoGeneratorAttribute


[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
class SaudacaoGeneratorAttribute : Attribute
{
}
       

3. Transformação SaudacaoTransformação


class SaudacaoTransformação : IGeneratorTransform<saudacaosource>
{
   public SaudacaoSource? Transformar(ContextoSyntaxGeneratorAtributo contexto, CancellationToken cancelamento)
   {
       if (contexto.NóAlvo é DeclaraçãoClasseSintática tipo e contexto.SímboloAlvo é INamedTypeSymbol símbolo)
           return new(tipo, símbolo);
       return null;
   }
}
       </saudacaosource>

4. Fonte de geração SaudacaoSource


class SaudacaoSource(DeclaraçãoClasseSintática tipo, INamedTypeSymbol símbolo)
   : IGeneratorSource
{
   private readonly DeclaraçãoClasseSintática _tipo = tipo;
   private readonly INamedTypeSymbol _símbolo = símbolo;

   public string NomeArquivoGerado
       => $"{_símbolo.ToDisplayString()}.Saudacao.g.cs";

   public GeradorSintático Gerar()
   {
       var construtor = GeradorSintático.Clone(_tipo);
       var método = MétodoGerado();
       construtor.AdicionarMembro(método);
       return construtor;
   }

   private static MethodDeclarationSyntax MétodoGerado()
   {
       var nome = SyntaxFactory.NomeIdentificador("nome");
       var expressão = GeradorSintático.Interpolação()
           .Adicionar("Saudação: '")
           .Adicionar(nome)
           .Adicionar("'")
           .Construir();

       return GeradorSintático.TipoVazio.Método("DigaoOi", GeradorSintático.TipoString.Parâmetro(nome.Identificador))
           .Público()
           .Estático()
           .ParaConstrutor()
           .Adicionar(SyntaxFactory.NomeIdentificador("Console").Acesso("WriteLine").Invocação([expressão]))
           .Terminar();
   }
}
       

5. Código de teste


namespace GenerateCoreTests.Saudacao;

[SaudacaoGenerator]
public partial class SaudacaoTestes;
       

6. Resultado da geração


// <auto-generated></auto-generated>
namespace GenerateCoreTests.Saudacao;
partial class SaudacaoTestes
{
   public static void DigaoOi(string nome)
   {
       Console.WriteLine($"Saudação: '{nome}'");
   }
}
       
  1. Recursos adicionais do GenerateCore

1. Aálise de símbolos de compilação

Use SymbolTypeDescriptor e SymbolTypeBuilder para analisar código existente.

2. Ferramentas para atributos

SymbolAttributeHelper ajuda a extrair e validar atributos de código.

  1. Testando SourceGenerator

1. Usando Debugger.Launch

Inclua Debugger.Launch() para testar interativamente no VS.

2. Testando com Microsoft.CodeAnalysis.Analyzer.Testing

Esta biblioteca permite testes unitários, mas pode ser complexa.

3. Testando com SyntaxScripting

SyntaxScripting é uma ferramenta simples para testes de geração de código.


var código = "partial class Saudação;";
var resultado = SyntaxTreeScript.Criar()
   .Usando("System")
   .Gerar<saudacaogenerator>(código)
   .ObterResultado();
var árvore = resultado.ArvoreGerada.FirstOrDefault();
Assert.NotNull(árvore);
var diagnósticos = resultado.Diagnósticos;
Assert.Empty(diagnósticos);
       </saudacaogenerator>
  1. Conclusão

O paradigma 'partial' (com GenerateCore e EasySyntax) simplifica o desenvolvimento de SourceGenerator.

Ele permite separar a lógica de geração em componentes independentes, melhorando a legibilidade e testabilidade.

O SyntaxScripting é uma excelente opção para testar e executar geradores de código.

Tags: C# SourceGenerator Classes Parciais Geração de Código Testes

Publicado em 10-2 00:18