woodpecker/vendor/github.com/jgautheron/goconst/parser.go
Lukas c28f7cb29f
Add golangci-lint (#502)
Initial part of #435
2021-11-14 21:01:54 +01:00

176 lines
3.8 KiB
Go

// Package goconst finds repeated strings that could be replaced by a constant.
//
// There are obvious benefits to using constants instead of repeating strings,
// mostly to ease maintenance. Cannot argue against changing a single constant versus many strings.
// While this could be considered a beginner mistake, across time,
// multiple packages and large codebases, some repetition could have slipped in.
package goconst
import (
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
const (
testSuffix = "_test.go"
)
type Parser struct {
// Meant to be passed via New()
path, ignore string
ignoreTests, matchConstant bool
minLength, minOccurrences int
numberMin, numberMax int
excludeTypes map[Type]bool
supportedTokens []token.Token
// Internals
strs Strings
consts Constants
}
// New creates a new instance of the parser.
// This is your entry point if you'd like to use goconst as an API.
func New(path, ignore string, ignoreTests, matchConstant, numbers bool, numberMin, numberMax, minLength, minOccurrences int, excludeTypes map[Type]bool) *Parser {
supportedTokens := []token.Token{token.STRING}
if numbers {
supportedTokens = append(supportedTokens, token.INT, token.FLOAT)
}
return &Parser{
path: path,
ignore: ignore,
ignoreTests: ignoreTests,
matchConstant: matchConstant,
minLength: minLength,
minOccurrences: minOccurrences,
numberMin: numberMin,
numberMax: numberMax,
supportedTokens: supportedTokens,
excludeTypes: excludeTypes,
// Initialize the maps
strs: Strings{},
consts: Constants{},
}
}
// ParseTree will search the given path for occurrences that could be moved into constants.
// If "..." is appended, the search will be recursive.
func (p *Parser) ParseTree() (Strings, Constants, error) {
pathLen := len(p.path)
// Parse recursively the given path if the recursive notation is found
if pathLen >= 5 && p.path[pathLen-3:] == "..." {
filepath.Walk(p.path[:pathLen-3], func(path string, f os.FileInfo, err error) error {
if err != nil {
log.Println(err)
// resume walking
return nil
}
if f.IsDir() {
p.parseDir(path)
}
return nil
})
} else {
p.parseDir(p.path)
}
p.ProcessResults()
return p.strs, p.consts, nil
}
// ProcessResults post-processes the raw results.
func (p *Parser) ProcessResults() {
for str, item := range p.strs {
// Filter out items whose occurrences don't match the min value
if len(item) < p.minOccurrences {
delete(p.strs, str)
}
// If the value is a number
if i, err := strconv.Atoi(str); err == nil {
if p.numberMin != 0 && i < p.numberMin {
delete(p.strs, str)
}
if p.numberMax != 0 && i > p.numberMax {
delete(p.strs, str)
}
}
}
}
func (p *Parser) parseDir(dir string) error {
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
valid, name := true, info.Name()
if p.ignoreTests {
if strings.HasSuffix(name, testSuffix) {
valid = false
}
}
if len(p.ignore) != 0 {
match, err := regexp.MatchString(p.ignore, dir+name)
if err != nil {
log.Fatal(err)
return true
}
if match {
valid = false
}
}
return valid
}, 0)
if err != nil {
return err
}
for _, pkg := range pkgs {
for fn, f := range pkg.Files {
ast.Walk(&treeVisitor{
fileSet: fset,
packageName: pkg.Name,
fileName: fn,
p: p,
}, f)
}
}
return nil
}
type Strings map[string][]ExtendedPos
type Constants map[string]ConstType
type ConstType struct {
token.Position
Name, packageName string
}
type ExtendedPos struct {
token.Position
packageName string
}
type Type int
const (
Assignment Type = iota
Binary
Case
Return
Call
)