github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/cmd/reactVet/react_vetter.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/build" 7 "go/importer" 8 "go/parser" 9 "go/token" 10 "go/types" 11 "path" 12 "sort" 13 "strings" 14 ) 15 16 const ( 17 jsxImportPath = "myitcv.io/react/jsx" 18 19 jsxHTML = "HTML" 20 jsxMarkdown = "Markdown" 21 jsxHTMLElem = "HTMLElem" 22 ) 23 24 type reactVetter struct { 25 wd string 26 bpkg *build.Package 27 pkgs map[string]*ast.Package 28 29 info *types.Info 30 31 errlist []vetErr 32 } 33 34 func (r *reactVetter) errorf(node ast.Node, format string, args ...interface{}) { 35 msg := fmt.Sprintf(format, args...) 36 r.errlist = append(r.errlist, vetErr{ 37 pos: fset.Position(node.Pos()), 38 msg: msg, 39 }) 40 } 41 42 var fset = token.NewFileSet() 43 44 func newReactVetter(bpkg *build.Package, wd string) *reactVetter { 45 pkgs, err := parser.ParseDir(fset, bpkg.Dir, nil, parser.ParseComments) 46 if err != nil { 47 fatalf("could not parse package directory for %v", bpkg.Name) 48 } 49 50 return &reactVetter{ 51 pkgs: pkgs, 52 bpkg: bpkg, 53 wd: wd, 54 } 55 } 56 57 func (r *reactVetter) vetPackages() { 58 pns := make([]string, 0, len(r.pkgs)) 59 60 for n := range r.pkgs { 61 pns = append(pns, n) 62 } 63 64 sort.Strings(pns) 65 66 for _, n := range pns { 67 pkg := r.pkgs[n] 68 69 files := make([]*ast.File, 0, len(pkg.Files)) 70 var jsxFiles []*jsxWalker 71 72 for _, f := range pkg.Files { 73 files = append(files, f) 74 75 for _, i := range f.Imports { 76 if strings.Trim(i.Path.Value, "\"") == jsxImportPath { 77 name := path.Base(jsxImportPath) 78 if i.Name != nil && i.Name.Name != "_" { 79 name = i.Name.Name 80 } 81 jsxFiles = append(jsxFiles, &jsxWalker{ 82 reactVetter: r, 83 f: f, 84 name: name, 85 }) 86 } 87 } 88 } 89 90 // TODO this logic will certainly need to be relaxed if we add more vet rules 91 if len(jsxFiles) == 0 { 92 continue 93 } 94 95 conf := types.Config{ 96 Importer: importer.Default(), 97 } 98 info := &types.Info{ 99 Defs: make(map[*ast.Ident]types.Object), 100 Types: make(map[ast.Expr]types.TypeAndValue), 101 Uses: make(map[*ast.Ident]types.Object), 102 } 103 _, err := conf.Check(r.bpkg.ImportPath, fset, files, info) 104 if err != nil { 105 fatalf("type checking failed, %v", err) 106 } 107 108 r.info = info 109 110 for _, j := range jsxFiles { 111 ast.Walk(j, j.f) 112 } 113 } 114 } 115 116 type jsxWalker struct { 117 *reactVetter 118 f *ast.File 119 name string 120 } 121 122 func (j *jsxWalker) Visit(n ast.Node) ast.Visitor { 123 switch n := n.(type) { 124 case *ast.CallExpr: 125 se, ok := n.Fun.(*ast.SelectorExpr) 126 if !ok { 127 break 128 } 129 130 i, ok := se.X.(*ast.Ident) 131 if !ok { 132 break 133 } 134 135 if i.Name != j.name { 136 break 137 } 138 139 switch se.Sel.Name { 140 case jsxHTML, jsxHTMLElem, jsxMarkdown: 141 default: 142 fatalf("unknown jsx package method %v", se.Sel.Name) 143 } 144 145 if v := len(n.Args); v != 1 { 146 fatalf("expected 1 arg; got %v", v) 147 } 148 149 a := n.Args[0] 150 151 tv, ok := j.info.Types[a] 152 if !ok || tv.Type != types.Typ[types.String] || tv.Value == nil { 153 j.errorf(a, "argument must be a constant string") 154 } 155 156 } 157 return j 158 }