pkger/parser/visitor.go

384 lines
6.0 KiB
Go

package parser
import (
"go/ast"
"go/token"
"path/filepath"
"strconv"
"github.com/markbates/pkger/here"
)
type visitor func(node ast.Node) (w ast.Visitor)
func (v visitor) Visit(node ast.Node) ast.Visitor {
return v(node)
}
// inspired by https://gist.github.com/cryptix/d1b129361cea51a59af2
type file struct {
fset *token.FileSet
astFile *ast.File
filename string
decls Decls
current here.Info
}
func (f *file) walk(fn func(ast.Node) bool) {
ast.Walk(walker(fn), f.astFile)
}
func (f *file) find() (Decls, error) {
// --- virtual calls first ---
if err := f.findMkdirAllCalls(); err != nil {
return nil, err
}
if err := f.findCreateCalls(); err != nil {
return nil, err
}
// -- physical calls second ---
if err := f.findStatCalls(); err != nil {
return nil, err
}
if err := f.findOpenCalls(); err != nil {
return nil, err
}
if err := f.findHTTPCalls(); err != nil {
return nil, err
}
if err := f.findWalkCalls(); err != nil {
return nil, err
}
return f.decls, nil
}
func (f *file) asValue(node ast.Node) (string, error) {
var s string
switch x := node.(type) {
case *ast.BasicLit:
s = x.Value
case *ast.Ident:
s = x.Name
}
return strconv.Unquote(s)
}
func (f *file) findMkdirAllCalls() error {
var err error
f.walk(func(node ast.Node) bool {
ce, ok := node.(*ast.CallExpr)
if !ok {
return true
}
exists := isPkgDot(ce.Fun, "pkger", "MkdirAll")
if !(exists) || len(ce.Args) != 2 {
return true
}
n := ce.Args[0]
s, err := f.asValue(n)
if err != nil {
return false
}
info, err := here.Dir(filepath.Dir(f.filename))
if err != nil {
return false
}
pf := &File{
Abs: f.filename,
Here: info,
Path: here.Path{
Pkg: info.Module.Path,
Name: s,
},
}
decl := MkdirAllDecl{
file: pf,
pos: f.fset.Position(n.Pos()),
value: s,
}
f.decls = append(f.decls, decl)
return true
})
return err
}
func (f *file) findStatCalls() error {
var err error
f.walk(func(node ast.Node) bool {
ce, ok := node.(*ast.CallExpr)
if !ok {
return true
}
exists := isPkgDot(ce.Fun, "pkger", "Stat")
if !(exists) || len(ce.Args) != 1 {
return true
}
n := ce.Args[0]
s, err := f.asValue(n)
if err != nil {
return false
}
info, err := here.Dir(filepath.Dir(f.filename))
if err != nil {
return false
}
pt, err := info.Parse(s)
if err != nil {
return false
}
if pt.Pkg != info.Module.Path {
info, err = here.Package(pt.Pkg)
if err != nil {
return false
}
}
pf := &File{
Abs: f.filename,
Here: info,
Path: pt,
}
decl := StatDecl{
file: pf,
pos: f.fset.Position(n.Pos()),
value: s,
}
f.decls = append(f.decls, decl)
return true
})
return err
}
func (f *file) findCreateCalls() error {
var err error
f.walk(func(node ast.Node) bool {
ce, ok := node.(*ast.CallExpr)
if !ok {
return true
}
exists := isPkgDot(ce.Fun, "pkger", "Create")
if !(exists) || len(ce.Args) != 1 {
return true
}
n := ce.Args[0]
s, err := f.asValue(n)
if err != nil {
return false
}
info, err := here.Dir(filepath.Dir(f.filename))
if err != nil {
return false
}
pf := &File{
Abs: f.filename,
Here: info,
Path: here.Path{
Pkg: info.Module.Path,
Name: s,
},
}
decl := CreateDecl{
file: pf,
pos: f.fset.Position(n.Pos()),
value: s,
}
f.decls = append(f.decls, decl)
return true
})
return err
}
func (f *file) findOpenCalls() error {
var err error
f.walk(func(node ast.Node) bool {
ce, ok := node.(*ast.CallExpr)
if !ok {
return true
}
exists := isPkgDot(ce.Fun, "pkger", "Open")
if !(exists) || len(ce.Args) != 1 {
return true
}
n := ce.Args[0]
s, err := f.asValue(n)
if err != nil {
return false
}
info, err := here.Dir(filepath.Dir(f.filename))
if err != nil {
return false
}
pf := &File{
Abs: f.filename,
Here: info,
Path: here.Path{
Pkg: info.Module.Path,
Name: s,
},
}
decl := OpenDecl{
file: pf,
pos: f.fset.Position(n.Pos()),
value: s,
}
f.decls = append(f.decls, decl)
return true
})
return err
}
func (f *file) findWalkCalls() error {
var err error
f.walk(func(node ast.Node) bool {
ce, ok := node.(*ast.CallExpr)
if !ok {
return true
}
exists := isPkgDot(ce.Fun, "pkger", "Walk")
if !(exists) || len(ce.Args) != 2 {
return true
}
n := ce.Args[0]
s, err := f.asValue(n)
if err != nil {
err = err
return false
}
dir := filepath.Dir(f.filename)
info, err := here.Dir(dir)
if err != nil {
err = err
return false
}
pf := &File{
Abs: f.filename,
Here: info,
Path: here.Path{
Pkg: info.Module.Path,
Name: s,
},
}
decl := WalkDecl{
file: pf,
pos: f.fset.Position(n.Pos()),
value: s,
}
f.decls = append(f.decls, decl)
return true
})
return err
}
func (f *file) findHTTPCalls() error {
var err error
f.walk(func(node ast.Node) bool {
ce, ok := node.(*ast.CallExpr)
if !ok {
return true
}
exists := isPkgDot(ce.Fun, "pkger", "Dir")
if !(exists) || len(ce.Args) != 1 {
return true
}
n := ce.Args[0]
s, err := f.asValue(n)
if err != nil {
return false
}
info, err := here.Dir(filepath.Dir(f.filename))
if err != nil {
return false
}
pf := &File{
Abs: f.filename,
Here: info,
Path: here.Path{
Pkg: info.Module.Path,
Name: s,
},
}
pos := f.fset.Position(n.Pos())
decl := HTTPDecl{
file: pf,
pos: pos,
value: s,
}
f.decls = append(f.decls, decl)
return true
})
return err
}
// helpers
// =======
func isPkgDot(expr ast.Expr, pkg, name string) bool {
sel, ok := expr.(*ast.SelectorExpr)
return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name)
}
func isIdent(expr ast.Expr, ident string) bool {
id, ok := expr.(*ast.Ident)
return ok && id.Name == ident
}
// wrap a function to fulfill ast.Visitor interface
type walker func(ast.Node) bool
func (w walker) Visit(node ast.Node) ast.Visitor {
if w(node) {
return w
}
return nil
}