golangci-lint Documentation

Fast linters runner for Go

19,242 stars Go Go 1 file ยท ~5,796 tokens #ci#go#golang#golangci-lint#linter
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy golangci-lint on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name golangci-lint --restart always golangci/golangci-lint:latest

File: README.md


golangci-lint is a fast Go linters runner.

It runs linters in parallel, uses caching, supports YAML configuration,
integrates with all major IDEs, and includes over a hundred linters.

Install `golangci-lint`

Documentation

Documentation is hosted at https://golangci-lint.run.

Social Networks




Support Us

golangci-lint is a free and open-source project built by volunteers.

If you value it, consider supporting us, we appreciate it! :heart:


Badges




Contributors

This project exists thanks to all the people who contribute. How to contribute.

Sponsors

 


File: docs/src/docs/contributing/architecture.mdx


title: Architecture

import ResponsiveContainer from "components/ResponsiveContainer";

There are the following golangci-lint execution steps:

mermaid
graph LR
    init[Init]
    loadPackages[Load packages]
    runLinters[Run linters]
    postprocess[Postprocess issues]
    print[Print issues]

    init --> loadPackages --> runLinters --> postprocess --> print

Init

The configuration is loaded from file and flags by config.Loader inside PersistentPreRun (or PreRun) of the commands that require configuration.

The linter database (linterdb.Manager) is fill based on the configuration:

  • The linters ("internals" and plugins) are built by linterdb.LinterBuilder and linterdb.PluginBuilder builders.
  • The configuration is validated by linterdb.Validator.

Load Packages

Loading packages is listing all packages and their recursive dependencies for analysis.
Also, depending on the enabled linters set some parsing of the source code can be performed at this step.

Packages loading starts here:

go title=pkg/lint/load.go
func (cl *ContextLoader) Load(ctx context.Context, linters []*linter.Config) (*linter.Context, error) {
	loadMode := cl.findLoadMode(linters)
	pkgs, err := cl.loadPackages(ctx, loadMode)
	if err != nil {
		return nil, fmt.Errorf("failed to load packages: %w", err)
	}

	// ...
	ret := &linter.Context{
		// ...
	}
	return ret, nil
}

First, we find a load mode as union of load modes for all enabled linters.
We use go/packages for packages loading and use it's enum packages.Need* for load modes.
Load mode sets which data does a linter needs for execution.

A linter that works only with AST need minimum of information: only filenames and AST. There is no need for
packages dependencies or type information. AST is built during go/analysis execution to reduce memory usage.
Such AST-based linters are configured with the following code:

go title=pkg/lint/linter/config.go
func (lc *Config) WithLoadFiles() *Config {
	lc.LoadMode |= packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles
	return lc
}

If a linter uses go/analysis and needs type information, we need to extract more data by go/packages:

go title=pkg/lint/linter/config.go
func (lc *Config) WithLoadForGoAnalysis() *Config {
	lc = lc.WithLoadFiles()
	lc.LoadMode |= packages.NeedImports | packages.NeedDeps | packages.NeedExportFile | packages.NeedTypesSizes
	lc.IsSlow = true
	return lc
}

After finding a load mode, we run go/packages: the library get list of dirs (or ./... as the default value) as input
and outputs list of packages and requested information about them: filenames, type information, AST, etc.

Run Linters

First, we need to find all enabled linters. All linters are registered here:

go title=pkg/lint/lintersdb/builder_linter.go
func (b LinterBuilder) Build(cfg *config.Config) []*linter.Config {
	// ...
	return []*linter.Config{
		// ...
		linter.NewConfig(golinters.NewBodyclose()).
			WithSince("v1.18.0").
			WithLoadForGoAnalysis().
			WithPresets(linter.PresetPerformance, linter.PresetBugs).
			WithURL("https://github.com/timakin/bodyclose"),
		// ...
		linter.NewConfig(golinters.NewGovet(govetCfg)).
			WithEnabledByDefault().
			WithSince("v1.0.0").
			WithLoadForGoAnalysis().
			WithPresets(linter.PresetBugs, linter.PresetMetaLinter).
			WithAlternativeNames("vet", "vetshadow").
			WithURL("https://pkg.go.dev/cmd/vet"),
		// ...
	}
}

