How to use and tweak Staticcheck (2024)

By Dominik Honnef, author of Staticcheck.

Staticcheck is a state of the art linter for the Go programming language. Using staticanalysis, it finds bugs and performance issues, offerssimplifications, and enforces style rules.

Its checks have been designed to be fast, precise and useful. When Staticcheck flags code, you can be sure that it isn’twasting your time with unactionable warnings. While checks have been designed to be useful out of the box, they stillprovide configuration where necessary, to fine-tune to your needs, without overwhelming you with hundreds of options.

Staticcheck can be used from the command line, in continuous integration (CI), and even directly from youreditor.

Staticcheck is open source and offered completely free of charge. Sponsors guarantee its continued development. Theplay-with-go.dev project is proud to sponsor the Staticcheck project. If you, your employer or your company useStaticcheck please consider sponsoring the project.

This guide gets you up and running with Staticcheck by analysing the pets module.

Prerequisites

You should already have completed:

  • Go fundamentals

This guide is running using:

$ go versiongo version go1.19.1 linux/amd64

Installing Staticcheck

In this guide you will install Staticcheck to your PATH. For details on how to add development tools as a projectmodule dependency, please see the “Developer tools as module dependencies” guide.

Use go get to install Staticcheck:

$ go install honnef.co/go/tools/cmd/staticcheck@v0.3.3go: downloading honnef.co/go/tools v0.3.3go: downloading golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501fgo: downloading golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936ego: downloading golang.org/x/sys v0.0.0-20211019181941-9d821ace8654go: downloading github.com/BurntSushi/toml v0.4.1go: downloading golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4

Note: so that this guide remains reproducible we have spcified an explicit version, v0.3.3.When running yourself you could use the special version latest.

The rather ugly use of a temporary directory ensures that go get is run outside of a module. See the“Setting up your PATH section in Installing Go to ensure your PATH is set correctly.

Check that staticcheck is on your PATH:

$ which staticcheck/home/gopher/go/bin/staticcheck

Run staticcheck as a quick check:

$ staticcheck -versionstaticcheck 2022.1.3 (v0.3.3)

You’re all set!

Create the pets module

Time to create an initial version of the pets module:

$ mkdir /home/gopher/pets$ cd /home/gopher/pets$ go mod init petsgo: creating new go.mod: module pets

Because you are not going to publish this module (or import the pets package; it’s just a toyexample), you do not need to initialise this directory as a git repository and can give the module whatever path youlike. Here, simply pets.

Create an inital version of the pets package in pets.go:

package petsimport ("errors""fmt")type Animal intconst (Dog Animal = iotaSnake)type Pet struct {Kind AnimalName string}func (p Pet) Walk() error {switch p.Kind {case Dog:fmt.Printf("Will take %v for a walk around the block\n")default:return errors.New(fmt.Sprintf("Cannot take %v for a walk", p.Name))}return nil}func (self Pet) String() string {return fmt.Sprintf("%s", self.Name)}

This code looks sensible enough. Build it to confirm there are no compile errors:

$ go build

All good. Or is it? Let’s run Staticcheck to see what it thinks.

Staticcheck can be run on code in several ways, mimicking the way the official Go tools work. At its core, it expects tobe run on well-formed Go packages. So let’s run it on the current package, the pets package:

$ staticcheck .pets.go:23:14: Printf format %v reads arg #1, but call has only 0 args (SA5009)pets.go:25:10: should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...)) (S1028)pets.go:30:7: receiver name should be a reflection of its identity; don't use generic names such as "this" or "self" (ST1006)pets.go:31:9: the argument is already a string, there's no need to use fmt.Sprintf (S1025)

Oh dear, Staticcheck has found some issues!

As you can see from the output, Staticcheck reports errors much like the Go compiler. Each line represents a problem,starting with a file position, then a description of the problem, with the Staticcheck check number in parentheses at theend of the line.

Staticcheck checks fall into different categories, with each category identified by a different code prefix. Some arelisted below:

  • Code simplification S1???
  • Correctness issues SA5???
  • Stylistic issues ST1???

