/
parser.go
129 lines (118 loc) · 3.04 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package gormgen
import (
"go/ast"
"go/build"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
)
// The Parser is used to parse a directory and expose information about the structs defined in the files of this directory.
type Parser struct {
dir string
pkgName string
types map[*ast.Ident]*ast.StructType
files []string
parsedFiles []*ast.File
fileSet *token.FileSet
defs map[*ast.Ident]types.Object
}
// NewParser create a new parser instance.
func NewParser() *Parser {
return &Parser{
types: make(map[*ast.Ident]*ast.StructType),
}
}
func (p *Parser) getFiles() {
pkg, err := build.Default.ImportDir(p.dir, 0)
if err != nil {
log.Fatalf("cannot process directory %s: %s", p.dir, err)
}
var files []string
p.pkgName = pkg.Name
files = append(files, pkg.GoFiles...)
files = append(files, pkg.CgoFiles...)
files = append(files, pkg.TestGoFiles...)
p.files = files
}
func (p *Parser) parseFiles() {
var parsedFiles []*ast.File
fs := token.NewFileSet()
for _, file := range p.files {
parsedFile, err := parser.ParseFile(fs, file, nil, 0)
if err != nil {
log.Fatalf("parsing package: %s: %s\n", file, err)
}
parsedFiles = append(parsedFiles, parsedFile)
}
p.parsedFiles, p.fileSet = parsedFiles, fs
}
func (p *Parser) parseTypes(file *ast.File) {
ast.Inspect(file, func(node ast.Node) bool {
decl, ok := node.(*ast.GenDecl)
if !ok || decl.Tok != token.TYPE {
return true
}
for _, spec := range decl.Specs {
typeSpec, ok := spec.(*ast.TypeSpec)
if !ok {
continue
}
// We only care about struct declaration (for now)
var structType *ast.StructType
if structType, ok = typeSpec.Type.(*ast.StructType); !ok {
continue
}
p.types[typeSpec.Name] = structType
}
return true
})
}
// Copied from the stringer package. Check the README
func (p *Parser) typeCheck() {
p.defs = make(map[*ast.Ident]types.Object)
config := types.Config{Importer: importer.Default(), FakeImportC: true}
info := &types.Info{
Defs: p.defs,
}
_, err := config.Check(p.dir, p.fileSet, p.parsedFiles, info)
if err != nil {
log.Fatalf("checking package: %s", err)
}
}
// ParseDir should be called before any type querying for the parser. It takes the directory to be parsed and extracts all the structs defined in this directory.
func (p *Parser) ParseDir(dir string) {
p.dir = dir
p.getFiles()
p.parseFiles()
for _, f := range p.parsedFiles {
p.parseTypes(f)
}
p.typeCheck()
}
// GetTypeByName takes the name of the struct and returns a pointer to types.Struct which contains all the information
// about this struct. It returns nil if the struct is not found.
func (p *Parser) GetTypeByName(name string) *types.Struct {
ident := p.getIdentByName(name)
if ident == nil {
return nil
}
def, ok := p.defs[ident]
if !ok {
return nil
}
structType, ok := def.Type().Underlying().(*types.Struct)
if !ok {
return nil
}
return structType
}
func (p *Parser) getIdentByName(name string) *ast.Ident {
for id := range p.types {
if id.Name == name {
return id
}
}
return nil
}