gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/jgautheron/goconst/parser.go (about) 1 // Package goconst finds repeated strings that could be replaced by a constant. 2 // 3 // There are obvious benefits to using constants instead of repeating strings, 4 // mostly to ease maintenance. Cannot argue against changing a single constant versus many strings. 5 // While this could be considered a beginner mistake, across time, 6 // multiple packages and large codebases, some repetition could have slipped in. 7 package goconst 8 9 import ( 10 "go/ast" 11 "go/parser" 12 "go/token" 13 "log" 14 "os" 15 "path/filepath" 16 "regexp" 17 "strings" 18 ) 19 20 const ( 21 testSuffix = "_test.go" 22 ) 23 24 type Parser struct { 25 // Meant to be passed via New() 26 path, ignore string 27 ignoreTests, matchConstant bool 28 minLength int 29 30 supportedTokens []token.Token 31 32 // Internals 33 strs Strings 34 consts Constants 35 } 36 37 // New creates a new instance of the parser. 38 // This is your entry point if you'd like to use goconst as an API. 39 func New(path, ignore string, ignoreTests, matchConstant, numbers bool, minLength int) *Parser { 40 supportedTokens := []token.Token{token.STRING} 41 if numbers { 42 supportedTokens = append(supportedTokens, token.INT, token.FLOAT) 43 } 44 45 return &Parser{ 46 path: path, 47 ignore: ignore, 48 ignoreTests: ignoreTests, 49 matchConstant: matchConstant, 50 minLength: minLength, 51 supportedTokens: supportedTokens, 52 53 // Initialize the maps 54 strs: Strings{}, 55 consts: Constants{}, 56 } 57 } 58 59 // ParseTree will search the given path for occurrences that could be moved into constants. 60 // If "..." is appended, the search will be recursive. 61 func (p *Parser) ParseTree() (Strings, Constants, error) { 62 pathLen := len(p.path) 63 // Parse recursively the given path if the recursive notation is found 64 if pathLen >= 5 && p.path[pathLen-3:] == "..." { 65 filepath.Walk(p.path[:pathLen-3], func(path string, f os.FileInfo, err error) error { 66 if err != nil { 67 log.Println(err) 68 // resume walking 69 return nil 70 } 71 72 if f.IsDir() { 73 p.parseDir(path) 74 } 75 return nil 76 }) 77 } else { 78 p.parseDir(p.path) 79 } 80 return p.strs, p.consts, nil 81 } 82 83 func (p *Parser) parseDir(dir string) error { 84 fset := token.NewFileSet() 85 pkgs, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool { 86 valid, name := true, info.Name() 87 88 if p.ignoreTests { 89 if strings.HasSuffix(name, testSuffix) { 90 valid = false 91 } 92 } 93 94 if len(p.ignore) != 0 { 95 match, err := regexp.MatchString(p.ignore, dir+name) 96 if err != nil { 97 log.Fatal(err) 98 return true 99 } 100 if match { 101 valid = false 102 } 103 } 104 105 return valid 106 }, 0) 107 if err != nil { 108 return err 109 } 110 111 for _, pkg := range pkgs { 112 for fn, f := range pkg.Files { 113 ast.Walk(&treeVisitor{ 114 fileSet: fset, 115 packageName: pkg.Name, 116 fileName: fn, 117 p: p, 118 }, f) 119 } 120 } 121 122 return nil 123 } 124 125 type Strings map[string][]ExtendedPos 126 type Constants map[string]ConstType 127 128 type ConstType struct { 129 token.Position 130 Name, packageName string 131 } 132 133 type ExtendedPos struct { 134 token.Position 135 packageName string 136 }