github.com/nozzle/golangci-lint@v1.49.0-nz3/pkg/golinters/scopelint.go (about)

     1  package golinters
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"sync"
     8  
     9  	"golang.org/x/tools/go/analysis"
    10  
    11  	"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
    12  	"github.com/golangci/golangci-lint/pkg/lint/linter"
    13  	"github.com/golangci/golangci-lint/pkg/result"
    14  )
    15  
    16  const scopelintName = "scopelint"
    17  
    18  //nolint:dupl
    19  func NewScopelint() *goanalysis.Linter {
    20  	var mu sync.Mutex
    21  	var resIssues []goanalysis.Issue
    22  
    23  	analyzer := &analysis.Analyzer{
    24  		Name: scopelintName,
    25  		Doc:  goanalysis.TheOnlyanalyzerDoc,
    26  		Run: func(pass *analysis.Pass) (interface{}, error) {
    27  			issues := runScopeLint(pass)
    28  
    29  			if len(issues) == 0 {
    30  				return nil, nil
    31  			}
    32  
    33  			mu.Lock()
    34  			resIssues = append(resIssues, issues...)
    35  			mu.Unlock()
    36  
    37  			return nil, nil
    38  		},
    39  	}
    40  
    41  	return goanalysis.NewLinter(
    42  		scopelintName,
    43  		"Scopelint checks for unpinned variables in go programs",
    44  		[]*analysis.Analyzer{analyzer},
    45  		nil,
    46  	).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
    47  		return resIssues
    48  	}).WithLoadMode(goanalysis.LoadModeSyntax)
    49  }
    50  
    51  func runScopeLint(pass *analysis.Pass) []goanalysis.Issue {
    52  	var lintIssues []result.Issue
    53  
    54  	for _, file := range pass.Files {
    55  		n := Node{
    56  			fset:          pass.Fset,
    57  			DangerObjects: map[*ast.Object]int{},
    58  			UnsafeObjects: map[*ast.Object]int{},
    59  			SkipFuncs:     map[*ast.FuncLit]int{},
    60  			issues:        &lintIssues,
    61  		}
    62  		ast.Walk(&n, file)
    63  	}
    64  
    65  	var issues []goanalysis.Issue
    66  	for i := range lintIssues {
    67  		issues = append(issues, goanalysis.NewIssue(&lintIssues[i], pass))
    68  	}
    69  
    70  	return issues
    71  }
    72  
    73  // The code below is copy-pasted from https://github.com/kyoh86/scopelint 92cbe2cc9276abda0e309f52cc9e309d407f174e
    74  
    75  // Node represents a Node being linted.
    76  type Node struct {
    77  	fset          *token.FileSet
    78  	DangerObjects map[*ast.Object]int
    79  	UnsafeObjects map[*ast.Object]int
    80  	SkipFuncs     map[*ast.FuncLit]int
    81  	issues        *[]result.Issue
    82  }
    83  
    84  // Visit method is invoked for each node encountered by Walk.
    85  // If the result visitor w is not nil, Walk visits each of the children
    86  // of node with the visitor w, followed by a call of w.Visit(nil).
    87  //
    88  //nolint:gocyclo,gocritic
    89  func (f *Node) Visit(node ast.Node) ast.Visitor {
    90  	switch typedNode := node.(type) {
    91  	case *ast.ForStmt:
    92  		switch init := typedNode.Init.(type) {
    93  		case *ast.AssignStmt:
    94  			for _, lh := range init.Lhs {
    95  				switch tlh := lh.(type) {
    96  				case *ast.Ident:
    97  					f.UnsafeObjects[tlh.Obj] = 0
    98  				}
    99  			}
   100  		}
   101  
   102  	case *ast.RangeStmt:
   103  		// Memory variables declared in range statement
   104  		switch k := typedNode.Key.(type) {
   105  		case *ast.Ident:
   106  			f.UnsafeObjects[k.Obj] = 0
   107  		}
   108  		switch v := typedNode.Value.(type) {
   109  		case *ast.Ident:
   110  			f.UnsafeObjects[v.Obj] = 0
   111  		}
   112  
   113  	case *ast.UnaryExpr:
   114  		if typedNode.Op == token.AND {
   115  			switch ident := typedNode.X.(type) {
   116  			case *ast.Ident:
   117  				if _, unsafe := f.UnsafeObjects[ident.Obj]; unsafe {
   118  					f.errorf(ident, "Using a reference for the variable on range scope %s", formatCode(ident.Name, nil))
   119  				}
   120  			}
   121  		}
   122  
   123  	case *ast.Ident:
   124  		if _, obj := f.DangerObjects[typedNode.Obj]; obj {
   125  			// It is the naked variable in scope of range statement.
   126  			f.errorf(node, "Using the variable on range scope %s in function literal", formatCode(typedNode.Name, nil))
   127  			break
   128  		}
   129  
   130  	case *ast.CallExpr:
   131  		// Ignore func literals that'll be called immediately.
   132  		switch funcLit := typedNode.Fun.(type) {
   133  		case *ast.FuncLit:
   134  			f.SkipFuncs[funcLit] = 0
   135  		}
   136  
   137  	case *ast.FuncLit:
   138  		if _, skip := f.SkipFuncs[typedNode]; !skip {
   139  			dangers := map[*ast.Object]int{}
   140  			for d := range f.DangerObjects {
   141  				dangers[d] = 0
   142  			}
   143  			for u := range f.UnsafeObjects {
   144  				dangers[u] = 0
   145  				f.UnsafeObjects[u]++
   146  			}
   147  			return &Node{
   148  				fset:          f.fset,
   149  				DangerObjects: dangers,
   150  				UnsafeObjects: f.UnsafeObjects,
   151  				SkipFuncs:     f.SkipFuncs,
   152  				issues:        f.issues,
   153  			}
   154  		}
   155  
   156  	case *ast.ReturnStmt:
   157  		unsafe := map[*ast.Object]int{}
   158  		for u := range f.UnsafeObjects {
   159  			if f.UnsafeObjects[u] == 0 {
   160  				continue
   161  			}
   162  			unsafe[u] = f.UnsafeObjects[u]
   163  		}
   164  		return &Node{
   165  			fset:          f.fset,
   166  			DangerObjects: f.DangerObjects,
   167  			UnsafeObjects: unsafe,
   168  			SkipFuncs:     f.SkipFuncs,
   169  			issues:        f.issues,
   170  		}
   171  	}
   172  	return f
   173  }
   174  
   175  // The variadic arguments may start with link and category types,
   176  // and must end with a format string and any arguments.
   177  //
   178  //nolint:interfacer
   179  func (f *Node) errorf(n ast.Node, format string, args ...interface{}) {
   180  	pos := f.fset.Position(n.Pos())
   181  	f.errorAtf(pos, format, args...)
   182  }
   183  
   184  func (f *Node) errorAtf(pos token.Position, format string, args ...interface{}) {
   185  	*f.issues = append(*f.issues, result.Issue{
   186  		Pos:        pos,
   187  		Text:       fmt.Sprintf(format, args...),
   188  		FromLinter: scopelintName,
   189  	})
   190  }