woodpecker/vendor/github.com/golangci/golangci-lint/pkg/golinters/scopelint.go
Lukas c28f7cb29f
Add golangci-lint (#502)
Initial part of #435
2021-11-14 21:01:54 +01:00

177 lines
4.4 KiB
Go

package golinters
import (
"fmt"
"go/ast"
"go/token"
"sync"
"golang.org/x/tools/go/analysis"
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
"github.com/golangci/golangci-lint/pkg/lint/linter"
"github.com/golangci/golangci-lint/pkg/result"
)
const scopelintName = "scopelint"
func NewScopelint() *goanalysis.Linter {
var mu sync.Mutex
var resIssues []goanalysis.Issue
analyzer := &analysis.Analyzer{
Name: scopelintName,
Doc: goanalysis.TheOnlyanalyzerDoc,
}
return goanalysis.NewLinter(
scopelintName,
"Scopelint checks for unpinned variables in go programs",
[]*analysis.Analyzer{analyzer},
nil,
).WithContextSetter(func(lintCtx *linter.Context) {
analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
var res []result.Issue
for _, file := range pass.Files {
n := Node{
fset: pass.Fset,
DangerObjects: map[*ast.Object]int{},
UnsafeObjects: map[*ast.Object]int{},
SkipFuncs: map[*ast.FuncLit]int{},
issues: &res,
}
ast.Walk(&n, file)
}
if len(res) == 0 {
return nil, nil
}
mu.Lock()
for i := range res {
resIssues = append(resIssues, goanalysis.NewIssue(&res[i], pass))
}
mu.Unlock()
return nil, nil
}
}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
return resIssues
}).WithLoadMode(goanalysis.LoadModeSyntax)
}
// The code below is copy-pasted from https://github.com/kyoh86/scopelint 92cbe2cc9276abda0e309f52cc9e309d407f174e
// Node represents a Node being linted.
type Node struct {
fset *token.FileSet
DangerObjects map[*ast.Object]int
UnsafeObjects map[*ast.Object]int
SkipFuncs map[*ast.FuncLit]int
issues *[]result.Issue
}
// Visit method is invoked for each node encountered by Walk.
// If the result visitor w is not nil, Walk visits each of the children
// of node with the visitor w, followed by a call of w.Visit(nil).
//nolint:gocyclo,gocritic
func (f *Node) Visit(node ast.Node) ast.Visitor {
switch typedNode := node.(type) {
case *ast.ForStmt:
switch init := typedNode.Init.(type) {
case *ast.AssignStmt:
for _, lh := range init.Lhs {
switch tlh := lh.(type) {
case *ast.Ident:
f.UnsafeObjects[tlh.Obj] = 0
}
}
}
case *ast.RangeStmt:
// Memory variables declared in range statement
switch k := typedNode.Key.(type) {
case *ast.Ident:
f.UnsafeObjects[k.Obj] = 0
}
switch v := typedNode.Value.(type) {
case *ast.Ident:
f.UnsafeObjects[v.Obj] = 0
}
case *ast.UnaryExpr:
if typedNode.Op == token.AND {
switch ident := typedNode.X.(type) {
case *ast.Ident:
if _, unsafe := f.UnsafeObjects[ident.Obj]; unsafe {
f.errorf(ident, "Using a reference for the variable on range scope %s", formatCode(ident.Name, nil))
}
}
}
case *ast.Ident:
if _, obj := f.DangerObjects[typedNode.Obj]; obj {
// It is the naked variable in scope of range statement.
f.errorf(node, "Using the variable on range scope %s in function literal", formatCode(typedNode.Name, nil))
break
}
case *ast.CallExpr:
// Ignore func literals that'll be called immediately.
switch funcLit := typedNode.Fun.(type) {
case *ast.FuncLit:
f.SkipFuncs[funcLit] = 0
}
case *ast.FuncLit:
if _, skip := f.SkipFuncs[typedNode]; !skip {
dangers := map[*ast.Object]int{}
for d := range f.DangerObjects {
dangers[d] = 0
}
for u := range f.UnsafeObjects {
dangers[u] = 0
f.UnsafeObjects[u]++
}
return &Node{
fset: f.fset,
DangerObjects: dangers,
UnsafeObjects: f.UnsafeObjects,
SkipFuncs: f.SkipFuncs,
issues: f.issues,
}
}
case *ast.ReturnStmt:
unsafe := map[*ast.Object]int{}
for u := range f.UnsafeObjects {
if f.UnsafeObjects[u] == 0 {
continue
}
unsafe[u] = f.UnsafeObjects[u]
}
return &Node{
fset: f.fset,
DangerObjects: f.DangerObjects,
UnsafeObjects: unsafe,
SkipFuncs: f.SkipFuncs,
issues: f.issues,
}
}
return f
}
// The variadic arguments may start with link and category types,
// and must end with a format string and any arguments.
//nolint:interfacer
func (f *Node) errorf(n ast.Node, format string, args ...interface{}) {
pos := f.fset.Position(n.Pos())
f.errorAtf(pos, format, args...)
}
func (f *Node) errorAtf(pos token.Position, format string, args ...interface{}) {
*f.issues = append(*f.issues, result.Issue{
Pos: pos,
Text: fmt.Sprintf(format, args...),
FromLinter: scopelintName,
})
}