Repository: swaggo/swag
Pull request #1856 (2024-07-29 02:04:43)

Update README
**Describe the PR**
Update Readme.md



Patch:
diff --git a/README.md b/README.md
index b453c41..ca30777 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,7 @@ OPTIONS:
    --outputTypes value, --ot value        Output types of generated files (docs.go, swagger.json, swagger.yaml) like go,json,yaml (default: "go,json,yaml")
    --parseVendor                          Parse go files in 'vendor' folder, disabled by default (default: false)
    --parseDependency, --pd                Parse go files inside dependency folder, disabled by default (default: false)
+   --parseDependencyLevel, --pdl          Enhancement of '--parseDependency', parse go files inside dependency folder, 0 disabled, 1 only parse models, 2 only parse operations, 3 parse all (default: 0)
    --markdownFiles value, --md value      Parse folder containing markdown files to use as description, disabled by default
    --codeExampleFiles value, --cef value  Parse folder containing code example files to use for the x-codeSamples extension, disabled by default
    --parseInternal                        Parse go files in internal packages, disabled by default (default: false)
diff --git a/README_zh-CN.md b/README_zh-CN.md
index 2b3d9d2..24a219c 100644
--- a/README_zh-CN.md
+++ b/README_zh-CN.md
@@ -90,6 +90,7 @@ OPTIONS:
    --output value, -o value               文件(swagger.json, swagger.yaml and doc.go)输出目录 (默认: "./docs")
    --parseVendor                          是否解析vendor目录里的go源文件，默认不
    --parseDependency                      是否解析依赖目录中的go源文件，默认不
+   --parseDependencyLevel, --pdl          对'--parseDependency'参数进行增强, 是否解析依赖目录中的go源文件, 0 不解析, 1 只解析对象模型, 2 只解析API, 3 对象模型和API都解析 (default: 0)
    --markdownFiles value, --md value      指定API的描述信息所使用的markdown文件所在的目录
    --generatedTime                        是否输出时间到输出文件docs.go的顶部，默认是
    --codeExampleFiles value, --cef value  解析包含用于 x-codeSamples 扩展的代码示例文件的文件夹，默认禁用
diff --git a/generics.go b/generics.go
index 07344bb..80e93a9 100644
--- a/generics.go
+++ b/generics.go
@@ -61,6 +61,7 @@ func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string,
 			Enums:      typeSpecDef.Enums,
 			PkgPath:    typeSpecDef.PkgPath,
 			ParentSpec: typeSpecDef.ParentSpec,
+			SchemaName: "array_" + typeSpecDef.SchemaName,
 			NotUnique:  false,
 		}
 	}
@@ -96,9 +97,9 @@ func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string,
 			Enums:      typeSpecDef.Enums,
 			PkgPath:    typeSpecDef.PkgPath,
 			ParentSpec: typeSpecDef.ParentSpec,
+			SchemaName: "map_" + parts[0] + "_" + typeSpecDef.SchemaName,
 			NotUnique:  false,
 		}
-
 	}
 	if IsGolangPrimitiveType(genericParam) {
 		return &TypeSpecDef{
@@ -106,6 +107,7 @@ func (pkgDefs *PackagesDefinitions) getTypeFromGenericParam(genericParam string,
 				Name: ast.NewIdent(genericParam),
 				Type: ast.NewIdent(genericParam),
 			},
+			SchemaName: genericParam,
 		}
 	}
 	return pkgDefs.FindTypeSpec(genericParam, file)
@@ -155,14 +157,27 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi
 	}
 
 	name = fmt.Sprintf("%s%s-", string(IgnoreNameOverridePrefix), original.TypeName())
+	schemaName := fmt.Sprintf("%s-", original.SchemaName)
+
 	var nameParts []string
