123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- // Package goparse contains logic for parsing Go files. Specifically it parses
- // source and test files into domain model for generating tests.
- package goparser
- import (
- "errors"
- "fmt"
- "go/ast"
- "go/parser"
- "go/token"
- "go/types"
- "io/ioutil"
- "strings"
- "go-common/app/tool/gorpc/model"
- )
- // ErrEmptyFile represents an empty file error.
- var ErrEmptyFile = errors.New("file is empty")
- // Result representats a parsed Go file.
- type Result struct {
- // The package name and imports of a Go file.
- Header *model.Header
- // All the functions and methods in a Go file.
- Funcs []*model.Function
- }
- // Parser can parse Go files.
- type Parser struct {
- // The importer to resolve packages from import paths.
- Importer types.Importer
- }
- // Parse parses a given Go file at srcPath, along any files that share the same
- // package, into a domain model for generating tests.
- func (p *Parser) Parse(srcPath string, files []model.Path) (*Result, error) {
- b, err := p.readFile(srcPath)
- if err != nil {
- return nil, err
- }
- fset := token.NewFileSet()
- f, err := p.parseFile(fset, srcPath)
- if err != nil {
- return nil, err
- }
- fs, err := p.parseFiles(fset, f, files)
- if err != nil {
- return nil, err
- }
- return &Result{
- Header: &model.Header{
- Comments: parseComment(f, f.Package),
- Package: f.Name.String(),
- Imports: parseImports(f.Imports),
- Code: goCode(b, f),
- },
- Funcs: p.parseFunctions(fset, f, fs),
- }, nil
- }
- func (p *Parser) readFile(srcPath string) ([]byte, error) {
- b, err := ioutil.ReadFile(srcPath)
- if err != nil {
- return nil, fmt.Errorf("ioutil.ReadFile: %v", err)
- }
- if len(b) == 0 {
- return nil, ErrEmptyFile
- }
- return b, nil
- }
- func (p *Parser) parseFile(fset *token.FileSet, srcPath string) (*ast.File, error) {
- f, err := parser.ParseFile(fset, srcPath, nil, parser.ParseComments)
- if err != nil {
- return nil, fmt.Errorf("target parser.ParseFile(): %v", err)
- }
- return f, nil
- }
- func (p *Parser) parseFiles(fset *token.FileSet, f *ast.File, files []model.Path) ([]*ast.File, error) {
- pkg := f.Name.String()
- var fs []*ast.File
- for _, file := range files {
- ff, err := parser.ParseFile(fset, string(file), nil, 0)
- if err != nil {
- return nil, fmt.Errorf("other file parser.ParseFile: %v", err)
- }
- if name := ff.Name.String(); name != pkg {
- continue
- }
- fs = append(fs, ff)
- }
- return fs, nil
- }
- func (p *Parser) parseFunctions(fset *token.FileSet, f *ast.File, fs []*ast.File) []*model.Function {
- ul, el := p.parseTypes(fset, fs)
- var funcs []*model.Function
- for _, d := range f.Decls {
- fDecl, ok := d.(*ast.FuncDecl)
- if !ok {
- continue
- }
- funcs = append(funcs, parseFunc(fDecl, ul, el))
- }
- return funcs
- }
- func (p *Parser) parseTypes(fset *token.FileSet, fs []*ast.File) (map[string]types.Type, map[*types.Struct]ast.Expr) {
- conf := &types.Config{
- Importer: p.Importer,
- // Adding a NO-OP error function ignores errors and performs best-effort
- // type checking. https://godoc.org/golang.org/x/tools/go/types#Config
- Error: func(error) {},
- }
- ti := &types.Info{
- Types: make(map[ast.Expr]types.TypeAndValue),
- }
- // Note: conf.Check can fail, but since Info is not required data, it's ok.
- conf.Check("", fset, fs, ti)
- ul := make(map[string]types.Type)
- el := make(map[*types.Struct]ast.Expr)
- for e, t := range ti.Types {
- // Collect the underlying types.
- ul[t.Type.String()] = t.Type.Underlying()
- // Collect structs to determine the fields of a receiver.
- if v, ok := t.Type.(*types.Struct); ok {
- el[v] = e
- }
- }
- return ul, el
- }
- func parseComment(f *ast.File, pkgPos token.Pos) []string {
- var comments []string
- var count int
- for _, comment := range f.Comments {
- if comment.End() < pkgPos && comment != f.Doc {
- for _, c := range comment.List {
- count += len(c.Text) + 1 // +1 for '\n'
- if count < int(c.End()) {
- n := int(c.End()) - count
- comments = append(comments, strings.Repeat("\n", n))
- count++ // for last of '\n'
- }
- comments = append(comments, c.Text)
- }
- }
- }
- return comments
- }
- // Returns the Go code below the imports block.
- func goCode(b []byte, f *ast.File) []byte {
- furthestPos := f.Name.End()
- for _, node := range f.Imports {
- if pos := node.End(); pos > furthestPos {
- furthestPos = pos
- }
- }
- if furthestPos < token.Pos(len(b)) {
- furthestPos++
- }
- return b[furthestPos:]
- }
- func parseFunc(fDecl *ast.FuncDecl, ul map[string]types.Type, el map[*types.Struct]ast.Expr) *model.Function {
- f := &model.Function{
- Name: fDecl.Name.String(),
- IsExported: fDecl.Name.IsExported(),
- Receiver: parseReceiver(fDecl.Recv, ul, el),
- Parameters: parseFieldList(fDecl.Type.Params, ul),
- }
- fs := parseFieldList(fDecl.Type.Results, ul)
- i := 0
- for _, fi := range fs {
- if fi.Type.String() == "error" {
- f.ReturnsError = true
- continue
- }
- fi.Index = i
- f.Results = append(f.Results, fi)
- i++
- }
- return f
- }
- func parseImports(imps []*ast.ImportSpec) []*model.Import {
- var is []*model.Import
- for _, imp := range imps {
- var n string
- if imp.Name != nil {
- n = imp.Name.String()
- }
- is = append(is, &model.Import{
- Name: n,
- Path: imp.Path.Value,
- })
- }
- return is
- }
- func parseReceiver(fl *ast.FieldList, ul map[string]types.Type, el map[*types.Struct]ast.Expr) *model.Receiver {
- if fl == nil {
- return nil
- }
- r := &model.Receiver{
- Field: parseFieldList(fl, ul)[0],
- }
- t, ok := ul[r.Type.Value]
- if !ok {
- return r
- }
- s, ok := t.(*types.Struct)
- if !ok {
- return r
- }
- st := el[s].(*ast.StructType)
- r.Fields = append(r.Fields, parseFieldList(st.Fields, ul)...)
- for i, f := range r.Fields {
- f.Name = s.Field(i).Name()
- }
- return r
- }
- func parseFieldList(fl *ast.FieldList, ul map[string]types.Type) []*model.Field {
- if fl == nil {
- return nil
- }
- i := 0
- var fs []*model.Field
- for _, f := range fl.List {
- for _, pf := range parseFields(f, ul) {
- pf.Index = i
- fs = append(fs, pf)
- i++
- }
- }
- return fs
- }
- func parseFields(f *ast.Field, ul map[string]types.Type) []*model.Field {
- t := parseExpr(f.Type, ul)
- if len(f.Names) == 0 {
- return []*model.Field{{
- Type: t,
- }}
- }
- var fs []*model.Field
- for _, n := range f.Names {
- fs = append(fs, &model.Field{
- Name: n.Name,
- Type: t,
- })
- }
- return fs
- }
- func parseExpr(e ast.Expr, ul map[string]types.Type) *model.Expression {
- switch v := e.(type) {
- case *ast.StarExpr:
- val := types.ExprString(v.X)
- return &model.Expression{
- Value: val,
- IsStar: true,
- Underlying: underlying(val, ul),
- }
- case *ast.Ellipsis:
- exp := parseExpr(v.Elt, ul)
- return &model.Expression{
- Value: exp.Value,
- IsStar: exp.IsStar,
- IsVariadic: true,
- Underlying: underlying(exp.Value, ul),
- }
- default:
- val := types.ExprString(e)
- return &model.Expression{
- Value: val,
- Underlying: underlying(val, ul),
- IsWriter: val == "io.Writer",
- }
- }
- }
- func underlying(val string, ul map[string]types.Type) string {
- if ul[val] != nil {
- return ul[val].String()
- }
- return ""
- }
|