Arquitetura
English | Português (Brasil)
ComplexityAnalysis.Analyzers é um pacote Roslyn Analyzer independente. A raiz do repositório representa a fronteira do produto: o código do analyzer fica em src/, os testes em tests/, a validação de performance em performance/ e a documentação em docs/.
O design prioriza resultados conservadores, análise limitada, comportamento determinístico e compatibilidade com hosts de compilador e IDE.
Pipeline de análise
Seção intitulada “Pipeline de análise”código-fonte C# | vsintaxe Roslyn + SemanticModel | vanálise de executable member (constructs executáveis C# suportados) | +-- configuração do analyzer +-- resolução de tamanho de entrada +-- operações básicas +-- limites de loop +-- operações BCL/LINQ conhecidas +-- chamadas seguras a métodos-fonte +-- extração de recursão direta +-- resolução de recorrências +-- métricas estruturais de fluxo de controle +-- métricas Halstead C# primitivas e derivadas | vmodelo de complexidade | +-- expressões atômicas +-- comparação de crescimento +-- composição +-- Unknown | vDiagnosticAnalyzer | +-- BIG0001 complexidade estimada +-- BIG1001 busca linear dentro de iteração +-- BIG1002 materialização dentro de iteração +-- BIG1003 ordenação dentro de iteração +-- BIG1004 chamada a método-fonte dentro de iteração +-- BIG1005 crescimento recursivo exponencial +-- BIG1006 threshold configurado excedido +-- BIG2001 threshold ciclomático excedido +-- BIG2002 threshold de maximum nesting excedido +-- BIG2003 threshold de NLOC excedido +-- BIG2004 threshold de statement count excedido +-- BIG2005 threshold de token count excedido +-- BIG2006 threshold de Parameter Count excedido +-- BIG2007 threshold de Cognitive Complexity excedido `-- BIG9000 probe de execuçãoFronteira do pacote
Seção intitulada “Fronteira do pacote”O pacote é carregado por hosts de compilador e IDE:
código-fonte da aplicação | | compilado por vcompilador Roslyn / host da IDE | | carrega vComplexityAnalysis.AnalyzersO projeto do analyzer targeteia netstandard2.0 para compatibilidade com hosts e é empacotado como asset de analyzer em:
analyzers/dotnet/cs/Ele não é uma biblioteca de runtime. Aplicações consumidoras não chamam classes do analyzer, e as dependências usadas para autoria com Roslyn permanecem privadas em vez de serem expostas transitivamente.
O SDK de build do repositório é uma preocupação separada. global.json seleciona o SDK 10.0.401 para restore, build, testes e pack do repositório, enquanto os hosts de compilador suportados são validados instalando o .nupkg produzido em projetos consumidores temporários.
Fronteira Das Ferramentas Em Nível De Projeto
Seção intitulada “Fronteira Das Ferramentas Em Nível De Projeto”ComplexityAnalysis.Tool é uma ferramenta separada do repositório para análise
em nível de projeto:
.csproj / .slnx / .sln | vComplexityAnalysis.Tool | +-- carregamento de projeto/fonte +-- modelo agregado de relatório +-- writers console e JSON +-- quality gates opt-in +-- detecção normalizada de código duplicado | vcalculadores de métricas compartilhados do analyzerA direção de dependência é de mão única: a CLI pode referenciar
ComplexityAnalysis.Analyzers para reutilizar implementações de métricas, mas
o projeto do analyzer e o pacote NuGet produzido não referenciam a CLI.
Carregamento de código em nível de projeto, detecção de duplicados, traversal
de filesystem, renderização de relatórios e exit codes de quality gate ficam,
portanto, fora de DiagnosticAnalyzer.Initialize e dos callbacks de execução
do analyzer.
A CLI suporta atualmente entradas .csproj, .slnx e .sln sem adicionar
Microsoft.CodeAnalysis.Workspaces ao pacote do analyzer. SARIF é uma futura
extensão de writer sobre o modelo agregado de relatório, não uma dependência
dos internals do analyzer.
Contratos de compatibilidade
Seção intitulada “Contratos de compatibilidade”| Contrato | Valor atual |
|---|---|
| SDK de build do repositório | .NET SDK 10.0.401 a partir de global.json. |
| Versão de linguagem C# do repositório | 12.0. |
| Target framework do analyzer | netstandard2.0, preservado para compatibilidade com hosts de compilador/IDE. |
| Baseline de API do compilador Roslyn | Microsoft.CodeAnalysis.CSharp 4.8.0, resolvendo Microsoft.CodeAnalysis.Common 4.8.0. |
| Regras de autoria de analyzer | Microsoft.CodeAnalysis.Analyzers 3.11.0. |
| Matriz de hosts SDK suportados | Builds consumidores com .NET 8, .NET 9 e .NET 10 no CI. |
| Path do analyzer no pacote | analyzers/dotnet/cs/ComplexityAnalysis.Analyzers.dll. |
| Assets runtime do pacote | Sem asset lib/ do analyzer e sem grupo de dependências transitivas de Roslyn. |
Compilar contra packages Roslyn mais novos pode introduzir referências a APIs que hosts de compilador ou IDE mais antigos não conseguem carregar. Upgrades de Roslyn são, portanto, conservadores: propostas de atualização precisam manter assets Roslyn privados, inspecionar o pacote gerado, executar testes de contrato de package/consumer e validar todos os hosts SDK suportados antes do merge.
Estrutura do repositório
Seção intitulada “Estrutura do repositório”As principais áreas do produto são:
src/ComplexityAnalysis.Analyzers/ Analysis/ Configuration/ Diagnostics/ Model/ ComplexityAnalyzer.cs
src/ComplexityAnalysis.Tool/ Cli/ Duplicates/ Project/ Reporting/
tests/performance/docs/O repositório não depende mais de projetos herdados da implementação anterior. O analyzer é desenvolvido e validado diretamente a partir da solução na raiz.
Modelo de complexidade
Seção intitulada “Modelo de complexidade”O modelo independente de Roslyn fica em:
src/ComplexityAnalysis.Analyzers/Model/Ele representa complexidade separadamente da sintaxe C#, mantendo o comportamento matemático imutável, determinístico e testável sem APIs do compilador.
O modelo suporta:
- formas constantes, polinomiais/logarítmicas, exponenciais, fatoriais e
Unknown; - formatação determinística, incluindo potências fracionárias como
O(n^1.585); - comparação de crescimento para expressões comparáveis;
- incomparabilidade conservadora para variáveis independentes;
- composição sequencial, aninhada e por branches.
Quando o analyzer não consegue comprovar um resultado seguro, o modelo preserva Unknown em vez de forçar uma classe de complexidade estimada.
Garantias e hipóteses das estimativas
Seção intitulada “Garantias e hipóteses das estimativas”Uma expressão Big-O e a natureza de sua garantia são valores separados no modelo:
| Garantia | Significado | Evidência exigida |
|---|---|---|
| Upper bound de pior caso | A expressão limita toda execução suportada sob eventuais hipóteses não probabilísticas registradas. | Uma expressão conhecida; hipóteses são opcionais e devem registrar custos condicionais de operação ou runtime. |
| Esperado | A expressão é uma expectativa, não um limite para toda execução. | Uma expressão conhecida e uma hipótese explícita sobre distribuição de entrada ou de hash. |
| Amortizado | A expressão vale ao longo de uma sequência de operações e não precisa limitar cada operação individual. | Uma expressão conhecida e uma hipótese explícita sobre a sequência de operações. |
Unknown |
A evidência disponível não comprova uma estimativa suportada. | A expressão Unknown, sem hipóteses alegadas. |
O modelo rejeita uma estimativa esperada sem hipótese de distribuição, uma
estimativa amortizada sem hipótese de sequência de operações e qualquer
garantia conhecida associada a Unknown. As hipóteses são valores tipados e
imutáveis, portanto esse contrato é determinístico e testável sem Roslyn.
Por compatibilidade, ToBigONotation() e os diagnósticos existentes continuam
renderizando a expressão como antes. A metadata de garantia qualifica esse
texto; ela não transforma um sinal esperado ou amortizado em prova matemática
universal. Por exemplo, lookup em tabela hash é O(1) esperado somente sob
distribuição de hash adequada e hashing/igualdade de custo constante, enquanto
uma operação não suportada permanece Unknown em vez de herdar essa estimativa.
Lookups lineares em listas e ordenação O(n log n) também registram igualdade
ou comparação de custo constante como condição explícita.
Evidências estruturadas da análise
Seção intitulada “Evidências estruturadas da análise”Um fluxo interno e opt-in de análise pode acompanhar o resultado de complexidade
com um trace determinístico de evidências. Cada etapa registra a categoria e a
origem da operação, uma localização no fonte relativa ao executable member
atual, as dimensões de entrada, a estimativa qualificada e suas hipóteses, a
regra de composição e um motivo tipado de inconclusão quando a etapa é
Unknown. O recorte vertical inicial cobre limites de loops, invocações
BCL/LINQ mapeadas e chamadas propagadas para métodos-fonte.
A coleta de evidências é isolada do fluxo normal de diagnósticos, não realiza I/O ou telemetria e nunca armazena paths de arquivos. Ela é limitada por padrão a 8 níveis e 64 etapas, com máximos absolutos de 16 níveis e 256 etapas. Atingir um desses limites marca o trace como truncado sem alterar a complexidade calculada. Ordenação estável, localizações relativas e ownership por análise mantêm snapshots determinísticos sob análise concorrente. O trace é um contrato interno da análise; as mensagens de diagnóstico e os formatos públicos de relatório existentes permanecem inalterados.
Cyclomatic Complexity, Maximum Control-Flow Nesting Depth e Cognitive Complexity são métricas estruturais inteiras separadas. Métricas Halstead são uma capacidade separada de classificação de tokens de fonte, com valores primitivos e derivados. Essas métricas são calculadas a partir da sintaxe do executable member e não são combinadas com o modelo de complexidade Big-O nem entre si.
Limites teóricos
Seção intitulada “Limites teóricos”O analyzer deliberadamente não tenta classificar o tempo assintótico de execução de todo programa C# possível. Um classificador total e exato para implementações arbitrárias encontra limites de computabilidade.
Uma redução direta a partir do Halting Problem mostra essa fronteira sem depender de programas de entrada que não terminam. Dada uma máquina arbitrária M, construa um programa P(n) que simula M por no máximo n passos. Se a simulação ainda não terminou, P executa trabalho linear; se já terminou, P executa trabalho quadrático. P(n) sempre termina porque a simulação é limitada. Se M nunca termina, P permanece O(n). Se M termina após um número fixo de passos, então, para todo n suficientemente grande, P executa trabalho quadrático e é O(n^2). Um classificador exato capaz de distinguir essas classes para programas arbitrários permitiria, portanto, decidir se M termina.
O Teorema de Rice está relacionado ao contexto mais amplo de computabilidade, mas não é a justificativa direta usada aqui. Em sua forma clássica, Rice trata de propriedades não triviais e extensionais da função parcial computada por um programa. A complexidade temporal de uma implementação específica não é extensional: dois programas podem calcular exatamente a mesma função e ainda possuir tempos assintóticos de execução diferentes.
Por isso, este analyzer trabalha sobre formatos de programa explicitamente suportados e evidências semânticas comprováveis, com análise interprocedural e resolução de recorrências limitadas. Quando o modelo disponível não consegue estabelecer um resultado seguro, Unknown é o resultado esperado, e não uma classe de complexidade inferida sem evidência suficiente.
Análise com Roslyn
Seção intitulada “Análise com Roslyn”A principal camada de análise fica em:
src/ComplexityAnalysis.Analyzers/Analysis/A análise parte de uma abstração interna de executable member e avalia informações sintáticas e semânticas suportadas. O analyzer normaliza métodos ordinários, construtores, accessors de propriedades/eventos, operadores, operadores de conversão, local functions, lambdas, anonymous methods e formas expression-bodied suportadas para o mesmo pipeline quando identidade de símbolo, ownership de body e localização de diagnóstico estão disponíveis. As responsabilidades incluem extração de membro/body, resolução de tamanho de entrada, classificação de operações básicas, análise de limites de loop, mapeamento de operações conhecidas, propagação de chamadas a métodos-fonte, tratamento de recursão direta e métricas estruturais de fluxo de controle como Cyclomatic Complexity e Maximum Control-Flow Nesting Depth.
Alguns componentes representativos são:
ExecutableMemberpara identidade, body, nome de exibição e localização de diagnóstico do membro analisado;ExecutableMemberSyntaxpara fronteiras de body executável que impedem local functions, lambdas e anonymous methods aninhados de inflarem o pai lexical;MethodComplexityExtractorpara composição do método e de seu corpo;MethodAnalysisContextpara contexto semântico e dimensões de entrada;InputSizeResolverpara dimensões canônicas comon,m,k,pe variáveis posteriores;BasicOperationAnalyzerpara trabalho básico comprovado;LoopBoundAnalyzerpara limites de loop suportados;KnownOperationComplexityAnalyzerpara custos BCL/LINQ suportados.CyclomaticComplexityAnalyzerpara pontuação estrutural de complexidade de caminhos, independente do Big-O.MaximumNestingDepthAnalyzerpara pontuação de profundidade máxima de nesting de fluxo de controle, independente de Big-O e Cyclomatic Complexity.CognitiveComplexityCalculatorpara o score documentado de compreensão estrutural, independente de Big-O, Cyclomatic Complexity e Maximum Nesting Depth.HalsteadMetricsAnalyzerpara contagens primitivas C# específicas e valores Halstead derivados, independentes de Big-O e dos diagnósticos públicos de threshold.
O analyzer não exige um call graph completo da compilation ou da solution. Constructs executáveis aninhados são analisados como raízes próprias. Um membro pai não atravessa automaticamente o body de uma local function, lambda ou anonymous method, salvo quando esse executável aninhado é alcançado por um caminho de chamada suportado.
Operações BCL e LINQ conhecidas
Seção intitulada “Operações BCL e LINQ conhecidas”As operações conhecidas são resolvidas pela identidade do símbolo Roslyn, e não apenas pelo nome textual do método. Isso evita que um método definido pelo usuário chamado Contains, Where ou ToList seja interpretado acidentalmente como uma operação do framework.
A infraestrutura de operações conhecidas fica em:
src/ComplexityAnalysis.Analyzers/Analysis/KnownOperations/Operações LINQ deferred como Where e OrderBy não são contabilizadas como enumeração completa apenas por serem criadas. O custo de enumeração ou ordenação é aplicado quando o consumo suportado é comprovado, por exemplo por uma operação terminal ou foreach.
A análise limitada de sequências deferred pode acompanhar uma pipeline por um
inicializador local não alterado. Ela resolve locais e alvos de delegate por
identidade de símbolo Roslyn, rejeita escritas intermediárias e reaplica o plano
da pipeline a cada consumo comprovado. O custo do corpo do delegate só é
propagado a partir de delegates inline, delegates locais estáveis sem estado
capturado mutável ou alvos fonte/locais seguros disponíveis na mesma syntax
tree; outros alvos tornam a pipeline consumida Unknown.
Operações não suportadas ou não resolvidas permanecem Unknown.
Os mappings de coleções usam o símbolo declarador resolvido estaticamente.
Operações concretas e não virtuais de List<T>, Dictionary<TKey,TValue> e
HashSet<T> podem, portanto, carregar garantias e hipóteses documentadas,
enquanto uma operação homônima alcançada por ICollection<T>, IList<T>,
ISet<T>, IDictionary<TKey,TValue> ou uma interface somente leitura permanece
Unknown. O analyzer não especula sobre a implementação de runtime por trás do
dispatch de interface.
Chamadas interprocedurais a métodos-fonte
Seção intitulada “Chamadas interprocedurais a métodos-fonte”A análise interprocedural pode incluir o custo de um callee fonte suportado na estimativa do caller. O resultado do callee pode ser representado como um template relativo aos seus parâmetros e depois substituído usando os argumentos do caller.
Caller | vresolução da invocação | +-- operação BCL/LINQ conhecida | `-- método-fonte suportado | template/cache | substituição de argumentos | vcomplexidade do CallerO traversal é sob demanda. Um método-fonte só é analisado quando é alcançado a partir da raiz atual, a resolução de operação conhecida não se aplica, o dispatch é seguro e o budget configurado/interno permite a expansão.
O dispatch suportado inclui métodos static, private, métodos ordinários não virtuais, dispatch sealed quando o alvo de runtime pode ser comprovado e invocações diretas de local functions. Dispatch virtual/interface inseguro, dynamic dispatch, delegates, reflection, métodos externos disponíveis apenas em metadata, construtores, acesso a propriedades, acesso a eventos, operadores, conversões, lambdas e anonymous methods como callees permanecem fora do escopo interprocedural suportado.
Ciclos são detectados de forma conservadora. Recursão direta pode ser tratada pelo pipeline de recorrências; recursão mútua é detectada, mas não resolvida.
Recursão direta e resolução de recorrências
Seção intitulada “Recursão direta e resolução de recorrências”A análise de recorrências fica em:
src/ComplexityAnalysis.Analyzers/Analysis/Recursion/O pipeline separa detecção, extração da recorrência e resolução. Uma recorrência suportada exige recursão direta identificada semanticamente, evidência compatível de base case, argumento comprovadamente redutor e trabalho local conhecido.
As famílias de solver suportadas incluem:
- recorrências de soma/decremento;
- um subconjunto exponencial simples e limitado;
- formas do Master Theorem;
- um subconjunto restrito e limitado de Akra-Bazzi.
A implementação é determinística e limitada. Ela não executa resolução simbólica geral de recorrências, integração numérica geral, processos externos, acesso de rede, integração com MathNet/SymPy ou chamadas a projetos solver externos.
Formatos não suportados, base case ausente, argumentos não redutores, trabalho local desconhecido, cancelamento, inconclusão numérica e recursão mútua permanecem Unknown.
Configuração
Seção intitulada “Configuração”A configuração é lida por meio do AnalyzerConfigOptionsProvider do Roslyn; o analyzer não faz parsing manual de .editorconfig.
As opções públicas de comportamento são:
complexity_analyzers.interprocedural_analysis;complexity_analyzers.recursion_analysis;complexity_analyzers.max_call_depth;complexity_analyzers.max_methods_per_root;complexity_analyzers.maximum_complexity;complexity_analyzers.maximum_cyclomatic_complexity;complexity_analyzers.cyclomatic_complexity_mode;complexity_analyzers.maximum_nesting_depth;complexity_analyzers.maximum_method_nloc;complexity_analyzers.maximum_statement_count;complexity_analyzers.maximum_token_count;complexity_analyzers.maximum_parameters;complexity_analyzers.maximum_cognitive_complexity.
Valores específicos por syntax tree sobrescrevem valores globais para aquela árvore. Valores inválidos retornam aos defaults documentados em vez de gerar falhas do analyzer.
Veja Configuração para detalhes.
Diagnósticos
Seção intitulada “Diagnósticos”ComplexityAnalyzer expõe:
BIG0001para estimativas de complexidade por método, opt-in;BIG1001para buscas lineares suportadas dentro de iteração analisável;BIG1002para materialização suportada dentro de iteração analisável;BIG1003para ordenação consumida e suportada dentro de iteração analisável;BIG1004para chamadas suportadas a métodos-fonte com custo dependente de entrada dentro de iteração analisável;BIG1005para recursão direta suportada com crescimento exponencial resolvido;BIG1006para estimativas conhecidas e comparáveis acima de um threshold configurado;BIG2001para executable members suportados acima de um threshold configurado de Cyclomatic Complexity;BIG2002para executable members suportados acima de um threshold configurado de Maximum Control-Flow Nesting Depth;BIG2003para executable members suportados acima de um threshold configurado de NLOC;BIG2004para executable members suportados acima de um threshold configurado de statement count;BIG2005para executable members suportados acima de um threshold configurado de token count;BIG2006para executable members suportados acima de um threshold configurado de Parameter Count source-declared;BIG2007para executable members suportados acima de um threshold configurado de Cognitive Complexity;BIG9000como probe de execução opt-in.
A análise de código gerado é desabilitada e a execução concorrente do analyzer é habilitada. Métricas Halstead são, por enquanto, apenas uma capacidade interna de análise; nenhum ID de diagnóstico Halstead público nem chave de configuração é exposto porque o projeto ainda não adotou um threshold de manutenibilidade defensável para uma única métrica Halstead derivada.
Veja o Catálogo de Analyzers para o comportamento de cada regra.
Performance e validação do pacote
Seção intitulada “Performance e validação do pacote”O analyzer foi projetado para execução dentro do compilador/IDE e mantém hot paths livres de acesso de rede, execução de processos, telemetria e scans obrigatórios da solution inteira.
A validação de performance usa workloads sintéticos determinísticos e invariantes estruturais em vez de thresholds estreitos dependentes da máquina. O caminho ReportAnalyzer=true do compilador é usado para verificar o reporting de execução do analyzer.
Budgets de performance fazem parte do contrato do analyzer. O traversal de métodos-fonte usa por padrão profundidade de chamada 5 e 32 expansões não cacheadas de métodos-fonte por raiz, com hard maximums públicos de 16 e 128. A resolução de recorrências permanece limitada pelas formas suportadas e por limites numéricos fixos no solver restrito de Akra-Bazzi. Nas fronteiras de budget, o analyzer mantém os resultados afetados conservadores, normalmente Unknown.
O CI trata checks estruturais determinísticos como gates bloqueantes: traversal limitado, ownership de cache, comportamento de cancellation, exclusão de código gerado, layout do pacote e compatibilidade de consumo. Tempo decorrido e timing de analyzer reportado pelo compilador são sinais informativos de tendência, a menos que evidência repetida mostre uma regressão material. A política detalhada de regressão e os comandos locais estão documentados em performance/README.md.
A validação do contrato do pacote garante que ComplexityAnalysis.Analyzers.dll seja empacotado em analyzers/dotnet/cs/, e não em lib/, e que as dependências de autoria não se tornem dependências de runtime do consumidor.
O CI também valida o consumo do pacote com hosts SDK .NET 8, .NET 9 e .NET 10. Cada job de compatibilidade restaura e compila um consumidor temporário usando o .nupkg real, habilita BIG9000 como probe de carregamento do pacote, habilita BIG1006 em um método quadrático conhecido, rejeita falhas de carregamento do analyzer e verifica que o pacote contribui assets de analyzer em vez de assets de compile/runtime.
Por que não há dependência de Workspaces
Seção intitulada “Por que não há dependência de Workspaces”Microsoft.CodeAnalysis.Workspaces está intencionalmente ausente. O projeto não implementa CodeFixProvider, carregamento de solution, análise baseada em workspace de projeto inteiro ou outros recursos de IDE que exijam essa dependência.