Pular para o conteúdo principal

victorstein.dev

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'