github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/cmd/internal/astutils/funcs.go (about)

     1  package astutils
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"github.com/sirupsen/logrus"
     9  	"github.com/unionj-cloud/go-doudou/toolkit/constants"
    10  	"github.com/unionj-cloud/go-doudou/toolkit/stringutils"
    11  	"go/ast"
    12  	"go/format"
    13  	"golang.org/x/tools/imports"
    14  	"io/ioutil"
    15  	"os"
    16  	"path/filepath"
    17  	"regexp"
    18  	"strings"
    19  	"text/template"
    20  	"unicode"
    21  )
    22  
    23  func GetImportStatements(input []byte) []byte {
    24  	reg := regexp.MustCompile("(?s)import \\((.*?)\\)")
    25  	if !reg.Match(input) {
    26  		return nil
    27  	}
    28  	matches := reg.FindSubmatch(input)
    29  	return matches[1]
    30  }
    31  
    32  func AppendImportStatements(src []byte, appendImports []byte) []byte {
    33  	reg := regexp.MustCompile("(?s)import \\((.*?)\\)")
    34  	if !reg.Match(src) {
    35  		return src
    36  	}
    37  	matches := reg.FindSubmatch(src)
    38  	old := matches[1]
    39  	re := regexp.MustCompile(`[\r\n]+`)
    40  	splits := re.Split(string(old), -1)
    41  	oldmap := make(map[string]struct{})
    42  	for _, item := range splits {
    43  		oldmap[strings.TrimSpace(item)] = struct{}{}
    44  	}
    45  	splits = re.Split(string(appendImports), -1)
    46  	var newimps []string
    47  	for _, item := range splits {
    48  		key := strings.TrimSpace(item)
    49  		if _, ok := oldmap[key]; !ok {
    50  			newimps = append(newimps, "\t"+key)
    51  		}
    52  	}
    53  	if len(newimps) == 0 {
    54  		return src
    55  	}
    56  	appendImports = []byte(constants.LineBreak + strings.Join(newimps, constants.LineBreak) + constants.LineBreak)
    57  	return reg.ReplaceAllFunc(src, func(i []byte) []byte {
    58  		old = append([]byte("import ("), old...)
    59  		old = append(old, appendImports...)
    60  		old = append(old, []byte(")")...)
    61  		return old
    62  	})
    63  }
    64  
    65  func GrpcRelatedModify(src []byte, metaName string, grpcSvcName string) []byte {
    66  	expr := fmt.Sprintf(`type %sImpl struct {`, metaName)
    67  	reg := regexp.MustCompile(expr)
    68  	unimpl := fmt.Sprintf("pb.Unimplemented%sServer", grpcSvcName)
    69  	if !strings.Contains(string(src), unimpl) {
    70  		appendUnimpl := []byte(constants.LineBreak + unimpl + constants.LineBreak)
    71  		src = reg.ReplaceAllFunc(src, func(i []byte) []byte {
    72  			return append([]byte(expr), appendUnimpl...)
    73  		})
    74  	}
    75  	var_pb := fmt.Sprintf("var _ pb.%sServer = (*%sImpl)(nil)", grpcSvcName, metaName)
    76  	if !strings.Contains(string(src), var_pb) {
    77  		appendVarPb := []byte(constants.LineBreak + var_pb + constants.LineBreak)
    78  		src = reg.ReplaceAllFunc(src, func(i []byte) []byte {
    79  			return append(appendVarPb, []byte(expr)...)
    80  		})
    81  	}
    82  	return src
    83  }
    84  
    85  func RestRelatedModify(src []byte, metaName string) []byte {
    86  	expr := fmt.Sprintf(`type %sImpl struct {`, metaName)
    87  	reg := regexp.MustCompile(expr)
    88  	var_ := fmt.Sprintf("var _ %s = (*%sImpl)(nil)", metaName, metaName)
    89  	if !strings.Contains(string(src), var_) {
    90  		appendVarPb := []byte(constants.LineBreak + var_ + constants.LineBreak)
    91  		src = reg.ReplaceAllFunc(src, func(i []byte) []byte {
    92  			return append(appendVarPb, []byte(expr)...)
    93  		})
    94  	}
    95  	return src
    96  }
    97  
    98  // FixImport format source code and add missing import syntax automatically
    99  func FixImport(src []byte, file string) {
   100  	var (
   101  		res []byte
   102  		err error
   103  	)
   104  	if res, err = imports.Process(file, src, &imports.Options{
   105  		TabWidth:  8,
   106  		TabIndent: true,
   107  		Comments:  true,
   108  		Fragment:  true,
   109  	}); err == nil {
   110  		_ = ioutil.WriteFile(file, res, os.ModePerm)
   111  		return
   112  	}
   113  	logrus.Error(err)
   114  	_ = ioutil.WriteFile(file, src, os.ModePerm)
   115  }
   116  
   117  // GetMethodMeta get method name then new MethodMeta struct from *ast.FuncDecl
   118  func GetMethodMeta(spec *ast.FuncDecl) MethodMeta {
   119  	methodName := ExprString(spec.Name)
   120  	mm := NewMethodMeta(spec.Type, ExprString)
   121  	mm.Name = methodName
   122  	return mm
   123  }
   124  
   125  // NewMethodMeta new MethodMeta struct from *ast.FuncDecl
   126  func NewMethodMeta(ft *ast.FuncType, exprString func(ast.Expr) string) MethodMeta {
   127  	var params, results []FieldMeta
   128  	for _, param := range ft.Params.List {
   129  		pt := exprString(param.Type)
   130  		if len(param.Names) > 0 {
   131  			for _, name := range param.Names {
   132  				params = append(params, FieldMeta{
   133  					Name: name.Name,
   134  					Type: pt,
   135  					Tag:  "",
   136  				})
   137  			}
   138  			continue
   139  		}
   140  		params = append(params, FieldMeta{
   141  			Name: "",
   142  			Type: pt,
   143  			Tag:  "",
   144  		})
   145  	}
   146  	if ft.Results != nil {
   147  		for _, result := range ft.Results.List {
   148  			rt := exprString(result.Type)
   149  			if len(result.Names) > 0 {
   150  				for _, name := range result.Names {
   151  					results = append(results, FieldMeta{
   152  						Name: name.Name,
   153  						Type: rt,
   154  						Tag:  "",
   155  					})
   156  				}
   157  				continue
   158  			}
   159  			results = append(results, FieldMeta{
   160  				Name: "",
   161  				Type: rt,
   162  				Tag:  "",
   163  			})
   164  		}
   165  	}
   166  	return MethodMeta{
   167  		Params:  params,
   168  		Results: results,
   169  	}
   170  }
   171  
   172  // NewStructMeta new StructMeta from *ast.StructType
   173  func NewStructMeta(structType *ast.StructType, exprString func(ast.Expr) string) StructMeta {
   174  	var fields []FieldMeta
   175  	re := regexp.MustCompile(`json:"(.*?)"`)
   176  	for _, field := range structType.Fields.List {
   177  		var fieldComments []string
   178  		if field.Doc != nil {
   179  			for _, comment := range field.Doc.List {
   180  				fieldComments = append(fieldComments, strings.TrimSpace(strings.TrimPrefix(comment.Text, "//")))
   181  			}
   182  		}
   183  
   184  		fieldType := exprString(field.Type)
   185  
   186  		var tag string
   187  		var docName string
   188  		if field.Tag != nil {
   189  			tag = strings.Trim(field.Tag.Value, "`")
   190  			if re.MatchString(tag) {
   191  				docName = strings.TrimSuffix(re.FindStringSubmatch(tag)[1], ",omitempty")
   192  			}
   193  		}
   194  
   195  		if len(field.Names) > 0 {
   196  			for _, name := range field.Names {
   197  				_docName := docName
   198  				if stringutils.IsEmpty(_docName) {
   199  					_docName = name.Name
   200  				}
   201  				fields = append(fields, FieldMeta{
   202  					Name:     name.Name,
   203  					Type:     fieldType,
   204  					Tag:      tag,
   205  					Comments: fieldComments,
   206  					IsExport: unicode.IsUpper(rune(name.Name[0])),
   207  					DocName:  _docName,
   208  				})
   209  			}
   210  		} else {
   211  			splits := strings.Split(fieldType, ".")
   212  			name := splits[len(splits)-1]
   213  			fieldType = "embed:" + fieldType
   214  			_docName := docName
   215  			if stringutils.IsEmpty(_docName) {
   216  				_docName = name
   217  			}
   218  			fields = append(fields, FieldMeta{
   219  				Name:     name,
   220  				Type:     fieldType,
   221  				Tag:      tag,
   222  				Comments: fieldComments,
   223  				IsExport: unicode.IsUpper(rune(name[0])),
   224  				DocName:  _docName,
   225  			})
   226  		}
   227  	}
   228  	return StructMeta{
   229  		Fields: fields,
   230  	}
   231  }
   232  
   233  // PackageMeta wraps package info
   234  type PackageMeta struct {
   235  	Name string
   236  }
   237  
   238  // FieldMeta wraps field info
   239  type FieldMeta struct {
   240  	Name     string
   241  	Type     string
   242  	Tag      string
   243  	Comments []string
   244  	IsExport bool
   245  	// used in OpenAPI 3.0 spec as property name
   246  	DocName string
   247  	// Annotations of the field
   248  	Annotations []Annotation
   249  	// ValidateTag based on https://github.com/go-playground/validator
   250  	// please refer to its documentation https://pkg.go.dev/github.com/go-playground/validator/v10
   251  	ValidateTag string
   252  }
   253  
   254  // StructMeta wraps struct info
   255  type StructMeta struct {
   256  	Name     string
   257  	Fields   []FieldMeta
   258  	Comments []string
   259  	Methods  []MethodMeta
   260  	IsExport bool
   261  	// go-doudou version
   262  	Version string
   263  }
   264  
   265  // EnumMeta wraps struct info
   266  type EnumMeta struct {
   267  	Name   string
   268  	Values []string
   269  }
   270  
   271  // ExprString return string representation from ast.Expr
   272  func ExprString(expr ast.Expr) string {
   273  	switch _expr := expr.(type) {
   274  	case *ast.Ident:
   275  		return _expr.Name
   276  	case *ast.StarExpr:
   277  		return "*" + ExprString(_expr.X)
   278  	case *ast.SelectorExpr:
   279  		return ExprString(_expr.X) + "." + _expr.Sel.Name
   280  	case *ast.InterfaceType:
   281  		return "interface{}"
   282  	case *ast.ArrayType:
   283  		if _expr.Len == nil {
   284  			return "[]" + ExprString(_expr.Elt)
   285  		}
   286  		return "[" + ExprString(_expr.Len) + "]" + ExprString(_expr.Elt)
   287  	case *ast.BasicLit:
   288  		return _expr.Value
   289  	case *ast.MapType:
   290  		return "map[" + ExprString(_expr.Key) + "]" + ExprString(_expr.Value)
   291  	case *ast.StructType:
   292  		structmeta := NewStructMeta(_expr, ExprString)
   293  		b, _ := json.Marshal(structmeta)
   294  		return "anonystruct«" + string(b) + "»"
   295  	case *ast.FuncType:
   296  		return NewMethodMeta(_expr, ExprString).String()
   297  	case *ast.ChanType:
   298  		var result string
   299  		if _expr.Dir == ast.SEND {
   300  			result += "chan<- "
   301  		} else if _expr.Dir == ast.RECV {
   302  			result += "<-chan "
   303  		} else {
   304  			result += "chan "
   305  		}
   306  		return result + ExprString(_expr.Value)
   307  	case *ast.Ellipsis:
   308  		if _expr.Ellipsis.IsValid() {
   309  			return "..." + ExprString(_expr.Elt)
   310  		}
   311  		panic(fmt.Sprintf("invalid ellipsis expression: %+v\n", expr))
   312  	default:
   313  		panic(fmt.Sprintf("not support expression: %+v\n", expr))
   314  	}
   315  }
   316  
   317  type Annotation struct {
   318  	Name   string
   319  	Params []string
   320  }
   321  
   322  var reAnno = regexp.MustCompile(`@(\S+?)\((.*?)\)`)
   323  
   324  func GetAnnotations(text string) []Annotation {
   325  	if !reAnno.MatchString(text) {
   326  		return nil
   327  	}
   328  	var annotations []Annotation
   329  	matches := reAnno.FindAllStringSubmatch(text, -1)
   330  	for _, item := range matches {
   331  		name := fmt.Sprintf(`@%s`, item[1])
   332  		var params []string
   333  		if stringutils.IsNotEmpty(item[2]) {
   334  			params = strings.Split(strings.TrimSpace(item[2]), ",")
   335  		}
   336  		annotations = append(annotations, Annotation{
   337  			Name:   name,
   338  			Params: params,
   339  		})
   340  	}
   341  	return annotations
   342  }
   343  
   344  // MethodMeta represents an api
   345  type MethodMeta struct {
   346  	// Recv method receiver
   347  	Recv string
   348  	// Name method name
   349  	Name string
   350  	// Params when generate client code from openapi3 spec json file, Params holds all method input parameters.
   351  	// when generate client code from service interface in svc.go file, if there is struct type param, this struct type param will put into request body,
   352  	// then others will be put into url as query string. if there is no struct type param and the api is a get request, all will be put into url as query string.
   353  	// if there is no struct type param and the api is Not a get request, all will be put into request body as application/x-www-form-urlencoded data.
   354  	// specially, if there is one or more v3.FileModel or []v3.FileModel params,
   355  	// all will be put into request body as multipart/form-data data.
   356  	Params []FieldMeta
   357  	// Results response
   358  	Results []FieldMeta
   359  	// PathVars not support when generate client code from service interface in svc.go file
   360  	// when generate client code from openapi3 spec json file, PathVars is parameters in url as path variable.
   361  	PathVars []FieldMeta
   362  	// HeaderVars not support when generate client code from service interface in svc.go file
   363  	// when generate client code from openapi3 spec json file, HeaderVars is parameters in header.
   364  	HeaderVars []FieldMeta
   365  	// BodyParams not support when generate client code from service interface in svc.go file
   366  	// when generate client code from openapi3 spec json file, BodyParams is parameters in request body as query string.
   367  	BodyParams *FieldMeta
   368  	// BodyJSON not support when generate client code from service interface in svc.go file
   369  	// when generate client code from openapi3 spec json file, BodyJSON is parameters in request body as json.
   370  	BodyJSON *FieldMeta
   371  	// Files not support when generate client code from service interface in svc.go file
   372  	// when generate client code from openapi3 spec json file, Files is parameters in request body as multipart file.
   373  	Files []FieldMeta
   374  	// Comments of the method
   375  	Comments []string
   376  	// Path api path
   377  	// not support when generate client code from service interface in svc.go file
   378  	Path string
   379  	// QueryParams not support when generate client code from service interface in svc.go file
   380  	// when generate client code from openapi3 spec json file, QueryParams is parameters in url as query string.
   381  	QueryParams *FieldMeta
   382  	// Annotations of the method
   383  	Annotations []Annotation
   384  }
   385  
   386  const methodTmpl = `func {{ if .Recv }}(receiver {{.Recv}}){{ end }} {{.Name}}({{- range $i, $p := .Params}}
   387      {{- if $i}},{{end}}
   388      {{- $p.Name}} {{$p.Type}}
   389      {{- end }}) ({{- range $i, $r := .Results}}
   390                       {{- if $i}},{{end}}
   391                       {{- $r.Name}} {{$r.Type}}
   392                       {{- end }})`
   393  
   394  func (mm MethodMeta) String() string {
   395  	if stringutils.IsNotEmpty(mm.Recv) && stringutils.IsEmpty(mm.Name) {
   396  		panic("not valid code")
   397  	}
   398  	var isAnony bool
   399  	if stringutils.IsEmpty(mm.Name) {
   400  		isAnony = true
   401  		mm.Name = "placeholder"
   402  	}
   403  	t, _ := template.New("method.tmpl").Parse(methodTmpl)
   404  	var buf bytes.Buffer
   405  	_ = t.Execute(&buf, mm)
   406  	var res []byte
   407  	res, _ = format.Source(buf.Bytes())
   408  	result := string(res)
   409  	if isAnony {
   410  		return strings.Replace(result, "func placeholder(", "func(", 1)
   411  	}
   412  	return result
   413  }
   414  
   415  // InterfaceMeta wraps interface info
   416  type InterfaceMeta struct {
   417  	Name     string
   418  	Methods  []MethodMeta
   419  	Comments []string
   420  }
   421  
   422  // Visit visit each files
   423  func Visit(files *[]string) filepath.WalkFunc {
   424  	return func(path string, info os.FileInfo, err error) error {
   425  		if err != nil {
   426  			logrus.Panicln(err)
   427  		}
   428  		if !info.IsDir() {
   429  			*files = append(*files, path)
   430  		}
   431  		return nil
   432  	}
   433  }
   434  
   435  // GetMod get module name from go.mod file
   436  func GetMod() string {
   437  	var (
   438  		f         *os.File
   439  		err       error
   440  		firstLine string
   441  	)
   442  	dir, _ := os.Getwd()
   443  	mod := filepath.Join(dir, "go.mod")
   444  	if f, err = os.Open(mod); err != nil {
   445  		panic(err)
   446  	}
   447  	reader := bufio.NewReader(f)
   448  	firstLine, _ = reader.ReadString('\n')
   449  	return strings.TrimSpace(strings.TrimPrefix(firstLine, "module"))
   450  }
   451  
   452  // GetImportPath get import path of pkg from dir
   453  func GetImportPath(dir string) string {
   454  	wd, _ := os.Getwd()
   455  	return GetMod() + strings.ReplaceAll(strings.TrimPrefix(dir, wd), `\`, `/`)
   456  }