Introdução
Analisadores de código estático são ferramentas usadas para analisar o código de software sem executá-lo. Eles podem examinar o código para encontrar code smells, vulnerabilidades, erros potenciais e código fora de um padrão definido, por exemplo. Eles funcionam analisando o código-fonte e avaliando sua sintaxe (estrutura do código) e semântica (significado do código).
Roslyn, o compilador C#, fornece ferramentas para desenvolver Roslyn Analyzers (Analisadores de código estático para Roslyn), dando acesso à sintaxe e semântica do código, que podem ser executados em tempo de desenvolvimento e build, fornecendo feedback quase em tempo real para os desenvolvedores.
Neste post, mostrarei um Analisador Roslyn que construí para usar Uniões Discriminadas em C#, exigindo verificar o tipo de união antes do acesso, e falarei sobre algumas lições aprendidas ao desenvolvê-lo.
Pacote DiscriminatedUnions.Net
O pacote permite o uso de Uniões Discriminadas, aplicando duas regras:
- O tipo de uma União deve ser verificado antes do acesso;
- Todos os tipos de uma União devem ser verificados (ou ter um caso
else
/default
/discard
).
Para usar o pacote, instale-o no projeto:
<PackageReference Include="DiscriminatedUnions.Net" Version="1.0.0.19" />
Estenda UnionValue
ao declarar os tipos que serão usados na União Discriminada:
|
|
Declare o membro Union
passando os tipos de valor possíveis:
Union<Dog, Bird> animal = new Dog();
E acesse-o verificando o tipo com If
, Switch/Case
ou Pattern Matching
:
|
|
Acessar o objeto sem verificar gerará um erro DUN002 - 'animal.Value' not checked before access
:
Não verificar todos os tipos possíveis gerará um erro DUN001 - 'animal' not being evaluated for all possible types
:
⚠️ Este pacote foi feito apenas para fins de estudo. Sinta-se à vontade para testá-lo, mas não o use para código de produção, pois ele não será mantido e pode não cobrir todos os casos extremos.
Código fonte do DiscriminatedUnions.NET
https://github.com/dgenezini/DiscriminatedUnions.NET
Lições aprendidas
Pacotes NuGet somente para tempo de desenvolvimento
Os analisadores Roslyn podem ser distribuídos como pacotes NuGet, mas é importante gerar o pacote como uma Dependência de Desenvolvimento apenas, definindo a propriedade DevelopmentDependency
como true
no arquivo csproj
:
<DevelopmentDependency>true</DevelopmentDependency>
Isso gerará duas tags nas propriedades do pacote NuGet dos projetos consumidores:
|
|
PrivateAssets
com o valorall
, indicando que este pacote não fluirá para projetos dependendo do projeto consumidor. Por exemplo, em um cenário ondeProject1
depende do pacoteDiscriminatedUnions.Net.Analyzers
eProject2
depende deProject1
, o analisador não estará disponível paraProject2
;IncludeAssets
sem o valorcompile
em seu valor, indicando que o consumidor do pacote não terá acesso às suas assemblies compiladas.
Os analisadores Roslyn podem ser usados para aplicar regras sobre como usar um pacote
Um ótimo caso de uso para Roslyn Analyzers é aplicar regras em pacotes NuGet.
O pacote DistributedUnion.Net é um exemplo. Ele tem os tipos Union
e UnionValue
e analisadores para aplicar as regras em seu uso.
Como as classes dentro dos pacotes Analyzers não são acessíveis pelos consumidores (e não deveriam ser), a maneira mais correta, na minha opinião, é ter as classes públicas em um pacote e este pacote consumirá outro pacote com os analisadores, mas removendo a configuração PrivateAssets
. Desta forma, os analisadores estarão disponíveis para os consumidores do pacote pai:
|
|
Os analisadores têm que evoluir junto com a linguagem
Como os analisadores usam a sintaxe do código, à medida que novas maneiras de fazer algo são adicionadas à linguagem, os analisadores têm que ser atualizados para tratá-los também. Por exemplo:
- Um analisador verificando as condições antes da correspondência de padrões ser lançado, precisaria ser atualizado para considerar esta nova sintaxe;
- Um analisador usando tipos para suas regras, precisaria ser atualizado para considerar tipos de referência anuláveis em sua lógica.
TDD é seu amigo
O modelo de projeto Roslyn Analyzers vem com um projeto VSIX que pode ser executado para testar os analisadores, mas não consegui fazê-los funcionar e decidi que não valia a pena o tempo.
Acontece que a melhor e mais fácil maneira de executar e experimentar o analisador é executando testes automatizados. Crie o teste, execute o teste e altere o analisador até que o teste passe.
Exemplo de um teste automatizado que espera o código de erro DUN001
no local 0
com o argumento unionBird
:
|
|
O local e o argumento são marcados com a sintaxe {|#location:argument|}
.
ℹ️ O modelo de projeto vem com alguns testes básicos como exemplos que são fáceis de alterar.
Casts… Casts em todos os lugares
Os métodos e propriedades dos analisadores definem tipos de interface gerais que precisam ser convertidos para o tipo específico antes de acessar suas propriedades.
Este é um trecho do pacote DiscriminatedUnions.Net:
|
|
Entendendo a árvore de sintaxe e encontrando as interfaces corretas
A maneira mais fácil de entender a árvore de sintaxe é usando o Syntax Visualizer do Visual Studio (Instruções de instalação).
Basta clicar em qualquer lugar no código e ele mostrará a árvore de sintaxe até aquele ponto:
Outra dica é digitar ISymbol
, IOperation
ou qualquer interface base para ver todas as interfaces específicas no intellisense:
Pouco material para ajudar os iniciantes
Como os analisadores Roslyn têm casos de uso muito específicos, não há muito material e documentação online. Aqui estão alguns links que me ajudaram a aprender (especialmente o Blog de Josh Varty e Meziantou):
Blog de Josh Varty - Série Learn Roslyn Now
Blog de Meziantou - Série Writing a Roslyn analyzer
Documentos Roslyn - Como começar
Documentos Roslyn GitHub - Como escrever um analisador C# e correção de código