Skip to content

Commit bdfec2b

Browse files
authored
parse global enums (#1387)
* parse global enums
1 parent 829fbe1 commit bdfec2b

File tree

13 files changed

+542
-39
lines changed

13 files changed

+542
-39
lines changed

const.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package swag
2+
3+
import (
4+
"go/ast"
5+
"go/token"
6+
"strconv"
7+
)
8+
9+
// ConstVariable a model to record an const variable
10+
type ConstVariable struct {
11+
Name *ast.Ident
12+
Type ast.Expr
13+
Value interface{}
14+
Comment *ast.CommentGroup
15+
}
16+
17+
// EvaluateValue evaluate the value
18+
func (cv *ConstVariable) EvaluateValue(constTable map[string]*ConstVariable) interface{} {
19+
if expr, ok := cv.Value.(ast.Expr); ok {
20+
value, evalType := evaluateConstValue(cv.Name.Name, cv.Name.Obj.Data.(int), expr, constTable, make(map[string]struct{}))
21+
if cv.Type == nil && evalType != nil {
22+
cv.Type = evalType
23+
}
24+
if value != nil {
25+
cv.Value = value
26+
}
27+
return value
28+
}
29+
return cv.Value
30+
}
31+
32+
func evaluateConstValue(name string, iota int, expr ast.Expr, constTable map[string]*ConstVariable, recursiveStack map[string]struct{}) (interface{}, ast.Expr) {
33+
if len(name) > 0 {
34+
if _, ok := recursiveStack[name]; ok {
35+
return nil, nil
36+
}
37+
recursiveStack[name] = struct{}{}
38+
}
39+
40+
switch valueExpr := expr.(type) {
41+
case *ast.Ident:
42+
if valueExpr.Name == "iota" {
43+
return iota, nil
44+
}
45+
if constTable != nil {
46+
if cv, ok := constTable[valueExpr.Name]; ok {
47+
if expr, ok = cv.Value.(ast.Expr); ok {
48+
value, evalType := evaluateConstValue(valueExpr.Name, cv.Name.Obj.Data.(int), expr, constTable, recursiveStack)
49+
if cv.Type == nil {
50+
cv.Type = evalType
51+
}
52+
if value != nil {
53+
cv.Value = value
54+
}
55+
return value, evalType
56+
}
57+
return cv.Value, cv.Type
58+
}
59+
}
60+
case *ast.BasicLit:
61+
switch valueExpr.Kind {
62+
case token.INT:
63+
x, err := strconv.ParseInt(valueExpr.Value, 10, 64)
64+
if err != nil {
65+
return nil, nil
66+
}
67+
return int(x), nil
68+
case token.STRING, token.CHAR:
69+
return valueExpr.Value[1 : len(valueExpr.Value)-1], nil
70+
}
71+
case *ast.UnaryExpr:
72+
x, evalType := evaluateConstValue("", iota, valueExpr.X, constTable, recursiveStack)
73+
switch valueExpr.Op {
74+
case token.SUB:
75+
return -x.(int), evalType
76+
case token.XOR:
77+
return ^(x.(int)), evalType
78+
}
79+
case *ast.BinaryExpr:
80+
x, evalTypex := evaluateConstValue("", iota, valueExpr.X, constTable, recursiveStack)
81+
y, evalTypey := evaluateConstValue("", iota, valueExpr.Y, constTable, recursiveStack)
82+
evalType := evalTypex
83+
if evalType == nil {
84+
evalType = evalTypey
85+
}
86+
switch valueExpr.Op {
87+
case token.ADD:
88+
if ix, ok := x.(int); ok {
89+
return ix + y.(int), evalType
90+
} else if sx, ok := x.(string); ok {
91+
return sx + y.(string), evalType
92+
}
93+
case token.SUB:
94+
return x.(int) - y.(int), evalType
95+
case token.MUL:
96+
return x.(int) * y.(int), evalType
97+
case token.QUO:
98+
return x.(int) / y.(int), evalType
99+
case token.REM:
100+
return x.(int) % y.(int), evalType
101+
case token.AND:
102+
return x.(int) & y.(int), evalType
103+
case token.OR:
104+
return x.(int) | y.(int), evalType
105+
case token.XOR:
106+
return x.(int) ^ y.(int), evalType
107+
case token.SHL:
108+
return x.(int) << y.(int), evalType
109+
case token.SHR:
110+
return x.(int) >> y.(int), evalType
111+
}
112+
case *ast.ParenExpr:
113+
return evaluateConstValue("", iota, valueExpr.X, constTable, recursiveStack)
114+
case *ast.CallExpr:
115+
//data conversion
116+
if ident, ok := valueExpr.Fun.(*ast.Ident); ok && len(valueExpr.Args) == 1 && IsGolangPrimitiveType(ident.Name) {
117+
arg, _ := evaluateConstValue("", iota, valueExpr.Args[0], constTable, recursiveStack)
118+
return arg, nil
119+
}
120+
}
121+
return nil, nil
122+
}

enums.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package swag
2+
3+
const (
4+
enumVarNamesExtension = "x-enum-varnames"
5+
enumCommentsExtension = "x-enum-comments"
6+
)
7+
8+
// EnumValue a model to record an enum const variable
9+
type EnumValue struct {
10+
key string
11+
Value interface{}
12+
Comment string
13+
}

enums_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package swag
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestParseGlobalEnums(t *testing.T) {
13+
searchDir := "testdata/enums"
14+
expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
15+
assert.NoError(t, err)
16+
17+
p := New()
18+
err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
19+
assert.NoError(t, err)
20+
b, err := json.MarshalIndent(p.swagger, "", " ")
21+
assert.NoError(t, err)
22+
assert.Equal(t, string(expected), string(b))
23+
}

field_parser.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,13 @@ func (ps *tagBaseFieldParser) ComplementSchema(schema *spec.Schema) error {
405405
if schema.Items.Schema.Extensions == nil {
406406
schema.Items.Schema.Extensions = map[string]interface{}{}
407407
}
408-
schema.Items.Schema.Extensions["x-enum-varnames"] = field.enumVarNames
408+
schema.Items.Schema.Extensions[enumVarNamesExtension] = field.enumVarNames
409409
} else {
410410
// Add to top level schema
411411
if schema.Extensions == nil {
412412
schema.Extensions = map[string]interface{}{}
413413
}
414-
schema.Extensions["x-enum-varnames"] = field.enumVarNames
414+
schema.Extensions[enumVarNamesExtension] = field.enumVarNames
415415
}
416416
}
417417

