
Estamos anunciando um novo linter (analisador estático) para o Go , que também é uma caixa de proteção para prototipar suas idéias no mundo da análise estática.
O go-critic é construído em torno das seguintes observações:
- É melhor ter uma implementação "suficientemente boa" do teste do que não tê-lo
- Se a verificação for controversa, isso não significa que não possa ser útil. Marcamos como "opinativo" e derramamos
- Escrever um linter a partir do zero é geralmente mais difícil do que adicionar uma nova verificação a uma estrutura existente se a estrutura em si for fácil de entender.
Neste post, veremos o uso e a arquitetura do go-critical, algumas das verificações implementadas nele , e também descreveremos as principais etapas para adicionar nossa função de analisador a ele.
Início rápido
$ cd $GOPATH $ go get -u github.com/go-critic/go-critic/... $ ./bin/gocritic check-package strings $GOROOT/src/strings/replace.go:450:22: unslice: could simplify s[:] to s $GOROOT/src/strings/replace.go:148:2: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:156:3: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:219:3: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:370:1: paramTypeCombine: func(pattern string, value string) *singleStringReplacer could be replaced with func(pattern, value string) *singleStringReplacer $GOROOT/src/strings/replace.go:259:2: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/replace.go:264:2: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/strings.go:791:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:800:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:809:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:44:1: unnamedResult: consider to give name to results $GOROOT/src/strings/strings.go:61:1: unnamedResult: consider to give name to results $GOROOT/src/strings/export_test.go:28:3: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/export_test.go:42:1: unnamedResult: consider to give name to results
(A formatação dos avisos foi editada; os originais estão disponíveis na essência .)
O utilitário gocritic pode verificar pacotes individuais pelo caminho de importação ( check-package
) e também percorrer recursivamente todos os diretórios ( check-project
). Por exemplo, você pode verificar todo o $GOROOT
ou $GOPATH
com um único comando:
$ gocritic check-project $GOROOT/src $ gocritic check-project $GOPATH/src
Há suporte para a "lista branca" de verificações, a fim de listar explicitamente quais verificações devem ser executadas (sinalizador -enable
). Por padrão, todas as verificações que não estão marcadas com o ícone Experimental
ou VeryOpinionated
são VeryOpinionated
.
Estão previstas integrações no golangci-lint e no gometalinter .
Como tudo começou
Ao realizar a próxima revisão de código do projeto Go, ou ao auditar alguma biblioteca de terceiros, é possível observar os mesmos problemas repetidamente.
Infelizmente, não foi possível encontrar um linter que diagnosticasse essa classe de problemas.
Sua primeira etapa pode ser uma tentativa de categorizar o problema e entrar em contato com os autores do linter existente, sugerindo que eles adicionem uma nova verificação. As chances de sua proposta ser aceita são altamente dependentes do projeto e podem ser bastante baixas. Mais provavelmente, meses de expectativa se seguirão.
Mas e se a verificação for completamente ambígua e puder ser percebida por alguém como subjetiva demais ou insuficientemente precisa?
Talvez faça sentido tentar escrever esse cheque você mesmo?
go-critic
existe para se tornar um lar para testes experimentais que são mais fáceis de implementar por nós do que anexá-los a analisadores estáticos existentes. O dispositivo go-critic
minimiza a quantidade de contexto e ações necessárias para adicionar uma nova verificação - podemos dizer que você precisa adicionar apenas um arquivo (sem contar os testes).
Como funciona o crítico
Um crítico é um conjunto de regras que descrevem as propriedades da verificação e os microcontroladores que implementam a inspeção de código para conformidade com uma regra.
Um aplicativo que incorpora um linter (por exemplo, cmd / gocritic ou golangci-lint ) recebe uma lista de regras suportadas, as filtra de uma maneira específica, cria uma função de verificação para cada regra selecionada e inicia cada uma delas no pacote em estudo.
O trabalho de adicionar um novo verificador se resume a três etapas principais:
- Adicionando testes.
- A implementação da verificação em si.
- Adicionando documentação para o linter.
Examinaremos todos esses pontos usando a regra captLocal de exemplo, que requer a ausência de nomes locais começando com uma letra maiúscula.

