Skip to content

Commit

Permalink
Added suport for parsing comments inside of function bodies (#1824)
Browse files Browse the repository at this point in the history
Added suport for parsing comments inside of function bodies

---------

Co-authored-by: Jonas Ha <[email protected]>
  • Loading branch information
j-d-ha and Jonas Ha committed Jun 21, 2024
1 parent e55c557 commit 7204462
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 26 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ OPTIONS:
--templateDelims value, --td value Provide custom delimiters for Go template generation. The format is leftDelim,rightDelim. For example: "[[,]]"
--collectionFormat value, --cf value Set default collection format (default: "csv")
--state value Initial state for the state machine (default: ""), @HostState in root file, @State in other files
--parseFuncBody Parse API info within body of functions in go files, disabled by default (default: false)
--help, -h show help (default: false)
```
Expand Down Expand Up @@ -931,7 +932,7 @@ func GetPosts(w http.ResponseWriter, r *http.Request) {
_ = web.GenericNestedResponse[types.Post]{}
}
```
See [this file](https://github.com/swaggo/swag/blob/master/testdata/generics_nested/api/api.go) for more details
See [this file](https://github.com/swaggo/swag/blob/master/testdata/generics_nested/api/api.go) for more details
and other examples.
### Change the default Go Template action delimiters
Expand Down
7 changes: 7 additions & 0 deletions cmd/swag/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
collectionFormatFlag = "collectionFormat"
packagePrefixFlag = "packagePrefix"
stateFlag = "state"
parseFuncBodyFlag = "parseFuncBody"
)

var initFlags = []cli.Flag{
Expand Down Expand Up @@ -180,6 +181,11 @@ var initFlags = []cli.Flag{
Value: "",
Usage: "Set host state for swagger.json",
},
&cli.BoolFlag{
Name: parseFuncBodyFlag,
// Value: false,
Usage: "Parse API info within body of functions in go files, disabled by default (default: false)",
},
}

func initAction(ctx *cli.Context) error {
Expand Down Expand Up @@ -261,6 +267,7 @@ func initAction(ctx *cli.Context) error {
CollectionFormat: collectionFormat,
PackagePrefix: ctx.String(packagePrefixFlag),
State: ctx.String(stateFlag),
ParseFuncBody: ctx.Bool(parseFuncBodyFlag),
})
}

Expand Down
4 changes: 4 additions & 0 deletions gen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ type Config struct {

// State set host state
State string

// ParseFuncBody whether swag should parse api info inside of funcs
ParseFuncBody bool
}

// Build builds swagger json file for given searchDir and mainAPIFile. Returns json.
Expand Down Expand Up @@ -213,6 +216,7 @@ func (g *Gen) Build(config *Config) error {
p.ParseInternal = config.ParseInternal
p.RequiredByDefault = config.RequiredByDefault
p.HostState = config.State
p.ParseFuncBody = config.ParseFuncBody

if err := p.ParseAPIMultiSearchDir(searchDirs, config.MainAPIFile, config.ParseDepth); err != nil {
return err
Expand Down
65 changes: 44 additions & 21 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ type Parser struct {

// HostState is the state of the host
HostState string

// ParseFuncBody whether swag should parse api info inside of funcs
ParseFuncBody bool
}

// FieldParserFactory create FieldParser.
Expand Down Expand Up @@ -1016,37 +1019,57 @@ func matchExtension(extensionToMatch string, comments []*ast.Comment) (match boo

// ParseRouterAPIInfo parses router api info for given astFile.
func (parser *Parser) ParseRouterAPIInfo(fileInfo *AstFileInfo) error {
DeclsLoop:
for _, astDescription := range fileInfo.File.Decls {
if (fileInfo.ParseFlag & ParseOperations) == ParseNone {
continue
if (fileInfo.ParseFlag & ParseOperations) == ParseNone {
return nil
}

// parse File.Comments instead of File.Decls.Doc if ParseFuncBody flag set to "true"
if parser.ParseFuncBody {
for _, astComments := range fileInfo.File.Comments {
if astComments.List != nil {
if err := parser.parseRouterAPIInfoComment(astComments.List, fileInfo); err != nil {
return err
}
}
}

return nil
}

for _, astDescription := range fileInfo.File.Decls {
astDeclaration, ok := astDescription.(*ast.FuncDecl)
if ok && astDeclaration.Doc != nil && astDeclaration.Doc.List != nil {
if parser.matchTags(astDeclaration.Doc.List) &&
matchExtension(parser.parseExtension, astDeclaration.Doc.List) {
// for per 'function' comment, create a new 'Operation' object
operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir))
for _, comment := range astDeclaration.Doc.List {
err := operation.ParseComment(comment.Text, fileInfo.File)
if err != nil {
return fmt.Errorf("ParseComment error in file %s :%+v", fileInfo.Path, err)
}
if operation.State != "" && operation.State != parser.HostState {
continue DeclsLoop
}
}
err := processRouterOperation(parser, operation)
if err != nil {
return err
}
if err := parser.parseRouterAPIInfoComment(astDeclaration.Doc.List, fileInfo); err != nil {
return err
}
}
}

return nil
}

func (parser *Parser) parseRouterAPIInfoComment(comments []*ast.Comment, fileInfo *AstFileInfo) error {
if parser.matchTags(comments) && matchExtension(parser.parseExtension, comments) {
// for per 'function' comment, create a new 'Operation' object
operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir))
for _, comment := range comments {
err := operation.ParseComment(comment.Text, fileInfo.File)
if err != nil {
return fmt.Errorf("ParseComment error in file %s :%+v", fileInfo.Path, err)
}
if operation.State != "" && operation.State != parser.HostState {
return nil
}
}
err := processRouterOperation(parser, operation)
if err != nil {
return err
}
}

return nil
}

func refRouteMethodOp(item *spec.PathItem, method string) (op **spec.Operation) {
switch method {
case http.MethodGet:
Expand Down
71 changes: 67 additions & 4 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3363,14 +3363,14 @@ func TestParseFunctionScopedStructDefinition(t *testing.T) {
src := `
package main
// @Param request body main.Fun.request true "query params"
// @Param request body main.Fun.request true "query params"
// @Success 200 {object} main.Fun.response
// @Router /test [post]
func Fun() {
type request struct {
Name string
}
type response struct {
Name string
Child string
Expand All @@ -3395,14 +3395,14 @@ func TestParseFunctionScopedStructRequestResponseJSON(t *testing.T) {
src := `
package main
// @Param request body main.Fun.request true "query params"
// @Param request body main.Fun.request true "query params"
// @Success 200 {object} main.Fun.response
// @Router /test [post]
func Fun() {
type request struct {
Name string
}
type response struct {
Name string
Child string
Expand Down Expand Up @@ -4123,3 +4123,66 @@ func TestParser_skipPackageByPrefix(t *testing.T) {
assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag/cmd"))
assert.False(t, parser.skipPackageByPrefix("github.com/swaggo/swag/gen"))
}

func TestParser_ParseRouterApiInFuncBody(t *testing.T) {
t.Parallel()

src := `
package test
func Test(){
// @Router /api/{id} [get]
_ = func() {
}
}
`
p := New()
p.ParseFuncBody = true
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)

err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)

ps := p.swagger.Paths.Paths

val, ok := ps["/api/{id}"]

assert.True(t, ok)
assert.NotNil(t, val.Get)
}

func TestParser_ParseRouterApiInfoInAndOutFuncBody(t *testing.T) {
t.Parallel()

src := `
package test
// @Router /api/outside [get]
func otherRoute(){
}
func Test(){
// @Router /api/inside [get]
_ = func() {
}
}
`
p := New()
p.ParseFuncBody = true
err := p.packages.ParseFile("api", "api/api.go", src, ParseAll)
assert.NoError(t, err)

err = p.packages.RangeFiles(p.ParseRouterAPIInfo)
assert.NoError(t, err)

ps := p.swagger.Paths.Paths

val1, ok := ps["/api/outside"]
assert.True(t, ok)
assert.NotNil(t, val1.Get)

val2, ok := ps["/api/inside"]
assert.True(t, ok)
assert.NotNil(t, val2.Get)
}

0 comments on commit 7204462

Please sign in to comment.