Site is currently on development mode

Using go/analysis to write a custom linter

Last updated 1 year ago by Fatih Arslan


If you ask people why they’re in love with Go, one of the answers is tooling. The reason is that it’s very easy to write tooling, especially for the Go language itself. One of the strong areas is linting. If you’re already using Go, you know and use some of the tools such as go vet, golint, staticcheck, etc..

All these tools are using under the hood go/{ast, packages, types, etc..} packages which enable us to parse and interpret any given Go code. However, there isn’t a common framework that provides an easy and performant way to analyze Go code. If you use the packages above, you have to implement most of the cruft yourself (flag parsing, efficient walking over the AST, passing context/information around, etc..).

To improve the current situation and laid out a better base for future work, the Go author’s introduced a new package: go/analysis.

The go/analysis package provides a common interface to implement checkers. A checker is an analysis that reports mistakes. The package is still in work in progress and things are changing quickly, so make sure to occasionally check for new updates.

In this blog post, we’re going to write a custom linter (a.k.a checker) using the new go/analysis package. If you haven’t used some of the tooling around parsing and checking Go source code (such as the go/parser and go/ast packages), please read first my previous blog post: The ultimate guide to writing a Go tool. This is required to understand the rest of the blog post.

Now, let’s continue writing our custom linter!

Requirements of the custom linter

First let us define the requirements for our custom linter. This is going to be a very simple. Let us call our linter: addlint. The duty of this checker is to report us the usages of integer additions:

language-go 3 + 2

As an example, suppose we have the following simple main package:

package mainimport "fmt"

func main() {
    sum := 3 + 2
    fmt.Printf("Sum: %d\n", sum)

If we run addlint on this file, it should report us the following:

language-bash $ addlint foo.go /Users/fatih/foo.go:6:9: integer addition found: '3 + 2'

It should also work on packages, just like how any other of the current Go tools are working:

language-bash $ addlint /Users/fatih/repo/foo.go:6:9: integer addition found: '3 + 2'

Implementing it the old style

Before we dive into using go/analysis, let us start implementing our custom checker by using the traditional, low-level packages such as go/parser, go/ast, etc.. We need to use these packages nevertheless, but it’ll give us a way to understand what go/analysis improves.

We need to understand first what 3 + 2 means. This is a binary expression in Go. A binary expression can be represented by the AST node type *ast.BinaryExpr. For example, a simple 3 + 2 binary expression can be written as:

language-bash expr := &ast.BinaryExpr{ X: &ast.BasicLit{ Value: "3", Kind: token.INT, }, Op: token.ADD, Y: &ast.BasicLit{ Value: "2", Kind: token.INT, }, }

Read full Article