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  }