Golang: running a dockerized linter
Let’s begin this article with Wikipedia’s definition for Linter:
Lint, or a linter, is a static code analysis tool used to flag programming errors, bugs, stylistic errors and suspicious constructs.
It is always a good idea to continuously submit your source code to a Linter. With that in mind, I’ll show how we can use a dockerized one.
Meet golangci-lint
golangci-lint is a fast Go linters runner. It runs linters in parallel, uses caching, supports yaml config, has integrations with all major IDE and has dozens of linters included.
It’s often used both on local development and on CI/CD systems.
Sample project
As you can see here, there are several supported linters. Some of them are enabled by default, while others can be enabled as you whish.
In this example we’ll use:
Configuration
Following the configuration instructions, here’s our .golangci.yml:
linters:
enable:
- asciicheck
- godot
- cyclop
- gomnd
linters-settings:
cyclop:
max-complexity: 5
Some “bad” code
We have two packages: “packageone” and “packagetwo”, both violating the linters we enabled in our configuration:
packageone/packageone.go
package packageone
import "fmt"
// NonAsciiIdentifier does nothing useful
func NonAsciiIdentifier() {
你好 := 1
fmt.Println(你好)
}
It violates:
- asciicheck
- godot
packagetwo/packagetwo.go
package packagetwo
func uselessFunc() {
}
// ComplexFunction has a considerable high cyclomatic complexity
func ComplexFunction(a, b, c int) bool {
var valid bool
if a == b {
if b > 2 {
uselessFunc()
} else if a == 2 {
uselessFunc()
} else {
uselessFunc()
}
} else if b == c {
for b == c {
uselessFunc()
b++
}
} else if c == a {
for i := 0; i < 10; i++ {
uselessFunc()
}
} else {
switch a {
case 7:
uselessFunc()
case 10:
uselessFunc()
case 13:
uselessFunc()
case 14:
uselessFunc()
default:
uselessFunc()
}
}
return valid
}
It violates:
- godot
- cyclop
- gomnd
Running it
Here’s our Makefile that enables us to run the dockerized linter for a single package or for all packages at once:
SHELL := /bin/bash
.PHONY: help
## help: shows this help message
help:
@ echo "Usage: make [target]"
@ sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
.PHONY: lint
## lint: runs linter for a given directory
lint:
@ if [ -z "$(PACKAGE)" ]; then echo >&2 please set directory via variable PACKAGE; exit 2; fi
@ docker run --rm -v "`pwd`:/workspace:cached" -w "/workspace/$(PACKAGE)" golangci/golangci-lint:latest golangci-lint run
.PHONY: lint-all
## lint-all: runs linter for all packages
lint-all:
@ docker run --rm -v "`pwd`:/workspace:cached" -w "/workspace/." golangci/golangci-lint:latest golangci-lint run
Let’s run it only for package “packagetwo”:
$ make lint PACKAGE=packagetwo
packagetwo.go:7:65: Comment should end in a period (godot)
// ComplexFunction has a considerable high cyclomatic complexity
^
packagetwo.go:8:1: calculated cyclomatic complexity for function ComplexFunction is 13, max is 5 (cyclop)
func ComplexFunction(a, b, c int) bool {
^
packagetwo.go:29:8: mnd: Magic number: 7, in <case> detected (gomnd)
case 7:
^
packagetwo.go:31:8: mnd: Magic number: 10, in <case> detected (gomnd)
case 10:
^
packagetwo.go:33:8: mnd: Magic number: 13, in <case> detected (gomnd)
case 13:
^
packagetwo.go:35:8: mnd: Magic number: 14, in <case> detected (gomnd)
case 14:
^
packagetwo.go:11:10: mnd: Magic number: 2, in <condition> detected (gomnd)
if b > 2 {
^
packagetwo.go:13:18: mnd: Magic number: 2, in <condition> detected (gomnd)
} else if a == 2 {
^
make: *** [lint] Error 1
Now for all packages:
$ make lint-all
packagetwo/packagetwo.go:7:65: Comment should end in a period (godot)
// ComplexFunction has a considerable high cyclomatic complexity
^
packageone/packageone.go:5:42: Comment should end in a period (godot)
// NonAsciiIdentifier does nothing useful
^
packageone/packageone.go:7:2: identifier "你好" contain non-ASCII character: U+4F60 '你' (asciicheck)
你好 := 1
^
packagetwo/packagetwo.go:8:1: calculated cyclomatic complexity for function ComplexFunction is 13, max is 5 (cyclop)
func ComplexFunction(a, b, c int) bool {
^
packagetwo/packagetwo.go:29:8: mnd: Magic number: 7, in <case> detected (gomnd)
case 7:
^
packagetwo/packagetwo.go:31:8: mnd: Magic number: 10, in <case> detected (gomnd)
case 10:
^
packagetwo/packagetwo.go:33:8: mnd: Magic number: 13, in <case> detected (gomnd)
case 13:
^
packagetwo/packagetwo.go:35:8: mnd: Magic number: 14, in <case> detected (gomnd)
case 14:
^
packagetwo/packagetwo.go:11:10: mnd: Magic number: 2, in <condition> detected (gomnd)
if b > 2 {
^
packagetwo/packagetwo.go:13:18: mnd: Magic number: 2, in <condition> detected (gomnd)
} else if a == 2 {
^
make: *** [lint-all] Error 1
Sweet.
Download the source
Here: https://bitbucket.org/tiagoharris/golangci-lint-example