The Staticcheck website lists and documents all the categories and checks. Many ofthe checks even have examples. You can also use the -explain flag to get details at the commandline:

$ staticcheck -explain SA5009Invalid Printf callAvailable since 2019.2Online documentation https://staticcheck.io/docs/checks#SA5009

Let’s consider one of the problems reported, ST1006, documented as “Poorlychosen receiver name”. The Staticcheck check documentation quotes from the Go Code Review Commentswiki:

The name of a method’s receiver should be a reflection of itsidentity; often a one or two letter abbreviation of its typesuffices (such as “c” or “cl” for “Client”). Don’t use genericnames such as “me”, “this” or “self”, identifiers typical ofobject-oriented languages that place more emphasis on methods asopposed to functions. The name need not be as descriptive as thatof a method argument, as its role is obvious and serves nodocumentary purpose. It can be very short as it will appear onalmost every line of every method of the type; familiarity admitsbrevity. Be consistent, too: if you call the receiver “c” in onemethod, don’t call it “cl” in another.

Each error message explains the problem, but also indicates how to fix the problem. Let’s fix up pets.go:

package petsimport ("fmt")type Animal intconst (Dog Animal = iotaSnake)type Pet struct {Kind AnimalName string}func (p Pet) Walk() error {switch p.Kind {case Dog:fmt.Printf("Will take %v for a walk around the block\n", p.Name)default:return fmt.Errorf("cannot take %v for a walk", p.Name)}return nil}func (p Pet) String() string {return p.Name}

And re-run Staticcheck to confirm:

$ staticcheck .

Excellent, much better.

Configuring Staticcheck

Staticcheck works out of the box with some sensible, battle-tested defaults. However, various aspects of Staticcheck canbe customized with configuration files.

Whilst fixing up the problems Staticcheck reported, you notice that the pets package is missing a packagecomment. You also happened to notice on the Staticcheck website that checkST1000 covers exactly thiscase, but that it is not enabled by default.

Staticcheck configuration files are named staticcheck.conf and containTOML.

Let’s create a Staticcheck configuration file to enable check ST1000, inheriting from theStaticcheck defaults:

checks = ["inherit", "ST1000"]

Re-run Staticcheck to verify ST1000 is reported:

$ staticcheck .pets.go:1:1: at least one file in a package should have a package comment (ST1000)

Excellent. Add a package comment to pets.go to fix the problem:

// Package pets contains useful functionality for pet ownerspackage petsimport ("fmt")type Animal intconst (Dog Animal = iotaSnake)type Pet struct {Kind AnimalName string}func (p Pet) Walk() error {switch p.Kind {case Dog:fmt.Printf("Will take %v for a walk around the block\n", p.Name)default:return fmt.Errorf("cannot take %v for a walk", p.Name)}return nil}func (p Pet) String() string {return p.Name}

Re-run Staticcheck to confirm there are no further problems:

$ staticcheck .

Ignoring problems

Before going much further, you decide it’s probably a good idea to be able to feed a pet, and so make the followingchange to pets.go:

// Package pets contains useful functionality for pet ownerspackage petsimport ("fmt")type Animal intconst (Dog Animal = iotaSnake)type Pet struct {Kind AnimalName string}func (p Pet) Walk() error {switch p.Kind {case Dog:fmt.Printf("Will take %v for a walk around the block\n", p.Name)default:return fmt.Errorf("cannot take %v for a walk", p.Name)}return nil}func (p Pet) Feed(food string) {food = foodfmt.Printf("Feeding %v some %v\n", p.Name, food)}func (p Pet) String() string {return p.Name}

Re-run Staticcheck to verify all is still fine:

$ staticcheck .pets.go:31:2: self-assignment of food to food (SA4018)

Oops, that was careless. Whilst it’s clear how you would fix this problem (and you really should!), is it possible totell Staticcheck to ignore problems of this kind?

In general, you shouldn’t have to ignore problems reported by Staticcheck. Great care is taken to minimize the number offalse positives and subjective suggestions. Dubious code should be rewritten and genuine false positives should bereported so that they can be fixed.

