Skip to content

Commit 292fa5b

Browse files
committed
chore: move code related to plugin to a dedicated file
1 parent c9d759e commit 292fa5b

File tree

3 files changed

+129
-111
lines changed

3 files changed

+129
-111
lines changed

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

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ title: New linters
44

55
## How to write a custom linter
66

7-
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
8-
from scratch and integrate it into `golangci-lint`.
7+
Use `go/analysis` and take a look at [this tutorial](https://disaev.me/p/writing-useful-go-analysis-linter/):
8+
it shows how to write `go/analysis` linter from scratch and integrate it into `golangci-lint`.
99

1010
## How to add a public linter to `golangci-lint`
1111

@@ -39,52 +39,61 @@ After that:
3939

4040
Some people and organizations may choose to have custom-made linters run as a part of `golangci-lint`.
4141
Typically, these linters can't be open-sourced or too specific.
42+
4243
Such linters can be added through Go's plugin library.
4344

4445
For a private linter (which acts as a plugin) to work properly,
4546
the plugin as well as the golangci-lint binary needs to be built for the same environment. `CGO_ENABLED` is another requirement.
47+
4648
This means that `golangci-lint` needs to be built for whatever machine you intend to run it on
4749
(cloning the golangci-lint repository and running a `CGO_ENABLED=1 make build` should do the trick for your machine).
4850

4951
### Configure a Plugin
5052

51-
If you already have a linter plugin available, you can follow these steps to define it's usage in a projects
52-
`.golangci.yml` file. An example linter can be found at [here](https://github.com/golangci/example-plugin-linter). If you're looking for
53-
instructions on how to configure your own custom linter, they can be found further down.
53+
If you already have a linter plugin available, you can follow these steps to define its usage in a projects `.golangci.yml` file.
54+
55+
An example linter can be found at [here](https://github.com/golangci/example-plugin-linter).
56+
57+
If you're looking for instructions on how to configure your own custom linter, they can be found further down.
5458

5559
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.
5660
2. Adjust the yaml to appropriate `linters-settings:custom` entries as so:
57-
58-
```yaml
59-
linters-settings:
60-
custom:
61-
example:
62-
path: /example.so
63-
description: The description of the linter
64-
original-url: github.com/golangci/example-linter
65-
```
61+
```yaml
62+
linters-settings:
63+
custom:
64+
example:
65+
path: /example.so
66+
description: The description of the linter
67+
original-url: github.com/golangci/example-linter
68+
```
6669
6770
That is all the configuration that is required to run a custom linter in your project.
71+
6872
Custom linters are disabled by default, and are not enabled when `linters.enable-all` is specified.
6973
They can be enabled by adding them the `linters.enable` list, or providing the enabled option on the command line (`golangci-lint run -Eexample`).
7074

7175
### Create a Plugin
7276

7377
Your linter must implement one or more `golang.org/x/tools/go/analysis.Analyzer` structs.
74-
Your project should also use `go.mod`. All versions of libraries that overlap `golangci-lint` (including replaced
75-
libraries) MUST be set to the same version as `golangci-lint`. You can see the versions by running `go version -m golangci-lint`.
7678

77-
You'll also need to create a go file like `plugin/example.go`. This MUST be in the package `main`, and define a
78-
variable of name `AnalyzerPlugin`. The `AnalyzerPlugin` instance MUST implement the following interface:
79+
Your project should also use `go.mod`.
80+
81+
All versions of libraries that overlap `golangci-lint` (including replaced libraries) MUST be set to the same version as `golangci-lint`.
82+
You can see the versions by running `go version -m golangci-lint`.
83+
84+
You'll also need to create a go file like `plugin/example.go`.
85+
This MUST be in the package `main`, and define a variable of name `AnalyzerPlugin`.
86+
87+
The `AnalyzerPlugin` instance MUST implement the following interface:
7988

8089
```go
8190
type AnalyzerPlugin interface {
8291
GetAnalyzers() []*analysis.Analyzer
8392
}
8493
```
8594

86-
The type of `AnalyzerPlugin` is not important, but is by convention `type analyzerPlugin struct {}`. See
87-
[plugin/example.go](https://github.com/golangci/example-plugin-linter/blob/master/plugin/example.go) for more info.
95+
The type of `AnalyzerPlugin` is not important, but is by convention `type analyzerPlugin struct {}`.
96+
See [plugin/example.go](https://github.com/golangci/example-plugin-linter/blob/master/plugin/example.go) for more info.
8897

89-
To build the plugin, from the root project directory, run `go build -buildmode=plugin plugin/example.go`. This will create a plugin `*.so`
90-
file that can be copied into your project or another well known location for usage in golangci-lint.
98+
To build the plugin, from the root project directory, run `go build -buildmode=plugin plugin/example.go`.
99+
This will create a plugin `*.so` file that can be copied into your project or another well known location for usage in golangci-lint.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package lintersdb
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
"plugin"
7+
8+
"github.com/spf13/viper"
9+
"golang.org/x/tools/go/analysis"
10+
11+
"github.com/golangci/golangci-lint/pkg/config"
12+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
13+
"github.com/golangci/golangci-lint/pkg/lint/linter"
14+
"github.com/golangci/golangci-lint/pkg/logutils"
15+
"github.com/golangci/golangci-lint/pkg/report"
16+
)
17+
18+
type AnalyzerPlugin interface {
19+
GetAnalyzers() []*analysis.Analyzer
20+
}
21+
22+
// WithCustomLinters loads private linters that are specified in the golangci config file.
23+
func (m *Manager) WithCustomLinters() *Manager {
24+
if m.log == nil {
25+
m.log = report.NewLogWrapper(logutils.NewStderrLog(logutils.DebugKeyEmpty), &report.Data{})
26+
}
27+
28+
if m.cfg == nil {
29+
return m
30+
}
31+
32+
for name, settings := range m.cfg.LintersSettings.Custom {
33+
lc, err := m.loadCustomLinterConfig(name, settings)
34+
35+
if err != nil {
36+
m.log.Errorf("Unable to load custom analyzer %s:%s, %v", name, settings.Path, err)
37+
} else {
38+
m.nameToLCs[name] = append(m.nameToLCs[name], lc)
39+
}
40+
}
41+
42+
return m
43+
}
44+
45+
// loadCustomLinterConfig loads the configuration of private linters.
46+
// Private linters are dynamically loaded from .so plugin files.
47+
func (m *Manager) loadCustomLinterConfig(name string, settings config.CustomLinterSettings) (*linter.Config, error) {
48+
analyzer, err := m.getAnalyzerPlugin(settings.Path)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
m.log.Infof("Loaded %s: %s", settings.Path, name)
54+
55+
customLinter := goanalysis.NewLinter(name, settings.Description, analyzer.GetAnalyzers(), nil).
56+
WithLoadMode(goanalysis.LoadModeTypesInfo)
57+
58+
linterConfig := linter.NewConfig(customLinter)
59+
linterConfig.EnabledByDefault = true
60+
linterConfig.IsSlow = false
61+
linterConfig.WithURL(settings.OriginalURL)
62+
63+
return linterConfig, nil
64+
}
65+
66+
// getAnalyzerPlugin loads a private linter as specified in the config file,
67+
// loads the plugin from a .so file,
68+
// and returns the 'AnalyzerPlugin' interface implemented by the private plugin.
69+
// An error is returned if the private linter cannot be loaded
70+
// or the linter does not implement the AnalyzerPlugin interface.
71+
func (m *Manager) getAnalyzerPlugin(path string) (AnalyzerPlugin, error) {
72+
if !filepath.IsAbs(path) {
73+
// resolve non-absolute paths relative to config file's directory
74+
configFilePath := viper.ConfigFileUsed()
75+
absConfigFilePath, err := filepath.Abs(configFilePath)
76+
if err != nil {
77+
return nil, fmt.Errorf("could not get absolute representation of config file path %q: %v", configFilePath, err)
78+
}
79+
path = filepath.Join(filepath.Dir(absConfigFilePath), path)
80+
}
81+
82+
plug, err := plugin.Open(path)
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
symbol, err := plug.Lookup("AnalyzerPlugin")
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
analyzerPlugin, ok := symbol.(AnalyzerPlugin)
93+
if !ok {
94+
return nil, fmt.Errorf("plugin %s does not abide by 'AnalyzerPlugin' interface", path)
95+
}
96+
97+
return analyzerPlugin, nil
98+
}

pkg/lint/lintersdb/manager.go

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
package lintersdb
22

33
import (
4-
"fmt"
5-
"path/filepath"
6-
"plugin"
7-
8-
"github.com/spf13/viper"
9-
"golang.org/x/tools/go/analysis"
10-
114
"github.com/golangci/golangci-lint/pkg/config"
125
"github.com/golangci/golangci-lint/pkg/golinters"
13-
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
146
"github.com/golangci/golangci-lint/pkg/lint/linter"
157
"github.com/golangci/golangci-lint/pkg/logutils"
16-
"github.com/golangci/golangci-lint/pkg/report"
178
)
189

1910
type Manager struct {
@@ -35,28 +26,6 @@ func NewManager(cfg *config.Config, log logutils.Log) *Manager {
3526
return m
3627
}
3728

38-
// WithCustomLinters loads private linters that are specified in the golangci config file.
39-
func (m *Manager) WithCustomLinters() *Manager {
40-
if m.log == nil {
41-
m.log = report.NewLogWrapper(logutils.NewStderrLog(logutils.DebugKeyEmpty), &report.Data{})
42-
}
43-
if m.cfg != nil {
44-
for name, settings := range m.cfg.LintersSettings.Custom {
45-
lc, err := m.loadCustomLinterConfig(name, settings)
46-
47-
if err != nil {
48-
m.log.Errorf("Unable to load custom analyzer %s:%s, %v",
49-
name,
50-
settings.Path,
51-
err)
52-
} else {
53-
m.nameToLCs[name] = append(m.nameToLCs[name], lc)
54-
}
55-
}
56-
}
57-
return m
58-
}
59-
6029
func (Manager) AllPresets() []string {
6130
return []string{
6231
linter.PresetBugs,
@@ -965,61 +934,3 @@ func (m Manager) GetAllLinterConfigsForPreset(p string) []*linter.Config {
965934

966935
return ret
967936
}
968-
969-
// loadCustomLinterConfig loads the configuration of private linters.
970-
// Private linters are dynamically loaded from .so plugin files.
971-
func (m Manager) loadCustomLinterConfig(name string, settings config.CustomLinterSettings) (*linter.Config, error) {
972-
analyzer, err := m.getAnalyzerPlugin(settings.Path)
973-
if err != nil {
974-
return nil, err
975-
}
976-
m.log.Infof("Loaded %s: %s", settings.Path, name)
977-
customLinter := goanalysis.NewLinter(
978-
name,
979-
settings.Description,
980-
analyzer.GetAnalyzers(),
981-
nil).WithLoadMode(goanalysis.LoadModeTypesInfo)
982-
linterConfig := linter.NewConfig(customLinter)
983-
linterConfig.EnabledByDefault = true
984-
linterConfig.IsSlow = false
985-
linterConfig.WithURL(settings.OriginalURL)
986-
return linterConfig, nil
987-
}
988-
989-
type AnalyzerPlugin interface {
990-
GetAnalyzers() []*analysis.Analyzer
991-
}
992-
993-
// getAnalyzerPlugin loads a private linter as specified in the config file,
994-
// loads the plugin from a .so file, and returns the 'AnalyzerPlugin' interface
995-
// implemented by the private plugin.
996-
// An error is returned if the private linter cannot be loaded or the linter
997-
// does not implement the AnalyzerPlugin interface.
998-
func (m Manager) getAnalyzerPlugin(path string) (AnalyzerPlugin, error) {
999-
if !filepath.IsAbs(path) {
1000-
// resolve non-absolute paths relative to config file's directory
1001-
configFilePath := viper.ConfigFileUsed()
1002-
absConfigFilePath, err := filepath.Abs(configFilePath)
1003-
if err != nil {
1004-
return nil, fmt.Errorf("could not get absolute representation of config file path %q: %v", configFilePath, err)
1005-
}
1006-
path = filepath.Join(filepath.Dir(absConfigFilePath), path)
1007-
}
1008-
1009-
plug, err := plugin.Open(path)
1010-
if err != nil {
1011-
return nil, err
1012-
}
1013-
1014-
symbol, err := plug.Lookup("AnalyzerPlugin")
1015-
if err != nil {
1016-
return nil, err
1017-
}
1018-
1019-
analyzerPlugin, ok := symbol.(AnalyzerPlugin)
1020-
if !ok {
1021-
return nil, fmt.Errorf("plugin %s does not abide by 'AnalyzerPlugin' interface", path)
1022-
}
1023-
1024-
return analyzerPlugin, nil
1025-
}

0 commit comments

Comments
 (0)