Skip to main content

Corso linters

Corso uses the golangci-lint GitHub action to run linters on every PR to main. This helps reduce the cognitive load on reviewers and can lead to better reviewer comments as they don’t get caught up with formatting issues.

Installing golangci-lint locally

You can install a local version of the linter Corso uses on any platform and there's also a docker container available. Instructions for installation are available on the golangci-lint website. The version that you install should match the version the GitHub workflow uses to avoid failures even after running locally. The current version in use can be found in .github/workflows/lint.yaml.

Running the linter

You can run the linter manually or with the Makefile in the repository. Running with the Makefile will also ensure you have the proper version of golangci-lint installed.

Running the actions linter

Installation:

go install github.com/rhysd/actionlint/cmd/actionlint@latest

Instructions for running locally.

Running with the Makefile

There’s a Makefile in the corso/src that will automatically check if the proper golangci-lint version is installed and run it. This make rule can be run with make lint. If golangci-lint isn't installed locally or the wrong version is present it will tell you what version it expects along with a link to the installation page.

Running manually

You can run golangci-lint manually by executing golangci-lint run in the corso/src directory. This will automatically use corso's .golangci.yml configuration.

Adding exceptions for lint errors

Sometimes the linter will report an issue but it's not something that you can or should fix. In those cases there are two ways to add a linter exception:

Single exception via comment

Adding a comment on the line before (or sometimes the offending line) with the form //nolint:<linter-name> will ignore a single error. <linter-name> must be replaced with the name of the linter that produced the report. Note there’s no space between the // and nolint. Having a space between the two may result in the linter still reporting that line.

Global exception

The golangci.yml file has a list of issues that are ignored in the whole project. These should be as targeted as possible to avoid silencing other lint errors that aren't related to the one in question. The golangci-lint issues configuration page has some information on this, but it's also useful to look at existing exceptions in the repository under the issues section.

The configuration file allows for regex in the text property, so it’s useful to include the linter/rule that triggered the message. This ensures the lint error is only ignored for that linter. Combining the linter/rule with the error message text specific to that error also helps minimize collisions with other lint errors.

Working with the linters

Some of the enabled linters, like wsl, are picky about how code is arranged. This section provides some tips on how to organize code to reduce lint errors.

wsl

wsl is a linter that requires blank lines in parts of the code. It helps make the codebase uniform and ensures the code doesn't feel too compact.

Short-assignments versus var declarations

Go allows declaring and assigning to a variable with either short-assignments (x := 42) or var assignments (var x = 42). wsl doesn't allow grouping these two types of variable declarations together. To work around this, you can convert all your declarations to one type or the other. Converting to short-assignments only works if the types in question have accessible and suitable default values.

For example, the mixed set of declarations and assignments:

var err error
x := 42

should be changed to the following because using a short-assignment for type error is cumbersome.

var (
err error
x = 42
)

Post-increment and assignments

wsl doesn't allow statements before an assignment without a blank line separating the two. Post-increment operators (e.x. x++) count as statements instead of assignments and may cause wsl to report an error. You can avoid this by moving the post-increment operator to be after the assignment instead of before it if the assignment doesn't depend on the increment operation.

For example, the snippet:

x++
found = true

should be converted to:

found = true
x++

Functions using recently assigned values

wsl allows functions immediately after assignments, but only if the function uses the assigned value. This requires an ordering for assignments and function calls.

For example, the following code

a := 7
b := 42
foo(a)

should be changed to

b := 42
a := 7
foo(a)

If both the second assignment and function call depend on the value of the first assignment then the assignments and function call must be separated by a blank line.

a := 7
b := a + 35

foo(a, b)

Function calls and checking returned error values

One of the other linters expects error checks to follow assignments to the error variable without blank lines separating the two. One the other hand, wsl has requirements about what statements can be mixed with assignments. To work around this, you should separate assignments that involve an error from other assignments. For example

a := 7
b := 42
c, err := foo()
if err != nil {
...
}

should be changed to

a := 7
b := 42

c, err := foo()
if err != nil {
...
}

Common problem linters

Some linter messages aren't clear about what the issue is. Here's common cryptic messages how you can fix the problems the linters flag. Each subsection also includes the version of golangci-lint it applies to and the linter in question.

gci Expected 's', Found 'a' at file.go

This applies to golangci-lint v1.45.2 for the gci linter and is due to an import ordering issue. It occurs because imports in the file aren't grouped according to the import rules for Corso. Corso code should have three distinct import groups: system imports, third party imports, and imports of other Corso packaged (see the example below). Typically the cause of a gci lint error is a Corso import in the block for third party libraries.

import (
"time"

"github.com/kopia/kopia"

"github.com/alcionai/corso/pkg/selector"
)