File: aliasPackage.go

package info (click to toggle)
golang-github-azure-azure-sdk-for-go 68.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 556,256 kB
  • sloc: javascript: 196; sh: 96; makefile: 7
file content (312 lines) | stat: -rw-r--r-- 7,629 bytes parent folder | download | duplicates (3)
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
// +build go1.9

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

// 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
}