Skip to content

Commit

Permalink
parse global enums (#1387)
Browse files Browse the repository at this point in the history
* parse global enums
  • Loading branch information
sdghchj committed Nov 20, 2022
1 parent 829fbe1 commit bdfec2b
Show file tree
Hide file tree
Showing 13 changed files with 542 additions and 39 deletions.
122 changes: 122 additions & 0 deletions const.go
@@ -0,0 +1,122 @@
package swag

import (
"go/ast"
"go/token"
"strconv"
)

// ConstVariable a model to record an const variable
type ConstVariable struct {
Name *ast.Ident
Type ast.Expr
Value interface{}
Comment *ast.CommentGroup
}

// EvaluateValue evaluate the value
func (cv *ConstVariable) EvaluateValue(constTable map[string]*ConstVariable) interface{} {
if expr, ok := cv.Value.(ast.Expr); ok {
value, evalType := evaluateConstValue(cv.Name.Name, cv.Name.Obj.Data.(int), expr, constTable, make(map[string]struct{}))
if cv.Type == nil && evalType != nil {
cv.Type = evalType
}
if value != nil {
cv.Value = value
}
return value
}
return cv.Value
}

func evaluateConstValue(name string, iota int, expr ast.Expr, constTable map[string]*ConstVariable, recursiveStack map[string]struct{}) (interface{}, ast.Expr) {
if len(name) > 0 {
if _, ok := recursiveStack[name]; ok {
return nil, nil
}
recursiveStack[name] = struct{}{}
}

switch valueExpr := expr.(type) {
case *ast.Ident:
if valueExpr.Name == "iota" {
return iota, nil
}
if constTable != nil {
if cv, ok := constTable[valueExpr.Name]; ok {
if expr, ok = cv.Value.(ast.Expr); ok {
value, evalType := evaluateConstValue(valueExpr.Name, cv.Name.Obj.Data.(int), expr, constTable, recursiveStack)
if cv.Type == nil {
cv.Type = evalType
}
if value != nil {
cv.Value = value
}
return value, evalType
}
return cv.Value, cv.Type
}
}
case *ast.BasicLit:
switch valueExpr.Kind {
case token.INT:
x, err := strconv.ParseInt(valueExpr.Value, 10, 64)
if err != nil {
return nil, nil
}
return int(x), nil
case token.STRING, token.CHAR:
return valueExpr.Value[1 : len(valueExpr.Value)-1], nil
}
case *ast.UnaryExpr:
x, evalType := evaluateConstValue("", iota, valueExpr.X, constTable, recursiveStack)
switch valueExpr.Op {
case token.SUB:
return -x.(int), evalType
case token.XOR:
return ^(x.(int)), evalType
}
case *ast.BinaryExpr:
x, evalTypex := evaluateConstValue("", iota, valueExpr.X, constTable, recursiveStack)
y, evalTypey := evaluateConstValue("", iota, valueExpr.Y, constTable, recursiveStack)
evalType := evalTypex
if evalType == nil {
evalType = evalTypey
}
switch valueExpr.Op {
case token.ADD:
if ix, ok := x.(int); ok {
return ix + y.(int), evalType
} else if sx, ok := x.(string); ok {
return sx + y.(string), evalType
}
case token.SUB:
return x.(int) - y.(int), evalType
case token.MUL:
return x.(int) * y.(int), evalType
case token.QUO:
return x.(int) / y.(int), evalType
case token.REM:
return x.(int) % y.(int), evalType
case token.AND:
return x.(int) & y.(int), evalType
case token.OR:
return x.(int) | y.(int), evalType
case token.XOR:
return x.(int) ^ y.(int), evalType
case token.SHL:
return x.(int) << y.(int), evalType
case token.SHR:
return x.(int) >> y.(int), evalType
}
case *ast.ParenExpr:
return evaluateConstValue("", iota, valueExpr.X, constTable, recursiveStack)
case *ast.CallExpr:
//data conversion
if ident, ok := valueExpr.Fun.(*ast.Ident); ok && len(valueExpr.Args) == 1 && IsGolangPrimitiveType(ident.Name) {
arg, _ := evaluateConstValue("", iota, valueExpr.Args[0], constTable, recursiveStack)
return arg, nil
}
}
return nil, nil
}
13 changes: 13 additions & 0 deletions enums.go
@@ -0,0 +1,13 @@
package swag

const (
enumVarNamesExtension = "x-enum-varnames"
enumCommentsExtension = "x-enum-comments"
)

// EnumValue a model to record an enum const variable
type EnumValue struct {
key string
Value interface{}
Comment string
}
23 changes: 23 additions & 0 deletions enums_test.go
@@ -0,0 +1,23 @@
package swag

import (
"encoding/json"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseGlobalEnums(t *testing.T) {
searchDir := "testdata/enums"
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
assert.NoError(t, err)

p := New()
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
assert.NoError(t, err)
b, err := json.MarshalIndent(p.swagger, "", " ")
assert.NoError(t, err)
assert.Equal(t, string(expected), string(b))
}
4 changes: 2 additions & 2 deletions field_parser.go
Expand Up @@ -405,13 +405,13 @@ func (ps *tagBaseFieldParser) ComplementSchema(schema *spec.Schema) error {
if schema.Items.Schema.Extensions == nil {
schema.Items.Schema.Extensions = map[string]interface{}{}
}
schema.Items.Schema.Extensions["x-enum-varnames"] = field.enumVarNames
schema.Items.Schema.Extensions[enumVarNamesExtension] = field.enumVarNames
} else {
// Add to top level schema
if schema.Extensions == nil {
schema.Extensions = map[string]interface{}{}
}
schema.Extensions["x-enum-varnames"] = field.enumVarNames
schema.Extensions[enumVarNamesExtension] = field.enumVarNames
}
}

Expand Down
58 changes: 58 additions & 0 deletions package.go
@@ -0,0 +1,58 @@
package swag

import "go/ast"

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

// NewPackageDefinitions new a PackageDefinitions object
func NewPackageDefinitions(name string) *PackageDefinitions {
return &PackageDefinitions{
Name: name,
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(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,
}
pkg.ConstTable[valueSpec.Names[i].Name] = variable
pkg.OrderedConst = append(pkg.OrderedConst, variable)
}
return pkg
}

0 comments on commit bdfec2b

Please sign in to comment.