eng/tools/profileBuilder/model/aliasPackage.go (239 lines of code) (raw):

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