+	var schemaNameParts []string
+
 	for _, def := range formals {
 		if specDef, ok := genericParamTypeDefs[def.Name]; ok {
-			nameParts = append(nameParts, specDef.TypeName())
+			nameParts = append(nameParts, specDef.Name)
+
+			schemaNamePart := specDef.Name
+
+			if specDef.TypeSpec != nil {
+				schemaNamePart = specDef.TypeSpec.SchemaName
+			}
+
+			schemaNameParts = append(schemaNameParts, schemaNamePart)
 		}
 	}
 
 	name += normalizeGenericTypeName(strings.Join(nameParts, "-"))
+	schemaName += normalizeGenericTypeName(strings.Join(schemaNameParts, "-"))
 
 	if typeSpec, ok := pkgDefs.uniqueDefinitions[name]; ok {
 		return typeSpec
@@ -180,6 +195,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi
 			Doc:    original.TypeSpec.Doc,
 			Assign: original.TypeSpec.Assign,
 		},
+		SchemaName: schemaName,
 	}
 	pkgDefs.uniqueDefinitions[name] = parametrizedTypeSpec
 
diff --git a/operation.go b/operation.go
index ac79e31..2ce9c9e 100644
--- a/operation.go
+++ b/operation.go
@@ -281,7 +281,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
 
 	requiredText := strings.ToLower(matches[4])
 	required := requiredText == "true" || requiredText == requiredLabel
-	description := matches[5]
+	description := strings.Join(strings.Split(matches[5], "\\n"), "\n")
 
 	param := createParameter(paramType, description, name, objectType, refType, required, enums, operation.parser.collectionFormatInQuery)
 
diff --git a/packages.go b/packages.go
index d064033..2032414 100644
--- a/packages.go
+++ b/packages.go
@@ -166,6 +166,8 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag
 							pkgDefs.uniqueDefinitions[fullName] = nil
 							anotherTypeDef.NotUnique = true
 							pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef
+							anotherTypeDef.SetSchemaName()
+
 							typeSpecDef.NotUnique = true
 							fullName = typeSpecDef.TypeName()
 							pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
@@ -174,6 +176,8 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag
 						pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
 					}
 
+					typeSpecDef.SetSchemaName()
+
 					if pkgDefs.packages[typeSpecDef.PkgPath] == nil {
 						pkgDefs.packages[typeSpecDef.PkgPath] = NewPackageDefinitions(astFile.Name.Name, typeSpecDef.PkgPath).AddTypeSpec(typeSpecDef.Name(), typeSpecDef)
 					} else if _, ok = pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[typeSpecDef.Name()]; !ok {
@@ -248,6 +252,8 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as
 										pkgDefs.uniqueDefinitions[fullName] = nil
 										anotherTypeDef.NotUnique = true
 										pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef
+										anotherTypeDef.SetSchemaName()
+
 										typeSpecDef.NotUnique = true
 										fullName = typeSpecDef.TypeName()
 										pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
@@ -257,6 +263,8 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as
 									functionScopedTypes[typeSpec.Name.Name] = typeSpecDef
 								}
 
