71/100 Dias de Golang - Criando um linter básico com golang
Table of Contents
#
Criando um linter básico com golang
Um linter é uma ferramenta que analisa o código e encontra problemas de estilo, inconsistências, possíveis erros, … Ele funciona como um corretor gramatical e de estilo para o código, sem realmente executá-lo, faz isso através de uma análise estática. No Python temos o ruff
, Pylint
, Flake8
, no javascript temos o eslint
e no golang temo o golangci-lint
. E de forma bem resumida eles funcionam da seguinte forma: o linter “lê” o código, gera uma árvore AST (árvore de sintaxe abstrata) e o linter lê essa árvore aplicando regras. No golang temos Oo pacote go/ast
permite ler, analisar e manipular o código-fonte Go como uma árvore de sintaxe abstrata. Vamos criar o victorlinter
todas as funções devem começar com o nome victor
.
Vamos começar criando nosso arquivo main.go
. A única coisa diferente desse código é a chamada da função ParseFile
, ela é uma função do do pacote go/parser
, e o que ela faz é analisar o arquivo.go
e transformá-lo numa AST. Essa árvore é usada para você poder percorrer e inspecionar o código de maneira estruturada — sem precisar fazer parsing manual de strings.
package main
import (
"fmt"
"go/parser"
"go/token"
"lintervictor/rules"
)
func main() {
filename := "arquivo.go"
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
fmt.Println("Erro ao analisar arquivo:", err)
return
}
rules.VictorFuncoes(fset, node)
}
Agora o arquivo name_function.go
dentro da pasta rules
:
package rules
import (
"fmt"
"go/ast"
"go/token"
"strings"
)
func VictorFuncoes(fset *token.FileSet, node *ast.File) {
ast.Inspect(node, func(n ast.Node) bool {
fn, ok := n.(*ast.FuncDecl)
if !ok {
return true
}
nome := fn.Name.Name
if !strings.HasPrefix(nome, "victor") {
pos := fset.Position(fn.Pos())
fmt.Printf("Função com nome incorreto '%s' em %s:%d — não começa com 'victor'\n",
nome, pos.Filename, pos.Line)
}
return true
})
}
Aqui que a “mágica” acontece. A função ast.Inspect
vai percorrer todos os nós e vai aplicar a nossa função anônima.
ast.Inspect(node, func(n ast.Node) bool {
A primeira coisa que temos que validar é se o nó é uma declaração de função, caso não seja ela retorna true
e passa para o próximo nó
fn, ok := n.(*ast.FuncDecl)
Se for, aqui na nossa regra, vou pegar o nome da função:
nome := fn.Name.Name
E aqui verificamos se a função começa com victor
, caso negativo imprimimos o erro com o arquivo e a posição do “erro”.
if !strings.HasPrefix(nome, "victor") {
pos := fset.Position(fn.Pos())
fmt.Printf("Função com nome incorreto '%s' em %s:%d — não começa com 'victor'\n",
nome, pos.Filename, pos.Line)
}
Como teste podemos criar um arquivo.go
package main
func victor_soma() {}
func soma() {}
Se rodarmos o main.go
teremos a seguinte saída:
Função com nome incorreto 'soma' em arquivo.go:4 — não começa com 'victor'