We filter requested in config and command-line linters in EnabledSet:

go title=pkg/lint/lintersdb/manager.go
func (m *Manager) GetEnabledLintersMap() (map[string]*linter.Config, error)

We merge enabled linters into one MetaLinter to improve execution time if we can:

go titlepkg/lint/lintersdb/manager.go
// GetOptimizedLinters returns enabled linters after optimization (merging) of multiple linters into a fewer number of linters.
// E.g. some go/analysis linters can be optimized into one metalinter for data reuse and speed up.
func (m *Manager) GetOptimizedLinters() ([]*linter.Config, error) {
	// ...
	m.combineGoAnalysisLinters(resultLintersSet)
	// ...
}

The MetaLinter just stores all merged linters inside to run them at once:

go title=pkg/golinters/goanalysis/metalinter.go
type MetaLinter struct {
	linters              []*Linter
	analyzerToLinterName map[*analysis.Analyzer]string
}

Currently, all linters except unused can be merged into this meta linter.
The unused isn't merged because it has high memory usage.

Linters execution starts in runAnalyzers.
It's the most complex part of the golangci-lint.
We use custom go/analysis runner there.
It runs as much as it can in parallel. It lazy-loads as much as it can to reduce memory usage.
Also, it sets all heavyweight data to nil as becomes unneeded to save memory.

We don't use existing multichecker because
it doesn't use caching and doesn't have some important performance optimizations.

All found by linters issues are represented with result.Issue struct:

go title=pkg/result/issue.go
type Issue struct {
	FromLinter string
	Text       string

	Severity string

	// Source lines of a code with the issue to show
	SourceLines []string

	// If we know how to fix the issue we can provide replacement lines
	Replacement *Replacement

	// Pkg is needed for proper caching of linting results
	Pkg *packages.Package `json:"-"`

	LineRange *Range `json:",omitempty"`

	Pos token.Position

	// HunkPos is used only when golangci-lint is run over a diff
	HunkPos int `json:",omitempty"`

	// If we are expecting a nolint (because this is from nolintlint), record the expected linter
	ExpectNoLint         bool
	ExpectedNoLintLinter string
}

Postprocess Issues

We have an abstraction of result.Processor to postprocess found issues:

sh
$ tree -L 1 ./pkg/result/processors/
./pkg/result/processors/
./pkg/result/processors/
โ”œโ”€โ”€ autogenerated_exclude.go
โ”œโ”€โ”€ autogenerated_exclude_test.go
โ”œโ”€โ”€ base_rule.go
โ”œโ”€โ”€ cgo.go
โ”œโ”€โ”€ diff.go
โ”œโ”€โ”€ exclude.go
โ”œโ”€โ”€ exclude_rules.go
โ”œโ”€โ”€ exclude_rules_test.go
โ”œโ”€โ”€ exclude_test.go
โ”œโ”€โ”€ filename_unadjuster.go
โ”œโ”€โ”€ fixer.go
โ”œโ”€โ”€ identifier_marker.go
โ”œโ”€โ”€ identifier_marker_test.go
โ”œโ”€โ”€ issues.go
โ”œโ”€โ”€ max_from_linter.go
โ”œโ”€โ”€ max_from_linter_test.go
โ”œโ”€โ”€ max_per_file_from_linter.go
โ”œโ”€โ”€ max_per_file_from_linter_test.go
โ”œโ”€โ”€ max_same_issues.go
โ”œโ”€โ”€ max_same_issues_test.go
โ”œโ”€โ”€ nolint.go
โ”œโ”€โ”€ nolint_test.go
โ”œโ”€โ”€ path_prefixer.go
โ”œโ”€โ”€ path_prefixer_test.go
โ”œโ”€โ”€ path_prettifier.go
โ”œโ”€โ”€ path_shortener.go
โ”œโ”€โ”€ processor.go
โ”œโ”€โ”€ processor_test.go
โ”œโ”€โ”€ severity_rules.go
โ”œโ”€โ”€ severity_rules_test.go
โ”œโ”€โ”€ skip_dirs.go
โ”œโ”€โ”€ skip_files.go
โ”œโ”€โ”€ skip_files_test.go
โ”œโ”€โ”€ sort_results.go
โ”œโ”€โ”€ sort_results_test.go
โ”œโ”€โ”€ source_code.go
โ”œโ”€โ”€ testdata
โ”œโ”€โ”€ uniq_by_line.go
โ””โ”€โ”€ uniq_by_line_test.go