+								typeSpecDef.SetSchemaName()
+
 								if pkgDefs.packages[typeSpecDef.PkgPath] == nil {
 									pkgDefs.packages[typeSpecDef.PkgPath] = NewPackageDefinitions(astFile.Name.Name, typeSpecDef.PkgPath).AddTypeSpec(fullName, typeSpecDef)
 								} else if _, ok = pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[fullName]; !ok {
@@ -374,7 +382,7 @@ func (pkgDefs *PackagesDefinitions) collectConstEnums(parsedSchemas map[*TypeSpe
 				continue
 			}
 
-			//delete it from parsed schemas, and will parse it again
+			// delete it from parsed schemas, and will parse it again
 			if _, ok = parsedSchemas[typeDef]; ok {
 				delete(parsedSchemas, typeDef)
 			}
@@ -490,7 +498,7 @@ func (pkgDefs *PackagesDefinitions) findPackagePathFromImports(pkg string, file
 				}
 				break
 			} else if imp.Name.Name == "_" && len(pkg) > 0 {
-				//for unused types
+				// for unused types
 				pd, ok := pkgDefs.packages[path]
 				if ok {
 					if pd.Name == pkg {
@@ -579,17 +587,27 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File
 		return typeDef
 	}
 
-	//in case that comment //@name renamed the type with a name without a dot
-	typeDef, ok = pkgDefs.uniqueDefinitions[typeName]
-	if ok {
-		return typeDef
-	}
-
 	name := parts[0]
 	typeDef, ok = pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, name)]
 	if !ok {
 		pkgPaths, externalPkgPaths := pkgDefs.findPackagePathFromImports("", file)
 		typeDef = pkgDefs.findTypeSpecFromPackagePaths(pkgPaths, externalPkgPaths, name)
 	}
-	return pkgDefs.parametrizeGenericType(file, typeDef, typeName)
+
+	if typeDef != nil {
+		return pkgDefs.parametrizeGenericType(file, typeDef, typeName)
+	}
+
+	// in case that comment //@name renamed the type with a name without a dot
+	for k, v := range pkgDefs.uniqueDefinitions {
+		if v == nil {
+			pkgDefs.debug.Printf("%s TypeSpecDef is nil", k)
+			continue
+		}
+		if v.SchemaName == typeName {
+			return v
+		}
+	}
+
+	return nil
 }
diff --git a/parser.go b/parser.go
index 2c694be..eb5d96c 100644
--- a/parser.go
+++ b/parser.go
@@ -1294,7 +1294,10 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error)
 	}
 
 	if definition.Description == "" {
-		fillDefinitionDescription(definition, typeSpecDef.File, typeSpecDef)
+		err = parser.fillDefinitionDescription(definition, typeSpecDef.File, typeSpecDef)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	if len(typeSpecDef.Enums) > 0 {
@@ -1316,8 +1319,14 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error)
 		}
 	}
 
+	schemaName := typeName
+
+	if typeSpecDef.SchemaName != "" {
+		schemaName = typeSpecDef.SchemaName
+	}
+
 	sch := Schema{
-		Name:    typeName,
+		Name:    schemaName,
 		PkgPath: typeSpecDef.PkgPath,
 		Schema:  definition,
 	}
@@ -1338,7 +1347,7 @@ func fullTypeName(parts ...string) string {
 
 // fillDefinitionDescription additionally fills fields in definition (spec.Schema)
 // TODO: If .go file contains many types, it may work for a long time
-func fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpecDef *TypeSpecDef) {
+func (parser *Parser) fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpecDef *TypeSpecDef) (err error) {
 	if file == nil {
 		return
 	}
@@ -1353,16 +1362,23 @@ func fillDefinitionDescription(definition *spec.Schema, file *ast.File, typeSpec
 			if !ok || typeSpec != typeSpecDef.TypeSpec {
 				continue
 			}
-
-			definition.Description =
-				extractDeclarationDescription(typeSpec.Doc, typeSpec.Comment, generalDeclaration.Doc)
+			var typeName string
+			if typeSpec.Name != nil {
+				typeName = typeSpec.Name.Name
+			}
+			definition.Description, err =
+				parser.extractDeclarationDescription(typeName, typeSpec.Doc, typeSpec.Comment, generalDeclaration.Doc)
+			if err != nil {
+				return
+			}
 		}
 	}
+	return nil
 }
 
 // extractDeclarationDescription gets first description
 // from attribute descriptionAttr in commentGroups (ast.CommentGroup)
