forked from golangci/golangci-lint
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbuilder_plugin_go.go
143 lines (113 loc) · 4.11 KB
/
builder_plugin_go.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package lintersdb
import (
"context"
"errors"
"fmt"
"path/filepath"
"plugin"
"golang.org/x/tools/go/analysis"
"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/fsutils"
"github.com/golangci/golangci-lint/pkg/goanalysis"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/logutils"
)
const goPluginType = "goplugin"
type AnalyzerPlugin interface {
GetAnalyzers() []*analysis.Analyzer
}
// PluginGoBuilder builds the custom linters (Go plugin) based on the configuration.
type PluginGoBuilder struct {
log logutils.Log
}
// NewPluginGoBuilder creates new PluginGoBuilder.
func NewPluginGoBuilder(log logutils.Log) *PluginGoBuilder {
return &PluginGoBuilder{log: log}
}
// Build loads custom linters that are specified in the golangci-lint config file.
func (b *PluginGoBuilder) Build(cfg *config.Config) ([]*linter.Config, error) {
if cfg == nil || b.log == nil {
return nil, nil
}
var linters []*linter.Config
for name, settings := range cfg.LintersSettings.Custom {
if settings.Type != goPluginType && settings.Type != "" {
continue
}
lc, err := b.loadConfig(cfg, name, &settings)
if err != nil {
return nil, fmt.Errorf("unable to load custom analyzer %q: %s, %w", name, settings.Path, err)
}
linters = append(linters, lc)
}
return linters, nil
}
// loadConfig loads the configuration of private linters.
// Private linters are dynamically loaded from .so plugin files.
func (b *PluginGoBuilder) loadConfig(cfg *config.Config, name string, settings *config.CustomLinterSettings) (*linter.Config, error) {
analyzers, err := b.getAnalyzerPlugin(cfg, settings.Path, settings.Settings)
if err != nil {
return nil, err
}
b.log.Infof("Loaded %s: %s", settings.Path, name)
customLinter := goanalysis.NewLinter(name, settings.Description, analyzers, 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 (b *PluginGoBuilder) getAnalyzerPlugin(cfg *config.Config, path string, settings any) ([]*analysis.Analyzer, error) {
if !filepath.IsAbs(path) {
basePath, err := fsutils.GetBasePath(context.Background(), cfg.Run.RelativePathMode, cfg.GetConfigDir())
if err != nil {
return nil, fmt.Errorf("get base path: %w", err)
}
// resolve non-absolute paths relative to config file's directory
path = filepath.Join(basePath, path)
}
plug, err := plugin.Open(path)
if err != nil {
return nil, err
}
analyzers, err := b.lookupPlugin(plug, settings)
if err != nil {
return nil, fmt.Errorf("lookup plugin %s: %w", path, err)
}
return analyzers, nil
}
func (b *PluginGoBuilder) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) {
symbol, err := plug.Lookup("New")
if err != nil {
analyzers, errP := b.lookupAnalyzerPlugin(plug)
if errP != nil {
return nil, errors.Join(err, errP)
}
return analyzers, nil
}
// 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 (b *PluginGoBuilder) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyzer, error) {
symbol, err := plug.Lookup("AnalyzerPlugin")
if err != nil {
return nil, err
}
b.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please use the new plugin signature: " +
"https://golangci-lint.run/plugins/go-plugins#create-a-plugin")
analyzerPlugin, ok := symbol.(AnalyzerPlugin)
if !ok {
return nil, fmt.Errorf("plugin does not abide by 'AnalyzerPlugin' interface: %T", symbol)
}
return analyzerPlugin.GetAnalyzers(), nil
}