-
Notifications
You must be signed in to change notification settings - Fork 2
/
source.go
188 lines (171 loc) · 4.88 KB
/
source.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package generate
import (
"encoding/json"
"go/ast"
"go/build"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"strings"
"frizz.io/generate/jast"
"github.com/pkg/errors"
"golang.org/x/tools/go/loader"
)
func scanSource(prog *scanDef) error {
conf := loader.Config{}
conf.Import("frizz.io/generate/helper") // contains references to useful interfaces
for _, input := range prog.input {
// Import all the input packages
conf.Import(input.path)
}
for path := range prog.Packages {
// Manually import all the packages that have been found while scanning data
conf.Import(path)
}
conf.Build = func() *build.Context { c := build.Default; return &c }() // make a copy of build.Default
conf.Build.GOPATH = prog.env.Getenv("GOPATH")
// We must exclude all generated files from the scan. This is because when a user makes changes
// to the signature of a type, the generated files would become invalid and go/types would
// throw an error when scanning. Additionally, performance is increased by preventing the
// scanning of the complex generated code. The results of scanning this code will not be needed
// during the generation process.
conf.Build.ReadDir = func(dir string) ([]os.FileInfo, error) {
raw, err := ioutil.ReadDir(dir)
if err != nil {
return nil, errors.WithStack(err)
}
var list []os.FileInfo
for _, fi := range raw {
if !strings.HasSuffix(fi.Name(), "generated.frizz.go") {
list = append(list, fi)
}
}
return list, nil
}
// Allow errors to ensure any references to the excluded generated file don't cause errors.
conf.AllowErrors = true
conf.ParserMode = parser.ParseComments
conf.TypeChecker.Error = func(e error) {}
loaded, err := conf.Load()
if err != nil {
return errors.WithStack(err)
}
prog.loaded = loaded
helper := loaded.Package("frizz.io/generate/helper")
for i, v := range helper.Defs {
switch i.Name {
case "packable":
prog.packable = v.Type().Underlying().(*types.Interface)
case "unmarshaler":
prog.unmarshaler = v.Type().Underlying().(*types.Interface)
case "marshaler":
prog.marshaler = v.Type().Underlying().(*types.Interface)
}
}
for pa, pi := range loaded.AllPackages {
if len(pi.Files) == 0 {
continue // virtual stdlib package
}
if strings.HasPrefix(loaded.Fset.File(pi.Files[0].Pos()).Name(), conf.Build.GOROOT) {
continue // stdlib package
}
packables := map[string]bool{}
for i, v := range pi.Defs {
if v, ok := v.(*types.TypeName); ok {
if types.Implements(types.NewPointer(v.Type()), prog.packable) {
packables[i.Name] = true
}
}
}
typeDefs := map[string]*typeDef{}
for _, f := range pi.Files {
for _, d := range f.Decls {
found, spec, annotation, err := isFrizzDecl(loaded.Fset, d)
if err != nil {
return err
}
if !found {
continue
}
td := &typeDef{
name: spec.Name.Name,
spec: spec.Type,
annotation: annotation,
packable: packables[spec.Name.Name],
}
typeDefs[spec.Name.Name] = td
}
}
if len(typeDefs) > 0 {
def, ok := prog.Packages[pa.Path()]
if !ok {
dir, _ := filepath.Split(loaded.Fset.File(pi.Files[0].Pos()).Name())
def = &packageDef{
scan: prog,
Path: pa.Path(),
Dir: dir,
stubs: map[string]*stub{},
imports: map[string]bool{},
}
prog.Packages[pa.Path()] = def
}
def.types = typeDefs
def.jast = jast.New(nil, pi.Uses)
def.info = pi
}
}
for _, def := range prog.Packages {
if def.info == nil {
continue
}
for _, importPkg := range def.info.Pkg.Imports() {
// only add to imports if the imported package is a frizz package
if _, ok := prog.Packages[importPkg.Path()]; ok {
def.imports[importPkg.Path()] = true
}
}
}
return nil
}
func isFrizzDecl(fset *token.FileSet, d ast.Decl) (found bool, spec *ast.TypeSpec, annotation string, err error) {
g, ok := d.(*ast.GenDecl)
if !ok {
return false, nil, "", nil
}
if g.Doc == nil {
return false, nil, "", nil
}
for _, c := range g.Doc.List {
if c.Text == "// frizz" {
found = true
break
}
if strings.HasPrefix(c.Text, "// frizz: ") {
s := strings.TrimPrefix(c.Text, "// frizz: ")
var ai interface{}
if err := json.Unmarshal([]byte(s), &ai); err != nil {
return false, nil, "", errors.Wrapf(err, "annotation %s must be well formed json", s)
}
annotation, _ = ai.(string)
found = true
break
}
}
if !found {
return false, nil, "", nil
}
if g.Tok != token.TYPE {
return false, nil, "", errors.Errorf("unsupported token tagged with frizz comment at %s", fset.Position(g.TokPos))
}
if len(g.Specs) != 1 {
return false, nil, "", errors.Errorf("must be a single spec in type definition at %s", fset.Position(g.TokPos))
}
ts, ok := g.Specs[0].(*ast.TypeSpec)
if !ok {
return false, nil, "", errors.Errorf("must be type spec at %s", fset.Position(g.TokPos))
}
return true, ts, annotation, nil
}