package builder import ( "go/ast" "go/parser" "go/token" "io/ioutil" "sort" "strings" "github.com/pkg/errors" ) type visitor struct { Path string Package string Boxes []string Errors []error } func newVisitor(path string) *visitor { return &visitor{ Path: path, Boxes: []string{}, Errors: []error{}, } } func (v *visitor) Run() error { b, err := ioutil.ReadFile(v.Path) if err != nil { return errors.WithStack(err) } fset := token.NewFileSet() file, err := parser.ParseFile(fset, v.Path, string(b), parser.ParseComments) if err != nil { return errors.WithStack(err) } v.Package = file.Name.Name ast.Walk(v, file) m := map[string]string{} for _, s := range v.Boxes { m[s] = s } v.Boxes = []string{} for k := range m { v.Boxes = append(v.Boxes, k) } sort.Strings(v.Boxes) if len(v.Errors) > 0 { s := make([]string, len(v.Errors)) for i, e := range v.Errors { s[i] = e.Error() } return errors.New(strings.Join(s, "\n")) } return nil } func (v *visitor) Visit(node ast.Node) ast.Visitor { if node == nil { return v } if err := v.eval(node); err != nil { v.Errors = append(v.Errors, err) } return v } func (v *visitor) eval(node ast.Node) error { switch t := node.(type) { case *ast.CallExpr: return v.evalExpr(t) case *ast.Ident: return v.evalIdent(t) case *ast.GenDecl: for _, n := range t.Specs { if err := v.eval(n); err != nil { return errors.WithStack(err) } } case *ast.FuncDecl: if t.Body == nil { return nil } for _, b := range t.Body.List { if err := v.evalStmt(b); err != nil { return errors.WithStack(err) } } return nil case *ast.ValueSpec: for _, e := range t.Values { if err := v.evalExpr(e); err != nil { return errors.WithStack(err) } } } return nil } func (v *visitor) evalStmt(stmt ast.Stmt) error { switch t := stmt.(type) { case *ast.ExprStmt: return v.evalExpr(t.X) case *ast.AssignStmt: for _, e := range t.Rhs { if err := v.evalArgs(e); err != nil { return errors.WithStack(err) } } } return nil } func (v *visitor) evalExpr(expr ast.Expr) error { switch t := expr.(type) { case *ast.CallExpr: if t.Fun == nil { return nil } for _, a := range t.Args { switch at := a.(type) { case *ast.CallExpr: if sel, ok := t.Fun.(*ast.SelectorExpr); ok { return v.evalSelector(at, sel) } if err := v.evalArgs(at); err != nil { return errors.WithStack(err) } case *ast.CompositeLit: for _, e := range at.Elts { if err := v.evalExpr(e); err != nil { return errors.WithStack(err) } } } } if ft, ok := t.Fun.(*ast.SelectorExpr); ok { return v.evalSelector(t, ft) } case *ast.KeyValueExpr: return v.evalExpr(t.Value) } return nil } func (v *visitor) evalArgs(expr ast.Expr) error { switch at := expr.(type) { case *ast.CompositeLit: for _, e := range at.Elts { if err := v.evalExpr(e); err != nil { return errors.WithStack(err) } } // case *ast.BasicLit: // fmt.Println("evalArgs", at.Value) // v.addBox(at.Value) case *ast.CallExpr: if at.Fun == nil { return nil } switch st := at.Fun.(type) { case *ast.SelectorExpr: if err := v.evalSelector(at, st); err != nil { return errors.WithStack(err) } case *ast.Ident: return v.evalIdent(st) } for _, a := range at.Args { if err := v.evalArgs(a); err != nil { return errors.WithStack(err) } } } return nil } func (v *visitor) evalSelector(expr *ast.CallExpr, sel *ast.SelectorExpr) error { x, ok := sel.X.(*ast.Ident) if !ok { return nil } if x.Name == "packr" && sel.Sel.Name == "NewBox" { for _, e := range expr.Args { switch at := e.(type) { case *ast.Ident: switch at.Obj.Kind { case ast.Var: if as, ok := at.Obj.Decl.(*ast.AssignStmt); ok { v.addVariable(as) } case ast.Con: if vs, ok := at.Obj.Decl.(*ast.ValueSpec); ok { v.addConstant(vs) } } return v.evalIdent(at) case *ast.BasicLit: v.addBox(at.Value) case *ast.CallExpr: return v.evalExpr(at) } } } return nil } func (v *visitor) evalIdent(i *ast.Ident) error { if i.Obj == nil { return nil } if s, ok := i.Obj.Decl.(*ast.AssignStmt); ok { return v.evalStmt(s) } return nil } func (v *visitor) addBox(b string) { b = strings.Replace(b, "\"", "", -1) v.Boxes = append(v.Boxes, b) } func (v *visitor) addVariable(as *ast.AssignStmt) error { if len(as.Rhs) == 1 { if bs, ok := as.Rhs[0].(*ast.BasicLit); ok { v.addBox(bs.Value) } } return nil } func (v *visitor) addConstant(vs *ast.ValueSpec) error { if len(vs.Values) == 1 { if bs, ok := vs.Values[0].(*ast.BasicLit); ok { v.addBox(bs.Value) } } return nil }