package.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package swag
2+
3+
import "go/ast"
4+
5+
// PackageDefinitions files and definition in a package.
6+
type PackageDefinitions struct {
7+
// files in this package, map key is file's relative path starting package path
8+
Files map[string]*ast.File
9+
10+
// definitions in this package, map key is typeName
11+
TypeDefinitions map[string]*TypeSpecDef
12+
13+
// const variables in this package, map key is the name
14+
ConstTable map[string]*ConstVariable
15+
16+
// const variables in order in this package
17+
OrderedConst []*ConstVariable
18+
19+
// package name
20+
Name string
21+
}
22+
23+
// NewPackageDefinitions new a PackageDefinitions object
24+
func NewPackageDefinitions(name string) *PackageDefinitions {
25+
return &PackageDefinitions{
26+
Name: name,
27+
Files: make(map[string]*ast.File),
28+
TypeDefinitions: make(map[string]*TypeSpecDef),
29+
ConstTable: make(map[string]*ConstVariable),
30+
}
31+
}
32+
33+
// AddFile add a file
34+
func (pkg *PackageDefinitions) AddFile(pkgPath string, file *ast.File) *PackageDefinitions {
35+
pkg.Files[pkgPath] = file
36+
return pkg
37+
}
38+
39+
// AddTypeSpec add a type spec.
40+
func (pkg *PackageDefinitions) AddTypeSpec(name string, typeSpec *TypeSpecDef) *PackageDefinitions {
41+
pkg.TypeDefinitions[name] = typeSpec
42+
return pkg
43+
}
44+
45+
// AddConst add a const variable.
46+
func (pkg *PackageDefinitions) AddConst(valueSpec *ast.ValueSpec) *PackageDefinitions {
47+
for i := 0; i < len(valueSpec.Names) && i < len(valueSpec.Values); i++ {
48+
variable := &ConstVariable{
49+
Name: valueSpec.Names[i],
50+
Type: valueSpec.Type,
51+
Value: valueSpec.Values[i],
52+
Comment: valueSpec.Comment,
53+
}
54+
pkg.ConstTable[valueSpec.Names[i].Name] = variable
55+
pkg.OrderedConst = append(pkg.OrderedConst, variable)
56+
}
57+
return pkg
58+
}

0 commit comments

Comments
 (0)