-func extractDeclarationDescription(commentGroups ...*ast.CommentGroup) string {
+func (parser *Parser) extractDeclarationDescription(typeName string, commentGroups ...*ast.CommentGroup) (string, error) {
 	var description string
 
 	for _, commentGroup := range commentGroups {
@@ -1377,9 +1393,23 @@ func extractDeclarationDescription(commentGroups ...*ast.CommentGroup) string {
 			if len(commentText) == 0 {
 				continue
 			}
-			attribute := FieldsByAnySpace(commentText, 2)[0]
+			fields := FieldsByAnySpace(commentText, 2)
+			attribute := fields[0]
 
-			if strings.ToLower(attribute) != descriptionAttr {
+			if attr := strings.ToLower(attribute); attr == descriptionMarkdownAttr {
+				if len(fields) > 1 {
+					typeName = fields[1]
+				}
+				if typeName == "" {
+					continue
+				}
+				desc, err := getMarkdownForTag(typeName, parser.markdownFileDir)
+				if err != nil {
+					return "", err
+				}
+				// if found markdown description, we will only use the markdown file content
+				return string(desc), nil
+			} else if attr != descriptionAttr {
 				if !isHandlingDescription {
 					continue
 				}
@@ -1392,7 +1422,7 @@ func extractDeclarationDescription(commentGroups ...*ast.CommentGroup) string {
 		}
 	}
 
-	return strings.TrimLeft(description, " ")
+	return strings.TrimLeft(description, " "), nil
 }
 
 // parseTypeExpr parses given type expression that corresponds to the type under
diff --git a/types.go b/types.go
index 0076a6b..5f3031e 100644
--- a/types.go
+++ b/types.go
@@ -30,6 +30,8 @@ type TypeSpecDef struct {
 	PkgPath    string
 	ParentSpec ast.Decl
 
+	SchemaName string
+
 	NotUnique bool
 }
 
@@ -46,20 +48,6 @@ func (t *TypeSpecDef) Name() string {
 func (t *TypeSpecDef) TypeName() string {
 	if ignoreNameOverride(t.TypeSpec.Name.Name) {
 		return t.TypeSpec.Name.Name[1:]
-	} else if t.TypeSpec.Comment != nil {
-		// get alias from comment '// @name '
-		const regexCaseInsensitive = "(?i)"
-		reTypeName, err := regexp.Compile(regexCaseInsensitive + `^@name\s+(\S+)`)
-		if err != nil {
-			panic(err)
-		}
-		for _, comment := range t.TypeSpec.Comment.List {
-			trimmedComment := strings.TrimSpace(strings.TrimLeft(comment.Text, "/"))
-			texts := reTypeName.FindStringSubmatch(trimmedComment)
-			if len(texts) > 1 {
-				return texts[1]
-			}
-		}
 	}
 
 	var names []string
@@ -86,6 +74,36 @@ func (t *TypeSpecDef) FullPath() string {
 	return t.PkgPath + "." + t.Name()
 }
 
+const regexCaseInsensitive = "(?i)"
+
+var reTypeName = regexp.MustCompile(regexCaseInsensitive + `^@name\s+(\S+)`)
+
+func (t *TypeSpecDef) Alias() string {
+	if t.TypeSpec.Comment == nil {
+		return ""
+	}
+
+	// get alias from comment '// @name '
+	for _, comment := range t.TypeSpec.Comment.List {
+		trimmedComment := strings.TrimSpace(strings.TrimLeft(comment.Text, "/"))
+		texts := reTypeName.FindStringSubmatch(trimmedComment)
+		if len(texts) > 1 {
+			return texts[1]
+		}
+	}
+
+	return ""
+}
+
+func (t *TypeSpecDef) SetSchemaName() {
+	if alias := t.Alias(); alias != "" {
+		t.SchemaName = alias
+		return
+	}
+
+	t.SchemaName = t.TypeName()
+}
+
 // AstFileInfo information of an ast.File.
 type AstFileInfo struct {
 	//FileSet the FileSet object which is used to parse this go source file


Test patch:
diff --git a/generics_test.go b/generics_test.go
index 033bcf5..fecaa4d 100644
--- a/generics_test.go
+++ b/generics_test.go
@@ -134,6 +134,22 @@ func TestParseGenericsPackageAlias(t *testing.T) {
 	assert.Equal(t, string(expected), string(b))
 }
 
+func TestParseGenericsFunctionScoped(t *testing.T) {
+	t.Parallel()
+
+	searchDir := "testdata/generics_function_scoped"
+	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))
+}
+
 func TestParametrizeStruct(t *testing.T) {
 	pd := PackagesDefinitions{
 		packages:          make(map[string]*PackageDefinitions),
diff --git a/operation_test.go b/operation_test.go
index 50e4ac3..9ce4698 100644
--- a/operation_test.go
+++ b/operation_test.go
@@ -1322,6 +1322,27 @@ func TestParseParamCommentByID(t *testing.T) {
 	assert.Equal(t, expected, string(b))
 }
 
+func TestParseParamCommentWithMultilineDescriptions(t *testing.T) {
+	t.Parallel()
+
+	comment := `@Param some_id query int true "First line\nSecond line\nThird line"`
+	operation := NewOperation(nil)
+	err := operation.ParseComment(comment, nil)
+
+	assert.NoError(t, err)
+	b, _ := json.MarshalIndent(operation.Parameters, "", "    ")
+	expected := `[
+    {
+        "type": "integer",
+        "description": "First line\nSecond line\nThird line",
+        "name": "some_id",
+        "in": "query",
+        "required": true
+    }
+]`
+	assert.Equal(t, expected, string(b))
+}
+
 func TestParseParamCommentByQueryType(t *testing.T) {
 	t.Parallel()
 
diff --git a/parser_test.go b/parser_test.go
index a8bcd46..6b64aec 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -4367,3 +4367,22 @@ func Test(){
 	assert.True(t, ok)
 	assert.NotNil(t, val2.Get)
 }
+
+func TestParser_EmbeddedStructAsOtherAliasGoListNested(t *testing.T) {
+	t.Parallel()
+
+	p := New(SetParseDependency(1), ParseUsingGoList(true))
+
+	p.parseGoList = true
+
+	searchDir := "testdata/alias_nested"
+	expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
+	assert.NoError(t, err)
+
+	err = p.ParseAPI(searchDir, "cmd/main/main.go", 0)
+	assert.NoError(t, err)
+
+	b, err := json.MarshalIndent(p.swagger, "", "    ")
+	assert.NoError(t, err)
+	assert.Equal(t, string(expected), string(b))
+}
diff --git a/testdata/alias_nested/cmd/main/main.go b/testdata/alias_nested/cmd/main/main.go
new file mode 100644
index 0000000..c80a0b9
--- /dev/null
+++ b/testdata/alias_nested/cmd/main/main.go
@@ -0,0 +1,9 @@
+package main
+
+import "github.com/swaggo/swag/testdata/alias_nested/pkg/good"
+
+// @Success 200 {object} good.Gen
+// @Router /api [get].
+func main() {
+	var _ good.Gen
+}
diff --git a/testdata/alias_nested/expected.json b/testdata/alias_nested/expected.json
new file mode 100644
index 0000000..f6542ca
--- /dev/null
+++ b/testdata/alias_nested/expected.json
@@ -0,0 +1,38 @@
+{
+    "swagger": "2.0",
+    "info": {
+        "contact": {}
+    },
+    "paths": {
+        "/api": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/Gen"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "Gen": {
+            "type": "object",
+            "properties": {
+                "emb": {
+                    "$ref": "#/definitions/github_com_swaggo_swag_testdata_alias_nested_pkg_good.Emb"
+                }
+            }
+        },
+        "github_com_swaggo_swag_testdata_alias_nested_pkg_good.Emb": {
+            "type": "object",
+            "properties": {
+                "good": {
+                    "type": "boolean"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/testdata/alias_nested/pkg/bad/data.go b/testdata/alias_nested/pkg/bad/data.go
new file mode 100644
index 0000000..795993d
--- /dev/null
+++ b/testdata/alias_nested/pkg/bad/data.go
@@ -0,0 +1,5 @@
+package bad
+
+type Emb struct {
+	Bad bool `json:"bad"`
+} // @name Emb
diff --git a/testdata/alias_nested/pkg/good/data.go b/testdata/alias_nested/pkg/good/data.go
new file mode 100644
index 0000000..e340b7d
--- /dev/null
+++ b/testdata/alias_nested/pkg/good/data.go
@@ -0,0 +1,9 @@
+package good
+
+type Gen struct {
+	Emb Emb `json:"emb"`
+} // @name Gen
+
+type Emb struct {
+	Good bool `json:"good"`
+}
diff --git a/testdata/generics_function_scoped/api/api.go b/testdata/generics_function_scoped/api/api.go
new file mode 100644
index 0000000..904bce3
--- /dev/null
+++ b/testdata/generics_function_scoped/api/api.go
@@ -0,0 +1,75 @@
+package api
+
+import (
+	"net/http"
+
+	"github.com/swaggo/swag/testdata/generics_function_scoped/types"
+)
+
+// @Summary Generic Response
+// @Produce  json
+// @Success 200 {object} types.GenericResponse[api.GetGeneric.User]
+// @Success 201 {object} types.GenericResponse[api.GetGeneric.Post]
+// @Router / [get]
+func GetGeneric(w http.ResponseWriter, r *http.Request) {
+	type User struct {
+		Username int    `json:"username"`
+		Email    string `json:"email"`
+	}
+	type Post struct {
+		Slug  int    `json:"slug"`
+		Title string `json:"title"`
+	}
+
+	_ = types.GenericResponse[any]{}
+}
+
+// @Summary Generic Response With Custom Type Names
+// @Produce  json
+// @Success 200 {object} types.GenericResponse[api.GetGenericRenamed.User]
+// @Success 201 {object} types.GenericResponse[api.GetGenericRenamed.Post]
+// @Router /renamed [get]
+func GetGenericRenamed(w http.ResponseWriter, r *http.Request) {
+	type User struct {
+		Username int    `json:"username"`
+		Email    string `json:"email"`
+	} // @Name RenamedUserData
+	type Post struct {
+		Slug  int    `json:"slug"`
+		Title string `json:"title"`
+	} // @Name RenamedPostData
+
+	_ = types.GenericResponse[any]{}
+}
+
+// @Summary Multiple Generic Response
+// @Produce  json
+// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructA, api.GetGenericMulti.MyStructB]
+// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructB, api.GetGenericMulti.MyStructA]
+// @Router /multi [get]
+func GetGenericMulti(w http.ResponseWriter, r *http.Request) {
+	type MyStructA struct {
+		SomeFieldA string `json:"some_field_a"`
+	}
+	type MyStructB struct {
+		SomeFieldB string `json:"some_field_b"`
+	}
+
+	_ = types.GenericMultiResponse[any, any]{}
+}
+
+// @Summary Multiple Generic Response With Custom Type Names
+// @Produce  json
+// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructA, api.GetGenericMultiRenamed.MyStructB]
+// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructB, api.GetGenericMultiRenamed.MyStructA]
+// @Router /multi-renamed [get]
+func GetGenericMultiRenamed(w http.ResponseWriter, r *http.Request) {
+	type MyStructA struct {
+		SomeFieldA string `json:"some_field_a"`
+	} // @Name NameForMyStructA
+	type MyStructB struct {
+		SomeFieldB string `json:"some_field_b"`
+	} // @Name NameForMyStructB
+
+	_ = types.GenericMultiResponse[any, any]{}
+}
diff --git a/testdata/generics_function_scoped/expected.json b/testdata/generics_function_scoped/expected.json
new file mode 100644
index 0000000..3fb99fa
--- /dev/null
+++ b/testdata/generics_function_scoped/expected.json
@@ -0,0 +1,279 @@
+{
+    "swagger": "2.0",
+    "info": {
+        "description": "This is a sample server.",
+        "title": "Swagger Example API",
+        "contact": {},
+        "version": "1.0"
+    },
+    "host": "localhost:8080",
+    "basePath": "/api",
+    "paths": {
+        "/": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Generic Response",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-api_GetGeneric_User"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-api_GetGeneric_Post"
+                        }
+                    }
+                }
+            }
+        },
+        "/multi": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Multiple Generic Response",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA"
+                        }
+                    }
+                }
+            }
+        },
+        "/multi-renamed": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Multiple Generic Response With Custom Type Names",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructA-NameForMyStructB"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructB-NameForMyStructA"
+                        }
+                    }
+                }
+            }
+        },
+        "/renamed": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Generic Response With Custom Type Names",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-RenamedUserData"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-RenamedPostData"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "NameForMyStructA": {
+            "type": "object",
+            "properties": {
+                "some_field_a": {
+                    "type": "string"
+                }
+            }
+        },
+        "NameForMyStructB": {
+            "type": "object",
+            "properties": {
+                "some_field_b": {
+                    "type": "string"
+                }
+            }
+        },
+        "RenamedPostData": {
+            "type": "object",
+            "properties": {
+                "slug": {
+                    "type": "integer"
+                },
+                "title": {
+                    "type": "string"
+                }
+            }
+        },
+        "RenamedUserData": {
+            "type": "object",
+            "properties": {
+                "email": {
+                    "type": "string"
+                },
+                "username": {
+                    "type": "integer"
+                }
+            }
+        },
+        "api.GetGeneric.Post": {
+            "type": "object",
+            "properties": {
+                "slug": {
+                    "type": "integer"
+                },
+                "title": {
+                    "type": "string"
+                }
+            }
+        },
+        "api.GetGeneric.User": {
+            "type": "object",
+            "properties": {
+                "email": {
+                    "type": "string"
+                },
+                "username": {
+                    "type": "integer"
+                }
+            }
+        },
+        "api.GetGenericMulti.MyStructA": {
+            "type": "object",
+            "properties": {
+                "some_field_a": {
+                    "type": "string"
+                }
+            }
+        },
+        "api.GetGenericMulti.MyStructB": {
+            "type": "object",
+            "properties": {
+                "some_field_b": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-NameForMyStructA-NameForMyStructB": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/NameForMyStructA"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/NameForMyStructB"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-NameForMyStructB-NameForMyStructA": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/NameForMyStructB"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/NameForMyStructA"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructA"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructB"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructB"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructA"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-RenamedPostData": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/RenamedPostData"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-RenamedUserData": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/RenamedUserData"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-api_GetGeneric_Post": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/api.GetGeneric.Post"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-api_GetGeneric_User": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/api.GetGeneric.User"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/testdata/generics_function_scoped/main.go b/testdata/generics_function_scoped/main.go
new file mode 100644
index 0000000..7dd44c3
--- /dev/null
+++ b/testdata/generics_function_scoped/main.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/swaggo/swag/testdata/generics_function_scoped/api"
+)
+
+// @title Swagger Example API
+// @version 1.0
+// @description This is a sample server.
+// @host localhost:8080
+// @basePath /api
+func main() {
+	http.HandleFunc("/", api.GetGeneric)
+	http.HandleFunc("/renamed", api.GetGenericRenamed)
+	http.HandleFunc("/multi", api.GetGenericMulti)
+	http.HandleFunc("/multi-renamed", api.GetGenericMulti)
+	http.ListenAndServe(":8080", nil)
+}
diff --git a/testdata/generics_function_scoped/types/response.go b/testdata/generics_function_scoped/types/response.go
new file mode 100644
index 0000000..d12e532
--- /dev/null
+++ b/testdata/generics_function_scoped/types/response.go
@@ -0,0 +1,12 @@
+package types
+
+type GenericResponse[T any] struct {
+	Status string `json:"status"`
+	Data   T      `json:"data"`
+}
+
+type GenericMultiResponse[T any, X any] struct {
+	Status string `json:"status"`
+	DataT  T      `json:"data_t"`
+	DataX  X      `json:"data_x"`
+}