mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-12 11:36:29 +00:00
296 lines
7.3 KiB
Go
296 lines
7.3 KiB
Go
|
package golinters
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"go/token"
|
||
|
"io/ioutil"
|
||
|
"reflect"
|
||
|
|
||
|
"github.com/BurntSushi/toml"
|
||
|
"github.com/mgechev/dots"
|
||
|
reviveConfig "github.com/mgechev/revive/config"
|
||
|
"github.com/mgechev/revive/lint"
|
||
|
"github.com/mgechev/revive/rule"
|
||
|
"github.com/pkg/errors"
|
||
|
"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"
|
||
|
"github.com/golangci/golangci-lint/pkg/logutils"
|
||
|
"github.com/golangci/golangci-lint/pkg/result"
|
||
|
)
|
||
|
|
||
|
const reviveName = "revive"
|
||
|
|
||
|
var reviveDebugf = logutils.Debug("revive")
|
||
|
|
||
|
// jsonObject defines a JSON object of a failure
|
||
|
type jsonObject struct {
|
||
|
Severity lint.Severity
|
||
|
lint.Failure `json:",inline"`
|
||
|
}
|
||
|
|
||
|
// NewRevive returns a new Revive linter.
|
||
|
func NewRevive(cfg *config.ReviveSettings) *goanalysis.Linter {
|
||
|
var issues []goanalysis.Issue
|
||
|
|
||
|
analyzer := &analysis.Analyzer{
|
||
|
Name: goanalysis.TheOnlyAnalyzerName,
|
||
|
Doc: goanalysis.TheOnlyanalyzerDoc,
|
||
|
}
|
||
|
|
||
|
return goanalysis.NewLinter(
|
||
|
reviveName,
|
||
|
"Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.",
|
||
|
[]*analysis.Analyzer{analyzer},
|
||
|
nil,
|
||
|
).WithContextSetter(func(lintCtx *linter.Context) {
|
||
|
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
|
||
|
var files []string
|
||
|
|
||
|
for _, file := range pass.Files {
|
||
|
files = append(files, pass.Fset.PositionFor(file.Pos(), false).Filename)
|
||
|
}
|
||
|
|
||
|
conf, err := getReviveConfig(cfg)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
formatter, err := reviveConfig.GetFormatter("json")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
revive := lint.New(ioutil.ReadFile)
|
||
|
|
||
|
lintingRules, err := reviveConfig.GetLintingRules(conf)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
packages, err := dots.ResolvePackages(files, []string{})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
failures, err := revive.Lint(packages, lintingRules, *conf)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
formatChan := make(chan lint.Failure)
|
||
|
exitChan := make(chan bool)
|
||
|
|
||
|
var output string
|
||
|
go func() {
|
||
|
output, err = formatter.Format(formatChan, *conf)
|
||
|
if err != nil {
|
||
|
lintCtx.Log.Errorf("Format error: %v", err)
|
||
|
}
|
||
|
exitChan <- true
|
||
|
}()
|
||
|
|
||
|
for f := range failures {
|
||
|
if f.Confidence < conf.Confidence {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
formatChan <- f
|
||
|
}
|
||
|
|
||
|
close(formatChan)
|
||
|
<-exitChan
|
||
|
|
||
|
var results []jsonObject
|
||
|
err = json.Unmarshal([]byte(output), &results)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
for i := range results {
|
||
|
issues = append(issues, reviveToIssue(pass, &results[i]))
|
||
|
}
|
||
|
|
||
|
return nil, nil
|
||
|
}
|
||
|
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
|
||
|
return issues
|
||
|
}).WithLoadMode(goanalysis.LoadModeSyntax)
|
||
|
}
|
||
|
|
||
|
func reviveToIssue(pass *analysis.Pass, object *jsonObject) goanalysis.Issue {
|
||
|
lineRangeTo := object.Position.End.Line
|
||
|
if object.RuleName == (&rule.ExportedRule{}).Name() {
|
||
|
lineRangeTo = object.Position.Start.Line
|
||
|
}
|
||
|
|
||
|
return goanalysis.NewIssue(&result.Issue{
|
||
|
Severity: string(object.Severity),
|
||
|
Text: fmt.Sprintf("%s: %s", object.RuleName, object.Failure.Failure),
|
||
|
Pos: token.Position{
|
||
|
Filename: object.Position.Start.Filename,
|
||
|
Line: object.Position.Start.Line,
|
||
|
Offset: object.Position.Start.Offset,
|
||
|
Column: object.Position.Start.Column,
|
||
|
},
|
||
|
LineRange: &result.Range{
|
||
|
From: object.Position.Start.Line,
|
||
|
To: lineRangeTo,
|
||
|
},
|
||
|
FromLinter: reviveName,
|
||
|
}, pass)
|
||
|
}
|
||
|
|
||
|
// This function mimics the GetConfig function of revive.
|
||
|
// This allow to get default values and right types.
|
||
|
// https://github.com/golangci/golangci-lint/issues/1745
|
||
|
// https://github.com/mgechev/revive/blob/389ba853b0b3587f0c3b71b5f0c61ea4e23928ec/config/config.go#L155
|
||
|
func getReviveConfig(cfg *config.ReviveSettings) (*lint.Config, error) {
|
||
|
conf := defaultConfig()
|
||
|
|
||
|
if !reflect.DeepEqual(cfg, &config.ReviveSettings{}) {
|
||
|
rawRoot := createConfigMap(cfg)
|
||
|
buf := bytes.NewBuffer(nil)
|
||
|
|
||
|
err := toml.NewEncoder(buf).Encode(rawRoot)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "failed to encode configuration")
|
||
|
}
|
||
|
|
||
|
conf = &lint.Config{}
|
||
|
_, err = toml.DecodeReader(buf, conf)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "failed to decode configuration")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
normalizeConfig(conf)
|
||
|
|
||
|
reviveDebugf("revive configuration: %#v", conf)
|
||
|
|
||
|
return conf, nil
|
||
|
}
|
||
|
|
||
|
func createConfigMap(cfg *config.ReviveSettings) map[string]interface{} {
|
||
|
rawRoot := map[string]interface{}{
|
||
|
"ignoreGeneratedHeader": cfg.IgnoreGeneratedHeader,
|
||
|
"confidence": cfg.Confidence,
|
||
|
"severity": cfg.Severity,
|
||
|
"errorCode": cfg.ErrorCode,
|
||
|
"warningCode": cfg.WarningCode,
|
||
|
"enableAllRules": cfg.EnableAllRules,
|
||
|
}
|
||
|
|
||
|
rawDirectives := map[string]map[string]interface{}{}
|
||
|
for _, directive := range cfg.Directives {
|
||
|
rawDirectives[directive.Name] = map[string]interface{}{
|
||
|
"severity": directive.Severity,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(rawDirectives) > 0 {
|
||
|
rawRoot["directive"] = rawDirectives
|
||
|
}
|
||
|
|
||
|
rawRules := map[string]map[string]interface{}{}
|
||
|
for _, s := range cfg.Rules {
|
||
|
rawRules[s.Name] = map[string]interface{}{
|
||
|
"severity": s.Severity,
|
||
|
"arguments": safeTomlSlice(s.Arguments),
|
||
|
"disabled": s.Disabled,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(rawRules) > 0 {
|
||
|
rawRoot["rule"] = rawRules
|
||
|
}
|
||
|
|
||
|
return rawRoot
|
||
|
}
|
||
|
|
||
|
func safeTomlSlice(r []interface{}) []interface{} {
|
||
|
if len(r) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if _, ok := r[0].(map[interface{}]interface{}); !ok {
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
var typed []interface{}
|
||
|
for _, elt := range r {
|
||
|
item := map[string]interface{}{}
|
||
|
for k, v := range elt.(map[interface{}]interface{}) {
|
||
|
item[k.(string)] = v
|
||
|
}
|
||
|
|
||
|
typed = append(typed, item)
|
||
|
}
|
||
|
|
||
|
return typed
|
||
|
}
|
||
|
|
||
|
// This element is not exported by revive, so we need copy the code.
|
||
|
// Extracted from https://github.com/mgechev/revive/blob/389ba853b0b3587f0c3b71b5f0c61ea4e23928ec/config/config.go#L15
|
||
|
var defaultRules = []lint.Rule{
|
||
|
&rule.VarDeclarationsRule{},
|
||
|
&rule.PackageCommentsRule{},
|
||
|
&rule.DotImportsRule{},
|
||
|
&rule.BlankImportsRule{},
|
||
|
&rule.ExportedRule{},
|
||
|
&rule.VarNamingRule{},
|
||
|
&rule.IndentErrorFlowRule{},
|
||
|
&rule.RangeRule{},
|
||
|
&rule.ErrorfRule{},
|
||
|
&rule.ErrorNamingRule{},
|
||
|
&rule.ErrorStringsRule{},
|
||
|
&rule.ReceiverNamingRule{},
|
||
|
&rule.IncrementDecrementRule{},
|
||
|
&rule.ErrorReturnRule{},
|
||
|
&rule.UnexportedReturnRule{},
|
||
|
&rule.TimeNamingRule{},
|
||
|
&rule.ContextKeysType{},
|
||
|
&rule.ContextAsArgumentRule{},
|
||
|
}
|
||
|
|
||
|
// This element is not exported by revive, so we need copy the code.
|
||
|
// Extracted from https://github.com/mgechev/revive/blob/389ba853b0b3587f0c3b71b5f0c61ea4e23928ec/config/config.go#L133
|
||
|
func normalizeConfig(cfg *lint.Config) {
|
||
|
if cfg.Confidence == 0 {
|
||
|
cfg.Confidence = 0.8
|
||
|
}
|
||
|
severity := cfg.Severity
|
||
|
if severity != "" {
|
||
|
for k, v := range cfg.Rules {
|
||
|
if v.Severity == "" {
|
||
|
v.Severity = severity
|
||
|
}
|
||
|
cfg.Rules[k] = v
|
||
|
}
|
||
|
for k, v := range cfg.Directives {
|
||
|
if v.Severity == "" {
|
||
|
v.Severity = severity
|
||
|
}
|
||
|
cfg.Directives[k] = v
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This element is not exported by revive, so we need copy the code.
|
||
|
// Extracted from https://github.com/mgechev/revive/blob/389ba853b0b3587f0c3b71b5f0c61ea4e23928ec/config/config.go#L182
|
||
|
func defaultConfig() *lint.Config {
|
||
|
defaultConfig := lint.Config{
|
||
|
Confidence: 0.0,
|
||
|
Severity: lint.SeverityWarning,
|
||
|
Rules: map[string]lint.RuleConfig{},
|
||
|
}
|
||
|
for _, r := range defaultRules {
|
||
|
defaultConfig.Rules[r.Name()] = lint.RuleConfig{}
|
||
|
}
|
||
|
return &defaultConfig
|
||
|
}
|