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