gitee.com/mirrors/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/deadcode/deadcode.go (about)

     1  package deadcode
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/parser"
     7  	"go/token"
     8  	"os"
     9  	"sort"
    10  	"strings"
    11  )
    12  
    13  var exitCode int
    14  
    15  func DeadCode(projectPath string) []string {
    16  	return doDir(projectPath)
    17  }
    18  
    19  // error formats the error to standard error, adding program
    20  // identification and a newline
    21  func errorf(format string, args ...interface{}) {
    22  	fmt.Fprintf(os.Stderr, "deadcode: "+format+"\n", args...)
    23  	exitCode = 2
    24  }
    25  
    26  func doDir(name string) []string {
    27  	deadCodes := make([]string, 0)
    28  	notests := func(info os.FileInfo) bool {
    29  		if !info.IsDir() && strings.HasSuffix(info.Name(), ".go") &&
    30  			!strings.HasSuffix(info.Name(), "_test.go") {
    31  			return true
    32  		}
    33  		return false
    34  	}
    35  	fs := token.NewFileSet()
    36  	pkgs, err := parser.ParseDir(fs, name, notests, parser.Mode(0))
    37  	if err != nil {
    38  		errorf("%s", err)
    39  		return nil
    40  	}
    41  	for _, pkg := range pkgs {
    42  		deadCodes = append(deadCodes, doPackage(fs, pkg)...)
    43  	}
    44  	return deadCodes
    45  }
    46  
    47  type Package struct {
    48  	p    *ast.Package
    49  	fs   *token.FileSet
    50  	decl map[string]ast.Node
    51  	used map[string]bool
    52  }
    53  
    54  func doPackage(fs *token.FileSet, pkg *ast.Package) []string {
    55  	p := &Package{
    56  		p:    pkg,
    57  		fs:   fs,
    58  		decl: make(map[string]ast.Node),
    59  		used: make(map[string]bool),
    60  	}
    61  	for _, file := range pkg.Files {
    62  		for _, decl := range file.Decls {
    63  			switch n := decl.(type) {
    64  			case *ast.GenDecl:
    65  				// var, const, types
    66  				for _, spec := range n.Specs {
    67  					switch s := spec.(type) {
    68  					case *ast.ValueSpec:
    69  						// constants and variables.
    70  						for _, name := range s.Names {
    71  							p.decl[name.Name] = n
    72  						}
    73  					case *ast.TypeSpec:
    74  						// type definitions.
    75  						p.decl[s.Name.Name] = n
    76  					}
    77  				}
    78  			case *ast.FuncDecl:
    79  				// function declarations
    80  				// TODO(remy): do methods
    81  				if n.Recv == nil {
    82  					p.decl[n.Name.Name] = n
    83  				}
    84  			}
    85  		}
    86  	}
    87  	// init() and _ are always used
    88  	p.used["init"] = true
    89  	p.used["_"] = true
    90  	if pkg.Name != "main" {
    91  		// exported names are marked used for non-main packages.
    92  		for name := range p.decl {
    93  			if ast.IsExported(name) {
    94  				p.used[name] = true
    95  			}
    96  		}
    97  	} else {
    98  		// in main programs, main() is called.
    99  		p.used["main"] = true
   100  	}
   101  	for _, file := range pkg.Files {
   102  		// walk file looking for used nodes.
   103  		ast.Walk(p, file)
   104  	}
   105  	// reports.
   106  	reports := Reports(nil)
   107  	for name, node := range p.decl {
   108  		if !p.used[name] {
   109  			reports = append(reports, Report{node.Pos(), name})
   110  		}
   111  	}
   112  	sort.Sort(reports)
   113  	deadCode := make([]string, 0)
   114  	for _, report := range reports {
   115  		// errorf("%s: %s is unused", fs.Position(report.pos), report.name)
   116  		deadCode = append(deadCode, fmt.Sprintf("%s: %s is unused", fs.Position(report.pos), report.name))
   117  	}
   118  
   119  	return deadCode
   120  }
   121  
   122  type Report struct {
   123  	pos  token.Pos
   124  	name string
   125  }
   126  type Reports []Report
   127  
   128  func (l Reports) Len() int           { return len(l) }
   129  func (l Reports) Less(i, j int) bool { return l[i].pos < l[j].pos }
   130  func (l Reports) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
   131  
   132  // Visits files for used nodes.
   133  func (p *Package) Visit(node ast.Node) ast.Visitor {
   134  	u := usedWalker(*p) // hopefully p fields are references.
   135  	switch n := node.(type) {
   136  	// don't walk whole file, but only:
   137  	case *ast.ValueSpec:
   138  		// - variable initializers
   139  		for _, value := range n.Values {
   140  			ast.Walk(&u, value)
   141  		}
   142  		// variable types.
   143  		if n.Type != nil {
   144  			ast.Walk(&u, n.Type)
   145  		}
   146  	case *ast.BlockStmt:
   147  		// - function bodies
   148  		for _, stmt := range n.List {
   149  			ast.Walk(&u, stmt)
   150  		}
   151  	case *ast.FuncDecl:
   152  		// - function signatures
   153  		ast.Walk(&u, n.Type)
   154  	case *ast.TypeSpec:
   155  		// - type declarations
   156  		ast.Walk(&u, n.Type)
   157  	}
   158  	return p
   159  }
   160  
   161  type usedWalker Package
   162  
   163  // Walks through the AST marking used identifiers.
   164  func (p *usedWalker) Visit(node ast.Node) ast.Visitor {
   165  	// just be stupid and mark all *ast.Ident
   166  	switch n := node.(type) {
   167  	case *ast.Ident:
   168  		p.used[n.Name] = true
   169  	}
   170  	return p
   171  }