From 943205d69028ff0afdbcb7921b4d990349e81c3f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sat, 3 Jun 2023 00:59:17 +0200 Subject: [PATCH 01/10] chore: move code related to plugin to a dedicated file --- docs/src/docs/contributing/new-linters.mdx | 53 +++++++----- pkg/lint/lintersdb/custom_linters.go | 98 ++++++++++++++++++++++ pkg/lint/lintersdb/manager.go | 91 -------------------- 3 files changed, 129 insertions(+), 113 deletions(-) create mode 100644 pkg/lint/lintersdb/custom_linters.go diff --git a/docs/src/docs/contributing/new-linters.mdx b/docs/src/docs/contributing/new-linters.mdx index 6bc875c8403c..5c4a8c82b566 100644 --- a/docs/src/docs/contributing/new-linters.mdx +++ b/docs/src/docs/contributing/new-linters.mdx @@ -4,8 +4,8 @@ title: New linters ## How to write a custom linter -Use `go/analysis` and take a look at [this tutorial](https://disaev.me/p/writing-useful-go-analysis-linter/): it shows how to write `go/analysis` linter -from scratch and integrate it into `golangci-lint`. +Use `go/analysis` and take a look at [this tutorial](https://disaev.me/p/writing-useful-go-analysis-linter/): +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` @@ -39,43 +39,52 @@ After that: 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 Go's plugin library. 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). ### Configure a Plugin -If you already have a linter plugin available, you can follow these steps to define it's usage in a projects -`.golangci.yml` file. An example linter can be found at [here](https://github.com/golangci/example-plugin-linter). If you're looking for -instructions on how to configure your own custom linter, they can be found further down. +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](https://github.com/golangci/example-plugin-linter). + +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](https://github.com/golangci/golangci-lint/blob/master/.golangci.yml) to the root directory. 2. Adjust the yaml to appropriate `linters-settings:custom` entries as so: - -```yaml -linters-settings: - custom: - example: - path: /example.so - description: The description of the linter - original-url: github.com/golangci/example-linter -``` + ```yaml + linters-settings: + custom: + example: + path: /example.so + description: The description of the linter + original-url: github.com/golangci/example-linter + ``` That is all the configuration that is required to run a custom linter in your project. + Custom linters are disabled by default, and are not enabled when `linters.enable-all` is specified. They can be enabled by adding them the `linters.enable` list, or providing the enabled option on the command line (`golangci-lint run -Eexample`). ### Create a Plugin Your linter must implement 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 MUST be in the package `main`, and define a -variable of name `AnalyzerPlugin`. The `AnalyzerPlugin` instance MUST implement the following interface: +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 MUST be in the package `main`, and define a variable of name `AnalyzerPlugin`. + +The `AnalyzerPlugin` instance MUST implement the following interface: ```go type AnalyzerPlugin interface { @@ -83,8 +92,8 @@ type AnalyzerPlugin interface { } ``` -The type of `AnalyzerPlugin` is not important, but is by convention `type analyzerPlugin struct {}`. See -[plugin/example.go](https://github.com/golangci/example-plugin-linter/blob/master/plugin/example.go) for more info. +The type of `AnalyzerPlugin` is not important, but is by convention `type analyzerPlugin struct {}`. +See [plugin/example.go](https://github.com/golangci/example-plugin-linter/blob/master/plugin/example.go) for more info. -To build the plugin, from the root project directory, run `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. +To build the plugin, from the root project directory, run `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. diff --git a/pkg/lint/lintersdb/custom_linters.go b/pkg/lint/lintersdb/custom_linters.go new file mode 100644 index 000000000000..fde204e5a905 --- /dev/null +++ b/pkg/lint/lintersdb/custom_linters.go @@ -0,0 +1,98 @@ +package lintersdb + +import ( + "fmt" + "path/filepath" + "plugin" + + "github.com/spf13/viper" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/pkg/config" + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/logutils" + "github.com/golangci/golangci-lint/pkg/report" +) + +type AnalyzerPlugin interface { + GetAnalyzers() []*analysis.Analyzer +} + +// WithCustomLinters loads private linters that are specified in the golangci config file. +func (m *Manager) WithCustomLinters() *Manager { + if m.log == nil { + m.log = report.NewLogWrapper(logutils.NewStderrLog(logutils.DebugKeyEmpty), &report.Data{}) + } + + if m.cfg == nil { + return m + } + + for name, settings := range m.cfg.LintersSettings.Custom { + lc, err := m.loadCustomLinterConfig(name, settings) + + if err != nil { + m.log.Errorf("Unable to load custom analyzer %s:%s, %v", name, settings.Path, err) + } else { + m.nameToLCs[name] = append(m.nameToLCs[name], lc) + } + } + + return m +} + +// loadCustomLinterConfig loads the configuration of private linters. +// Private linters are dynamically loaded from .so plugin files. +func (m *Manager) loadCustomLinterConfig(name string, settings config.CustomLinterSettings) (*linter.Config, error) { + analyzer, err := m.getAnalyzerPlugin(settings.Path) + if err != nil { + return nil, err + } + + m.log.Infof("Loaded %s: %s", settings.Path, name) + + customLinter := goanalysis.NewLinter(name, settings.Description, analyzer.GetAnalyzers(), nil). + WithLoadMode(goanalysis.LoadModeTypesInfo) + + linterConfig := linter.NewConfig(customLinter). + WithEnabledByDefault(). + WithLoadForGoAnalysis(). + WithURL(settings.OriginalURL) + + return linterConfig, nil +} + +// getAnalyzerPlugin loads a private linter as specified in the config file, +// loads the plugin from a .so file, +// and returns the 'AnalyzerPlugin' interface implemented by the private plugin. +// An error is returned if the private linter cannot be loaded +// or the linter does not implement the AnalyzerPlugin interface. +func (m *Manager) getAnalyzerPlugin(path string) (AnalyzerPlugin, error) { + if !filepath.IsAbs(path) { + // resolve non-absolute paths relative to config file's directory + configFilePath := viper.ConfigFileUsed() + absConfigFilePath, err := filepath.Abs(configFilePath) + if err != nil { + return nil, fmt.Errorf("could not get absolute representation of config file path %q: %v", configFilePath, err) + } + path = filepath.Join(filepath.Dir(absConfigFilePath), path) + } + + plug, err := plugin.Open(path) + if err != nil { + return nil, err + } + + symbol, err := plug.Lookup("AnalyzerPlugin") + if err != nil { + return nil, err + } + + analyzerPlugin, ok := symbol.(AnalyzerPlugin) + if !ok { + return nil, fmt.Errorf("plugin %s does not abide by 'AnalyzerPlugin' interface", path) + } + + return analyzerPlugin, nil +} diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 5a04fe193b13..b01d6116e460 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -1,19 +1,10 @@ package lintersdb import ( - "fmt" - "path/filepath" - "plugin" - - "github.com/spf13/viper" - "golang.org/x/tools/go/analysis" - "github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/golinters" - "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/golangci/golangci-lint/pkg/lint/linter" "github.com/golangci/golangci-lint/pkg/logutils" - "github.com/golangci/golangci-lint/pkg/report" ) type Manager struct { @@ -37,28 +28,6 @@ func NewManager(cfg *config.Config, log logutils.Log) *Manager { return m } -// WithCustomLinters loads private linters that are specified in the golangci config file. -func (m *Manager) WithCustomLinters() *Manager { - if m.log == nil { - m.log = report.NewLogWrapper(logutils.NewStderrLog(logutils.DebugKeyEmpty), &report.Data{}) - } - if m.cfg != nil { - for name, settings := range m.cfg.LintersSettings.Custom { - lc, err := m.loadCustomLinterConfig(name, settings) - - if err != nil { - m.log.Errorf("Unable to load custom analyzer %s:%s, %v", - name, - settings.Path, - err) - } else { - m.nameToLCs[name] = append(m.nameToLCs[name], lc) - } - } - } - return m -} - func (Manager) AllPresets() []string { return []string{ linter.PresetBugs, @@ -950,63 +919,3 @@ func (m Manager) GetAllLinterConfigsForPreset(p string) []*linter.Config { return ret } - -// loadCustomLinterConfig loads the configuration of private linters. -// Private linters are dynamically loaded from .so plugin files. -func (m Manager) loadCustomLinterConfig(name string, settings config.CustomLinterSettings) (*linter.Config, error) { - analyzer, err := m.getAnalyzerPlugin(settings.Path) - if err != nil { - return nil, err - } - m.log.Infof("Loaded %s: %s", settings.Path, name) - customLinter := goanalysis.NewLinter( - name, - settings.Description, - analyzer.GetAnalyzers(), - nil).WithLoadMode(goanalysis.LoadModeTypesInfo) - - linterConfig := linter.NewConfig(customLinter). - WithEnabledByDefault(). - WithLoadForGoAnalysis(). - WithURL(settings.OriginalURL) - - return linterConfig, nil -} - -type AnalyzerPlugin interface { - GetAnalyzers() []*analysis.Analyzer -} - -// getAnalyzerPlugin loads a private linter as specified in the config file, -// loads the plugin from a .so file, and returns the 'AnalyzerPlugin' interface -// implemented by the private plugin. -// An error is returned if the private linter cannot be loaded or the linter -// does not implement the AnalyzerPlugin interface. -func (m Manager) getAnalyzerPlugin(path string) (AnalyzerPlugin, error) { - if !filepath.IsAbs(path) { - // resolve non-absolute paths relative to config file's directory - configFilePath := viper.ConfigFileUsed() - absConfigFilePath, err := filepath.Abs(configFilePath) - if err != nil { - return nil, fmt.Errorf("could not get absolute representation of config file path %q: %v", configFilePath, err) - } - path = filepath.Join(filepath.Dir(absConfigFilePath), path) - } - - plug, err := plugin.Open(path) - if err != nil { - return nil, err - } - - symbol, err := plug.Lookup("AnalyzerPlugin") - if err != nil { - return nil, err - } - - analyzerPlugin, ok := symbol.(AnalyzerPlugin) - if !ok { - return nil, fmt.Errorf("plugin %s does not abide by 'AnalyzerPlugin' interface", path) - } - - return analyzerPlugin, nil -} From 63621b4f433c529bb596c9fb6954dc0bb5d64da1 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 4 Jun 2023 16:04:29 +0200 Subject: [PATCH 02/10] feat: allow to use settings for plugins --- go.mod | 2 +- netlify.toml | 6 ++--- pkg/config/linters_settings.go | 5 +++- pkg/lint/lintersdb/custom_linters.go | 39 ++++++++++++++++++++++++---- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 7049fad571e4..0d3409ac2dc4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/golangci/golangci-lint -go 1.19 +go 1.20 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 diff --git a/netlify.toml b/netlify.toml index dcd0c45f1cf1..32db72872c08 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,5 +1,5 @@ [context.production.environment] - GO_VERSION = "1.19" + GO_VERSION = "1.20" NODE_VERSION = "17" # TODO https://github.com/golangci/golangci-lint/pull/2904#issuecomment-1146870535 # NPM_FLAGS = "--legacy-peer-deps" @@ -7,7 +7,7 @@ NPM_VERSION = "8.5.5" [context.deploy-preview.environment] - GO_VERSION = "1.19" + GO_VERSION = "1.20" NODE_VERSION = "17" # TODO https://github.com/golangci/golangci-lint/pull/2904#issuecomment-1146870535 # NPM_FLAGS = "--legacy-peer-deps" @@ -15,7 +15,7 @@ NPM_VERSION = "8.5.5" [context.branch-deploy.environment] - GO_VERSION = "1.19" + GO_VERSION = "1.20" NODE_VERSION = "17" # TODO https://github.com/golangci/golangci-lint/pull/2904#issuecomment-1146870535 # NPM_FLAGS = "--legacy-peer-deps" diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index b520ea4c6eeb..c219be1b792d 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -828,6 +828,9 @@ type CustomLinterSettings struct { Path string // Description describes the purpose of the private linter. Description string - // The URL containing the source code for the private linter. + // OriginalURL The URL containing the source code for the private linter. OriginalURL string `mapstructure:"original-url"` + + // Settings plugin settings only work with linterdb.PluginConstructor symbol. + Settings any } diff --git a/pkg/lint/lintersdb/custom_linters.go b/pkg/lint/lintersdb/custom_linters.go index fde204e5a905..67e5c80b75e9 100644 --- a/pkg/lint/lintersdb/custom_linters.go +++ b/pkg/lint/lintersdb/custom_linters.go @@ -1,6 +1,7 @@ package lintersdb import ( + "errors" "fmt" "path/filepath" "plugin" @@ -45,14 +46,14 @@ func (m *Manager) WithCustomLinters() *Manager { // loadCustomLinterConfig loads the configuration of private linters. // Private linters are dynamically loaded from .so plugin files. func (m *Manager) loadCustomLinterConfig(name string, settings config.CustomLinterSettings) (*linter.Config, error) { - analyzer, err := m.getAnalyzerPlugin(settings.Path) + analyzers, err := m.getAnalyzerPlugin(settings.Path, settings.Settings) if err != nil { return nil, err } m.log.Infof("Loaded %s: %s", settings.Path, name) - customLinter := goanalysis.NewLinter(name, settings.Description, analyzer.GetAnalyzers(), nil). + customLinter := goanalysis.NewLinter(name, settings.Description, analyzers, nil). WithLoadMode(goanalysis.LoadModeTypesInfo) linterConfig := linter.NewConfig(customLinter). @@ -68,7 +69,7 @@ func (m *Manager) loadCustomLinterConfig(name string, settings config.CustomLint // and returns the 'AnalyzerPlugin' interface implemented by the private plugin. // An error is returned if the private linter cannot be loaded // or the linter does not implement the AnalyzerPlugin interface. -func (m *Manager) getAnalyzerPlugin(path string) (AnalyzerPlugin, error) { +func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Analyzer, error) { if !filepath.IsAbs(path) { // resolve non-absolute paths relative to config file's directory configFilePath := viper.ConfigFileUsed() @@ -84,6 +85,34 @@ func (m *Manager) getAnalyzerPlugin(path string) (AnalyzerPlugin, error) { return nil, err } + analyzers, errP := lookupPluginNew(plug, settings) + if errP != nil { + // fallback to the old plugin interface. + analyzers, err = lookupAnalyzerPlugin(plug) + if err != nil { + return nil, fmt.Errorf("lookup plugin %s: %w", path, errors.Join(errP, err)) + } + } + + return analyzers, nil +} + +func lookupPluginNew(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { + symbol, err := plug.Lookup("New") + if err != nil { + return nil, err + } + + // The type func cannot be used here, must be the explicit signature. + constructor, ok := symbol.(func(any) ([]*analysis.Analyzer, error)) + if !ok { + return nil, fmt.Errorf("plugin does not abide by 'New' function: %T", symbol) + } + + return constructor(settings) +} + +func lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyzer, error) { symbol, err := plug.Lookup("AnalyzerPlugin") if err != nil { return nil, err @@ -91,8 +120,8 @@ func (m *Manager) getAnalyzerPlugin(path string) (AnalyzerPlugin, error) { analyzerPlugin, ok := symbol.(AnalyzerPlugin) if !ok { - return nil, fmt.Errorf("plugin %s does not abide by 'AnalyzerPlugin' interface", path) + return nil, fmt.Errorf("plugin does not abide by 'AnalyzerPlugin' interface: %T", symbol) } - return analyzerPlugin, nil + return analyzerPlugin.GetAnalyzers(), nil } From 9129bc8b213c83661cfc08aed8b0c8b4cd81ac0f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 5 Jun 2023 13:45:17 +0200 Subject: [PATCH 03/10] feat: improve fallback --- pkg/lint/lintersdb/custom_linters.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/lint/lintersdb/custom_linters.go b/pkg/lint/lintersdb/custom_linters.go index 67e5c80b75e9..21671df434ce 100644 --- a/pkg/lint/lintersdb/custom_linters.go +++ b/pkg/lint/lintersdb/custom_linters.go @@ -85,22 +85,23 @@ func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Anal return nil, err } - analyzers, errP := lookupPluginNew(plug, settings) - if errP != nil { - // fallback to the old plugin interface. - analyzers, err = lookupAnalyzerPlugin(plug) - if err != nil { - return nil, fmt.Errorf("lookup plugin %s: %w", path, errors.Join(errP, err)) - } + analyzers, err := lookupPlugin(plug, settings) + if err != nil { + return nil, fmt.Errorf("lookup plugin %s: %w", path, err) } return analyzers, nil } -func lookupPluginNew(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { +func lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { symbol, err := plug.Lookup("New") if err != nil { - return nil, err + analyzers, errP := lookupAnalyzerPlugin(plug) + if err != nil { + return nil, errors.Join(err, errP) + } + + return analyzers, nil } // The type func cannot be used here, must be the explicit signature. From f9fc8ba866e565d6f70f18753d623a4023fa73da Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 6 Jun 2023 23:56:50 +0200 Subject: [PATCH 04/10] docs: update doc to use the New function --- docs/src/docs/contributing/new-linters.mdx | 45 ++++++++++++++-------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/docs/src/docs/contributing/new-linters.mdx b/docs/src/docs/contributing/new-linters.mdx index 5c4a8c82b566..4b53bd2b78a7 100644 --- a/docs/src/docs/contributing/new-linters.mdx +++ b/docs/src/docs/contributing/new-linters.mdx @@ -2,7 +2,7 @@ title: New linters --- -## How to write a custom linter +## How to write a linter Use `go/analysis` and take a look at [this tutorial](https://disaev.me/p/writing-useful-go-analysis-linter/): it shows how to write `go/analysis` linter from scratch and integrate it into `golangci-lint`. @@ -16,8 +16,14 @@ After that: 1. Implement functional tests for the linter: - Add one file into directory [`test/testdata`](https://github.com/golangci/golangci-lint/tree/master/test/testdata). - - Run `T=yourlintername.go make test_linters` to ensure that test fails. - - Run `go run ./cmd/golangci-lint/ run --no-config --disable-all --enable=yourlintername ./test/testdata/yourlintername.go` + - Run the test to ensure that test fails: + ```bash + T=yourlintername.go make test_linters + ``` + - Run: + ```bash + go run ./cmd/golangci-lint/ run --no-config --disable-all --enable=yourlintername ./test/testdata/yourlintername.go + ``` 2. Add a new file `pkg/golinters/{yourlintername}.go`. Look at other linters in this directory. Implement linter integration and check that test passes. @@ -33,7 +39,7 @@ After that: if you think that this project needs not default values. - [config struct](https://github.com/golangci/golangci-lint/blob/master/pkg/config/config.go) - don't forget about `mapstructure` tag for proper configuration files parsing by [pflag](https://github.com/spf13/pflag). -5. Take a look at the example of [Pull Request with new linter support](https://github.com/golangci/golangci-lint/pulls?q=is%3Apr+is%3Amerged+label%3A%22linter%3A+new%22). +5. Take a look at the example of [pull requests with new linter support](https://github.com/golangci/golangci-lint/pulls?q=is%3Apr+is%3Amerged+label%3A%22linter%3A+new%22). ## How to add a private linter to `golangci-lint` @@ -43,7 +49,9 @@ Typically, these linters can't be open-sourced or too specific. Such linters can be added through Go's plugin library. 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. +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). @@ -65,6 +73,12 @@ If you're looking for instructions on how to configure your own custom linter, t path: /example.so description: The description of the linter 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. @@ -74,26 +88,27 @@ They can be enabled by adding them the `linters.enable` list, or providing the e ### Create a Plugin -Your linter must implement one or more `golang.org/x/tools/go/analysis.Analyzer` structs. +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 MUST be in the package `main`, and define a variable of name `AnalyzerPlugin`. - -The `AnalyzerPlugin` instance MUST implement the following interface: +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 -type AnalyzerPlugin interface { - GetAnalyzers() []*analysis.Analyzer +func New(conf any) ([]*analysis.Analyzer, error) { + // ... } ``` -The type of `AnalyzerPlugin` is not important, but is by convention `type analyzerPlugin struct {}`. See [plugin/example.go](https://github.com/golangci/example-plugin-linter/blob/master/plugin/example.go) for more info. -To build the plugin, from the root project directory, run `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. +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`. From d0e8a23342b8bf9a408ff88acf44c9fda5d6b9dd Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 6 Jun 2023 23:58:17 +0200 Subject: [PATCH 05/10] feat: deprecate AnalyzerPlugin --- pkg/lint/lintersdb/custom_linters.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/lint/lintersdb/custom_linters.go b/pkg/lint/lintersdb/custom_linters.go index 21671df434ce..63790a2e2e0b 100644 --- a/pkg/lint/lintersdb/custom_linters.go +++ b/pkg/lint/lintersdb/custom_linters.go @@ -85,7 +85,7 @@ func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Anal return nil, err } - analyzers, err := lookupPlugin(plug, settings) + analyzers, err := m.lookupPlugin(plug, settings) if err != nil { return nil, fmt.Errorf("lookup plugin %s: %w", path, err) } @@ -93,10 +93,10 @@ func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Anal return analyzers, nil } -func lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { +func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { symbol, err := plug.Lookup("New") if err != nil { - analyzers, errP := lookupAnalyzerPlugin(plug) + analyzers, errP := m.lookupAnalyzerPlugin(plug) if err != nil { return nil, errors.Join(err, errP) } @@ -113,12 +113,14 @@ func lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, erro return constructor(settings) } -func lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyzer, error) { +func (m *Manager) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyzer, error) { symbol, err := plug.Lookup("AnalyzerPlugin") if err != nil { return nil, err } + m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please the new plugin signature: https://golangci-lint.run/contributing/new-linters/#create-a-plugin") + analyzerPlugin, ok := symbol.(AnalyzerPlugin) if !ok { return nil, fmt.Errorf("plugin does not abide by 'AnalyzerPlugin' interface: %T", symbol) From 16ca82225368c753e2d089cc72980e098ceea1fd Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 7 Jun 2023 00:11:37 +0200 Subject: [PATCH 06/10] docs: add settings limitations --- docs/src/docs/contributing/new-linters.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/docs/contributing/new-linters.mdx b/docs/src/docs/contributing/new-linters.mdx index 4b53bd2b78a7..b5a9900ba2b7 100644 --- a/docs/src/docs/contributing/new-linters.mdx +++ b/docs/src/docs/contributing/new-linters.mdx @@ -86,6 +86,9 @@ That is all the configuration that is required to run a custom linter in your pr Custom linters are disabled by default, and are not enabled when `linters.enable-all` is specified. They can be enabled by adding them 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. + ### Create a Plugin Your linter must provide one or more `golang.org/x/tools/go/analysis.Analyzer` structs. From b27c6740afbaeca6e1e19d0a478e3aaad4b3969b Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 7 Jun 2023 00:20:44 +0200 Subject: [PATCH 07/10] chore: downgrade to go1.19 --- go.mod | 2 +- netlify.toml | 6 +++--- pkg/lint/lintersdb/custom_linters.go | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 0d3409ac2dc4..7049fad571e4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/golangci/golangci-lint -go 1.20 +go 1.19 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 diff --git a/netlify.toml b/netlify.toml index 32db72872c08..dcd0c45f1cf1 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,5 +1,5 @@ [context.production.environment] - GO_VERSION = "1.20" + GO_VERSION = "1.19" NODE_VERSION = "17" # TODO https://github.com/golangci/golangci-lint/pull/2904#issuecomment-1146870535 # NPM_FLAGS = "--legacy-peer-deps" @@ -7,7 +7,7 @@ NPM_VERSION = "8.5.5" [context.deploy-preview.environment] - GO_VERSION = "1.20" + GO_VERSION = "1.19" NODE_VERSION = "17" # TODO https://github.com/golangci/golangci-lint/pull/2904#issuecomment-1146870535 # NPM_FLAGS = "--legacy-peer-deps" @@ -15,7 +15,7 @@ NPM_VERSION = "8.5.5" [context.branch-deploy.environment] - GO_VERSION = "1.20" + GO_VERSION = "1.19" NODE_VERSION = "17" # TODO https://github.com/golangci/golangci-lint/pull/2904#issuecomment-1146870535 # NPM_FLAGS = "--legacy-peer-deps" diff --git a/pkg/lint/lintersdb/custom_linters.go b/pkg/lint/lintersdb/custom_linters.go index 63790a2e2e0b..dae6c7740e3d 100644 --- a/pkg/lint/lintersdb/custom_linters.go +++ b/pkg/lint/lintersdb/custom_linters.go @@ -1,7 +1,6 @@ package lintersdb import ( - "errors" "fmt" "path/filepath" "plugin" @@ -98,7 +97,8 @@ func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.A if err != nil { analyzers, errP := m.lookupAnalyzerPlugin(plug) if err != nil { - return nil, errors.Join(err, errP) + // TODO(ldez): use `errors.Join` when we will upgrade to go1.20. + return nil, fmt.Errorf("%s: %w", err, errP) } return analyzers, nil @@ -119,7 +119,8 @@ func (m *Manager) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyze return nil, err } - m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please the new plugin signature: https://golangci-lint.run/contributing/new-linters/#create-a-plugin") + m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please the new plugin signature:" + + "https://golangci-lint.run/contributing/new-linters/#create-a-plugin") analyzerPlugin, ok := symbol.(AnalyzerPlugin) if !ok { From bb1ff3ffdf1f5c49f010b106afc87abcffb577c2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 9 Jun 2023 19:19:31 +0200 Subject: [PATCH 08/10] Update pkg/lint/lintersdb/custom_linters.go Co-authored-by: Simon Sawert --- pkg/lint/lintersdb/custom_linters.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/lint/lintersdb/custom_linters.go b/pkg/lint/lintersdb/custom_linters.go index dae6c7740e3d..dff08403dcec 100644 --- a/pkg/lint/lintersdb/custom_linters.go +++ b/pkg/lint/lintersdb/custom_linters.go @@ -119,7 +119,7 @@ func (m *Manager) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyze return nil, err } - m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please the new plugin signature:" + + m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please use the new plugin signature:" + "https://golangci-lint.run/contributing/new-linters/#create-a-plugin") analyzerPlugin, ok := symbol.(AnalyzerPlugin) From 3bb0ccbcf23981ed91d0848b4c1b1323979ae3a1 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 13 Jun 2023 21:36:58 +0200 Subject: [PATCH 09/10] chore: use multierror --- pkg/lint/lintersdb/custom_linters.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/lint/lintersdb/custom_linters.go b/pkg/lint/lintersdb/custom_linters.go index dff08403dcec..bcde63fcb334 100644 --- a/pkg/lint/lintersdb/custom_linters.go +++ b/pkg/lint/lintersdb/custom_linters.go @@ -5,6 +5,7 @@ import ( "path/filepath" "plugin" + "github.com/hashicorp/go-multierror" "github.com/spf13/viper" "golang.org/x/tools/go/analysis" @@ -98,7 +99,7 @@ func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.A analyzers, errP := m.lookupAnalyzerPlugin(plug) if err != nil { // TODO(ldez): use `errors.Join` when we will upgrade to go1.20. - return nil, fmt.Errorf("%s: %w", err, errP) + return nil, multierror.Append(err, errP) } return analyzers, nil From 0874476aecd328277313016bf6ec7d62868b55ac Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 13 Jun 2023 23:58:33 +0200 Subject: [PATCH 10/10] fix: error handling --- pkg/lint/lintersdb/custom_linters.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/lint/lintersdb/custom_linters.go b/pkg/lint/lintersdb/custom_linters.go index bcde63fcb334..69989abd5d61 100644 --- a/pkg/lint/lintersdb/custom_linters.go +++ b/pkg/lint/lintersdb/custom_linters.go @@ -97,7 +97,7 @@ func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.A symbol, err := plug.Lookup("New") if err != nil { analyzers, errP := m.lookupAnalyzerPlugin(plug) - if err != nil { + if errP != nil { // TODO(ldez): use `errors.Join` when we will upgrade to go1.20. return nil, multierror.Append(err, errP) } @@ -120,7 +120,7 @@ func (m *Manager) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyze return nil, err } - m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please use the new plugin signature:" + + m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please use the new plugin signature: " + "https://golangci-lint.run/contributing/new-linters/#create-a-plugin") analyzerPlugin, ok := symbol.(AnalyzerPlugin)