Skip to content

sudo-suhas/operationalising-golangci-lint

Repository files navigation

Operationalising golangci-lint

Presentation slides and material for talk presented at Golang Meetup Bangalore on 31st August 2024.

Ideally the slides should have been accessible at https://go-talks.appspot.com/github.com/sudo-suhas/operationalising-golangci-lint/2024-08-golang-bangalore-meetup.slide. However, due to golang/go#58906, the functionality is currently broken.

As an alternative, the slides have been hosted via Google App Engine and can be accessed here.

Video

Operationalising golangci-lint

Configuration sheet

golangci-lint-linters-v1.57.1 sheet

This sheet helps to manage the configuration for golangci-lint. It can also help with generating the 2 configuration files.

⚠️ The configuration was last updated for version v1.57.1 of golangci-lint.

available-linters

This sheet lists the linters that were available in version v1.57.1 with the following columns:

  • Linter: The name of the linter with a link to the source or website for it. If applicable, there is also a link to the golangci-lint’s documentation for configuring the linter. For example, https://staticcheck.io/ and https://golangci-lint.run/usage/linters/#staticcheck.
  • Description: A short description of the linter.
  • Local: This field indicates whether the linter is enabled or disable in the local config file variant - .golangci.toml.
  • Prod: This field indicates whether the linter is enabled or disable in the prod config file variant - .golangci-prod.toml.
  • Comments: Self explanatory. The comments try to justify why a particular linter is enabled or disabled.
  • Settings: If the linter is enabled, the value in this field is used to configure the linter. The settings are in TOML format. These settings are hand-coded with 2 exceptions. In case of gocritic and revive, the settings are generated from their respective sheets (explained below).

Why do we need 2 variants?

In my experience, having these 2 variants works well:

  1. .golangci.toml - local development and testing
  2. .golangci-prod.toml - continuous integration (CI)

The difference is that the prod version includes some more intensive checks that may take longer to run, but are important for ensuring code quality and security. Some examples of heavy linters that we would want to enable only in CI: unused, gocognit, gocritic, gosec.

gocritic@v0.11.2

This sheet lists the checks that were available in version v0.11.2 of gocritic that was packaged into golangci-lint@v1.57.1. The sheet is similar to the available-linters sheet and is used to manage the 100+ checks provided by gocritic. The checks that are enabled along with their optional settings are used to build the complete settings for gocritic in golangci-lint’s configuration.

📒 The function BUILD_GOCRITIC_SETTINGS is defined in the Apps Script project linked to the sheet.

revive@v1.3.7

This sheet lists the checks that were available in version v1.3.7 of revive that was packaged into golangci-lint@v1.57.1. The sheet is similar to the available-linters sheet and is used to manage the 77 checks provided by revive. The checks that are enabled along with their optional settings are used to build the complete settings for revive in golangci-lint’s configuration.

📒 The function BUILD_REVIVE_SETTINGS is defined in the Apps Script project linked to the sheet.

sections

This has the static header and footer for the generation of golangci-lint configuration.

.golangci.toml, .golangci-prod.toml

This has the configuration for golangci-lint generated by stitching together the following:

  1. Header from the sections sheet.
  2. Linters enabled for 'local' / 'prod' in available-linters sheet
  3. Linter settings for enabled linters in available-linters sheet
  4. Footer from the sections sheet.

The configuration is updated in a few seconds whenever any change is made in the sheet by the Apps Script project linked to the sheet.

⚠️ When the configuration is copied from the sheet, it is wrapped in double quotes. So we need to replace "" with " and remove the quotes at the beginning and the end.

Why TOML?

YAML: probably not so great after all

Integrating golangci-lint into an existing project

When we are creating a new project, it is straightforward to integrate golangci-lint into it. However, if we are integrating golangci-lint into a pre-existing project where either the linter was not integrated or the configuration was minimal, we need a strategy to iteratively fix the large number of issues that would be reported by the linter with the new/updated configuration. Fixing all the issues in a single effort can be inhibitively expensive. golangci-lint provides a mechanism for doing so by reporting issues only for new and modified lines in the commit. See golangci-lint FAQ.

