/
importer.go
175 lines (151 loc) · 4.88 KB
/
importer.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
//Package importer provides implementation of go/type/importer.
//
//It provides an ability to collect information, such as types, definitions,
//usages about the package and all inner packages.
//
//The main method is Collect. It will analyze package and return
//pointer to package and file set. Also, CollectInfoImporter.Info structure will
//bee filled.
//For example,
// info := types.Info{
// Types: make(map[ast.Expr]types.TypeAndValue),
// Defs: make(map[*ast.Ident]types.Object),
// Uses: make(map[*ast.Ident]types.Object),
// }
// importer := new(CollectInfoImporter)
// importer.Pkg = pkg + "/testfunc/main"
// importer.Info = &info
// resultPkg, fset, err := importer.Collect()
package importer
import (
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"strings"
"github.com/dooman87/gounexport/fs"
"github.com/dooman87/gounexport/util"
)
// CollectInfoImporter importing packages with dependencies and
// collecting information to info field. You need to provide
// Pkg and Info prior to use it
type CollectInfoImporter struct {
//Info struct that will be filled by Collect() method
Info *types.Info
//Package that should be a start point to collect info
Pkg string
fset *token.FileSet
astFiles []*ast.File
packages map[string]*types.Package
}
func (*CollectInfoImporter) errorHandler(err error) {
util.Warn("error while checking source: %v", err)
}
var (
defaultImporter = importer.Default()
)
//Collect going through package and collect info
//using conf.Check method. It's using this implementation
//of importer for check all inner packages and go/types/importer.Default()
//to check all built in packages (without sources)
func (_importer *CollectInfoImporter) Collect() (*types.Package, *token.FileSet, error) {
var conf types.Config
conf.Importer = _importer
conf.Error = _importer.errorHandler
if _importer.packages == nil {
_importer.packages = make(map[string]*types.Package)
}
var pkg *types.Package
var err error
var files []string
if files, err = fs.SourceFiles(_importer.Pkg, false); err != nil {
return nil, nil, err
}
if _importer.fset, _importer.astFiles, err = doParseFiles(files, _importer.fset); err != nil {
return nil, nil, err
}
//XXX: return positive result if check() returns error.
pkg, _ = conf.Check(_importer.Pkg, _importer.fset, _importer.astFiles, _importer.Info)
// if pkg, err = conf.Check(_importer.Pkg, _importer.fset, _importer.astFiles, _importer.Info); err != nil {
// return pkg, _importer.fset, err
// }
_importer.packages[_importer.Pkg] = pkg
util.Debug("package [%s] successfully parsed\n", pkg.Name())
return pkg, _importer.fset, nil
}
//Import parses the package or returns it from cache if it was
//already imported. Also, it collects information if path is under
//Pkg package
func (_importer *CollectInfoImporter) Import(path string) (*types.Package, error) {
if _importer.packages[path] != nil {
return _importer.packages[path], nil
}
util.Info("importing package [%s]", path)
var pkg *types.Package
var err error
if strings.Contains(path, _importer.Pkg) {
if pkg, err = _importer.doImport(path, true); err != nil {
return pkg, err
}
}
pkg, err = defaultImporter.Import(path)
if err != nil {
pkg, err = _importer.doImport(path, true)
}
if pkg != nil {
_importer.packages[path] = pkg
}
util.Info("package [%s] imported: [%v] [%v]", path, pkg, err)
return pkg, err
}
func (_importer *CollectInfoImporter) doImport(path string, collectInfo bool) (*types.Package, error) {
var pkg *types.Package
var err error
var conf types.Config
conf.Importer = _importer
conf.Error = _importer.errorHandler
files, err := fs.SourceFiles(path, false)
if err != nil {
return nil, err
}
fset, astFiles, err := doParseFiles(files, _importer.fset)
if err != nil {
return nil, err
}
if collectInfo {
pkg, err = conf.Check(path, fset, astFiles, _importer.Info)
} else {
pkg, err = conf.Check(path, fset, astFiles, nil)
}
return pkg, err
}
func doParseFiles(filePathes []string, fset *token.FileSet) (*token.FileSet, []*ast.File, error) {
if fset == nil {
fset = token.NewFileSet()
}
util.Info("parsing files %v", filePathes)
astFiles := make([]*ast.File, 0, len(filePathes))
for _, f := range filePathes {
//XXX: Ignoring files with packages ends with _test.
//XXX: Doing that because getting error in check()
//XXX: cause source file is still going to current
//XXX: packages. Need to analyze package before
//XXX: and check both packages separately.
tempFset := token.NewFileSet()
astFile, err := parser.ParseFile(tempFset, f, nil, 0)
if !strings.HasSuffix(astFile.Name.Name, "_test") {
if err != nil {
return nil, nil, err
}
astFile, _ := parser.ParseFile(fset, f, nil, 0)
astFiles = append(astFiles, astFile)
}
}
iterateFunc := func(f *token.File) bool {
util.Debug("\t%s", f.Name())
return true
}
fset.Iterate(iterateFunc)
return fset, astFiles, nil
}