niki/vendor/github.com/swaggo/swag/package.go

188 lines
5.8 KiB
Go
Raw Normal View History

2024-05-14 13:07:09 +00:00
package swag
import (
"go/ast"
"go/token"
"reflect"
"strconv"
"strings"
)
// PackageDefinitions files and definition in a package.
type PackageDefinitions struct {
// files in this package, map key is file's relative path starting package path
Files map[string]*ast.File
// definitions in this package, map key is typeName
TypeDefinitions map[string]*TypeSpecDef
// const variables in this package, map key is the name
ConstTable map[string]*ConstVariable
// const variables in order in this package
OrderedConst []*ConstVariable
// package name
Name string
// package path
Path string
}
// ConstVariableGlobalEvaluator an interface used to evaluate enums across packages
type ConstVariableGlobalEvaluator interface {
EvaluateConstValue(pkg *PackageDefinitions, cv *ConstVariable, recursiveStack map[string]struct{}) (interface{}, ast.Expr)
EvaluateConstValueByName(file *ast.File, pkgPath, constVariableName string, recursiveStack map[string]struct{}) (interface{}, ast.Expr)
FindTypeSpec(typeName string, file *ast.File) *TypeSpecDef
}
// NewPackageDefinitions new a PackageDefinitions object
func NewPackageDefinitions(name, pkgPath string) *PackageDefinitions {
return &PackageDefinitions{
Name: name,
Path: pkgPath,
Files: make(map[string]*ast.File),
TypeDefinitions: make(map[string]*TypeSpecDef),
ConstTable: make(map[string]*ConstVariable),
}
}
// AddFile add a file
func (pkg *PackageDefinitions) AddFile(pkgPath string, file *ast.File) *PackageDefinitions {
pkg.Files[pkgPath] = file
return pkg
}
// AddTypeSpec add a type spec.
func (pkg *PackageDefinitions) AddTypeSpec(name string, typeSpec *TypeSpecDef) *PackageDefinitions {
pkg.TypeDefinitions[name] = typeSpec
return pkg
}
// AddConst add a const variable.
func (pkg *PackageDefinitions) AddConst(astFile *ast.File, valueSpec *ast.ValueSpec) *PackageDefinitions {
for i := 0; i < len(valueSpec.Names) && i < len(valueSpec.Values); i++ {
variable := &ConstVariable{
Name: valueSpec.Names[i],
Type: valueSpec.Type,
Value: valueSpec.Values[i],
Comment: valueSpec.Comment,
File: astFile,
}
pkg.ConstTable[valueSpec.Names[i].Name] = variable
pkg.OrderedConst = append(pkg.OrderedConst, variable)
}
return pkg
}
func (pkg *PackageDefinitions) evaluateConstValue(file *ast.File, iota int, expr ast.Expr, globalEvaluator ConstVariableGlobalEvaluator, recursiveStack map[string]struct{}) (interface{}, ast.Expr) {
switch valueExpr := expr.(type) {
case *ast.Ident:
if valueExpr.Name == "iota" {
return iota, nil
}
if pkg.ConstTable != nil {
if cv, ok := pkg.ConstTable[valueExpr.Name]; ok {
return globalEvaluator.EvaluateConstValue(pkg, cv, recursiveStack)
}
}
case *ast.SelectorExpr:
pkgIdent, ok := valueExpr.X.(*ast.Ident)
if !ok {
return nil, nil
}
return globalEvaluator.EvaluateConstValueByName(file, pkgIdent.Name, valueExpr.Sel.Name, recursiveStack)
case *ast.BasicLit:
switch valueExpr.Kind {
case token.INT:
// handle underscored number, such as 1_000_000
if strings.ContainsRune(valueExpr.Value, '_') {
valueExpr.Value = strings.Replace(valueExpr.Value, "_", "", -1)
}
if len(valueExpr.Value) >= 2 && valueExpr.Value[0] == '0' {
var start, base = 2, 8
switch valueExpr.Value[1] {
case 'x', 'X':
//hex
base = 16
case 'b', 'B':
//binary
base = 2
default:
//octet
start = 1
}
if x, err := strconv.ParseInt(valueExpr.Value[start:], base, 64); err == nil {
return int(x), nil
} else if x, err := strconv.ParseUint(valueExpr.Value[start:], base, 64); err == nil {
return x, nil
} else {
panic(err)
}
}
//a basic literal integer is int type in default, or must have an explicit converting type in front
if x, err := strconv.ParseInt(valueExpr.Value, 10, 64); err == nil {
return int(x), nil
} else if x, err := strconv.ParseUint(valueExpr.Value, 10, 64); err == nil {
return x, nil
} else {
panic(err)
}
case token.STRING:
if valueExpr.Value[0] == '`' {
return valueExpr.Value[1 : len(valueExpr.Value)-1], nil
}
return EvaluateEscapedString(valueExpr.Value[1 : len(valueExpr.Value)-1]), nil
case token.CHAR:
return EvaluateEscapedChar(valueExpr.Value[1 : len(valueExpr.Value)-1]), nil
}
case *ast.UnaryExpr:
x, evalType := pkg.evaluateConstValue(file, iota, valueExpr.X, globalEvaluator, recursiveStack)
if x == nil {
return x, evalType
}
return EvaluateUnary(x, valueExpr.Op, evalType)
case *ast.BinaryExpr:
x, evalTypex := pkg.evaluateConstValue(file, iota, valueExpr.X, globalEvaluator, recursiveStack)
y, evalTypey := pkg.evaluateConstValue(file, iota, valueExpr.Y, globalEvaluator, recursiveStack)
if x == nil || y == nil {
return nil, nil
}
return EvaluateBinary(x, y, valueExpr.Op, evalTypex, evalTypey)
case *ast.ParenExpr:
return pkg.evaluateConstValue(file, iota, valueExpr.X, globalEvaluator, recursiveStack)
case *ast.CallExpr:
//data conversion
if len(valueExpr.Args) != 1 {
return nil, nil
}
arg := valueExpr.Args[0]
if ident, ok := valueExpr.Fun.(*ast.Ident); ok {
name := ident.Name
if name == "uintptr" {
name = "uint"
}
value, _ := pkg.evaluateConstValue(file, iota, arg, globalEvaluator, recursiveStack)
if IsGolangPrimitiveType(name) {
value = EvaluateDataConversion(value, name)
return value, nil
} else if name == "len" {
return reflect.ValueOf(value).Len(), nil
}
typeDef := globalEvaluator.FindTypeSpec(name, file)
if typeDef == nil {
return nil, nil
}
return value, valueExpr.Fun
} else if selector, ok := valueExpr.Fun.(*ast.SelectorExpr); ok {
typeDef := globalEvaluator.FindTypeSpec(fullTypeName(selector.X.(*ast.Ident).Name, selector.Sel.Name), file)
if typeDef == nil {
return nil, nil
}
return arg, typeDef.TypeSpec.Type
}
}
return nil, nil
}