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  }