The reality of things, however, is that not all corner cases can be taken into consideration. Sometimes code just has tolook weird enough to confuse tools, and sometimes suggestions, though well-meant, just aren’t applicable. For those rarecases, there are several ways of ignoring unwanted problems.

This is not a rare or corner case, but let’s use it as an opportunity to demonstrate linter directives.

The most fine-grained way of ignoring reported problems is to annotate the offending lines of code with linter directives. Let’signore SA4018 using a line directive, updating pets.go:

// Package pets contains useful functionality for pet ownerspackage petsimport ("fmt")type Animal intconst (Dog Animal = iotaSnake)type Pet struct {Kind AnimalName string}func (p Pet) Walk() error {switch p.Kind {case Dog:fmt.Printf("Will take %v for a walk around the block\n", p.Name)default:return fmt.Errorf("cannot take %v for a walk", p.Name)}return nil}func (p Pet) Feed(food string) {//lint:ignore SA4018 trying out line-based linter directivesfood = foodfmt.Printf("Feeding %v some %v\n", p.Name, food)}func (p Pet) String() string {return p.Name}

Verify that Staticcheck no longer complains:

$ staticcheck .

In some cases, however, you may want to disable checks for an entire file. For example, code generation may leave behinda lot of unused code, as it simplifies the generation process. Instead of manually annotating every instance of unusedcode, the code generator can inject a single, file-wide ignore directive to ignore the problem.

Let’s change the line-based linter directive to a file-based one in pets.go:

// Package pets contains useful functionality for pet ownerspackage petsimport ("fmt")//lint:file-ignore SA4018 trying out file-based linter directivestype Animal intconst (Dog Animal = iotaSnake)type Pet struct {Kind AnimalName string}func (p Pet) Walk() error {switch p.Kind {case Dog:fmt.Printf("Will take %v for a walk around the block\n", p.Name)default:return fmt.Errorf("cannot take %v for a walk", p.Name)}return nil}func (p Pet) Feed(food string) {food = foodfmt.Printf("Feeding %v some %v\n", p.Name, food)}func (p Pet) String() string {return p.Name}

Verify that Staticcheck continues to ignore this check:

$ staticcheck .

Great. That’s both line and file-based linter directives covered, demonstrating how to ignore certain problems.

Finally, let’s remove the linter directive, and fix up your code:

// Package pets contains useful functionality for pet ownerspackage petsimport ("fmt")type Animal intconst (Dog Animal = iotaSnake)type Pet struct {Kind AnimalName string}func (p Pet) Walk() error {switch p.Kind {case Dog:fmt.Printf("Will take %v for a walk around the block\n", p.Name)default:return fmt.Errorf("cannot take %v for a walk", p.Name)}return nil}func (p Pet) Feed(food string) {fmt.Printf("Feeding %v some %v\n", p.Name, food)}func (p Pet) String() string {return p.Name}

And check that Staticcheck is happy one last time:

$ staticcheck .

We can now be sure of lots of happy pets!

Conclusion

This guide has provided you with an introduction to Staticcheck, and the power of static analysis. To learn more see:

  • the “Developer tools as module dependencies” guide guide to see how to add toolslike Staticcheck to a project.
  • the Staticcheck documentation for more details about Staticcheck itself.

As a next step you might like to consider:

  • Developer tools as module dependencies
  • Working with private modules
  • Installing Go
How to use and tweak Staticcheck (2024)

References

Top Articles
Latest Posts
Article information

Author: Foster Heidenreich CPA

Last Updated:

Views: 5716

Rating: 4.6 / 5 (76 voted)

Reviews: 91% of readers found this page helpful

Author information

Name: Foster Heidenreich CPA

Birthday: 1995-01-14

Address: 55021 Usha Garden, North Larisa, DE 19209

Phone: +6812240846623

Job: Corporate Healthcare Strategist

Hobby: Singing, Listening to music, Rafting, LARPing, Gardening, Quilting, Rappelling

Introduction: My name is Foster Heidenreich CPA, I am a delightful, quaint, glorious, quaint, faithful, enchanting, fine person who loves writing and wants to share my knowledge and understanding with you.