github.com/chenfeining/golangci-lint@v1.0.2-0.20230730162517-14c6c67868df/pkg/lint/lintersdb/custom_linters.go (about) 1 package lintersdb 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "plugin" 7 8 "github.com/hashicorp/go-multierror" 9 "github.com/spf13/viper" 10 "golang.org/x/tools/go/analysis" 11 12 "github.com/chenfeining/golangci-lint/pkg/config" 13 "github.com/chenfeining/golangci-lint/pkg/golinters/goanalysis" 14 "github.com/chenfeining/golangci-lint/pkg/lint/linter" 15 ) 16 17 type AnalyzerPlugin interface { 18 GetAnalyzers() []*analysis.Analyzer 19 } 20 21 // getCustomLinterConfigs loads private linters that are specified in the golangci config file. 22 func (m *Manager) getCustomLinterConfigs() []*linter.Config { 23 if m.cfg == nil || m.log == nil { 24 return nil 25 } 26 27 var linters []*linter.Config 28 29 for name, settings := range m.cfg.LintersSettings.Custom { 30 lc, err := m.loadCustomLinterConfig(name, settings) 31 if err != nil { 32 m.log.Errorf("Unable to load custom analyzer %s:%s, %v", name, settings.Path, err) 33 } else { 34 linters = append(linters, lc) 35 } 36 } 37 38 return linters 39 } 40 41 // loadCustomLinterConfig loads the configuration of private linters. 42 // Private linters are dynamically loaded from .so plugin files. 43 func (m *Manager) loadCustomLinterConfig(name string, settings config.CustomLinterSettings) (*linter.Config, error) { 44 analyzers, err := m.getAnalyzerPlugin(settings.Path, settings.Settings) 45 if err != nil { 46 return nil, err 47 } 48 49 m.log.Infof("Loaded %s: %s", settings.Path, name) 50 51 customLinter := goanalysis.NewLinter(name, settings.Description, analyzers, nil). 52 WithLoadMode(goanalysis.LoadModeTypesInfo) 53 54 linterConfig := linter.NewConfig(customLinter). 55 WithEnabledByDefault(). 56 WithLoadForGoAnalysis(). 57 WithURL(settings.OriginalURL) 58 59 return linterConfig, nil 60 } 61 62 // getAnalyzerPlugin loads a private linter as specified in the config file, 63 // loads the plugin from a .so file, 64 // and returns the 'AnalyzerPlugin' interface implemented by the private plugin. 65 // An error is returned if the private linter cannot be loaded 66 // or the linter does not implement the AnalyzerPlugin interface. 67 func (m *Manager) getAnalyzerPlugin(path string, settings any) ([]*analysis.Analyzer, error) { 68 if !filepath.IsAbs(path) { 69 // resolve non-absolute paths relative to config file's directory 70 configFilePath := viper.ConfigFileUsed() 71 absConfigFilePath, err := filepath.Abs(configFilePath) 72 if err != nil { 73 return nil, fmt.Errorf("could not get absolute representation of config file path %q: %v", configFilePath, err) 74 } 75 path = filepath.Join(filepath.Dir(absConfigFilePath), path) 76 } 77 78 plug, err := plugin.Open(path) 79 if err != nil { 80 return nil, err 81 } 82 83 analyzers, err := m.lookupPlugin(plug, settings) 84 if err != nil { 85 return nil, fmt.Errorf("lookup plugin %s: %w", path, err) 86 } 87 88 return analyzers, nil 89 } 90 91 func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) { 92 symbol, err := plug.Lookup("New") 93 if err != nil { 94 analyzers, errP := m.lookupAnalyzerPlugin(plug) 95 if errP != nil { 96 // TODO(ldez): use `errors.Join` when we will upgrade to go1.20. 97 return nil, multierror.Append(err, errP) 98 } 99 100 return analyzers, nil 101 } 102 103 // The type func cannot be used here, must be the explicit signature. 104 constructor, ok := symbol.(func(any) ([]*analysis.Analyzer, error)) 105 if !ok { 106 return nil, fmt.Errorf("plugin does not abide by 'New' function: %T", symbol) 107 } 108 109 return constructor(settings) 110 } 111 112 func (m *Manager) lookupAnalyzerPlugin(plug *plugin.Plugin) ([]*analysis.Analyzer, error) { 113 symbol, err := plug.Lookup("AnalyzerPlugin") 114 if err != nil { 115 return nil, err 116 } 117 118 m.log.Warnf("plugin: 'AnalyzerPlugin' plugins are deprecated, please use the new plugin signature: " + 119 "https://golangci-lint.run/contributing/new-linters/#create-a-plugin") 120 121 analyzerPlugin, ok := symbol.(AnalyzerPlugin) 122 if !ok { 123 return nil, fmt.Errorf("plugin does not abide by 'AnalyzerPlugin' interface: %T", symbol) 124 } 125 126 return analyzerPlugin.GetAnalyzers(), nil 127 }