workgroups/vendor/github.com/jgautheron/goconst/visitor.go
Marvin Preuss 1d4ae27878
All checks were successful
continuous-integration/drone/push Build is passing
ci: drone yaml with reusable anchors
2021-09-24 17:34:17 +02:00

161 lines
3.5 KiB
Go

package goconst
import (
"go/ast"
"go/token"
"strconv"
"strings"
)
// treeVisitor carries the package name and file name
// for passing it to the imports map, and the fileSet for
// retrieving the token.Position.
type treeVisitor struct {
p *Parser
fileSet *token.FileSet
packageName, fileName string
}
// Visit browses the AST tree for strings that could be potentially
// replaced by constants.
// A map of existing constants is built as well (-match-constant).
func (v *treeVisitor) Visit(node ast.Node) ast.Visitor {
if node == nil {
return v
}
// A single case with "ast.BasicLit" would be much easier
// but then we wouldn't be able to tell in which context
// the string is defined (could be a constant definition).
switch t := node.(type) {
// Scan for constants in an attempt to match strings with existing constants
case *ast.GenDecl:
if !v.p.matchConstant {
return v
}
if t.Tok != token.CONST {
return v
}
for _, spec := range t.Specs {
val := spec.(*ast.ValueSpec)
for i, str := range val.Values {
lit, ok := str.(*ast.BasicLit)
if !ok || !v.isSupported(lit.Kind) {
continue
}
v.addConst(val.Names[i].Name, lit.Value, val.Names[i].Pos())
}
}
// foo := "moo"
case *ast.AssignStmt:
for _, rhs := range t.Rhs {
lit, ok := rhs.(*ast.BasicLit)
if !ok || !v.isSupported(lit.Kind) {
continue
}
v.addString(lit.Value, rhs.(*ast.BasicLit).Pos(), Assignment)
}
// if foo == "moo"
case *ast.BinaryExpr:
if t.Op != token.EQL && t.Op != token.NEQ {
return v
}
var lit *ast.BasicLit
var ok bool
lit, ok = t.X.(*ast.BasicLit)
if ok && v.isSupported(lit.Kind) {
v.addString(lit.Value, lit.Pos(), Binary)
}
lit, ok = t.Y.(*ast.BasicLit)
if ok && v.isSupported(lit.Kind) {
v.addString(lit.Value, lit.Pos(), Binary)
}
// case "foo":
case *ast.CaseClause:
for _, item := range t.List {
lit, ok := item.(*ast.BasicLit)
if ok && v.isSupported(lit.Kind) {
v.addString(lit.Value, lit.Pos(), Case)
}
}
// return "boo"
case *ast.ReturnStmt:
for _, item := range t.Results {
lit, ok := item.(*ast.BasicLit)
if ok && v.isSupported(lit.Kind) {
v.addString(lit.Value, lit.Pos(), Return)
}
}
// fn("http://")
case *ast.CallExpr:
for _, item := range t.Args {
lit, ok := item.(*ast.BasicLit)
if ok && v.isSupported(lit.Kind) {
v.addString(lit.Value, lit.Pos(), Call)
}
}
}
return v
}
// addString adds a string in the map along with its position in the tree.
func (v *treeVisitor) addString(str string, pos token.Pos, typ Type) {
ok, excluded := v.p.excludeTypes[typ]
if ok && excluded {
return
}
// Drop quotes if any
if strings.HasPrefix(str, `"`) || strings.HasPrefix(str, "`") {
str, _ = strconv.Unquote(str)
}
// Ignore empty strings
if len(str) == 0 {
return
}
if len(str) < v.p.minLength {
return
}
_, ok = v.p.strs[str]
if !ok {
v.p.strs[str] = make([]ExtendedPos, 0)
}
v.p.strs[str] = append(v.p.strs[str], ExtendedPos{
packageName: v.packageName,
Position: v.fileSet.Position(pos),
})
}
// addConst adds a const in the map along with its position in the tree.
func (v *treeVisitor) addConst(name string, val string, pos token.Pos) {
val = strings.Replace(val, `"`, "", 2)
v.p.consts[val] = ConstType{
Name: name,
packageName: v.packageName,
Position: v.fileSet.Position(pos),
}
}
func (v *treeVisitor) isSupported(tk token.Token) bool {
for _, s := range v.p.supportedTokens {
if tk == s {
return true
}
}
return false
}