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:
graph LR
init[Init]
loadPackages[Load packages]
runLinters[Run linters]
postprocess[Postprocess issues]
print[Print issues]
init --> loadPackages --> runLinters --> postprocess --> printInit
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.LinterBuilderandlinterdb.PluginBuilderbuilders. - 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:
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:
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:
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:
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:
func (m *Manager) GetEnabledLintersMap() (map[string]*linter.Config, error)We merge enabled linters into one MetaLinter to improve execution time if we can:
// 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:
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:
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:
$ 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.goThe abstraction is simple:
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).
Print Issues
We have an abstraction for printing found issues.
$ 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.goNeeded 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.
golangci-lint run -vIf 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:
GL_DEBUG="loader,gocritic" golangci-lint runGL_DEBUG="loader,env" golangci-lint runFile: 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
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:
- Implement functional tests for the linter:
- Add one file into directory
pkg/golinters/{yourlintername}/testdata/.
- Add one file into directory
- Add a new file
pkg/golinters/{yourlintername}/{yourlintername}.go.
Other linters implementation can help you. - Add the new struct for the linter (which you've implemented in
pkg/golinters/{yourlintername}/{yourlintername}.go) to the
list of all supported linters inpkg/lint/lintersdb/builder_linter.go
to the methodLinterBuilder.Build.- Add
WithSince("next_version"), wherenext_versionmust be replaced by the next minor version. (ex: v1.2.0 if the current version is v1.1.0)
- Add
- Find out what options do you need to configure for the linter.
For example,nakedrethas only 1 option:max-func-lines.
Choose default values to not being annoying for users of golangci-lint. Add configuration options to:- .golangci.next.reference.yml: the example of a configuration file.
You can also add them to .golangci.yml
if you think that this project needs not default values. - config struct:
don't forget aboutmapstructuretags for proper configuration files parsing.
- .golangci.next.reference.yml: the example of a configuration file.
- Take a look at the example of pull requests with new linter support.
- 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:
File: docs/src/docs/contributing/quick-start.mdx
title: Contributing Quick Start
- Contributing workflow: about our workflow and environment setup.
- Architecture of golangci-lint
- How to add new linters
- Debugging golangci-lint
- Contributing FAQ
- 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.
Navigation
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:
npm install --legacy-peer-deps
npm run startAnd 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:
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:
make- Go
Fork and clone golangci-lint repository.
A good way of making sure everything is all right is running the following:
make build
./golangci-lint run -vTest your change
When you are satisfied with the changes, we suggest you run:
$ make testWhich 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 themaster 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:
make assets/github-action-config.jsonFile: 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:
func New(conf any) ([]*analysis.Analyzer, error) {
// ...
}See plugin/example.go for more info.
To build the plugin, from the root project directory, run:
go build -buildmode=plugin plugin/example.goThis 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.
- If the project you want to lint does not have one already, copy the .golangci.yml to the root directory.
- Adjust the YAML to appropriate
linters-settings.customentries as so:
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: BarThat 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(orgolangci-lint custom -vto have logs). - Define the plugin inside the
linters-settings.customsection with the typemodule. - Run your custom version of golangci-lint.
Requirements:
- Go
- git
Configuration Example
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/plugin2linters-settings:
custom:
foo:
type: "module"
settings:
message: hello
linters:
disable-all: true
enable:
- fooThe 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.customsection with the typemodule. - Run your custom version of golangci-lint.
Configuration Example
linters-settings:
custom:
foo:
type: "module"
settings:
message: hello
linters:
disable-all: true
enable:
- fooReference
The configuration file can be validated with the JSON Schema: https://golangci-lint.run/jsonschema/custom-gcl.jsonschema.json
{ .CustomGCLReference }