Project tooling

Most repositories utilise multiple tools for formatting, code/doc generation and testing. Some examples:

The version of these tools needs to be kept consistent between local and CI. Sometimes the version of these tools are different across projects and different developers might have different local versions of these tools.

We can leverage Go modules to manage the necessary tooling by doing the following:

  • Add a go.mod file under tools directory:
    go mod init github.com/netskope/spm-{{repo}}/tools
  • Add a tools.go file in the same folder with a build constraint so that it does not get included in the build:
    //go:build tools
    
    package tools
    
    import (
      _ "github.com/golangci/golangci-lint/cmd/golangci-lint"
      // add more imports as needed
    )
  • Run go mod tidy to sync the tools/go.mod with the tools that are declared in tools.go.

Using the tools/go.mod, we can build the tool binaries. Task can be used for building the binaries on demand and for managing the commonly used commands. Taskfile is a more modern replacement for Makefiles and also has features for re-building the tools based on checksum of module files. Taskfile example: Taskfile.yml

Developer workflow (with Task)

Installing task

To install task on macOS with Homebrew, following command needs to be executed:

brew install go-task

For other operating systems please refer task installation page.

Install tools locally

Users can install and cache tools locally for formatting, linter checks etc. by using the below command:

task install-tools

User can run task --list to list all configured commands for the current repository.

Formatting code

Formatting issues can be reported by github.com/mvdan/gofumpt and github.com/daixiang0/gci. We just need to run task fmt to fix all formatting issues. It is also possible to only fix the order of import statements by running task imports.

➜ task fmt
task: Task "install-gci" is up to date
task: Task "install-gofumpt" is up to date
task: [imports] .tools/gci write ./ --section standard --section default --section "Prefix(github.com/netskope,github.com/netSkope)" --skip-generated --skip-vendor
task: [fmt] .tools/gofumpt -l -w -extra .

By integrating the tools to the IDE, we can enable format on save so that files are always formatted correctly.

IDE configuration

Goland

Set up linter integration for the IDE:
  • Install the Go Linter plugin for GoLand.
  • Under Tools | Go Linter, For the "Path to golangci-lint", select the golangci-lint binary present in the .tools directory inside the project (run task install-tools if binary is not present under .tools).

Go Linter should automatically pick .golangci.toml as the configuration file. It is recommended to use this with the IDE plugin to avoid running some of the heavy linters like unused during development. task lint can be used to lint the source files against .golangci-prod.toml.

Import the file watchers settings:
  1. Open IDE preferences with + ,.
  2. Navigate to the 'File Watchers' settings: Tools | File Watchers.
  3. Click the 'Import' icon (import icon) and select the file .idea/watchers-cfg.xml. To show hidden folders in the Finder app on macOS, press + + ..

These file watchers will run gci and gofumpt for updating import lines and formatting the code idiomatically on saving the file.

To avoid conflicts with some inbuilt formatting features, do the following:
  • Under Code Style | Go:
  • Select the "Imports" tab and set "Sorting type" to "None".
  • Under the "Other" tab:
  • Deselect "On Reformat Code action"
  • Select "Add a leading space to comments". under "Except for comments starting with:" add nolint and go:.
  • Under Tools | Actions on Save, deselect "Reformat Code".

False Positives

False positives are inevitable but golangci-lint provides flexible mechanisms for working around false positives. We can either use //nolint directives to ignore a specific error or we can update the .golangci-lint*.toml and add an exclude-rule. See https://golangci-lint.run/usage/false-positives/ for more details.

When adding or updating exclude-rules, remember to update both .golangci.toml and .golangci-prod.toml.

About

Slides and material for talk presented at Golang Meetup Bangalore

Topics

Resources

License

Stars

Watchers

Forks

Languages