-
Notifications
You must be signed in to change notification settings - Fork 56
/
resolver.go
149 lines (129 loc) · 3.32 KB
/
resolver.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
package goast
import (
"fmt"
"go/ast"
"go/token"
"strconv"
"sync"
"github.com/dave/dst/decorator/resolver"
"github.com/dave/dst/decorator/resolver/guess"
)
func New() *DecoratorResolver {
return &DecoratorResolver{}
}
func WithResolver(resolver resolver.RestorerResolver) *DecoratorResolver {
return &DecoratorResolver{RestorerResolver: resolver}
}
// DecoratorResolver is a simple ident resolver that parses the imports block of the file and resolves
// qualified identifiers using resolved package names. It is not possible to resolve identifiers in
// dot-imported packages without the full export data of the imported package, so this resolver will
// return an error if it encounters a dot-import. See gotypes.DecoratorResolver for a dot-imports
// capable ident resolver.
type DecoratorResolver struct {
RestorerResolver resolver.RestorerResolver
filesM sync.Mutex
files map[*ast.File]map[string]string
}
func (r *DecoratorResolver) ResolveIdent(file *ast.File, parent ast.Node, parentField string, id *ast.Ident) (string, error) {
if r.RestorerResolver == nil {
r.RestorerResolver = guess.New()
}
imports, err := r.imports(file)
if err != nil {
return "", err
}
se, ok := parent.(*ast.SelectorExpr)
if !ok || parentField != "Sel" {
return "", nil
}
xid, ok := se.X.(*ast.Ident)
if !ok {
return "", nil
}
if xid.Obj != nil {
// Obj != nil -> not a qualified ident
return "", nil
}
path, ok := imports[xid.Name]
if !ok {
return "", nil
}
return path, nil
}
func (r *DecoratorResolver) imports(file *ast.File) (map[string]string, error) {
r.filesM.Lock()
defer r.filesM.Unlock()
if r.files == nil {
r.files = map[*ast.File]map[string]string{}
}
imports, ok := r.files[file]
if ok {
return imports, nil
}
imports = map[string]string{}
var done bool
var outer error
ast.Inspect(file, func(node ast.Node) bool {
if done || outer != nil {
return false
}
switch node := node.(type) {
case *ast.FuncDecl:
// Import decls must come before all other decls, so as soon as we find a func decl, we
// can finish.
done = true
return false
case *ast.GenDecl:
if node.Tok != token.IMPORT {
// Import decls must come before all other decls, so as soon as we find a non-import
// gen decl, we can finish.
done = true
return false
}
return true
case *ast.ImportSpec:
path := mustUnquote(node.Path.Value)
if path == "C" {
return false
}
var name string
if node.Name != nil {
name = node.Name.Name
}
switch name {
case ".":
// We can't resolve "." imports, so throw an error
outer = fmt.Errorf("goast.DecoratorResolver unsupported dot-import found for %s", path)
return false
case "_":
// Don't need to worry about _ imports
return false
case "":
var err error
name, err = r.RestorerResolver.ResolvePackage(path)
if err != nil {
outer = err
return false
}
}
if p, ok := imports[name]; ok {
outer = fmt.Errorf("goast.DecoratorResolver found multiple packages using name %s: %s and %s", name, p, path)
return false
}
imports[name] = path
}
return true
})
if outer != nil {
return nil, outer
}
r.files[file] = imports
return imports, nil
}
func mustUnquote(s string) string {
out, err := strconv.Unquote(s)
if err != nil {
panic(err)
}
return out
}