forked from Azure/azure-sdk-for-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
aliasPackage.go
323 lines (282 loc) · 7.9 KB
/
aliasPackage.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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
// +build go1.9
// Copyright 2018 Microsoft Corporation and contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package model holds the business logic for the operations made available by
// profileBuilder.
//
// This package is not governed by the SemVer associated with the rest of the
// Azure-SDK-for-Go.
package model
import (
"errors"
"fmt"
"go/ast"
"go/token"
"sort"
)
// AliasPackage is an abstraction around ast.Package to provide convenience methods for manipulating it.
type AliasPackage ast.Package
// ErrorUnexpectedToken is returned when AST parsing encounters an unexpected token, it includes the expected token.
type ErrorUnexpectedToken struct {
Expected token.Token
Received token.Token
}
var errUnexpectedNil = errors.New("unexpected nil")
func (utoken ErrorUnexpectedToken) Error() string {
return fmt.Sprintf("Unexpected token %d expecting type: %d", utoken.Received, utoken.Expected)
}
const modelFile = "models.go"
const origImportAlias = "original"
// ModelFile is a getter for the file accumulating aliased content.
func (alias AliasPackage) ModelFile() *ast.File {
if alias.Files != nil {
return alias.Files[modelFile]
}
return nil
}
// NewAliasPackage creates an alias package from the specified input package.
// Parameter importPath is the import path specified to consume the package.
func NewAliasPackage(original *ast.Package, importPath string) (*AliasPackage, error) {
models := &ast.File{
Name: &ast.Ident{
Name: original.Name,
NamePos: token.Pos(len("package") + 2),
},
Package: 1,
}
alias := &AliasPackage{
Name: original.Name,
Files: map[string]*ast.File{
modelFile: models,
},
}
models.Decls = append(models.Decls, &ast.GenDecl{
Tok: token.IMPORT,
Specs: []ast.Spec{
&ast.ImportSpec{
Name: &ast.Ident{
Name: origImportAlias,
},
Path: &ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf("\"%s\"", importPath),
},
},
},
})
genDecls := []*ast.GenDecl{}
funcDecls := []*ast.FuncDecl{}
// node traversal is non-deterministic so we maintain a collection
// that allows us to emit the nodes in a sort order of our choice
ast.Inspect(original, func(n ast.Node) bool {
switch node := n.(type) {
case *ast.FuncDecl:
// exclude methods as they're exposed on the aliased types
if node.Recv == nil {
funcDecls = append(funcDecls, node)
}
// return false as we don't care about the function body
return false
case *ast.GenDecl:
genDecls = append(genDecls, node)
}
return true
})
// genDecls contains constants and type definitions. group them so that
// type defs for consts are next to their respective list of constants.
type constType struct {
name string
typeSpec *ast.GenDecl
values *ast.GenDecl
}
untypedConsts := []*ast.GenDecl{}
constTypeMap := map[string]*constType{}
// first build a map from all the constants
for _, gd := range genDecls {
if gd.Tok == token.CONST {
// get the const type from the first item
vs := gd.Specs[0].(*ast.ValueSpec)
if vs.Type == nil {
// untyped consts go first
untypedConsts = append(untypedConsts, gd)
continue
}
typeName := vs.Type.(*ast.Ident).Name
constTypeMap[typeName] = &constType{
name: typeName,
values: gd,
}
}
}
typeSpecs := []*ast.GenDecl{}
// now update the map with the type specs
for _, gd := range genDecls {
if gd.Tok == token.TYPE {
spec := gd.Specs[0].(*ast.TypeSpec)
// check if the typespec is in the map, if it is it's for a constant
if typeMap, ok := constTypeMap[spec.Name.Name]; ok {
typeMap.typeSpec = gd
} else {
typeSpecs = append(typeSpecs, gd)
}
}
}
// add consts, types, and funcs, in that order, in sorted order
sort.SliceStable(untypedConsts, func(i, j int) bool {
tsI := untypedConsts[i].Specs[0].(*ast.TypeSpec)
tsJ := untypedConsts[j].Specs[0].(*ast.TypeSpec)
return tsI.Name.Name < tsJ.Name.Name
})
for _, uc := range untypedConsts {
err := alias.AddConst(uc)
if err != nil {
return nil, err
}
}
// convert to slice for sorting
constDecls := []*constType{}
for _, v := range constTypeMap {
constDecls = append(constDecls, v)
}
sort.SliceStable(constDecls, func(i, j int) bool {
return constDecls[i].name < constDecls[j].name
})
for _, cd := range constDecls {
err := alias.AddType(cd.typeSpec)
if err != nil {
return nil, err
}
err = alias.AddConst(cd.values)
if err != nil {
return nil, err
}
}
// now do the typespecs
sort.SliceStable(typeSpecs, func(i, j int) bool {
tsI := typeSpecs[i].Specs[0].(*ast.TypeSpec)
tsJ := typeSpecs[j].Specs[0].(*ast.TypeSpec)
return tsI.Name.Name < tsJ.Name.Name
})
for _, td := range typeSpecs {
err := alias.AddType(td)
if err != nil {
return nil, err
}
}
// funcs
sort.SliceStable(funcDecls, func(i, j int) bool {
return funcDecls[i].Name.Name < funcDecls[j].Name.Name
})
for _, fd := range funcDecls {
err := alias.AddFunc(fd)
if err != nil {
return nil, err
}
}
return alias, nil
}
// AddGeneral handles dispatching a GenDecl to either AddConst or AddType.
func (alias *AliasPackage) AddGeneral(decl *ast.GenDecl) error {
var adder func(*ast.GenDecl) error
switch decl.Tok {
case token.CONST:
adder = alias.AddConst
case token.TYPE:
adder = alias.AddType
default:
adder = func(item *ast.GenDecl) error {
return fmt.Errorf("Unusable token: %v", item.Tok)
}
}
return adder(decl)
}
// AddConst adds a Const block with indiviual aliases for each Spec in `decl`.
func (alias *AliasPackage) AddConst(decl *ast.GenDecl) error {
if decl == nil {
return errors.New("unexpected nil")
} else if decl.Tok != token.CONST {
return ErrorUnexpectedToken{Expected: token.CONST, Received: decl.Tok}
}
targetFile := alias.ModelFile()
for _, spec := range decl.Specs {
cast := spec.(*ast.ValueSpec)
for j, name := range cast.Names {
cast.Values[j] = &ast.SelectorExpr{
X: &ast.Ident{
Name: origImportAlias,
},
Sel: &ast.Ident{
Name: name.Name,
},
}
}
}
targetFile.Decls = append(targetFile.Decls, decl)
return nil
}
// AddType adds a Type delcaration block with individual alias for each Spec handed in `decl`
func (alias *AliasPackage) AddType(decl *ast.GenDecl) error {
if decl == nil {
return errUnexpectedNil
} else if decl.Tok != token.TYPE {
return ErrorUnexpectedToken{Expected: token.TYPE, Received: decl.Tok}
}
targetFile := alias.ModelFile()
for _, spec := range decl.Specs {
cast := spec.(*ast.TypeSpec)
cast.Assign = 1
cast.Type = &ast.SelectorExpr{
X: &ast.Ident{
Name: origImportAlias,
},
Sel: &ast.Ident{
Name: cast.Name.Name,
},
}
}
targetFile.Decls = append(targetFile.Decls, decl)
return nil
}
// AddFunc creates a stub method to redirect the call to the original package, then adds it to the model file.
func (alias *AliasPackage) AddFunc(decl *ast.FuncDecl) error {
if decl == nil {
return errUnexpectedNil
}
arguments := []ast.Expr{}
for _, p := range decl.Type.Params.List {
arguments = append(arguments, p.Names[0])
}
decl.Body = &ast.BlockStmt{
List: []ast.Stmt{
&ast.ReturnStmt{
Results: []ast.Expr{
&ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{
Name: origImportAlias,
},
Sel: &ast.Ident{
Name: decl.Name.Name,
},
},
Args: arguments,
},
},
},
},
}
targetFile := alias.ModelFile()
targetFile.Decls = append(targetFile.Decls, decl)
return nil
}