Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added suport for parsing comments inside of function bodies #1824

Merged
merged 2 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -41,6 +41,7 @@ const (
collectionFormatFlag = "collectionFormat"
packagePrefixFlag = "packagePrefix"
stateFlag = "state"
parseFuncBodyFlag = "parseFuncBody"
)

var initFlags = []cli.Flag{
Expand Down Expand Up @@ -179,6 +180,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 @@ -249,6 +255,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)
}
Loading