|  | 
|  | 1 | +package golinters | 
|  | 2 | + | 
|  | 3 | +import ( | 
|  | 4 | +	"context" | 
|  | 5 | +	"fmt" | 
|  | 6 | +	"go/ast" | 
|  | 7 | +	"go/token" | 
|  | 8 | + | 
|  | 9 | +	"github.com/golangci/golangci-lint/pkg/lint/linter" | 
|  | 10 | +	"github.com/golangci/golangci-lint/pkg/result" | 
|  | 11 | +) | 
|  | 12 | + | 
|  | 13 | +type Scopelint struct{} | 
|  | 14 | + | 
|  | 15 | +func (Scopelint) Name() string { | 
|  | 16 | +	return "scopelint" | 
|  | 17 | +} | 
|  | 18 | + | 
|  | 19 | +func (Scopelint) Desc() string { | 
|  | 20 | +	return "Scopelint checks for unpinned variables in go programs" | 
|  | 21 | +} | 
|  | 22 | + | 
|  | 23 | +func (lint Scopelint) Run(ctx context.Context, lintCtx *linter.Context) ([]result.Issue, error) { | 
|  | 24 | +	var res []result.Issue | 
|  | 25 | + | 
|  | 26 | +	for _, f := range lintCtx.ASTCache.GetAllValidFiles() { | 
|  | 27 | +		n := Node{ | 
|  | 28 | +			fset:          f.Fset, | 
|  | 29 | +			dangerObjects: map[*ast.Object]struct{}{}, | 
|  | 30 | +			unsafeObjects: map[*ast.Object]struct{}{}, | 
|  | 31 | +			skipFuncs:     map[*ast.FuncLit]struct{}{}, | 
|  | 32 | +			issues:        &res, | 
|  | 33 | +		} | 
|  | 34 | +		ast.Walk(&n, f.F) | 
|  | 35 | +	} | 
|  | 36 | + | 
|  | 37 | +	return res, nil | 
|  | 38 | +} | 
|  | 39 | + | 
|  | 40 | +// The code below is copy-pasted from https://github.com/kyoh86/scopelint | 
|  | 41 | + | 
|  | 42 | +// Node represents a Node being linted. | 
|  | 43 | +type Node struct { | 
|  | 44 | +	fset          *token.FileSet | 
|  | 45 | +	dangerObjects map[*ast.Object]struct{} | 
|  | 46 | +	unsafeObjects map[*ast.Object]struct{} | 
|  | 47 | +	skipFuncs     map[*ast.FuncLit]struct{} | 
|  | 48 | +	issues        *[]result.Issue | 
|  | 49 | +} | 
|  | 50 | + | 
|  | 51 | +// Visit method is invoked for each node encountered by Walk. | 
|  | 52 | +// If the result visitor w is not nil, Walk visits each of the children | 
|  | 53 | +// of node with the visitor w, followed by a call of w.Visit(nil). | 
|  | 54 | +//nolint:gocyclo | 
|  | 55 | +func (f *Node) Visit(node ast.Node) ast.Visitor { | 
|  | 56 | +	switch typedNode := node.(type) { | 
|  | 57 | +	case *ast.ForStmt: | 
|  | 58 | +		switch init := typedNode.Init.(type) { | 
|  | 59 | +		case *ast.AssignStmt: | 
|  | 60 | +			for _, lh := range init.Lhs { | 
|  | 61 | +				switch tlh := lh.(type) { | 
|  | 62 | +				case *ast.Ident: | 
|  | 63 | +					f.unsafeObjects[tlh.Obj] = struct{}{} | 
|  | 64 | +				} | 
|  | 65 | +			} | 
|  | 66 | +		} | 
|  | 67 | + | 
|  | 68 | +	case *ast.RangeStmt: | 
|  | 69 | +		// Memory variables declarated in range statement | 
|  | 70 | +		switch k := typedNode.Key.(type) { | 
|  | 71 | +		case *ast.Ident: | 
|  | 72 | +			f.unsafeObjects[k.Obj] = struct{}{} | 
|  | 73 | +		} | 
|  | 74 | +		switch v := typedNode.Value.(type) { | 
|  | 75 | +		case *ast.Ident: | 
|  | 76 | +			f.unsafeObjects[v.Obj] = struct{}{} | 
|  | 77 | +		} | 
|  | 78 | + | 
|  | 79 | +	case *ast.UnaryExpr: | 
|  | 80 | +		if typedNode.Op == token.AND { | 
|  | 81 | +			switch ident := typedNode.X.(type) { | 
|  | 82 | +			case *ast.Ident: | 
|  | 83 | +				if _, unsafe := f.unsafeObjects[ident.Obj]; unsafe { | 
|  | 84 | +					f.errorf(ident, "Using a reference for the variable on range scope %s", formatCode(ident.Name, nil)) | 
|  | 85 | +				} | 
|  | 86 | +			} | 
|  | 87 | +		} | 
|  | 88 | + | 
|  | 89 | +	case *ast.Ident: | 
|  | 90 | +		if _, obj := f.dangerObjects[typedNode.Obj]; obj { | 
|  | 91 | +			// It is the naked variable in scope of range statement. | 
|  | 92 | +			f.errorf(node, "Using the variable on range scope %s in function literal", formatCode(typedNode.Name, nil)) | 
|  | 93 | +			break | 
|  | 94 | +		} | 
|  | 95 | + | 
|  | 96 | +	case *ast.CallExpr: | 
|  | 97 | +		// Ignore func literals that'll be called immediately. | 
|  | 98 | +		switch funcLit := typedNode.Fun.(type) { | 
|  | 99 | +		case *ast.FuncLit: | 
|  | 100 | +			f.skipFuncs[funcLit] = struct{}{} | 
|  | 101 | +		} | 
|  | 102 | + | 
|  | 103 | +	case *ast.FuncLit: | 
|  | 104 | +		if _, skip := f.skipFuncs[typedNode]; !skip { | 
|  | 105 | +			dangers := map[*ast.Object]struct{}{} | 
|  | 106 | +			for d := range f.dangerObjects { | 
|  | 107 | +				dangers[d] = struct{}{} | 
|  | 108 | +			} | 
|  | 109 | +			for u := range f.unsafeObjects { | 
|  | 110 | +				dangers[u] = struct{}{} | 
|  | 111 | +			} | 
|  | 112 | +			return &Node{ | 
|  | 113 | +				fset:          f.fset, | 
|  | 114 | +				dangerObjects: dangers, | 
|  | 115 | +				unsafeObjects: f.unsafeObjects, | 
|  | 116 | +				skipFuncs:     f.skipFuncs, | 
|  | 117 | +				issues:        f.issues, | 
|  | 118 | +			} | 
|  | 119 | +		} | 
|  | 120 | +	} | 
|  | 121 | +	return f | 
|  | 122 | +} | 
|  | 123 | + | 
|  | 124 | +// The variadic arguments may start with link and category types, | 
|  | 125 | +// and must end with a format string and any arguments. | 
|  | 126 | +// It returns the new Problem. | 
|  | 127 | +//nolint:interfacer | 
|  | 128 | +func (f *Node) errorf(n ast.Node, format string, args ...interface{}) { | 
|  | 129 | +	pos := f.fset.Position(n.Pos()) | 
|  | 130 | +	f.errorfAt(pos, format, args...) | 
|  | 131 | +} | 
|  | 132 | + | 
|  | 133 | +func (f *Node) errorfAt(pos token.Position, format string, args ...interface{}) { | 
|  | 134 | +	*f.issues = append(*f.issues, result.Issue{ | 
|  | 135 | +		Pos:        pos, | 
|  | 136 | +		Text:       fmt.Sprintf(format, args...), | 
|  | 137 | +		FromLinter: Scopelint{}.Name(), | 
|  | 138 | +	}) | 
|  | 139 | +} | 
0 commit comments