fix #243: support Scopelint linter
This commit is contained in:
parent
ccac35a87e
commit
84c9c65f39
2
Gopkg.lock
generated
2
Gopkg.lock
generated
@ -555,7 +555,7 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:aac721c5ec779073b35cf7a51b7764d13f6db2243d650965d2c71469e8994e5b"
|
digest = "1:c09b8b20f35b802fa418b0715e3147d6366b706c6003bbdef9df78272ffe80a5"
|
||||||
name = "golang.org/x/tools"
|
name = "golang.org/x/tools"
|
||||||
packages = [
|
packages = [
|
||||||
"go/ast/astutil",
|
"go/ast/astutil",
|
||||||
|
@ -199,6 +199,7 @@ lll: Reports long lines [fast: true]
|
|||||||
unparam: Reports unused function parameters [fast: false]
|
unparam: Reports unused function parameters [fast: false]
|
||||||
nakedret: Finds naked returns in functions greater than a specified function length [fast: true]
|
nakedret: Finds naked returns in functions greater than a specified function length [fast: true]
|
||||||
prealloc: Finds slice declarations that could potentially be preallocated [fast: true]
|
prealloc: Finds slice declarations that could potentially be preallocated [fast: true]
|
||||||
|
scopelint: Scopelint checks for unpinned variables in go programs [fast: true]
|
||||||
```
|
```
|
||||||
|
|
||||||
Pass `-E/--enable` to enable linter and `-D/--disable` to disable:
|
Pass `-E/--enable` to enable linter and `-D/--disable` to disable:
|
||||||
@ -398,6 +399,7 @@ golangci-lint help linters
|
|||||||
- [unparam](https://github.com/mvdan/unparam) - Reports unused function parameters
|
- [unparam](https://github.com/mvdan/unparam) - Reports unused function parameters
|
||||||
- [nakedret](https://github.com/alexkohler/nakedret) - Finds naked returns in functions greater than a specified function length
|
- [nakedret](https://github.com/alexkohler/nakedret) - Finds naked returns in functions greater than a specified function length
|
||||||
- [prealloc](https://github.com/alexkohler/prealloc) - Finds slice declarations that could potentially be preallocated
|
- [prealloc](https://github.com/alexkohler/prealloc) - Finds slice declarations that could potentially be preallocated
|
||||||
|
- [scopelint](https://github.com/kyoh86/scopelint) - Scopelint checks for unpinned variables in go programs
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@ -807,6 +809,7 @@ Thanks to developers and authors of used linters:
|
|||||||
- [client9](https://github.com/client9)
|
- [client9](https://github.com/client9)
|
||||||
- [walle](https://github.com/walle)
|
- [walle](https://github.com/walle)
|
||||||
- [alexkohler](https://github.com/alexkohler)
|
- [alexkohler](https://github.com/alexkohler)
|
||||||
|
- [kyoh86](https://github.com/kyoh86)
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
139
pkg/golinters/scopelint.go
Normal file
139
pkg/golinters/scopelint.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package golinters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
|
||||||
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Scopelint struct{}
|
||||||
|
|
||||||
|
func (Scopelint) Name() string {
|
||||||
|
return "scopelint"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Scopelint) Desc() string {
|
||||||
|
return "Scopelint checks for unpinned variables in go programs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lint Scopelint) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) {
|
||||||
|
var res []result.Issue
|
||||||
|
|
||||||
|
for _, f := range lintCtx.ASTCache.GetAllValidFiles() {
|
||||||
|
n := Node{
|
||||||
|
fset: f.Fset,
|
||||||
|
dangerObjects: map[*ast.Object]struct{}{},
|
||||||
|
unsafeObjects: map[*ast.Object]struct{}{},
|
||||||
|
skipFuncs: map[*ast.FuncLit]struct{}{},
|
||||||
|
issues: &res,
|
||||||
|
}
|
||||||
|
ast.Walk(&n, f.F)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The code below is copy-pasted from https://github.com/kyoh86/scopelint
|
||||||
|
|
||||||
|
// Node represents a Node being linted.
|
||||||
|
type Node struct {
|
||||||
|
fset *token.FileSet
|
||||||
|
dangerObjects map[*ast.Object]struct{}
|
||||||
|
unsafeObjects map[*ast.Object]struct{}
|
||||||
|
skipFuncs map[*ast.FuncLit]struct{}
|
||||||
|
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
|
||||||
|
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] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.RangeStmt:
|
||||||
|
// Memory variables declarated in range statement
|
||||||
|
switch k := typedNode.Key.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
f.unsafeObjects[k.Obj] = struct{}{}
|
||||||
|
}
|
||||||
|
switch v := typedNode.Value.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
f.unsafeObjects[v.Obj] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ast.FuncLit:
|
||||||
|
if _, skip := f.skipFuncs[typedNode]; !skip {
|
||||||
|
dangers := map[*ast.Object]struct{}{}
|
||||||
|
for d := range f.dangerObjects {
|
||||||
|
dangers[d] = struct{}{}
|
||||||
|
}
|
||||||
|
for u := range f.unsafeObjects {
|
||||||
|
dangers[u] = struct{}{}
|
||||||
|
}
|
||||||
|
return &Node{
|
||||||
|
fset: f.fset,
|
||||||
|
dangerObjects: dangers,
|
||||||
|
unsafeObjects: f.unsafeObjects,
|
||||||
|
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.
|
||||||
|
// It returns the new Problem.
|
||||||
|
//nolint:interfacer
|
||||||
|
func (f *Node) errorf(n ast.Node, format string, args ...interface{}) {
|
||||||
|
pos := f.fset.Position(n.Pos())
|
||||||
|
f.errorfAt(pos, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Node) errorfAt(pos token.Position, format string, args ...interface{}) {
|
||||||
|
*f.issues = append(*f.issues, result.Issue{
|
||||||
|
Pos: pos,
|
||||||
|
Text: fmt.Sprintf(format, args...),
|
||||||
|
FromLinter: Scopelint{}.Name(),
|
||||||
|
})
|
||||||
|
}
|
@ -83,6 +83,7 @@ func TestGetEnabledLintersSet(t *testing.T) {
|
|||||||
m := NewManager()
|
m := NewManager()
|
||||||
es := NewEnabledSet(m, NewValidator(m), nil, nil)
|
es := NewEnabledSet(m, NewValidator(m), nil, nil)
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
|
c := c
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
defaultLinters := []linter.Config{}
|
defaultLinters := []linter.Config{}
|
||||||
for _, ln := range c.def {
|
for _, ln := range c.def {
|
||||||
|
@ -49,6 +49,7 @@ func (m Manager) GetLinterConfig(name string) *linter.Config {
|
|||||||
func enableLinterConfigs(lcs []linter.Config, isEnabled func(lc *linter.Config) bool) []linter.Config {
|
func enableLinterConfigs(lcs []linter.Config, isEnabled func(lc *linter.Config) bool) []linter.Config {
|
||||||
var ret []linter.Config
|
var ret []linter.Config
|
||||||
for _, lc := range lcs {
|
for _, lc := range lcs {
|
||||||
|
lc := lc
|
||||||
lc.EnabledByDefault = isEnabled(&lc)
|
lc.EnabledByDefault = isEnabled(&lc)
|
||||||
ret = append(ret, lc)
|
ret = append(ret, lc)
|
||||||
}
|
}
|
||||||
@ -186,6 +187,10 @@ func (Manager) GetAllSupportedLinterConfigs() []linter.Config {
|
|||||||
WithPresets(linter.PresetPerformance).
|
WithPresets(linter.PresetPerformance).
|
||||||
WithSpeed(8).
|
WithSpeed(8).
|
||||||
WithURL("https://github.com/alexkohler/prealloc"),
|
WithURL("https://github.com/alexkohler/prealloc"),
|
||||||
|
linter.NewConfig(golinters.Scopelint{}).
|
||||||
|
WithPresets(linter.PresetBugs).
|
||||||
|
WithSpeed(8).
|
||||||
|
WithURL("https://github.com/kyoh86/scopelint"),
|
||||||
}
|
}
|
||||||
|
|
||||||
isLocalRun := os.Getenv("GOLANGCI_COM_RUN") == ""
|
isLocalRun := os.Getenv("GOLANGCI_COM_RUN") == ""
|
||||||
|
@ -222,6 +222,7 @@ func (r Runner) processLintResults(inCh <-chan lintRes) <-chan lintRes {
|
|||||||
// finalize processors: logging, clearing, no heavy work here
|
// finalize processors: logging, clearing, no heavy work here
|
||||||
|
|
||||||
for _, p := range r.Processors {
|
for _, p := range r.Processors {
|
||||||
|
p := p
|
||||||
sw.TrackStage(p.Name(), func() {
|
sw.TrackStage(p.Name(), func() {
|
||||||
p.Finish()
|
p.Finish()
|
||||||
})
|
})
|
||||||
@ -273,6 +274,7 @@ func (r *Runner) processIssues(issues []result.Issue, sw *timeutils.Stopwatch) [
|
|||||||
for _, p := range r.Processors {
|
for _, p := range r.Processors {
|
||||||
var newIssues []result.Issue
|
var newIssues []result.Issue
|
||||||
var err error
|
var err error
|
||||||
|
p := p
|
||||||
sw.TrackStage(p.Name(), func() {
|
sw.TrackStage(p.Name(), func() {
|
||||||
newIssues, err = p.Process(issues)
|
newIssues, err = p.Process(issues)
|
||||||
})
|
})
|
||||||
|
@ -33,6 +33,7 @@ func (p *Tab) Print(ctx context.Context, issues <-chan result.Issue) error {
|
|||||||
w := tabwriter.NewWriter(logutils.StdOut, 0, 0, 2, ' ', 0)
|
w := tabwriter.NewWriter(logutils.StdOut, 0, 0, 2, ' ', 0)
|
||||||
|
|
||||||
for i := range issues {
|
for i := range issues {
|
||||||
|
i := i
|
||||||
p.printIssue(&i, w)
|
p.printIssue(&i, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ func (p Text) SprintfColored(ca color.Attribute, format string, args ...interfac
|
|||||||
|
|
||||||
func (p *Text) Print(ctx context.Context, issues <-chan result.Issue) error {
|
func (p *Text) Print(ctx context.Context, issues <-chan result.Issue) error {
|
||||||
for i := range issues {
|
for i := range issues {
|
||||||
|
i := i
|
||||||
p.printIssue(&i)
|
p.printIssue(&i)
|
||||||
|
|
||||||
if !p.printIssuedLine {
|
if !p.printIssuedLine {
|
||||||
|
@ -148,6 +148,7 @@ func (e *rangeExpander) Visit(node ast.Node) ast.Visitor {
|
|||||||
var foundRange *ignoredRange
|
var foundRange *ignoredRange
|
||||||
for _, r := range e.inlineRanges {
|
for _, r := range e.inlineRanges {
|
||||||
if r.To == nodeStartLine-1 && nodeStartPos.Column == r.col {
|
if r.To == nodeStartLine-1 && nodeStartPos.Column == r.col {
|
||||||
|
r := r
|
||||||
foundRange = &r
|
foundRange = &r
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,7 @@ func TestNolintAliases(t *testing.T) {
|
|||||||
|
|
||||||
p := newTestNolintProcessor(getOkLogger(ctrl))
|
p := newTestNolintProcessor(getOkLogger(ctrl))
|
||||||
for _, line := range []int{47, 49, 51} {
|
for _, line := range []int{47, 49, 51} {
|
||||||
|
line := line
|
||||||
t.Run(fmt.Sprintf("line-%d", line), func(t *testing.T) {
|
t.Run(fmt.Sprintf("line-%d", line), func(t *testing.T) {
|
||||||
processAssertEmpty(t, p, newNolintFileIssue(line, "gosec"))
|
processAssertEmpty(t, p, newNolintFileIssue(line, "gosec"))
|
||||||
})
|
})
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
func filterIssues(issues []result.Issue, filter func(i *result.Issue) bool) []result.Issue {
|
func filterIssues(issues []result.Issue, filter func(i *result.Issue) bool) []result.Issue {
|
||||||
retIssues := make([]result.Issue, 0, len(issues))
|
retIssues := make([]result.Issue, 0, len(issues))
|
||||||
for _, i := range issues {
|
for _, i := range issues {
|
||||||
|
i := i
|
||||||
if filter(&i) {
|
if filter(&i) {
|
||||||
retIssues = append(retIssues, i)
|
retIssues = append(retIssues, i)
|
||||||
}
|
}
|
||||||
@ -20,6 +21,7 @@ func filterIssues(issues []result.Issue, filter func(i *result.Issue) bool) []re
|
|||||||
func filterIssuesErr(issues []result.Issue, filter func(i *result.Issue) (bool, error)) ([]result.Issue, error) {
|
func filterIssuesErr(issues []result.Issue, filter func(i *result.Issue) (bool, error)) ([]result.Issue, error) {
|
||||||
retIssues := make([]result.Issue, 0, len(issues))
|
retIssues := make([]result.Issue, 0, len(issues))
|
||||||
for _, i := range issues {
|
for _, i := range issues {
|
||||||
|
i := i
|
||||||
ok, err := filter(&i)
|
ok, err := filter(&i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't filter issue %#v: %s", i, err)
|
return nil, fmt.Errorf("can't filter issue %#v: %s", i, err)
|
||||||
@ -36,6 +38,7 @@ func filterIssuesErr(issues []result.Issue, filter func(i *result.Issue) (bool,
|
|||||||
func transformIssues(issues []result.Issue, transform func(i *result.Issue) *result.Issue) []result.Issue {
|
func transformIssues(issues []result.Issue, transform func(i *result.Issue) *result.Issue) []result.Issue {
|
||||||
retIssues := make([]result.Issue, 0, len(issues))
|
retIssues := make([]result.Issue, 0, len(issues))
|
||||||
for _, i := range issues {
|
for _, i := range issues {
|
||||||
|
i := i
|
||||||
newI := transform(&i)
|
newI := transform(&i)
|
||||||
if newI != nil {
|
if newI != nil {
|
||||||
retIssues = append(retIssues, *newI)
|
retIssues = append(retIssues, *newI)
|
||||||
|
@ -368,6 +368,7 @@ func TestEnabledLinters(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
|
c := c
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
runArgs := []string{"-v"}
|
runArgs := []string{"-v"}
|
||||||
if !c.noImplicitFast {
|
if !c.noImplicitFast {
|
||||||
|
17
test/testdata/scopelint.go
vendored
Normal file
17
test/testdata/scopelint.go
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// args: -Escopelint
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func ScopelintTest() {
|
||||||
|
values := []string{"a", "b", "c"}
|
||||||
|
var funcs []func()
|
||||||
|
for _, val := range values {
|
||||||
|
funcs = append(funcs, func() {
|
||||||
|
fmt.Println(val) // ERROR "Using the variable on range scope `val` in function literal"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, f := range funcs {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user