The abstraction is simple:

go title=pkg/result/processors/processor.go
type Processor interface {
	Process(issues []result.Issue) ([]result.Issue, error)
	Name() string
	Finish()
}

A processor can hide issues (nolint, exclude) or change issues (path_shortener).

We have an abstraction for printing found issues.

sh
$ tree -L 1 ./pkg/printers/
./pkg/printers/
โ”œโ”€โ”€ checkstyle.go
โ”œโ”€โ”€ checkstyle_test.go
โ”œโ”€โ”€ codeclimate.go
โ”œโ”€โ”€ codeclimate_test.go
โ”œโ”€โ”€ github.go
โ”œโ”€โ”€ github_test.go
โ”œโ”€โ”€ html.go
โ”œโ”€โ”€ html_test.go
โ”œโ”€โ”€ json.go
โ”œโ”€โ”€ json_test.go
โ”œโ”€โ”€ junitxml.go
โ”œโ”€โ”€ junitxml_test.go
โ”œโ”€โ”€ printer.go
โ”œโ”€โ”€ tab.go
โ”œโ”€โ”€ tab_test.go
โ”œโ”€โ”€ teamcity.go
โ”œโ”€โ”€ teamcity_test.go
โ”œโ”€โ”€ text.go
โ””โ”€โ”€ text_test.go

Needed printer is selected by command line option --out-format.


File: docs/src/docs/contributing/debug.mdx


title: Debugging

You can see a verbose output of linter by using -v option.

bash
golangci-lint run -v

If you would like to see more detailed logs you can use the environment variable GL_DEBUG.
Its value is a list of debug tags.

The existing debug tags are documented in the following file: https://github.com/golangci/golangci-lint/blob/HEAD/pkg/logutils/logutils.go

For example:

bash
GL_DEBUG="loader,gocritic" golangci-lint run
bash
GL_DEBUG="loader,env" golangci-lint run

File: docs/src/docs/contributing/faq.mdx


title: Contributing FAQ

How to add a new open-source linter to `golangci-lint`

See there.

How to add a new private linter to `golangci-lint`

See there.

How to update an existing linter

We use Dependabot to update dependencies, including linters.
The updates happen at least automatically once a week (Sunday 11am UTC).

No pull requests to update a linter will be accepted unless you are the author of the linter and specific changes are required.

How to add a configuration option to an existing linter

Add a new field to the linter settings struct.
Document it in .golangci.next.reference.yml.
Pass it to the linter.

How to see `INFO` or `DEBUG` logs

See tutorial about debugging.


File: docs/src/docs/contributing/new-linters.mdx


title: New linters

How to write a linter

Use go/analysis and take a look at this tutorial:
it shows how to write go/analysis linter from scratch and integrate it into golangci-lint.

How to add a public linter to `golangci-lint`

You need to implement a new linter using go/analysis API.
We don't accept non go/analysis linters.

