github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/cmd/reactGen/gen.go (about) 1 // Copyright (c) 2016 Paul Jolly <paul@myitcv.org.uk>, all rights reserved. 2 // Use of this document is governed by a license found in the LICENSE document. 3 4 package main 5 6 import ( 7 "bytes" 8 "go/ast" 9 "go/build" 10 "go/parser" 11 "go/printer" 12 "go/token" 13 "os/exec" 14 "path" 15 "strings" 16 17 "myitcv.io/gogenerate" 18 ) 19 20 const ( 21 reactPkg = "myitcv.io/react" 22 compDefName = "ComponentDef" 23 compDefSuffix = "Def" 24 25 stateTypeSuffix = "State" 26 propsTypeSuffix = "Props" 27 propsTypeTmplPrefix = "_" 28 29 getInitialState = "GetInitialState" 30 componentWillReceiveProps = "ComponentWillReceiveProps" 31 equals = "Equals" 32 ) 33 34 type typeFile struct { 35 ts *ast.TypeSpec 36 file *ast.File 37 } 38 39 type funcFile struct { 40 fn *ast.FuncDecl 41 file *ast.File 42 } 43 44 var fset = token.NewFileSet() 45 46 func astNodeString(i interface{}) string { 47 b := bytes.NewBuffer(nil) 48 err := printer.Fprint(b, fset, i) 49 if err != nil { 50 fatalf("failed to astNodeString %v: %v", i, err) 51 } 52 53 return b.String() 54 } 55 56 func goFmtBuf(b *bytes.Buffer) (*bytes.Buffer, error) { 57 out := bytes.NewBuffer(nil) 58 cmd := exec.Command("gofmt", "-s") 59 cmd.Stdin = b 60 cmd.Stdout = out 61 62 err := cmd.Run() 63 64 return out, err 65 } 66 67 type gen struct { 68 pkg string 69 pkgImpPath string 70 71 isReactCore bool 72 73 propsTmpls map[string]typeFile 74 components map[string]typeFile 75 types map[string]typeFile 76 pointMeths map[string][]funcFile 77 nonPointMeths map[string][]funcFile 78 } 79 80 func dogen(dir, license string) { 81 82 bpkg, err := build.ImportDir(dir, 0) 83 if err != nil { 84 fatalf("unable to import pkg in dir %v: %v", dir, err) 85 } 86 87 isReactCore := bpkg.ImportPath == reactPkg 88 89 pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments) 90 if err != nil { 91 fatalf("unable to parse %v: %v", dir, err) 92 } 93 94 // we intentionally walk all packages, i.e. the package in the current directory 95 // and any x-test package that may also be present 96 for pn, pkg := range pkgs { 97 g := &gen{ 98 pkg: pn, 99 pkgImpPath: bpkg.ImportPath, 100 101 isReactCore: isReactCore, 102 103 propsTmpls: make(map[string]typeFile), 104 components: make(map[string]typeFile), 105 types: make(map[string]typeFile), 106 pointMeths: make(map[string][]funcFile), 107 nonPointMeths: make(map[string][]funcFile), 108 } 109 110 for fn, file := range pkg.Files { 111 112 if gogenerate.FileGeneratedBy(fn, "reactGen") { 113 continue 114 } 115 116 foundImp := false 117 impName := "" 118 119 for _, i := range file.Imports { 120 p := strings.Trim(i.Path.Value, "\"") 121 122 if p == reactPkg { 123 foundImp = true 124 125 if i.Name != nil { 126 impName = i.Name.Name 127 } else { 128 impName = path.Base(reactPkg) 129 } 130 131 break 132 } 133 } 134 135 if !foundImp && !isReactCore { 136 continue 137 } 138 139 for _, d := range file.Decls { 140 switch d := d.(type) { 141 case *ast.FuncDecl: 142 if d.Recv == nil { 143 continue 144 } 145 146 f := d.Recv.List[0] 147 148 switch v := f.Type.(type) { 149 case *ast.StarExpr: 150 id, ok := v.X.(*ast.Ident) 151 if !ok { 152 continue 153 } 154 g.pointMeths[id.Name] = append(g.pointMeths[id.Name], funcFile{d, file}) 155 case *ast.Ident: 156 g.nonPointMeths[v.Name] = append(g.nonPointMeths[v.Name], funcFile{d, file}) 157 } 158 159 case *ast.GenDecl: 160 if d.Tok != token.TYPE { 161 continue 162 } 163 164 for _, ts := range d.Specs { 165 ts := ts.(*ast.TypeSpec) 166 167 st, ok := ts.Type.(*ast.StructType) 168 if !ok || st.Fields == nil { 169 continue 170 } 171 172 if n := ts.Name.Name; strings.HasPrefix(n, propsTypeTmplPrefix) && 173 strings.HasSuffix(n, propsTypeSuffix) { 174 175 if ts.Doc == nil { 176 ts.Doc = d.Doc 177 } 178 179 g.propsTmpls[n] = typeFile{ 180 ts: ts, 181 file: file, 182 } 183 184 continue 185 } 186 187 foundAnon := false 188 189 for _, f := range st.Fields.List { 190 if f.Names != nil { 191 // it must be anonymous 192 continue 193 } 194 195 se, ok := f.Type.(*ast.SelectorExpr) 196 if !ok { 197 continue 198 } 199 200 if se.Sel.Name != compDefName { 201 continue 202 } 203 204 id, ok := se.X.(*ast.Ident) 205 if !ok { 206 continue 207 } 208 209 if id.Name != impName { 210 continue 211 } 212 213 foundAnon = true 214 } 215 216 if foundAnon && strings.HasSuffix(ts.Name.Name, compDefSuffix) { 217 g.components[ts.Name.Name] = typeFile{ts, file} 218 } else { 219 g.types[ts.Name.Name] = typeFile{ts, file} 220 } 221 } 222 } 223 } 224 } 225 226 // at this point we have the components and their methods 227 for cd := range g.components { 228 g.genComp(cd) 229 } 230 } 231 }