golangci-lint/pkg/lint/lintersdb/custom_linters.go
2024-02-10 14:43:58 +01:00

127 lines
3.7 KiB
Go

package lintersdb
import (
"errors"
"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"
)
type AnalyzerPlugin interface {
GetAnalyzers() []*analysis.Analyzer
}
// getCustomLinterConfigs loads private linters that are specified in the golangci config file.
func (m *Manager) getCustomLinterConfigs() []*linter.Config {
if m.cfg == nil || m.log == nil {
return nil
}
var linters []*linter.Config
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 {
linters = append(linters, lc)
}
}
return linters
}
// 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) {
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, 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 (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()
absConfigFilePath, err := filepath.Abs(configFilePath)
if err != nil {
return nil, fmt.Errorf("could not get absolute representation of config file path %q: %w", configFilePath, err)
}
path = filepath.Join(filepath.Dir(absConfigFilePath), path)
}
plug, err := plugin.Open(path)
if err != nil {
return nil, err
}
analyzers, err := m.lookupPlugin(plug, settings)
if err != nil {
return nil, fmt.Errorf("lookup plugin %s: %w", path, err)
}
return analyzers, nil
}
func (m *Manager) lookupPlugin(plug *plugin.Plugin, settings any) ([]*analysis.Analyzer, error) {
symbol, err := plug.Lookup("New")
if err != nil {
analyzers, errP := m.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 (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 use 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)
}
return analyzerPlugin.GetAnalyzers(), nil
}