After that:

  1. Implement functional tests for the linter:
    • Add one file into directory pkg/golinters/{yourlintername}/testdata/.
  2. Add a new file pkg/golinters/{yourlintername}/{yourlintername}.go.
    Other linters implementation can help you.
  3. Add the new struct for the linter (which you've implemented in pkg/golinters/{yourlintername}/{yourlintername}.go) to the
    list of all supported linters in pkg/lint/lintersdb/builder_linter.go
    to the method LinterBuilder.Build.
    • Add WithSince("next_version"), where next_version must be replaced by the next minor version. (ex: v1.2.0 if the current version is v1.1.0)
  4. Find out what options do you need to configure for the linter.
    For example, nakedret has only 1 option: max-func-lines.
    Choose default values to not being annoying for users of golangci-lint. Add configuration options to:
  5. Take a look at the example of pull requests with new linter support.
  6. Run the tests:
    bash
    go run ./cmd/golangci-lint/ run --no-config --disable-all --enable={yourlintername} ./pkg/golinters/{yourlintername}/testdata/{yourlintername}.go

How to add a private linter to `golangci-lint`

Some people and organizations may choose to have custom-made linters run as a part of golangci-lint.
Typically, these linters can't be open-sourced or too specific.

Such linters can be added through 2 plugin systems:

  1. Module Plugin System
  2. Go Plugin System

File: docs/src/docs/contributing/quick-start.mdx


title: Contributing Quick Start

  1. Contributing workflow: about our workflow and environment setup.
  2. Architecture of golangci-lint
  3. How to add new linters
  4. Debugging golangci-lint
  5. Contributing FAQ
  6. Contributing to the website

File: docs/src/docs/contributing/website.mdx


title: Website architecture

Technology

We use Gatsby for static site generation because sites built with it are very fast.

This framework uses React and JavaScript/TypeScript.

Source Code

The website lives in docs/ directory of golangci-lint repository.

Theme

Initially the site is based on @rocketseat theme.
Later we've merged its code into src/@rocketseat because we needed too much changes
and gatsby shadowing doesn't allow shadowing gatsby-node.js or gatsby-config.js.

Left menu is configured in src/config/sidebar.yml.

Articles

Articles are located in src/docs/ in *.mdx files. MDX is Markdown
allowing to use React components.

Templating

We use templates like {.SomeField} inside our mdx files.
There templates are expanded by running make website_expand_templates in the root of the repository.
It runs script scripts/website/expand_templates/ that rewrites mdx files with replaced templates.

Hosting

We use GitHub Pages as static website hosting and CD.

GitHub deploys the website to production after merging anything to a master branch.

Local Testing

Install Node.js (v20 or newer).

Run:

sh
npm install --legacy-peer-deps
npm run start

And navigate to http://localhost:8000 after successful Gatsby build.
There is no need to restart Gatsby server almost for all changes: it supports hot reload.
Also, there is no need to refresh a webpage: hot reload updates changed content on the open page.

Website Build

To do it run:

sh
go run ./scripts/website/expand_templates/

File: docs/src/docs/contributing/workflow.mdx


title: Contributing Workflow

Contributing

By participating in this project, you agree to abide our code of conduct.

Set up your machine

golangci-lint is written in Go.

Prerequisites:

Fork and clone golangci-lint repository.

A good way of making sure everything is all right is running the following:

sh
make build
./golangci-lint run -v

Test your change

When you are satisfied with the changes, we suggest you run:

sh
$ make test

Which runs all the linters and tests.

Create or update parameters for docs

Add your new or updated parameters to .golangci.next.reference.yml so they will be shown in the docs

Submit a pull request

Push your branch to your golangci-lint fork and open a pull request against the
master branch.

Pull request checks

First, please, accept CLA - CLA assistant will make a comment on the pull request about it.

Also, we run a few checks in CI by using GitHub Actions, you can see them here.

New releases

First, see our versioning policy.

To make a new release create a tag vx.y.z. Don't forget to add zero patch version for a new minor release, e.g. v1.99.0.
A GitHub Action workflow will start building and publishing release after that.

After making a release you need to update
GitHub Action config by running:

sh
make assets/github-action-config.json

File: docs/src/docs/plugins/go-plugins.mdx


title: Go Plugin System

Private linters can be added through Go's plugin system.

For a private linter (which acts as a plugin) to work properly,
the plugin as well as the golangci-lint binary needs to be built for the same environment.

CGO_ENABLED is another requirement.

This means that golangci-lint needs to be built for whatever machine you intend to run it on
(cloning the golangci-lint repository and running a CGO_ENABLED=1 make build should do the trick for your machine).

Create a Plugin

Your linter must provide one or more golang.org/x/tools/go/analysis.Analyzer structs.

Your project should also use go.mod.

All versions of libraries that overlap golangci-lint (including replaced libraries) MUST be set to the same version as golangci-lint.
You can see the versions by running go version -m golangci-lint.

You'll also need to create a Go file like plugin/example.go.

This file MUST be in the package main, and MUST define an exposed function called New with the following signature:

go
func New(conf any) ([]*analysis.Analyzer, error) {
	// ...
}

See plugin/example.go for more info.

To build the plugin, from the root project directory, run:

bash
go build -buildmode=plugin plugin/example.go

This will create a plugin *.so file that can be copied into your project or another well known location for usage in golangci-lint.

Configure a Plugin

If you already have a linter plugin available, you can follow these steps to define its usage in a projects .golangci.yml file.

An example linter can be found at here.

If you're looking for instructions on how to configure your own custom linter, they can be found further down.

  1. If the project you want to lint does not have one already, copy the .golangci.yml to the root directory.
  2. Adjust the YAML to appropriate linters-settings.custom entries as so:
yaml title=.golangci.yml
linters-settings:
  custom:
    example:
      path: /example.so
      original-url: github.com/golangci/example-linter
      settings: # Settings are optional.
        one: Foo
        two:
          - name: Bar
        three:
          name: Bar

That is all the configuration that is required to run a custom linter in your project.

Custom linters are enabled by default, but abide by the same rules as other linters.

If the disable all option is specified either on command line or in .golang.yml files linters.disable-all: true, custom linters will be disabled;
they can be re-enabled by adding them to the linters.enable list,
or providing the enabled option on the command line, golangci-lint run -Eexample.

The configuration inside the settings field of linter have some limitations (there are NOT related to the plugin system itself):
we use Viper to handle the configuration but Viper put all the keys in lowercase, and . cannot be used inside a key.


File: docs/src/docs/plugins/module-plugins.mdx


title: Module Plugin System

An example linter can be found at here.

The Automatic Way

  • Define your building configuration into .custom-gcl.yml.
  • Run the command golangci-lint custom (or golangci-lint custom -v to have logs).
  • Define the plugin inside the linters-settings.custom section with the type module.
  • Run your custom version of golangci-lint.

Requirements:

  • Go
  • git

Configuration Example

yaml title=.custom-gcl.yml
version: {.LatestVersion}
plugins:
  # a plugin from a Go proxy
  - module: 'github.com/golangci/plugin1'
    import: 'github.com/golangci/plugin1/foo'
    version: v1.0.0

  # a plugin from local source
  - module: 'github.com/golangci/plugin2'
    path: /my/local/path/plugin2
yaml title=.golangci.yml
linters-settings:
  custom:
    foo:
      type: "module"
      settings:
        message: hello

linters:
  disable-all: true
  enable:
    - foo

The Manual Way

  • Add a blank-import of your module inside cmd/golangci-lint/plugins.go.
  • Run go mod tidy (the module containing the plugin will be imported).
  • Run make build.
  • Define the plugin inside the configuration linters-settings.custom section with the type module.
  • Run your custom version of golangci-lint.

Configuration Example

yaml title=.golangci.yml
linters-settings:
  custom:
    foo:
      type: "module"
      settings:
        message: hello

linters:
  disable-all: true
  enable:
    - foo

Reference

The configuration file can be validated with the JSON Schema: https://golangci-lint.run/jsonschema/custom-gcl.jsonschema.json

yml
{ .CustomGCLReference }