1 Star 0 Fork 0

妙音/bindgen

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
gen.go 12.90 KB
一键复制 编辑 原始数据 按行查看 历史
miaoyin 提交于 2024-06-24 10:15 +08:00 . feat: ignore Field
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
package internal
import (
"bytes"
"fmt"
"go/ast"
"go/printer"
"go/token"
"go/types"
"sort"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/xuender/kit/los"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/packages"
)
// Gen is the file-bindgen generator state.
type Gen struct {
pkg *packages.Package
buf bytes.Buffer
imports map[string]ImportInfo
anonImports map[string]bool
values map[ast.Expr]string
binder *Bind
}
func NewGen(pkg *packages.Package) *Gen {
return &Gen{
pkg: pkg,
anonImports: make(map[string]bool),
imports: make(map[string]ImportInfo),
values: make(map[ast.Expr]string),
binder: NewBind(),
}
}
// ImportInfo holds info about an import.
type ImportInfo struct {
// name is the identifier that is used in the generated source.
name string
// differs is true if the import is given an identifier that does not
// match the package's identifier.
differs bool
}
func (g *Gen) Pf(format string, args ...any) {
fmt.Fprintf(&g.buf, format, args...)
}
// writeAST prints an AST node into the generated output, rewriting any
// package references it encounters.
func (g *Gen) writeAST(info *types.Info, node ast.Node) {
node = g.rewritePkgRefs(info, node)
if err := printer.Fprint(&g.buf, g.pkg.Fset, node); err != nil {
panic(err)
}
}
// rewritePkgRefs rewrites any package references in an AST into references for the
// generated package.
func (g *Gen) rewritePkgRefs(info *types.Info, node ast.Node) ast.Node {
start, end := node.Pos(), node.End()
node = copyAST(node)
// First, rewrite all package names. This lets us know all the
// potentially colliding identifiers.
node = astutil.Apply(node, func(cur *astutil.Cursor) bool {
switch node := cur.Node().(type) {
case *ast.Ident:
// This is an unqualified identifier (qualified identifiers are peeled off below).
obj := info.ObjectOf(node)
if obj == nil {
return false
}
if pkg := obj.Pkg(); pkg != nil && obj.Parent() == pkg.Scope() && pkg.Path() != g.pkg.PkgPath {
// An identifier from either a dot import or read from a different package.
newPkgID := g.qualifyImport(pkg.Name(), pkg.Path())
cur.Replace(&ast.SelectorExpr{
X: ast.NewIdent(newPkgID),
Sel: ast.NewIdent(node.Name),
})
return false
}
return true
case *ast.SelectorExpr:
pkgIdent, isOk := node.X.(*ast.Ident)
if !isOk {
return true
}
pkgName, isOk := info.ObjectOf(pkgIdent).(*types.PkgName)
if !isOk {
return true
}
// This is a qualified identifier. Rewrite and avoid visiting subexpressions.
imported := pkgName.Imported()
newPkgID := g.qualifyImport(imported.Name(), imported.Path())
cur.Replace(&ast.SelectorExpr{
X: ast.NewIdent(newPkgID),
Sel: ast.NewIdent(node.Sel.Name),
})
return false
default:
return true
}
}, nil)
// Now that we have all the identifiers, rename any variables declared
// in this scope to not collide.
newNames := make(map[types.Object]string)
inNewNames := func(str string) bool {
for _, other := range newNames {
if other == str {
return true
}
}
return false
}
var scopeStack []*types.Scope
pkgScope := g.pkg.Types.Scope()
node = astutil.Apply(node, func(cur *astutil.Cursor) bool {
if scope := info.Scopes[cur.Node()]; scope != nil {
scopeStack = append(scopeStack, scope)
}
id, isOk := cur.Node().(*ast.Ident)
if !isOk {
return true
}
obj := info.ObjectOf(id)
if obj == nil {
// We rewrote this identifier earlier, so it does not need
// further rewriting.
return true
}
if n, isOk := newNames[obj]; isOk {
// We picked a new name for this symbol. Rewrite it.
cur.Replace(ast.NewIdent(n))
return false
}
if par := obj.Parent(); par == nil || par == pkgScope {
// Don't rename methods, field names, or top-level identifiers.
return true
}
// Rename any symbols defined within rewritePkgRefs's node that conflict
// with any symbols in the generated file.
objName := obj.Name()
if pos := obj.Pos(); pos < start || end <= pos || !(g.nameInFileScope(objName) || inNewNames(objName)) {
return true
}
newName := disambiguate(objName, func(str string) bool {
if g.nameInFileScope(str) || inNewNames(str) {
return true
}
if len(scopeStack) > 0 {
if _, obj := scopeStack[len(scopeStack)-1].LookupParent(str, token.NoPos); obj != nil {
return true
}
}
return false
})
newNames[obj] = newName
cur.Replace(ast.NewIdent(newName))
return false
}, func(cur *astutil.Cursor) bool {
if info.Scopes[cur.Node()] != nil {
// Should be top of stack; pop it.
scopeStack = scopeStack[:len(scopeStack)-1]
}
return true
})
return node
}
// Frame bakes the built up source body into an unformatted Go source file.
func (g *Gen) Frame(tags string) []byte {
if g.buf.Len() == 0 {
return nil
}
var buf bytes.Buffer
if len(tags) > 0 {
tags = fmt.Sprintf(" gen -tags \"%s\"", tags)
}
buf.WriteString("// Code generated by Bindgen. DO NOT EDIT.\n\n")
buf.WriteString("// version: " + Version)
buf.WriteString("// build time: " + BuildTime)
buf.WriteString("//go:generate go run gitee.com/xuender/bindgen/cmd/bindgen" + tags + "\n")
buf.WriteString("//+build !bindgen\n\n")
buf.WriteString("package " + g.pkg.Name + "\n\n")
if len(g.imports) > 0 {
buf.WriteString("import (\n")
imps := make([]string, 0, len(g.imports))
for path := range g.imports {
imps = append(imps, path)
}
sort.Strings(imps)
for _, path := range imps {
info := g.imports[path]
if info.differs {
fmt.Fprintf(&buf, "\t%s %q\n", info.name, path)
} else {
fmt.Fprintf(&buf, "\t%q\n", path)
}
}
buf.WriteString(")\n\n")
}
if len(g.anonImports) > 0 {
buf.WriteString("import (\n")
anonImps := make([]string, 0, len(g.anonImports))
for path := range g.anonImports {
anonImps = append(anonImps, path)
}
sort.Strings(anonImps)
for _, path := range anonImps {
fmt.Fprintf(&buf, "\t_ %s\n", path)
}
buf.WriteString(")\n\n")
}
buf.Write(g.buf.Bytes())
return buf.Bytes()
}
func (g *Gen) nameInFileScope(name string) bool {
for _, other := range g.imports {
if other.name == name {
return true
}
}
for _, other := range g.values {
if other == name {
return true
}
}
_, obj := g.pkg.Types.Scope().LookupParent(name, token.NoPos)
return obj != nil
}
// disambiguate picks a unique name, preferring name if it is already unique.
// It also disambiguates against Go's reserved keywords.
func disambiguate(name string, collides func(string) bool) string {
if !token.Lookup(name).IsKeyword() && !collides(name) {
return name
}
buf := []byte(name)
if len(buf) > 0 && buf[len(buf)-1] >= '0' && buf[len(buf)-1] <= '9' {
buf = append(buf, '_')
}
base := len(buf)
ten := 10
for n := 2; ; n++ {
buf = strconv.AppendInt(buf[:base], int64(n), ten)
sbuf := string(buf)
if !token.Lookup(sbuf).IsKeyword() && !collides(sbuf) {
return sbuf
}
}
}
func (g *Gen) qualifyImport(name, path string) string {
if path == g.pkg.PkgPath {
return ""
}
const vendorPart = "vendor/"
unvendored := path
if i := strings.LastIndex(path, vendorPart); i != -1 && (i == 0 || path[i-1] == '/') {
unvendored = path[i+len(vendorPart):]
}
if info, isOk := g.imports[unvendored]; isOk {
return info.name
}
newName := disambiguate(name, func(str string) bool {
return str == "err" || g.nameInFileScope(str)
})
g.imports[unvendored] = ImportInfo{
name: newName,
differs: newName != name,
}
return newName
}
// bind emits the code for an binder.
func (g *Gen) bind(pos token.Pos, name string, ignores []string, sig *types.Signature, doc *ast.CommentGroup) []error {
if err := funcOutput(sig); err != nil {
return []error{notePosition(g.pkg.Fset.Position(pos),
fmt.Errorf("inject %s: %w", name, err))}
}
bindPass(name, ignores, g.binder, sig, doc, &bindGen{
g: g,
errVar: disambiguate("err", g.nameInFileScope),
discard: true,
})
bindPass(name, ignores, g.binder, sig, doc, &bindGen{
g: g,
errVar: disambiguate("err", g.nameInFileScope),
discard: false,
})
// if len(pendingVars) > 0 {
// g.P("var (\n")
// for _, pv := range pendingVars {
// g.P("\t%s = ", pv.name)
// g.writeAST(pv.typeInfo, pv.expr)
// g.P("\n")
// }
// g.P(")\n\n")
// }
return nil
}
// bindGen is the bind pass generator state.
type bindGen struct {
g *Gen
paramNames []string
localNames []string
cleanupNames []string
errVar string
// discard causes ig.p and ig.writeAST to no-op. Useful to run
// generation for side-effects like filling in g.imports.
discard bool
}
func (ig *bindGen) Pf(format string, args ...any) {
if ig.discard {
return
}
ig.g.Pf(format, args...)
}
// bindPass generates an bind given the output from analysis.
// The sig passed in should be verified.
func bindPass(name string, ignores []string, bind *Bind, sig *types.Signature, doc *ast.CommentGroup, igen *bindGen) {
params := sig.Params()
los.Must0(funcOutput(sig))
if doc != nil {
for _, c := range doc.List {
igen.Pf("%s\n", c.Text)
}
}
start := 1
target := params.At(0)
if sig.Recv() == nil {
igen.Pf("func %s(", name)
} else {
start = 0
target = sig.Recv()
sig.Recv().Pkg()
igen.Pf("func (%s %s) %s(", target.Name(), getMod(target.Type().String(), sig.Recv().Pkg().Name()), name)
}
for idx := 0; idx < params.Len(); idx++ {
if idx > 0 {
igen.Pf(", ")
}
param := params.At(idx)
aName := param.Name()
if aName == "" || aName == "_" {
aName = typeVariableName(param.Type(), "arg", unexport, igen.nameInInjector)
} else {
aName = disambiguate(aName, igen.nameInInjector)
}
igen.paramNames = append(igen.paramNames, aName)
if sig.Variadic() && idx == params.Len()-1 {
igen.Pf("%s ...%s", igen.paramNames[idx], types.TypeString(param.Type().(*types.Slice).Elem(), igen.g.qualifyPkg))
} else {
igen.Pf("%s %s", igen.paramNames[idx], types.TypeString(param.Type(), igen.g.qualifyPkg))
}
}
igen.Pf(") {\n")
for i := start; i < params.Len(); i++ {
bind.Bind(igen, target, params.At(i), ignores)
}
igen.Pf("}\n\n")
}
func getMod(mod, pkg string) string {
star := strings.HasPrefix(mod, "*")
idx := strings.LastIndex(mod, "/")
mod = mod[idx+1:]
idx = strings.Index(mod, ".")
if pkg == mod[:idx] {
if star {
return "*" + mod[idx+1:]
}
return mod[idx+1:]
}
if star {
return mod
}
return mod
}
func typeVariableName(
varType types.Type,
defaultName string,
transform func(string) string,
collides func(string) bool,
) string {
if p, isOk := varType.(*types.Pointer); isOk {
varType = p.Elem()
}
var names []string
switch tmp := varType.(type) {
case *types.Basic:
if tmp.Name() != "" {
names = append(names, tmp.Name())
}
case *types.Named:
obj := tmp.Obj()
if name := obj.Name(); name != "" {
names = append(names, name)
}
// Provide an alternate name prefixed with the package name if possible.
// E.g., in case of collisions, we'll use "fooCfg" instead of "cfg2".
if pkg := obj.Pkg(); pkg != nil && pkg.Name() != "" {
names = append(names, fmt.Sprintf("%s%s", pkg.Name(), strings.ToTitle(obj.Name())))
}
}
// If we were unable to derive a name, use defaultName.
if len(names) == 0 {
names = append(names, defaultName)
}
// Transform the name(s).
for i, name := range names {
names[i] = transform(name)
}
// See if there's an unambiguous name; if so, use it.
for _, name := range names {
if !token.Lookup(name).IsKeyword() && !collides(name) {
return name
}
}
// Otherwise, disambiguate the first name.
return disambiguate(names[0], collides)
}
// nameInInjector reports whether name collides with any other identifier
// in the current injector.
func (ig *bindGen) nameInInjector(name string) bool {
if name == ig.errVar {
return true
}
for _, aName := range ig.paramNames {
if aName == name {
return true
}
}
for _, l := range ig.localNames {
if l == name {
return true
}
}
for _, l := range ig.cleanupNames {
if l == name {
return true
}
}
return ig.g.nameInFileScope(name)
}
func (g *Gen) qualifyPkg(pkg *types.Package) string {
return g.qualifyImport(pkg.Name(), pkg.Path())
}
// unexport converts a name that is potentially exported to an unexported name.
func unexport(name string) string {
if name == "" {
return ""
}
str, size := utf8.DecodeRuneInString(name)
if !unicode.IsUpper(str) {
// foo -> foo
return name
}
rs2, sz2 := utf8.DecodeRuneInString(name[size:])
if !unicode.IsUpper(rs2) {
// Foo -> foo
return string(unicode.ToLower(str)) + name[size:]
}
// UPPERWord -> upperWord
sbuf := new(strings.Builder)
sbuf.WriteRune(unicode.ToLower(str))
idx := size
str, size = rs2, sz2
for unicode.IsUpper(str) && size > 0 {
rs2, sz2 := utf8.DecodeRuneInString(name[idx+size:])
if sz2 > 0 && unicode.IsLower(rs2) {
break
}
idx += size
sbuf.WriteRune(unicode.ToLower(str))
str, size = rs2, sz2
}
sbuf.WriteString(name[idx:])
return sbuf.String()
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/xuender/bindgen.git
git@gitee.com:xuender/bindgen.git
xuender
bindgen
bindgen
v0.0.5

搜索帮助