aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/golangci/prealloc/prealloc.go
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2020-07-04 11:12:55 +0200
committerDmitry Vyukov <dvyukov@google.com>2020-07-04 15:05:30 +0200
commitc7d7f10bdff703e4a3c0414e8a33d4e45c91eb35 (patch)
tree0dff0ee1f98dbfa3ad8776112053a450d176592b /vendor/github.com/golangci/prealloc/prealloc.go
parent9573094ce235bd9afe88f5da27a47dd6bcc1e13b (diff)
go.mod: vendor golangci-lint
Diffstat (limited to 'vendor/github.com/golangci/prealloc/prealloc.go')
-rw-r--r--vendor/github.com/golangci/prealloc/prealloc.go403
1 files changed, 403 insertions, 0 deletions
diff --git a/vendor/github.com/golangci/prealloc/prealloc.go b/vendor/github.com/golangci/prealloc/prealloc.go
new file mode 100644
index 000000000..1235ad363
--- /dev/null
+++ b/vendor/github.com/golangci/prealloc/prealloc.go
@@ -0,0 +1,403 @@
+package prealloc
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/build"
+ "go/parser"
+ "go/token"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// Support: (in order of priority)
+// * Full make suggestion with type?
+// * Test flag
+// * Embedded ifs?
+// * Use an import rather than the duplcated import.go
+
+const (
+ pwd = "./"
+)
+
+func usage() {
+ log.Printf("Usage of %s:\n", os.Args[0])
+ log.Printf("\nprealloc [flags] # runs on package in current directory\n")
+ log.Printf("\nprealloc [flags] [packages]\n")
+ log.Printf("Flags:\n")
+ flag.PrintDefaults()
+}
+
+type sliceDeclaration struct {
+ name string
+ // sType string
+ genD *ast.GenDecl
+}
+
+type returnsVisitor struct {
+ // flags
+ simple bool
+ includeRangeLoops bool
+ includeForLoops bool
+ // visitor fields
+ sliceDeclarations []*sliceDeclaration
+ preallocHints []Hint
+ returnsInsideOfLoop bool
+ arrayTypes []string
+}
+
+func NoMain() {
+
+ // Remove log timestamp
+ log.SetFlags(0)
+
+ simple := flag.Bool("simple", true, "Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them")
+ includeRangeLoops := flag.Bool("rangeloops", true, "Report preallocation suggestions on range loops")
+ includeForLoops := flag.Bool("forloops", false, "Report preallocation suggestions on for loops")
+ setExitStatus := flag.Bool("set_exit_status", false, "Set exit status to 1 if any issues are found")
+ flag.Usage = usage
+ flag.Parse()
+
+ hints, err := checkForPreallocations(flag.Args(), simple, includeRangeLoops, includeForLoops)
+ if err != nil {
+ log.Println(err)
+ }
+
+ for _, hint := range hints {
+ log.Println(hint)
+ }
+ if *setExitStatus && len(hints) > 0 {
+ os.Exit(1)
+ }
+}
+
+func checkForPreallocations(args []string, simple, includeRangeLoops *bool, includeForLoops *bool) ([]Hint, error) {
+
+ fset := token.NewFileSet()
+
+ files, err := parseInput(args, fset)
+ if err != nil {
+ return nil, fmt.Errorf("could not parse input %v", err)
+ }
+
+ if simple == nil {
+ return nil, errors.New("simple nil")
+ }
+
+ if includeRangeLoops == nil {
+ return nil, errors.New("includeRangeLoops nil")
+ }
+
+ if includeForLoops == nil {
+ return nil, errors.New("includeForLoops nil")
+ }
+
+ hints := []Hint{}
+ for _, f := range files {
+ retVis := &returnsVisitor{
+ simple: *simple,
+ includeRangeLoops: *includeRangeLoops,
+ includeForLoops: *includeForLoops,
+ }
+ ast.Walk(retVis, f)
+ // if simple is true, then we actually have to check if we had returns
+ // inside of our loop. Otherwise, we can just report all messages.
+ if !retVis.simple || !retVis.returnsInsideOfLoop {
+ hints = append(hints, retVis.preallocHints...)
+ }
+ }
+
+ return hints, nil
+}
+
+func Check(files []*ast.File, simple, includeRangeLoops, includeForLoops bool) []Hint {
+ hints := []Hint{}
+ for _, f := range files {
+ retVis := &returnsVisitor{
+ simple: simple,
+ includeRangeLoops: includeRangeLoops,
+ includeForLoops: includeForLoops,
+ }
+ ast.Walk(retVis, f)
+
+ // if simple is true, then we actually have to check if we had returns
+ // inside of our loop. Otherwise, we can just report all messages.
+ if !retVis.simple || !retVis.returnsInsideOfLoop {
+ hints = append(hints, retVis.preallocHints...)
+ }
+ }
+
+ return hints
+}
+
+func parseInput(args []string, fset *token.FileSet) ([]*ast.File, error) {
+ var directoryList []string
+ var fileMode bool
+ files := make([]*ast.File, 0)
+
+ if len(args) == 0 {
+ directoryList = append(directoryList, pwd)
+ } else {
+ for _, arg := range args {
+ if strings.HasSuffix(arg, "/...") && isDir(arg[:len(arg)-len("/...")]) {
+
+ for _, dirname := range allPackagesInFS(arg) {
+ directoryList = append(directoryList, dirname)
+ }
+
+ } else if isDir(arg) {
+ directoryList = append(directoryList, arg)
+
+ } else if exists(arg) {
+ if strings.HasSuffix(arg, ".go") {
+ fileMode = true
+ f, err := parser.ParseFile(fset, arg, nil, 0)
+ if err != nil {
+ return nil, err
+ }
+ files = append(files, f)
+ } else {
+ return nil, fmt.Errorf("invalid file %v specified", arg)
+ }
+ } else {
+
+ //TODO clean this up a bit
+ imPaths := importPaths([]string{arg})
+ for _, importPath := range imPaths {
+ pkg, err := build.Import(importPath, ".", 0)
+ if err != nil {
+ return nil, err
+ }
+ var stringFiles []string
+ stringFiles = append(stringFiles, pkg.GoFiles...)
+ // files = append(files, pkg.CgoFiles...)
+ stringFiles = append(stringFiles, pkg.TestGoFiles...)
+ if pkg.Dir != "." {
+ for i, f := range stringFiles {
+ stringFiles[i] = filepath.Join(pkg.Dir, f)
+ }
+ }
+
+ fileMode = true
+ for _, stringFile := range stringFiles {
+ f, err := parser.ParseFile(fset, stringFile, nil, 0)
+ if err != nil {
+ return nil, err
+ }
+ files = append(files, f)
+ }
+
+ }
+ }
+ }
+ }
+
+ // if we're not in file mode, then we need to grab each and every package in each directory
+ // we can to grab all the files
+ if !fileMode {
+ for _, fpath := range directoryList {
+ pkgs, err := parser.ParseDir(fset, fpath, nil, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, pkg := range pkgs {
+ for _, f := range pkg.Files {
+ files = append(files, f)
+ }
+ }
+ }
+ }
+
+ return files, nil
+}
+
+func isDir(filename string) bool {
+ fi, err := os.Stat(filename)
+ return err == nil && fi.IsDir()
+}
+
+func exists(filename string) bool {
+ _, err := os.Stat(filename)
+ return err == nil
+}
+
+func contains(slice []string, item string) bool {
+ for _, s := range slice {
+ if s == item {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor {
+
+ v.sliceDeclarations = nil
+ v.returnsInsideOfLoop = false
+
+ switch n := node.(type) {
+ case *ast.TypeSpec:
+ if _, ok := n.Type.(*ast.ArrayType); ok {
+ if n.Name != nil {
+ v.arrayTypes = append(v.arrayTypes, n.Name.Name)
+ }
+ }
+ case *ast.FuncDecl:
+ if n.Body != nil {
+ for _, stmt := range n.Body.List {
+ switch s := stmt.(type) {
+ // Find non pre-allocated slices
+ case *ast.DeclStmt:
+ genD, ok := s.Decl.(*ast.GenDecl)
+ if !ok {
+ continue
+ }
+ if genD.Tok == token.TYPE {
+ for _, spec := range genD.Specs {
+ tSpec, ok := spec.(*ast.TypeSpec)
+ if !ok {
+ continue
+ }
+
+ if _, ok := tSpec.Type.(*ast.ArrayType); ok {
+ if tSpec.Name != nil {
+ v.arrayTypes = append(v.arrayTypes, tSpec.Name.Name)
+ }
+ }
+ }
+ } else if genD.Tok == token.VAR {
+ for _, spec := range genD.Specs {
+ vSpec, ok := spec.(*ast.ValueSpec)
+ if !ok {
+ continue
+ }
+ var isArrType bool
+ switch val := vSpec.Type.(type) {
+ case *ast.ArrayType:
+ isArrType = true
+ case *ast.Ident:
+ isArrType = contains(v.arrayTypes, val.Name)
+ }
+ if isArrType {
+ if vSpec.Names != nil {
+ /*atID, ok := arrayType.Elt.(*ast.Ident)
+ if !ok {
+ continue
+ }*/
+
+ // We should handle multiple slices declared on same line e.g. var mySlice1, mySlice2 []uint32
+ for _, vName := range vSpec.Names {
+ v.sliceDeclarations = append(v.sliceDeclarations, &sliceDeclaration{name: vName.Name /*sType: atID.Name,*/, genD: genD})
+ }
+ }
+ }
+ }
+ }
+
+ case *ast.RangeStmt:
+ if v.includeRangeLoops {
+ if len(v.sliceDeclarations) == 0 {
+ continue
+ }
+ if s.Body != nil {
+ v.handleLoops(s.Body)
+ }
+ }
+
+ case *ast.ForStmt:
+ if v.includeForLoops {
+ if len(v.sliceDeclarations) == 0 {
+ continue
+ }
+ if s.Body != nil {
+ v.handleLoops(s.Body)
+ }
+ }
+
+ default:
+ }
+ }
+ }
+ }
+ return v
+}
+
+// handleLoops is a helper function to share the logic required for both *ast.RangeLoops and *ast.ForLoops
+func (v *returnsVisitor) handleLoops(blockStmt *ast.BlockStmt) {
+
+ for _, stmt := range blockStmt.List {
+ switch bodyStmt := stmt.(type) {
+ case *ast.AssignStmt:
+ asgnStmt := bodyStmt
+ for _, expr := range asgnStmt.Rhs {
+ callExpr, ok := expr.(*ast.CallExpr)
+ if !ok {
+ continue // should this be break? comes back to multi-call support I think
+ }
+ ident, ok := callExpr.Fun.(*ast.Ident)
+ if !ok {
+ continue
+ }
+ if ident.Name == "append" {
+ // see if this append is appending the slice we found
+ for _, lhsExpr := range asgnStmt.Lhs {
+ lhsIdent, ok := lhsExpr.(*ast.Ident)
+ if !ok {
+ continue
+ }
+ for _, sliceDecl := range v.sliceDeclarations {
+ if sliceDecl.name == lhsIdent.Name {
+ // This is a potential mark, we just need to make sure there are no returns/continues in the
+ // range loop.
+ // now we just need to grab whatever we're ranging over
+ /*sxIdent, ok := s.X.(*ast.Ident)
+ if !ok {
+ continue
+ }*/
+
+ v.preallocHints = append(v.preallocHints, Hint{
+ Pos: sliceDecl.genD.Pos(),
+ DeclaredSliceName: sliceDecl.name,
+ })
+ }
+ }
+ }
+
+ }
+ }
+ case *ast.IfStmt:
+ ifStmt := bodyStmt
+ if ifStmt.Body != nil {
+ for _, ifBodyStmt := range ifStmt.Body.List {
+ // TODO should probably handle embedded ifs here
+ switch /*ift :=*/ ifBodyStmt.(type) {
+ case *ast.BranchStmt, *ast.ReturnStmt:
+ v.returnsInsideOfLoop = true
+ default:
+ }
+ }
+ }
+
+ default:
+
+ }
+ }
+
+}
+
+// Hint stores the information about an occurence of a slice that could be
+// preallocated.
+type Hint struct {
+ Pos token.Pos
+ DeclaredSliceName string
+}
+
+func (h Hint) String() string {
+ return fmt.Sprintf("%v: Consider preallocating %v", h.Pos, h.DeclaredSliceName)
+}