Adicionando testes
Para adicionar dados de teste para uma nova verificação, você precisa criar um novo diretório em lint / testdata .
Cada um desses diretórios deve ter um arquivo positive_tests.go , que descreve exemplos de código nos quais a verificação deve funcionar. Para testar a ausência de falsos positivos, os testes são complementados com um código "correto", no qual a nova verificação não deve encontrar nenhum problema ( negative_tests.go ).
Exemplos:
Você pode executar testes após adicionar um novo linter.
Implementação de verificação
Crie um arquivo com o nome do verificador: lint/captLocal_checker.go
.
Por convenção, todos os arquivos de _checker
têm o sufixo _checker
.
package lint
checkerBase é um tipo que deve ser incorporado em cada verificador.
Ele fornece implementações padrão, o que permite escrever menos código em cada ponteiro.
Entre outras coisas, checkerBase inclui um ponteiro para lint.context
, que contém informações de tipo e outros metadados sobre o arquivo que está sendo verificado.
O campo upcaseNames
conterá uma tabela de nomes conhecidos, que ofereceremos para substituir pela versão strings.ToLower(name)
. Para os nomes que não estão contidos no mapa, será sugerido não usar uma letra maiúscula, mas nenhuma substituição correta será fornecida.
O estado interno é inicializado uma vez para cada instância.
O método Init()
deve ser definido apenas para os linters que precisam executar a inicialização preliminar.
func (c *captLocalChecker) Init() { c.upcaseNames = map[string]bool{ "IN": true, "OUT": true, "INOUT": true, } }
Agora você precisa definir a própria função de verificação.
No caso de captLocal
, precisamos verificar todo o ast.Ident
local que introduz novas variáveis.
Para verificar todas as definições locais de nomes, você deve implementar um método com a seguinte assinatura no seu verificador:
VisitLocalDef(name astwalk.Name, initializer ast.Expr)
A lista de interfaces de visitantes disponíveis pode ser encontrada no arquivo lint / internal / visitor.go .
captLocal
implementa LocalDefVisitor
.
Por convenção, os métodos que geram avisos geralmente são colocados em métodos separados. Existem raras exceções, mas seguir esta regra é considerado uma boa prática.
Adicionando documentação
Outro método de implementação necessário é o InitDocumentation
:
func (c *captLocalChecker) InitDocumentation(d *Documentation) { d.Summary = "Detects capitalized names for local variables" d.Before = `func f(IN int, OUT *int) (ERR error) {}` d.After = `func f(in int, out *int) (err error) {}` }
Normalmente, basta preencher 3 campos:
Summary
- uma descrição da ação de validação em uma frase.Before
- código antes da correção.- Código posterior após correção (não deve causar um aviso).
Geração de documentaçãoGerar documentação novamente não é um pré-requisito para um novo interlocutor; talvez em um futuro próximo essa etapa seja totalmente automatizada. Mas se você ainda deseja verificar a aparência do arquivo de redução de preço, use o comando make docs
. O arquivo docs/overview.md
será atualizado.
Registre um novo linter e execute testes
O toque final está registrando um novo linter:
addChecker
espera um ponteiro para o valor zero do novo ponteiro. A seguir, vem o argumento variável, permitindo que você passe zero ou mais atributos que descrevem as propriedades da implementação da regra.
attrSyntaxOnly
é um marcador opcional para linters que não usam informações de tipo em sua implementação, o que permite executá-las sem executar verificações de tipo. golangci-lint
marca esses linters com a bandeira "fast" (porque correm muito mais rápido).
attrExperimental
é um atributo atribuído a todas as novas implementações. A remoção deste atributo é possível somente após a estabilização da verificação implementada.
Agora que o novo linter está registrado por meio do addChecker, você pode executar os testes:
Mesclagem otimista (quase)
Ao considerar solicitações de recebimento, tentamos aderir à estratégia de mesclagem otimista . Isso é expresso principalmente na aceitação daqueles PRs aos quais o revisor pode ter algumas reivindicações, em particular puramente subjetivas. Imediatamente após a injeção desse patch, um PR pode seguir o revisor, que corrige essas deficiências, o autor do patch original é adicionado ao CC (cópia).
Também temos dois marcadores linter que podem ser usados para evitar bandeiras vermelhas na ausência de consenso total:
Experimental
: uma implementação pode ter uma grande quantidade de falso positivo, ser ineficaz (a origem do problema é identificada) ou "cair" em algumas situações. Você pode infundir essa implementação se a marcar com o atributo attrExperimental
. Às vezes, com a ajuda do experimental, são indicadas essas verificações que não encontraram um bom nome no primeiro commit.VeryOpinionated
: se o teste puder ter defensores e inimigos, vale a pena marcá-lo com o atributo attrVeryOpinionated
. Dessa forma, podemos evitar rejeitar idéias sobre o estilo do código que podem não corresponder ao gosto de alguns esquilos.
Experimental
é uma propriedade de implementação potencialmente temporária e corrigível. VeryOpinionated
é uma propriedade de regra mais fundamental que é independente de implementação.
É recomendável criar um tíquete [checker-request]
no github antes de enviar a implementação, mas se você já tiver enviado uma solicitação pull, poderá abrir o problema correspondente.
Para mais detalhes sobre o processo de desenvolvimento, consulte CONTRIBUTING.md .
As regras básicas estão listadas na seção principal de regras .
Palavras de despedida
Você pode participar do projeto não apenas adicionando um novo linter.
Existem muitas outras maneiras:
- Experimente-o em seus projetos ou em projetos de código aberto grandes / conhecidos e relate falsos positivos, falsos negativos e outras deficiências. Ficaríamos gratos se você também adicionar uma nota sobre o problema encontrado / corrigido na página de troféus .
- Sugira idéias para novas inspeções. É o suficiente para criar um problema no nosso rastreador.
- Adicione testes para linters existentes.
go-critic
critica seu código Go com as vozes de todos os programadores envolvidos em seu desenvolvimento. Todos podem criticar, portanto - participe!
