Repository: swaggo/swag
Pull request #1859 (2024-08-02 16:01:49)

Feat: Add support for parenthesis in router patterns
**Describe the PR**
Allow route patterns with parenthesis i.e. `/odata/Resource({id}) [get]`

**Relation issue**
[e.g. https://github.com/swaggo/swag/pull/118/files](https://github.com/swaggo/swag/issues/1799)

**Additional context**
Odata specification allows for single resource lookups using the described syntax, but it currently generates a `cannot parse router comment` error. This just adds parenthesis to the regex to fix the error.

![Screenshot 2024-08-02 at 12 01 12 PM](https://github.com/user-attachments/assets/66119370-cf5f-443d-9c86-c182e231fbbc)




Patch:
diff --git a/README.md b/README.md
index b453c41..7504f6e 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,8 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie
 	- [User defined structure with an array type](#user-defined-structure-with-an-array-type)
 	- [Function scoped struct declaration](#function-scoped-struct-declaration)
 	- [Model composition in response](#model-composition-in-response)
-	- [Add a headers in response](#add-a-headers-in-response)
+        - [Add request headers](#add-request-headers)
+	- [Add response headers](#add-response-headers)
 	- [Use multiple path params](#use-multiple-path-params)
 	- [Example value of struct](#example-value-of-struct)
 	- [SchemaExample of body](#schemaexample-of-body)
@@ -103,6 +104,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)
@@ -646,7 +648,14 @@ type DeepObject struct { //in `proto` package
 }
 @success 200 {object} jsonresult.JSONResult{data1=proto.Order{data=proto.DeepObject},data2=[]proto.Order{data=[]proto.DeepObject}} "desc"
 ```
-### Add a headers in response
+### Add response request
+
+```go
+// @Param        X-MyHeader	  header    string    true   	"MyHeader must be set for valid response"
+// @Param        X-API-VERSION    header    string    true   	"API version eg.: 1.0"
+```
+
+### Add response headers
 
 ```go
 // @Success      200              {string}  string    "ok"
@@ -947,6 +956,17 @@ swag init -g http/api.go -td "[[,]]"
 ```
 The new delimiter is a string with the format "`<left delimiter>`,`<right delimiter>`".
 
+### Parse Internal and Dependency Packages
+
+If the struct is defined in a dependency package, use `--parseDependency`.
+
+If the struct is defined in your main project, use `--parseInternal`.
+
+if you want to include both internal and from dependencies use both flags 
+```
+swag init --parseDependency --parseInternal
+```
+
 ## About the Project
 This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks). Gopher image source is [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). It has licenses [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en).
 ## Contributors
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/field_parser.go b/field_parser.go
index f2bef6f..de8945d 100644
--- a/field_parser.go
+++ b/field_parser.go
@@ -65,35 +65,38 @@ func (ps *tagBaseFieldParser) ShouldSkip() bool {
 	return false
 }
 
-func (ps *tagBaseFieldParser) FieldName() (string, error) {
-	var name string
+func (ps *tagBaseFieldParser) FieldNames() ([]string, error) {
+	if len(ps.field.Names) <= 1 {
+		// if embedded but with a json/form name ??
+		if ps.field.Tag != nil {
+			// json:"tag,hoge"
+			name := strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0])
+			if name != "" {
+				return []string{name}, nil
+			}
 
-	if ps.field.Tag != nil {
-		// json:"tag,hoge"
-		name = strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0])
-		if name != "" {
-			return name, nil
+			// use "form" tag over json tag
+			name = ps.FormName()
+			if name != "" {
+				return []string{name}, nil
+			}
 		}
-
-		// use "form" tag over json tag
-		name = ps.FormName()
-		if name != "" {
-			return name, nil
+		if len(ps.field.Names) == 0 {
+			return nil, nil
 		}
 	}
-
-	if ps.field.Names == nil {
-		return "", nil
-	}
-
-	switch ps.p.PropNamingStrategy {
-	case SnakeCase:
-		return toSnakeCase(ps.field.Names[0].Name), nil
-	case PascalCase:
-		return ps.field.Names[0].Name, nil
-	default:
-		return toLowerCamelCase(ps.field.Names[0].Name), nil
+	var names = make([]string, 0, len(ps.field.Names))
+	for _, name := range ps.field.Names {
+		switch ps.p.PropNamingStrategy {
+		case SnakeCase:
+			names = append(names, toSnakeCase(name.Name))
+		case PascalCase:
+			names = append(names, name.Name)
+		default:
+			names = append(names, toLowerCamelCase(name.Name))
+		}
 	}
+	return names, nil
 }
 
 func (ps *tagBaseFieldParser) firstTagValue(tag string) string {
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..ba3a05b 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)
 
@@ -707,7 +707,7 @@ func parseMimeTypeList(mimeTypeList string, typeList *[]string, format string) e
 	return nil
 }
 
-var routerPattern = regexp.MustCompile(`^(/[\w./\-{}+:$]*)[[:blank:]]+\[(\w+)]`)
+var routerPattern = regexp.MustCompile(`^(/[\w./\-{}\(\)+:$]*)[[:blank:]]+\[(\w+)]`)
 
 // ParseRouterComment parses comment for given `router` comment string.
 func (operation *Operation) ParseRouterComment(commentLine string, deprecated bool) error {
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..cd2a46c 100644
--- a/parser.go
+++ b/parser.go
@@ -190,7 +190,7 @@ type FieldParserFactory func(ps *Parser, field *ast.Field) FieldParser
 // FieldParser parse struct field.
 type FieldParser interface {
 	ShouldSkip() bool
-	FieldName() (string, error)
+	FieldNames() ([]string, error)
 	FormName() string
 	HeaderName() string
 	PathName() string
@@ -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
@@ -1497,20 +1527,20 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st
 		return nil, nil, nil
 	}
 
-	fieldName, err := ps.FieldName()
+	fieldNames, err := ps.FieldNames()
 	if err != nil {
 		return nil, nil, err
 	}
 
-	if fieldName == "" {
+	if len(fieldNames) == 0 {
 		typeName, err := getFieldType(file, field.Type, nil)
 		if err != nil {
-			return nil, nil, fmt.Errorf("%s: %w", fieldName, err)
+			return nil, nil, err
 		}
 
 		schema, err := parser.getTypeSchema(typeName, file, false)
 		if err != nil {
-			return nil, nil, fmt.Errorf("%s: %w", fieldName, err)
+			return nil, nil, err
 		}
 
 		if len(schema.Type) > 0 && schema.Type[0] == OBJECT {
@@ -1532,7 +1562,7 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st
 
 	schema, err := ps.CustomSchema()
 	if err != nil {
-		return nil, nil, fmt.Errorf("%s: %w", fieldName, err)
+		return nil, nil, fmt.Errorf("%v: %w", fieldNames, err)
 	}
 
 	if schema == nil {
@@ -1546,24 +1576,24 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st
 		}
 
 		if err != nil {
-			return nil, nil, fmt.Errorf("%s: %w", fieldName, err)
+			return nil, nil, fmt.Errorf("%v: %w", fieldNames, err)
 		}
 	}
 
 	err = ps.ComplementSchema(schema)
 	if err != nil {
-		return nil, nil, fmt.Errorf("%s: %w", fieldName, err)
+		return nil, nil, fmt.Errorf("%v: %w", fieldNames, err)
 	}
 
 	var tagRequired []string
 
 	required, err := ps.IsRequired()
 	if err != nil {
-		return nil, nil, fmt.Errorf("%s: %w", fieldName, err)
+		return nil, nil, fmt.Errorf("%v: %w", fieldNames, err)
 	}
 
 	if required {
-		tagRequired = append(tagRequired, fieldName)
+		tagRequired = append(tagRequired, fieldNames...)
 	}
 
 	if schema.Extensions == nil {
@@ -1578,8 +1608,11 @@ func (parser *Parser) parseStructField(file *ast.File, field *ast.Field) (map[st
 	if pathName := ps.PathName(); len(pathName) > 0 {
 		schema.Extensions["path"] = pathName
 	}
-
-	return map[string]spec.Schema{fieldName: *schema}, tagRequired, nil
+	fields := make(map[string]spec.Schema)
+	for _, name := range fieldNames {
+		fields[name] = *schema
+	}
+	return fields, tagRequired, nil
 }
 
 func getFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) {
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/enums_test.go b/enums_test.go
index e345637..d6645c7 100644
--- a/enums_test.go
+++ b/enums_test.go
@@ -2,6 +2,7 @@ package swag
 
 import (
 	"encoding/json"
+	"math/bits"
 	"os"
 	"path/filepath"
 	"testing"
@@ -21,7 +22,7 @@ func TestParseGlobalEnums(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, string(expected), string(b))
 	constsPath := "github.com/swaggo/swag/testdata/enums/consts"
-	assert.Equal(t, 64, p.packages.packages[constsPath].ConstTable["uintSize"].Value)
+	assert.Equal(t, bits.UintSize, p.packages.packages[constsPath].ConstTable["uintSize"].Value)
 	assert.Equal(t, int32(62), p.packages.packages[constsPath].ConstTable["maxBase"].Value)
 	assert.Equal(t, 8, p.packages.packages[constsPath].ConstTable["shlByLen"].Value)
 	assert.Equal(t, 255, p.packages.packages[constsPath].ConstTable["hexnum"].Value)
diff --git a/field_parser_test.go b/field_parser_test.go
index 0d8723f..28f8cdf 100644
--- a/field_parser_test.go
+++ b/field_parser_test.go
@@ -676,9 +676,11 @@ func TestValidTags(t *testing.T) {
 		schema.Type = []string{"integer"}
 		err = newTagBaseFieldParser(
 			&Parser{},
-			&ast.Field{Tag: &ast.BasicLit{
-				Value: `json:"test" validate:"required,oneof=one two"`,
-			}},
+			&ast.Field{
+				Names: []*ast.Ident{{Name: "Test"}},
+				Tag: &ast.BasicLit{
+					Value: `json:"test" validate:"required,oneof=one two"`,
+				}},
 		).ComplementSchema(&schema)
 		assert.NoError(t, err)
 		assert.Empty(t, schema.Enum)
@@ -687,22 +689,41 @@ func TestValidTags(t *testing.T) {
 	t.Run("Form Filed Name", func(t *testing.T) {
 		t.Parallel()
 
-		filedname, err := newTagBaseFieldParser(
+		filednames, err := newTagBaseFieldParser(
 			&Parser{},
-			&ast.Field{Tag: &ast.BasicLit{
-				Value: `form:"test[]"`,
-			}},
-		).FieldName()
+			&ast.Field{
+				Names: []*ast.Ident{{Name: "Test"}},
+				Tag: &ast.BasicLit{
+					Value: `form:"test[]"`,
+				}},
+		).FieldNames()
 		assert.NoError(t, err)
-		assert.Equal(t, "test", filedname)
+		assert.Equal(t, "test", filednames[0])
 
-		filedname, err = newTagBaseFieldParser(
+		filednames, err = newTagBaseFieldParser(
 			&Parser{},
-			&ast.Field{Tag: &ast.BasicLit{
-				Value: `form:"test"`,
-			}},
-		).FieldName()
+			&ast.Field{
+				Names: []*ast.Ident{{Name: "Test"}},
+				Tag: &ast.BasicLit{
+					Value: `form:"test"`,
+				}},
+		).FieldNames()
+		assert.NoError(t, err)
+		assert.Equal(t, "test", filednames[0])
+	})
+
+	t.Run("Two Names", func(t *testing.T) {
+		t.Parallel()
+
+		fieldnames, err := newTagBaseFieldParser(
+			&Parser{},
+			&ast.Field{
+				Names: []*ast.Ident{{Name: "X"}, {Name: "Y"}},
+			},
+		).FieldNames()
 		assert.NoError(t, err)
-		assert.Equal(t, "test", filedname)
+		assert.Equal(t, 2, len(fieldnames))
+		assert.Equal(t, "x", fieldnames[0])
+		assert.Equal(t, "y", fieldnames[1])
 	})
 }
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..e6bd13e 100644
--- a/operation_test.go
+++ b/operation_test.go
@@ -175,6 +175,18 @@ func TestParseRouterCommentWithDollarSign(t *testing.T) {
 	assert.Equal(t, "POST", operation.RouterProperties[0].HTTPMethod)
 }
 
+func TestParseRouterCommentWithParens(t *testing.T) {
+	t.Parallel()
+
+	comment := `/@Router /customer({id}) [get]`
+	operation := NewOperation(nil)
+	err := operation.ParseComment(comment, nil)
+	assert.NoError(t, err)
+	assert.Len(t, operation.RouterProperties, 1)
+	assert.Equal(t, "/customer({id})", operation.RouterProperties[0].Path)
+	assert.Equal(t, "GET", operation.RouterProperties[0].HTTPMethod)
+}
+
 func TestParseRouterCommentNoDollarSignAtPathStartErr(t *testing.T) {
 	t.Parallel()
 
@@ -1322,6 +1334,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"`
+}