1 Star 1 Fork 0

go-wena/wena

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
parser.go 13.68 KB
一键复制 编辑 原始数据 按行查看 历史
k3x 提交于 4年前 . -
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
package parser
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"log"
"net/http"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"gitee.com/go-errors/errors"
"gitee.com/go-wena/wena/internal/mod"
"github.com/segmentio/go-snakecase"
)
// Import 导入 <alias> "<package>"
type Import struct {
Name string `json:"name"` //别名
Path string `json:"path"` //包名
Alias bool `json:"alias"` //是否别名
}
func (imp *Import) String() string {
if imp.Alias {
return fmt.Sprintf("%s %q", imp.Name, imp.Path)
}
return fmt.Sprintf("%q", imp.Path)
}
// Type 类型标识, <package>.<name>
type Type struct {
Path string `json:"path"` //类型所在包
Sel string `json:"sel"` //类型选择前缀
Name string `json:"name"` //类型名称
Star bool `json:"star"` //加星
Len string `json:"array"` //集合: 集合长度
Key *Type `json:"key"` //表: 表键类型
Value *Type `json:"value"` //表、星: 值类型
}
func (t *Type) Elem() *Type {
if t.Star && t.Value != nil {
return t.Value.Elem()
}
return t
}
func (t *Type) Go() string {
if t == nil {
return "interface{}"
}
s := strings.Builder{}
if t.Star {
s.WriteByte('*')
}
switch t.Path + "." + t.Name {
case ".map":
s.WriteString("map[")
s.WriteString(t.Key.String())
s.WriteByte(']')
s.WriteString(t.Value.String())
case ".slice":
s.WriteString("[]")
s.WriteString(t.Value.String())
case ".array":
s.WriteByte('[')
s.WriteString(t.Len)
s.WriteByte(']')
s.WriteString(t.Value.String())
default:
if t.Sel != "" {
s.WriteString(t.Sel)
s.WriteByte('.')
}
s.WriteString(t.Name)
}
return s.String()
}
func (t *Type) String() string {
if t == nil {
return "interface{}"
}
s := strings.Builder{}
if t.Star {
s.WriteByte('*')
}
switch t.Path + "." + t.Name {
case ".map":
s.WriteString("map[")
s.WriteString(t.Key.String())
s.WriteByte(']')
s.WriteString(t.Value.String())
case ".slice":
s.WriteString("[]")
s.WriteString(t.Value.String())
case ".array":
s.WriteByte('[')
s.WriteString(t.Len)
s.WriteByte(']')
s.WriteString(t.Value.String())
default:
if t.Path != "" {
s.WriteString(t.Path)
s.WriteByte('.')
}
s.WriteString(t.Name)
}
return s.String()
}
//Struct 消息
type Struct struct {
Type *Type `json:"type"` //结构体类型
Fields []*Field `json:"fields"` //结构体字段
}
// Field 消息字段
type Field struct {
Name string `json:"name"` //字段名
Type *Type `json:"type"` //字段类型
Bind string `json:"bind"` //字段绑定 //[]*FieldBind
Comment string `json:"comment"` //注释
}
func (f *Field) String() string {
return fmt.Sprintf("%s %s %s %s", f.Name, f.Type, f.Bind, f.Comment)
}
// Func 方法
type Func struct {
Path string `json:"path"` //包名
Sel string `json:"sel"` //选择器
Name string `json:"name"` //名
Router string `json:"router"` //路由
Params []*Param `json:"params"` //输入参数
Results []*Param `json:"results"` //输出参数
Method []string `json:"method"` //请求方法
Comment []string `json:"comment"` //注释
}
func (f *Func) Package() string {
return f.Path + "." + f.Name
}
func (f *Func) ID() string {
return snakecase.Snakecase(f.Package())
}
func (f *Func) Go() string {
return f.Sel + "." + f.Name
}
func (f *Func) ResultIDs() string {
s := make([]string, 0, len(f.Results))
for _, param := range f.Results {
s = append(s, param.Name)
}
return strings.Join(s, ", ")
}
func (f *Func) ParamIDs() string {
s := make([]string, 0, len(f.Params))
for _, param := range f.Params {
s = append(s, param.Name)
}
return strings.Join(s, ", ")
}
// Param 参数
type Param struct {
Index int `json:"index"` //索引位置
Name string `json:"name"` //参数名
Type *Type `json:"type"` //参数类型
}
func (p *Param) NoName() bool {
return p == nil || p.Name == "" || p.Name == "_"
}
func (p *Param) Context() bool {
return p.Type != nil && p.Type.Path == "context" && p.Type.Name == "Context"
}
func (p *Param) Err() bool {
return p.Type != nil && p.Type.Path == "" && p.Type.Name == "error"
}
// Project 项目
type Project struct {
Root string `json:"root"` //跟目录
Mod string `json:"mod"` //模块名
Imports *Imports `json:"imports"` //导入
Structs []*Struct `json:"structs"` //消息
Funs []*Func `json:"funcs"` //方法
//Name string
//Version string
//GitVersion string
//BuildTime string
}
func Parse(srcMod *mod.File) (prj *Project, err error) {
builtins := getBuiltinType()
_ = builtins
prj = &Project{
Root: filepath.Dir(srcMod.Path()),
Mod: srcMod.Package(),
Imports: &Imports{},
}
var files []*File
err = filepath.WalkDir(prj.Root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
name := d.Name()
if d.IsDir() {
if len(name) == 0 || name[0] == '.' || name[0] == '_' {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(name, ".api.go") && !strings.HasSuffix(name, ".model.go") {
return nil
}
f, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ParseComments)
if err != nil {
return errors.Wrap(err)
}
file := &File{path: path, File: f, root: prj.Root, mod: prj.Mod}
if dir := filepath.Join(filepath.Dir(filepath.Dir(file.path)), getIdent(file.Name)); strings.HasPrefix(dir, file.root) {
file.rel = filepath.ToSlash(strings.TrimPrefix(dir, file.root))
}
file.pkg = file.mod + file.rel
files = append(files, file)
return nil
})
prj.Imports.Find("gitee.com/go-wena/app")
prj.Imports.Find("context")
prj.Imports.Find("log")
prj.Imports.Find("time")
for _, file := range files {
file.parseImport()
prj.parseFuncs(file)
}
for _, file := range files {
prj.parseStructs(file)
}
return prj, err
}
func (prj *Project) parseFuncs(file *File) {
for _, decl := range file.Decls {
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
if isExportIdent(funcDecl.Name) {
prj.Funs = append(prj.Funs, file.parseFunc(funcDecl, prj.Imports))
}
}
}
}
func (prj *Project) parseStructs(file *File) {
for _, decl := range file.Decls {
if genDecl, ok := decl.(*ast.GenDecl); ok {
for _, spec := range genDecl.Specs {
if ts, ok := spec.(*ast.TypeSpec); ok {
if st, ok := ts.Type.(*ast.StructType); ok {
prj.Structs = append(prj.Structs, file.parseStruct(st, getIdent(ts.Name)))
}
}
}
}
}
}
type File struct {
root string
mod string
path string
pkg string
rel string
fis map[string]string
*ast.File
}
func (file *File) parseImport() {
file.fis = make(map[string]string, len(file.Imports))
for _, spec := range file.Imports {
var (
name = getIdent(spec.Name)
path = strings.Trim(spec.Path.Value, `"`)
)
if name != "" {
file.fis[name] = path
} else {
name = mod.GetNameFromPath(path)
if _, find := file.fis[name]; !find {
file.fis[name] = path
}
}
}
return
}
func (file *File) parseStruct(st *ast.StructType, name string) *Struct {
s := &Struct{Type: &Type{Path: file.pkg, Name: name}}
for _, astField := range st.Fields.List {
field := file.parseField(astField)
if isExport(field.Name) {
s.Fields = append(s.Fields, field)
}
}
return s
}
func (file *File) parseField(field *ast.Field) *Field {
return &Field{
Name: getIdents(field.Names),
Type: file.getFieldType(field.Type, nil),
Bind: getBasicValue(field.Tag),
Comment: getGroupComment(field.Comment),
}
}
func (file *File) parseFunc(funcDecl *ast.FuncDecl, imports *Imports) *Func {
f := &Func{Name: getIdent(funcDecl.Name), Path: file.pkg, Sel: imports.Find(file.pkg)}
f.Router, f.Method, f.Comment = parseFuncComment(funcDecl.Doc)
for i, param := range funcDecl.Type.Params.List {
p := &Param{Index: i, Name: getIdents(param.Names), Type: file.getFieldType(param.Type, imports)}
p.Name = "i" + strconv.Itoa(i+1)
f.Params = append(f.Params, p)
}
if f.Router == "" {
f.Router = "/" + strings.Trim(f.Path[len(file.mod):]+"."+snakecase.Snakecase(f.Name), "./ ")
}
if len(f.Method) == 0 {
f.Method = []string{http.MethodPost}
}
if funcDecl.Type.Results != nil {
for i, param := range funcDecl.Type.Results.List {
p := &Param{Index: i, Name: getIdents(param.Names), Type: file.getFieldType(param.Type, imports)}
p.Name = "r" + strconv.Itoa(i+1)
f.Results = append(f.Results, p)
}
}
return f
}
func (file *File) getFieldType(expr ast.Expr, imports *Imports) *Type {
switch typ := expr.(type) {
case *ast.SelectorExpr:
if ident, isIdent := typ.X.(*ast.Ident); isIdent {
path := getIdent(ident)
sel := path
if pkg, find := file.fis[path]; find {
if path = pkg; imports != nil {
sel = imports.Find(path)
}
}
return &Type{Name: getIdent(typ.Sel), Path: path, Sel: sel}
}
log.Fatalf("unknown selector: %T", typ.X)
case *ast.StarExpr:
t := file.getFieldType(typ.X, imports)
t.Star = true
return t
case *ast.Ident:
name := getIdent(typ)
if isBuiltin(name) {
return &Type{Name: name}
}
sel := ""
if imports != nil {
sel = imports.Find(file.pkg)
}
return &Type{Name: name, Path: file.pkg, Sel: sel}
case *ast.StructType:
return &Type{Name: "struct"}
case *ast.ArrayType:
if typ.Len == nil {
return &Type{Name: "slice", Value: file.getFieldType(typ.Elt, imports)}
}
l, _ := typ.Len.(*ast.BasicLit)
return &Type{Name: "array", Value: file.getFieldType(typ.Elt, imports), Len: getBasicValue(l)}
case *ast.MapType:
return &Type{Name: "map", Key: file.getFieldType(typ.Key, imports), Value: file.getFieldType(typ.Value, imports)}
default:
log.Fatalf("unknown type:<%T: %v>", typ, typ)
}
return nil
}
func parseFuncComment(doc *ast.CommentGroup) (router string, methods []string, comments []string) {
if doc != nil && len(doc.List) > 0 {
routerMatch := regexp.MustCompile(`@router(\s+(?P<path>/[a-zA-Z_\-/]+))?(\s+\[(?P<method>[a-zA-Z,\s]+)])?`)
for _, cmt := range doc.List {
text := strings.TrimLeft(cmt.Text, "/ ")
if strings.HasPrefix(text, "@router ") {
if match := findRegexpMatch(text, routerMatch); len(match) > 0 {
router = match["path"]
methods = strings.Fields(strings.ToUpper(match["method"]))
methods = split(strings.ToUpper(match["method"]), ",")
continue
}
}
comments = append(comments, strings.TrimLeft(cmt.Text, "/ "))
}
}
if len(methods) == 0 {
methods = []string{"POST"}
}
return
}
func findRegexpMatch(s string, exp *regexp.Regexp) (result map[string]string) {
match := exp.FindStringSubmatch(s)
if len(match) > 0 {
groupNames := exp.SubexpNames()
result = make(map[string]string, len(match))
for i, name := range groupNames {
if i > 0 {
if name != "" {
result[name] = match[i]
}
}
}
}
return result
}
func split(s string, spt string) (list []string) {
ss := strings.Split(strings.TrimSpace(s), spt)
for _, si := range ss {
if si = strings.TrimSpace(si); si != "" {
list = append(list, si)
}
}
return
}
func getBasicValue(b *ast.BasicLit) string {
if b != nil {
return b.Value
}
return ""
}
func getIdents(vs []*ast.Ident) string {
return getIdent(vs...)
}
func getIdent(vs ...*ast.Ident) string {
switch len(vs) {
case 0:
return ""
case 1:
if vs[0] != nil {
return vs[0].Name
}
return ""
default:
s := strings.Builder{}
for _, v := range vs {
if v != nil && v.Name != "" {
if s.Len() > 0 {
s.WriteByte('.')
}
s.WriteString(v.Name)
}
}
return s.String()
}
}
func isExport(name string) bool {
return len(name) > 0 && name[0] >= 'A' && name[0] <= 'Z'
}
func isExportIdent(vs ...*ast.Ident) bool {
name := getIdents(vs)
return len(name) > 0 && name[0] >= 'A' && name[0] <= 'Z'
}
func getGroupComment(c *ast.CommentGroup) string {
if c != nil {
return "// " + strings.TrimSpace(c.Text())
}
return ""
}
func isBuiltin(typeName string) bool {
if builtinTypeMap == nil {
builtinTypeMap = getBuiltinType()
}
return builtinTypeMap[typeName]
}
var builtinTypeMap map[string]bool
func getBuiltinType() (builtinTypeMap map[string]bool) {
return map[string]bool{
"rune": true,
"bool": true,
"uint64": true,
"int16": true,
"float64": true,
"complex64": true,
"uint": true,
"byte": true,
"uint32": true,
"int8": true,
"int32": true,
"float32": true,
"complex128": true,
"int": true,
"uint8": true,
"uint16": true,
"int64": true,
"string": true,
"uintptr": true,
"error": true,
}
//builtinTypeMap = map[string]bool{}
//builtinFn := filepath.Join(runtime.GOROOT(), "src/builtin/builtin.go")
//file, err := parser.ParseFile(token.NewFileSet(), builtinFn, nil, 0)
//if err != nil {
// return
//}
//for _, decl := range file.Decls {
// if gen, ok := decl.(*ast.GenDecl); ok {
// for _, spec := range gen.Specs {
// if ts, ok := spec.(*ast.TypeSpec); ok {
// name := getIdent(ts.Name)
// if !isExport(name) {
// builtinTypeMap[name] = true
// }
// }
// }
// }
//}
//
//return
}
type Imports struct {
selPackage map[string]map[string]string
Imports []*Import `json:"imports"`
}
func (imp *Imports) Find(path string) (name string) {
var alias bool
name = mod.GetNameFromPath(path)
if len(imp.selPackage) == 0 {
imp.selPackage = map[string]map[string]string{name: {path: name}}
} else if packages, find := imp.selPackage[name]; find {
if name, find = packages[path]; find {
return
}
if i := len(packages) - 1; i > 0 {
name = name + strconv.Itoa(i)
alias = true
}
imp.selPackage[name][path] = name
} else {
imp.selPackage[name] = map[string]string{path: name}
}
imp.Imports = append(imp.Imports, &Import{Name: name, Path: path, Alias: alias})
sort.SliceStable(imp.Imports, func(i, j int) bool { return imp.Imports[i].Path < imp.Imports[j].Path })
return
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/go-wena/wena.git
git@gitee.com:go-wena/wena.git
go-wena
wena
wena
v0.0.4

搜索帮助