mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-12 11:36:29 +00:00
56a854fe14
* update github.com/docker/cli * update github.com/docker/distribution * update github.com/docker/docker * update github.com/gin-gonic/gin * update github.com/golang-jwt/jwt/v4 * update github.com/golangci/golangci-lint * update github.com/gorilla/securecookie * update github.com/mattn/go-sqlite3 * update github.com/moby/moby * update github.com/prometheus/client_golang * update github.com/xanzy/go-gitlab
204 lines
5.6 KiB
Go
204 lines
5.6 KiB
Go
package golinters
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/types"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
gocriticlinter "github.com/go-critic/go-critic/framework/linter"
|
|
"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/result"
|
|
)
|
|
|
|
const gocriticName = "gocritic"
|
|
|
|
func NewGocritic() *goanalysis.Linter {
|
|
var mu sync.Mutex
|
|
var resIssues []goanalysis.Issue
|
|
|
|
sizes := types.SizesFor("gc", runtime.GOARCH)
|
|
|
|
analyzer := &analysis.Analyzer{
|
|
Name: gocriticName,
|
|
Doc: goanalysis.TheOnlyanalyzerDoc,
|
|
}
|
|
return goanalysis.NewLinter(
|
|
gocriticName,
|
|
`Provides diagnostics that check for bugs, performance and style issues.
|
|
Extensible without recompilation through dynamic rules.
|
|
Dynamic rules are written declaratively with AST patterns, filters, report message and optional suggestion.`,
|
|
[]*analysis.Analyzer{analyzer},
|
|
nil,
|
|
).WithContextSetter(func(lintCtx *linter.Context) {
|
|
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
|
|
linterCtx := gocriticlinter.NewContext(pass.Fset, sizes)
|
|
enabledCheckers, err := buildEnabledCheckers(lintCtx, linterCtx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
linterCtx.SetPackageInfo(pass.TypesInfo, pass.Pkg)
|
|
pkgIssues := runGocriticOnPackage(linterCtx, enabledCheckers, pass.Files)
|
|
res := make([]goanalysis.Issue, 0, len(pkgIssues))
|
|
|
|
for i := range pkgIssues {
|
|
res = append(res, goanalysis.NewIssue(&pkgIssues[i], pass))
|
|
}
|
|
if len(res) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
mu.Lock()
|
|
resIssues = append(resIssues, res...)
|
|
mu.Unlock()
|
|
|
|
return nil, nil
|
|
}
|
|
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
|
|
return resIssues
|
|
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
|
|
}
|
|
|
|
func normalizeCheckerInfoParams(info *gocriticlinter.CheckerInfo) gocriticlinter.CheckerParams {
|
|
// lowercase info param keys here because golangci-lint's config parser lowercases all strings
|
|
ret := gocriticlinter.CheckerParams{}
|
|
for k, v := range info.Params {
|
|
ret[strings.ToLower(k)] = v
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func configureCheckerInfo(
|
|
lintCtx *linter.Context,
|
|
info *gocriticlinter.CheckerInfo,
|
|
allParams map[string]config.GocriticCheckSettings) error {
|
|
params := allParams[strings.ToLower(info.Name)]
|
|
if params == nil { // no config for this checker
|
|
return nil
|
|
}
|
|
|
|
infoParams := normalizeCheckerInfoParams(info)
|
|
for k, p := range params {
|
|
v, ok := infoParams[k]
|
|
if ok {
|
|
v.Value = normalizeCheckerParamsValue(lintCtx, p)
|
|
continue
|
|
}
|
|
|
|
// param `k` isn't supported
|
|
if len(info.Params) == 0 {
|
|
return fmt.Errorf("checker %s config param %s doesn't exist: checker doesn't have params",
|
|
info.Name, k)
|
|
}
|
|
|
|
var supportedKeys []string
|
|
for sk := range info.Params {
|
|
supportedKeys = append(supportedKeys, sk)
|
|
}
|
|
sort.Strings(supportedKeys)
|
|
|
|
return fmt.Errorf("checker %s config param %s doesn't exist, all existing: %s",
|
|
info.Name, k, supportedKeys)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// normalizeCheckerParamsValue normalizes value types.
|
|
// go-critic asserts that CheckerParam.Value has some specific types,
|
|
// but the file parsers (TOML, YAML, JSON) don't create the same representation for raw type.
|
|
// then we have to convert value types into the expected value types.
|
|
// Maybe in the future, this kind of conversion will be done in go-critic itself.
|
|
func normalizeCheckerParamsValue(lintCtx *linter.Context, p interface{}) interface{} {
|
|
rv := reflect.ValueOf(p)
|
|
switch rv.Type().Kind() {
|
|
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
|
|
return int(rv.Int())
|
|
case reflect.Bool:
|
|
return rv.Bool()
|
|
case reflect.String:
|
|
// Perform variable substitution.
|
|
return strings.ReplaceAll(rv.String(), "${configDir}", lintCtx.Cfg.GetConfigDir())
|
|
default:
|
|
return p
|
|
}
|
|
}
|
|
|
|
func buildEnabledCheckers(lintCtx *linter.Context, linterCtx *gocriticlinter.Context) ([]*gocriticlinter.Checker, error) {
|
|
s := lintCtx.Settings().Gocritic
|
|
allParams := s.GetLowercasedParams()
|
|
|
|
var enabledCheckers []*gocriticlinter.Checker
|
|
for _, info := range gocriticlinter.GetCheckersInfo() {
|
|
if !s.IsCheckEnabled(info.Name) {
|
|
continue
|
|
}
|
|
|
|
if err := configureCheckerInfo(lintCtx, info, allParams); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c, err := gocriticlinter.NewChecker(linterCtx, info)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
enabledCheckers = append(enabledCheckers, c)
|
|
}
|
|
|
|
return enabledCheckers, nil
|
|
}
|
|
|
|
func runGocriticOnPackage(linterCtx *gocriticlinter.Context, checkers []*gocriticlinter.Checker,
|
|
files []*ast.File) []result.Issue {
|
|
var res []result.Issue
|
|
for _, f := range files {
|
|
filename := filepath.Base(linterCtx.FileSet.Position(f.Pos()).Filename)
|
|
linterCtx.SetFileInfo(filename, f)
|
|
|
|
issues := runGocriticOnFile(linterCtx, f, checkers)
|
|
res = append(res, issues...)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func runGocriticOnFile(ctx *gocriticlinter.Context, f *ast.File, checkers []*gocriticlinter.Checker) []result.Issue {
|
|
var res []result.Issue
|
|
|
|
for _, c := range checkers {
|
|
// All checkers are expected to use *lint.Context
|
|
// as read-only structure, so no copying is required.
|
|
for _, warn := range c.Check(f) {
|
|
pos := ctx.FileSet.Position(warn.Node.Pos())
|
|
issue := result.Issue{
|
|
Pos: pos,
|
|
Text: fmt.Sprintf("%s: %s", c.Info.Name, warn.Text),
|
|
FromLinter: gocriticName,
|
|
}
|
|
|
|
if warn.HasQuickFix() {
|
|
issue.Replacement = &result.Replacement{
|
|
Inline: &result.InlineFix{
|
|
StartCol: pos.Column - 1,
|
|
Length: int(warn.Node.End() - warn.Node.Pos()),
|
|
NewString: string(warn.Suggestion.Replacement),
|
|
},
|
|
}
|
|
}
|
|
|
|
res = append(res, issue)
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|