From 09880798a9dccbed5c0c70d4bd791395cb5fc746 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Mon, 24 Oct 2022 15:18:09 +0800 Subject: [PATCH 1/8] fix issue #1353 about generics Signed-off-by: sdghchj --- generics.go | 72 +++++++++++--------- generics_other.go | 4 -- generics_test.go | 14 ++-- packages.go | 29 ++++---- testdata/generics_names/api/api_alias_pkg.go | 19 ++++++ testdata/generics_names/expected.json | 31 +++++++++ testdata/generics_names/web/handler.go | 6 ++ 7 files changed, 115 insertions(+), 60 deletions(-) create mode 100644 testdata/generics_names/api/api_alias_pkg.go diff --git a/generics.go b/generics.go index 66c84b85c..2dc80cd5b 100644 --- a/generics.go +++ b/generics.go @@ -22,17 +22,6 @@ type genericTypeSpec struct { Name string } -func (s *genericTypeSpec) Type() ast.Expr { - if s.TypeSpec != nil { - return &ast.SelectorExpr{ - X: &ast.Ident{Name: ""}, - Sel: &ast.Ident{Name: s.Name}, - } - } - - return &ast.Ident{Name: s.Name} -} - func (s *genericTypeSpec) TypeDocName() string { if s.TypeSpec != nil { return strings.Replace(TypeDocName(s.TypeSpec.FullName(), s.TypeSpec.TypeSpec), "-", "_", -1) @@ -41,25 +30,11 @@ func (s *genericTypeSpec) TypeDocName() string { return s.Name } -func typeSpecFullName(typeSpecDef *TypeSpecDef) string { - fullName := typeSpecDef.FullName() - - if typeSpecDef.TypeSpec.TypeParams != nil { - fullName = fullName + "[" - for i, typeParam := range typeSpecDef.TypeSpec.TypeParams.List { - if i > 0 { - fullName = fullName + "-" - } - - fullName = fullName + typeParam.Names[0].Name - } - fullName = fullName + "]" +func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { + if original.TypeSpec.TypeParams == nil || len(original.TypeSpec.TypeParams.List) == 0 { + return original } - return fullName -} - -func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { genericDefinitionsMutex.RLock() tSpec, ok := genericsDefinitions[original][fullGenericForm] genericDefinitionsMutex.RUnlock() @@ -68,7 +43,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi } pkgName := strings.Split(fullGenericForm, ".")[0] - genericTypeName, genericParams := splitStructName(fullGenericForm) + genericTypeName, genericParams := splitGenericsTypeName(fullGenericForm) if genericParams == nil { return nil } @@ -156,8 +131,8 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi return parametrizedTypeSpec } -// splitStructName splits a generic struct name in his parts -func splitStructName(fullGenericForm string) (string, []string) { +// splitGenericsTypeName splits a generic struct name in his parts +func splitGenericsTypeName(fullGenericForm string) (string, []string) { //remove all spaces character fullGenericForm = strings.Map(func(r rune) rune { if unicode.IsSpace(r) { @@ -197,11 +172,44 @@ func splitStructName(fullGenericForm string) (string, []string) { return genericTypeName, genericParams } +func (pkgDefs *PackagesDefinitions) getParametrizedType(genTypeSpec *genericTypeSpec) ast.Expr { + if genTypeSpec.TypeSpec != nil && strings.Contains(genTypeSpec.Name, ".") { + parts := strings.SplitN(genTypeSpec.Name, ".", 2) + + if genTypeSpec.TypeSpec.File.Name.Name == parts[0] { //no package alias + return &ast.SelectorExpr{ + X: &ast.Ident{Name: parts[0]}, + Sel: &ast.Ident{Name: parts[1]}, + } + } + + // if there is an alias of package, use the package path as the unique package name + parts[0] = strings.Map(func(r rune) rune { + if unicode.IsDigit(r) || unicode.IsLetter(r) { + return r + } + return '_' + }, genTypeSpec.TypeSpec.PkgPath) + typeName := fullTypeName(parts[0], parts[1]) + if _, ok := pkgDefs.uniqueDefinitions[typeName]; !ok { + pkgDefs.uniqueDefinitions[typeName] = genTypeSpec.TypeSpec + } + + return &ast.SelectorExpr{ + X: &ast.Ident{Name: parts[0]}, + Sel: &ast.Ident{Name: parts[1]}, + } + } + + //a primitive type name or a type name in current package + return &ast.Ident{Name: genTypeSpec.Name} +} + func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec, parseDependency bool) ast.Expr { switch astExpr := expr.(type) { case *ast.Ident: if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok { - retType := genTypeSpec.Type() + retType := pkgDefs.getParametrizedType(genTypeSpec) for i := 0; i < genTypeSpec.ArrayDepth; i++ { retType = &ast.ArrayType{Elt: retType} } diff --git a/generics_other.go b/generics_other.go index e82ef7329..deab83167 100644 --- a/generics_other.go +++ b/generics_other.go @@ -15,10 +15,6 @@ type genericTypeSpec struct { Name string } -func typeSpecFullName(typeSpecDef *TypeSpecDef) string { - return typeSpecDef.FullName() -} - func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { return original } diff --git a/generics_test.go b/generics_test.go index 44b7cd24c..2292b11d9 100644 --- a/generics_test.go +++ b/generics_test.go @@ -172,30 +172,30 @@ func TestParametrizeStruct(t *testing.T) { assert.Nil(t, typeSpec) } -func TestSplitStructNames(t *testing.T) { +func TestSplitGenericsTypeNames(t *testing.T) { t.Parallel() - field, params := splitStructName("test.Field") + field, params := splitGenericsTypeName("test.Field") assert.Empty(t, field) assert.Nil(t, params) - field, params = splitStructName("test.Field]") + field, params = splitGenericsTypeName("test.Field]") assert.Empty(t, field) assert.Nil(t, params) - field, params = splitStructName("test.Field[string") + field, params = splitGenericsTypeName("test.Field[string") assert.Empty(t, field) assert.Nil(t, params) - field, params = splitStructName("test.Field[string] ") + field, params = splitGenericsTypeName("test.Field[string] ") assert.Equal(t, "test.Field", field) assert.Equal(t, []string{"string"}, params) - field, params = splitStructName("test.Field[string, []string]") + field, params = splitGenericsTypeName("test.Field[string, []string]") assert.Equal(t, "test.Field", field) assert.Equal(t, []string{"string", "[]string"}, params) - field, params = splitStructName("test.Field[test.Field[ string, []string] ]") + field, params = splitGenericsTypeName("test.Field[test.Field[ string, []string] ]") assert.Equal(t, "test.Field", field) assert.Equal(t, []string{"test.Field[string,[]string]"}, params) } diff --git a/packages.go b/packages.go index 39048ac62..ac442c5ca 100644 --- a/packages.go +++ b/packages.go @@ -135,7 +135,7 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef) } - fullName := typeSpecFullName(typeSpecDef) + fullName := typeSpecDef.FullName() anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName] if ok { @@ -190,7 +190,7 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef) } - fullName := typeSpecFullName(typeSpecDef) + fullName := typeSpecDef.FullName() anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName] if ok { @@ -389,11 +389,9 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File } } - if def := pkgDefs.findGenericTypeSpec(typeName, file, parseDependency); def != nil { - return def - } + typeDef := pkgDefs.findTypeSpec(pkgPath, parts[1]) - return pkgDefs.findTypeSpec(pkgPath, parts[1]) + return pkgDefs.parametrizeGenericType(file, typeDef, typeName, parseDependency) } if def := pkgDefs.findGenericTypeSpec(fullTypeName(file.Name.Name, typeName), file, parseDependency); def != nil { @@ -406,20 +404,17 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File } typeDef = pkgDefs.findTypeSpec(pkgDefs.files[file].PackagePath, typeName) - if typeDef != nil { - return typeDef - } - - for _, imp := range file.Imports { - if imp.Name != nil && imp.Name.Name == "." { - typeDef := pkgDefs.findTypeSpec(strings.Trim(imp.Path.Value, `"`), typeName) - if typeDef != nil { - return typeDef + if typeDef == nil { + for _, imp := range file.Imports { + if imp.Name != nil && imp.Name.Name == "." { + typeDef := pkgDefs.findTypeSpec(strings.Trim(imp.Path.Value, `"`), typeName) + if typeDef != nil { + break + } } } } - - return nil + return pkgDefs.parametrizeGenericType(file, typeDef, typeName, parseDependency) } func (pkgDefs *PackagesDefinitions) findGenericTypeSpec(typeName string, file *ast.File, parseDependency bool) *TypeSpecDef { diff --git a/testdata/generics_names/api/api_alias_pkg.go b/testdata/generics_names/api/api_alias_pkg.go new file mode 100644 index 000000000..2ae39d69a --- /dev/null +++ b/testdata/generics_names/api/api_alias_pkg.go @@ -0,0 +1,19 @@ +package api + +import ( + "net/http" + + mytypes "github.com/swaggo/swag/testdata/generics_names/types" + myweb "github.com/swaggo/swag/testdata/generics_names/web" +) + +// @Summary Add a new pet to the store +// @Description get string by ID +// @Accept json +// @Produce json +// @Success 200 {object} myweb.AliasPkgGenericResponse[mytypes.Post] +// @Router /posts/aliaspkg [post] +func GetPostFromAliasPkg(w http.ResponseWriter, r *http.Request) { + //write your code + _ = myweb.AliasPkgGenericResponse[mytypes.Post]{} +} diff --git a/testdata/generics_names/expected.json b/testdata/generics_names/expected.json index 4891eaf4e..cd35f090d 100644 --- a/testdata/generics_names/expected.json +++ b/testdata/generics_names/expected.json @@ -131,6 +131,26 @@ } } } + }, + "/posts/aliaspkg": { + "post": { + "description": "get string by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add a new pet to the store", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/myweb.AliasPkgGenericResponse-Post" + } + } + } + } } }, "definitions": { @@ -266,6 +286,17 @@ } } }, + "myweb.AliasPkgGenericResponse-Post": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/Post" + }, + "status": { + "type": "string" + } + } + }, "web.APIError": { "description": "API error with information about it", "type": "object", diff --git a/testdata/generics_names/web/handler.go b/testdata/generics_names/web/handler.go index c0d89d1cf..20c0a80d8 100644 --- a/testdata/generics_names/web/handler.go +++ b/testdata/generics_names/web/handler.go @@ -38,3 +38,9 @@ type APIError struct { ErrorCtx string // Error `context` tick comment CreatedAt time.Time // Error time } + +type AliasPkgGenericResponse[T any] struct { + Data T + + Status string +} From 87c68f73377e98dd50cae95224722f0b35b7185b Mon Sep 17 00:00:00 2001 From: sdghchj Date: Tue, 25 Oct 2022 18:01:37 +0800 Subject: [PATCH 2/8] fix issue #1353 about generics Signed-off-by: sdghchj --- generics.go | 145 +++++++++-------------- generics_test.go | 7 +- packages.go | 74 +++++++----- parser.go | 21 +--- parser_test.go | 2 +- schema.go | 50 -------- schema_test.go | 55 --------- testdata/generics_basic/expected.json | 28 +++-- testdata/generics_names/expected.json | 24 ++-- testdata/generics_nested/expected.json | 56 ++++----- testdata/generics_property/expected.json | 8 +- types.go | 41 +++++-- 12 files changed, 204 insertions(+), 307 deletions(-) diff --git a/generics.go b/generics.go index 2dc80cd5b..7c744bb57 100644 --- a/generics.go +++ b/generics.go @@ -6,15 +6,14 @@ package swag import ( "errors" "fmt" - "github.com/go-openapi/spec" "go/ast" "strings" - "sync" "unicode" + + "github.com/go-openapi/spec" ) -var genericDefinitionsMutex = &sync.RWMutex{} -var genericsDefinitions = map[*TypeSpecDef]map[string]*TypeSpecDef{} +//var genericsDefinitions = map[*TypeSpecDef]map[string]*TypeSpecDef{} type genericTypeSpec struct { ArrayDepth int @@ -22,28 +21,19 @@ type genericTypeSpec struct { Name string } -func (s *genericTypeSpec) TypeDocName() string { - if s.TypeSpec != nil { - return strings.Replace(TypeDocName(s.TypeSpec.FullName(), s.TypeSpec.TypeSpec), "-", "_", -1) +func (t *genericTypeSpec) TypeName() string { + if t.TypeSpec != nil { + return t.TypeSpec.TypeName() } - - return s.Name + return t.Name } func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { - if original.TypeSpec.TypeParams == nil || len(original.TypeSpec.TypeParams.List) == 0 { + if original == nil || original.TypeSpec.TypeParams == nil || len(original.TypeSpec.TypeParams.List) == 0 { return original } - genericDefinitionsMutex.RLock() - tSpec, ok := genericsDefinitions[original][fullGenericForm] - genericDefinitionsMutex.RUnlock() - if ok { - return tSpec - } - - pkgName := strings.Split(fullGenericForm, ".")[0] - genericTypeName, genericParams := splitGenericsTypeName(fullGenericForm) + name, genericParams := splitGenericsTypeName(fullGenericForm) if genericParams == nil { return nil } @@ -63,71 +53,65 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi arrayDepth++ } - tdef := pkgDefs.FindTypeSpec(genericParam, file, parseDependency) - if tdef != nil && !strings.Contains(genericParam, ".") { - genericParam = fullTypeName(file.Name.Name, genericParam) + typeDef := pkgDefs.FindTypeSpec(genericParam, file, parseDependency) + if typeDef != nil { + genericParam = typeDef.TypeName() + if _, ok := pkgDefs.uniqueDefinitions[genericParam]; !ok { + pkgDefs.uniqueDefinitions[genericParam] = typeDef + } } genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ ArrayDepth: arrayDepth, - TypeSpec: tdef, + TypeSpec: typeDef, Name: genericParam, } } - parametrizedTypeSpec := &TypeSpecDef{ - File: original.File, - PkgPath: original.PkgPath, - TypeSpec: &ast.TypeSpec{ - Doc: original.TypeSpec.Doc, - Comment: original.TypeSpec.Comment, - Assign: original.TypeSpec.Assign, - }, - } - - ident := &ast.Ident{ - NamePos: original.TypeSpec.Name.NamePos, - Obj: original.TypeSpec.Name.Obj, - } - - if strings.Contains(genericTypeName, ".") { - genericTypeName = strings.Split(genericTypeName, ".")[1] - } - - var typeName = []string{TypeDocName(fullTypeName(pkgName, genericTypeName), parametrizedTypeSpec.TypeSpec)} - + name = fmt.Sprintf("%s%s-", string(IgnoreNameOverridePrefix), original.TypeName()) + var nameParts []string for _, def := range original.TypeSpec.TypeParams.List { if specDef, ok := genericParamTypeDefs[def.Names[0].Name]; ok { var prefix = "" - if specDef.ArrayDepth > 0 { + if specDef.ArrayDepth == 1 { prefix = "array_" - if specDef.ArrayDepth > 1 { - prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth) - } + } else if specDef.ArrayDepth > 1 { + prefix = fmt.Sprintf("array%d_", specDef.ArrayDepth) } - typeName = append(typeName, prefix+specDef.TypeDocName()) + nameParts = append(nameParts, prefix+specDef.TypeName()) } } - ident.Name = strings.Join(typeName, "-") - ident.Name = strings.Replace(ident.Name, ".", "_", -1) - pkgNamePrefix := pkgName + "_" - if strings.HasPrefix(ident.Name, pkgNamePrefix) { - ident.Name = fullTypeName(pkgName, ident.Name[len(pkgNamePrefix):]) + name += strings.Replace(strings.Join(nameParts, "-"), ".", "_", -1) + /*if genericsMap, ok := genericsDefinitions[original]; ok { + if parametrizedTypeSpec, ok := genericsMap[name]; ok { + return parametrizedTypeSpec + } + } else { + genericsDefinitions[original] = map[string]*TypeSpecDef{} + }*/ + if typeSpec, ok := pkgDefs.uniqueDefinitions[name]; ok { + return typeSpec } - ident.Name = string(IgnoreNameOverridePrefix) + ident.Name - - parametrizedTypeSpec.TypeSpec.Name = ident - newType := pkgDefs.resolveGenericType(original.File, original.TypeSpec.Type, genericParamTypeDefs, parseDependency) - - genericDefinitionsMutex.Lock() - defer genericDefinitionsMutex.Unlock() - parametrizedTypeSpec.TypeSpec.Type = newType - if genericsDefinitions[original] == nil { - genericsDefinitions[original] = map[string]*TypeSpecDef{} + parametrizedTypeSpec := &TypeSpecDef{ + File: original.File, + PkgPath: original.PkgPath, + IsUnique: true, + TypeSpec: &ast.TypeSpec{ + Name: &ast.Ident{ + Name: name, + NamePos: original.TypeSpec.Name.NamePos, + Obj: original.TypeSpec.Name.Obj, + }, + Type: pkgDefs.resolveGenericType(original.File, original.TypeSpec.Type, genericParamTypeDefs, parseDependency), + Doc: original.TypeSpec.Doc, + Assign: original.TypeSpec.Assign, + }, } - genericsDefinitions[original][fullGenericForm] = parametrizedTypeSpec + pkgDefs.uniqueDefinitions[name] = parametrizedTypeSpec + //genericsDefinitions[original][name] = parametrizedTypeSpec + return parametrizedTypeSpec } @@ -174,27 +158,8 @@ func splitGenericsTypeName(fullGenericForm string) (string, []string) { func (pkgDefs *PackagesDefinitions) getParametrizedType(genTypeSpec *genericTypeSpec) ast.Expr { if genTypeSpec.TypeSpec != nil && strings.Contains(genTypeSpec.Name, ".") { - parts := strings.SplitN(genTypeSpec.Name, ".", 2) - - if genTypeSpec.TypeSpec.File.Name.Name == parts[0] { //no package alias - return &ast.SelectorExpr{ - X: &ast.Ident{Name: parts[0]}, - Sel: &ast.Ident{Name: parts[1]}, - } - } - - // if there is an alias of package, use the package path as the unique package name - parts[0] = strings.Map(func(r rune) rune { - if unicode.IsDigit(r) || unicode.IsLetter(r) { - return r - } - return '_' - }, genTypeSpec.TypeSpec.PkgPath) - typeName := fullTypeName(parts[0], parts[1]) - if _, ok := pkgDefs.uniqueDefinitions[typeName]; !ok { - pkgDefs.uniqueDefinitions[typeName] = genTypeSpec.TypeSpec - } - + uniqueFullTypeName := genTypeSpec.TypeName() + parts := strings.SplitN(uniqueFullTypeName, ".", 2) return &ast.SelectorExpr{ X: &ast.Ident{Name: parts[0]}, Sel: &ast.Ident{Name: parts[1]}, @@ -228,7 +193,7 @@ func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast. } case *ast.IndexExpr, *ast.IndexListExpr: fullGenericName, _ := getGenericFieldType(file, expr, genericParamTypeDefs) - typeDef := pkgDefs.findGenericTypeSpec(fullGenericName, file, parseDependency) + typeDef := pkgDefs.FindTypeSpec(fullGenericName, file, parseDependency) if typeDef != nil { return typeDef.TypeSpec.Type } @@ -282,7 +247,7 @@ func getExtendedGenericFieldType(file *ast.File, field ast.Expr, genericParamTyp TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec), PkgPath: file.Name.Name, } - return tSpec.FullName(), nil + return tSpec.TypeName(), nil default: return getFieldType(file, field) } @@ -351,14 +316,14 @@ func getGenericTypeName(file *ast.File, field ast.Expr) (string, error) { TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec), PkgPath: file.Name.Name, } - return tSpec.FullName(), nil + return tSpec.TypeName(), nil case *ast.ArrayType: tSpec := &TypeSpecDef{ File: file, TypeSpec: fieldType.Elt.(*ast.Ident).Obj.Decl.(*ast.TypeSpec), PkgPath: file.Name.Name, } - return tSpec.FullName(), nil + return tSpec.TypeName(), nil case *ast.SelectorExpr: return fmt.Sprintf("%s.%s", fieldType.X.(*ast.Ident).Name, fieldType.Sel.Name), nil } diff --git a/generics_test.go b/generics_test.go index 2292b11d9..4d82c2cfd 100644 --- a/generics_test.go +++ b/generics_test.go @@ -105,18 +105,23 @@ func TestParseGenericsNames(t *testing.T) { func TestParametrizeStruct(t *testing.T) { pd := PackagesDefinitions{ - packages: make(map[string]*PackageDefinitions), + packages: make(map[string]*PackageDefinitions), + uniqueDefinitions: make(map[string]*TypeSpecDef), } // valid typeSpec := pd.parametrizeGenericType( &ast.File{Name: &ast.Ident{Name: "test2"}}, &TypeSpecDef{ + IsUnique: true, + File: &ast.File{Name: &ast.Ident{Name: "test"}}, TypeSpec: &ast.TypeSpec{ Name: &ast.Ident{Name: "Field"}, TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}}, Type: &ast.StructType{Struct: 100, Fields: &ast.FieldList{Opening: 101, Closing: 102}}, }}, "test.Field[string, []string]", false) + assert.NotNil(t, typeSpec) assert.Equal(t, "$test.Field-string-array_string", typeSpec.Name()) + assert.Equal(t, "test.Field-string-array_string", typeSpec.TypeName()) // definition contains one type params, but two type params are provided typeSpec = pd.parametrizeGenericType( diff --git a/packages.go b/packages.go index ac442c5ca..9eb83431f 100644 --- a/packages.go +++ b/packages.go @@ -109,6 +109,7 @@ func (pkgDefs *PackagesDefinitions) ParseTypes() (map[*TypeSpecDef]*Schema, erro pkgDefs.parseTypesFromFile(astFile, info.PackagePath, parsedSchemas) pkgDefs.parseFunctionScopedTypesFromFile(astFile, info.PackagePath, parsedSchemas) } + pkgDefs.removeAllNotUniqueTypes() return parsedSchemas, nil } @@ -121,6 +122,7 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag PkgPath: packagePath, File: astFile, TypeSpec: typeSpec, + IsUnique: true, } if idt, ok := typeSpec.Type.(*ast.Ident); ok && IsGolangPrimitiveType(idt.Name) && parsedSchemas != nil { @@ -135,14 +137,18 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef) } - fullName := typeSpecDef.FullName() + fullName := typeSpecDef.TypeName() anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName] if ok { if typeSpecDef.PkgPath == anotherTypeDef.PkgPath { continue } else { - delete(pkgDefs.uniqueDefinitions, fullName) + anotherTypeDef.IsUnique = false + typeSpecDef.IsUnique = false + pkgDefs.uniqueDefinitions[fullName] = nil + pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = typeSpecDef + pkgDefs.uniqueDefinitions[typeSpecDef.TypeName()] = typeSpecDef } } else { pkgDefs.uniqueDefinitions[fullName] = typeSpecDef @@ -176,6 +182,7 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as File: astFile, TypeSpec: typeSpec, ParentSpec: astDeclaration, + IsUnique: true, } if idt, ok := typeSpec.Type.(*ast.Ident); ok && IsGolangPrimitiveType(idt.Name) && parsedSchemas != nil { @@ -190,14 +197,18 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as pkgDefs.uniqueDefinitions = make(map[string]*TypeSpecDef) } - fullName := typeSpecDef.FullName() + fullName := typeSpecDef.TypeName() anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName] if ok { if typeSpecDef.PkgPath == anotherTypeDef.PkgPath { continue } else { - delete(pkgDefs.uniqueDefinitions, fullName) + anotherTypeDef.IsUnique = false + typeSpecDef.IsUnique = false + pkgDefs.uniqueDefinitions[fullName] = nil + pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = typeSpecDef + pkgDefs.uniqueDefinitions[typeSpecDef.TypeName()] = typeSpecDef } } else { pkgDefs.uniqueDefinitions[fullName] = typeSpecDef @@ -221,6 +232,14 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as } } +func (pkgDefs *PackagesDefinitions) removeAllNotUniqueTypes() { + for key, ud := range pkgDefs.uniqueDefinitions { + if ud == nil { + delete(pkgDefs.uniqueDefinitions, key) + } + } +} + func (pkgDefs *PackagesDefinitions) findTypeSpec(pkgPath string, typeName string) *TypeSpecDef { if pkgDefs.packages == nil { return nil @@ -353,6 +372,11 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File return pkgDefs.uniqueDefinitions[typeName] } + typeDef, ok := pkgDefs.uniqueDefinitions[typeName] + if ok { + return typeDef + } + parts := strings.Split(strings.Split(typeName, "[")[0], ".") if len(parts) > 1 { isAliasPkgName := func(file *ast.File, pkgName string) bool { @@ -394,44 +418,30 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File return pkgDefs.parametrizeGenericType(file, typeDef, typeName, parseDependency) } - if def := pkgDefs.findGenericTypeSpec(fullTypeName(file.Name.Name, typeName), file, parseDependency); def != nil { - return def - } - - typeDef, ok := pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)] + typeDef, ok = pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)] if ok { return typeDef } - typeDef = pkgDefs.findTypeSpec(pkgDefs.files[file].PackagePath, typeName) - if typeDef == nil { + typeDef = func() *TypeSpecDef { + name := parts[0] + typeDef, ok := pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, name)] + if ok { + return typeDef + } + typeDef = pkgDefs.findTypeSpec(pkgDefs.files[file].PackagePath, name) + if typeDef != nil { + return typeDef + } for _, imp := range file.Imports { if imp.Name != nil && imp.Name.Name == "." { - typeDef := pkgDefs.findTypeSpec(strings.Trim(imp.Path.Value, `"`), typeName) + typeDef = pkgDefs.findTypeSpec(strings.Trim(imp.Path.Value, `"`), name) if typeDef != nil { break } } } - } + return typeDef + }() return pkgDefs.parametrizeGenericType(file, typeDef, typeName, parseDependency) } - -func (pkgDefs *PackagesDefinitions) findGenericTypeSpec(typeName string, file *ast.File, parseDependency bool) *TypeSpecDef { - if strings.Contains(typeName, "[") { - // genericName differs from typeName in that it does not contain any type parameters - genericName := strings.SplitN(typeName, "[", 2)[0] - for tName, tSpec := range pkgDefs.uniqueDefinitions { - if !strings.Contains(tName, "[") { - continue - } - - if strings.Contains(tName, genericName) { - if parametrized := pkgDefs.parametrizeGenericType(file, tSpec, typeName, parseDependency); parametrized != nil { - return parametrized - } - } - } - } - return nil -} diff --git a/parser.go b/parser.go index c1bbb90d0..103fea510 100644 --- a/parser.go +++ b/parser.go @@ -1014,14 +1014,7 @@ func (parser *Parser) isInStructStack(typeSpecDef *TypeSpecDef) bool { // given name and package, and populates swagger schema definitions registry // with a schema for the given type func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) { - typeName := typeSpecDef.FullName() - var refTypeName string - if fn, ok := (typeSpecDef.ParentSpec).(*ast.FuncDecl); ok { - refTypeName = TypeDocNameFuncScoped(typeName, typeSpecDef.TypeSpec, fn.Name.Name) - } else { - refTypeName = TypeDocName(typeName, typeSpecDef.TypeSpec) - } - + typeName := typeSpecDef.TypeName() schema, found := parser.parsedSchemas[typeSpecDef] if found { parser.debug.Printf("Skipping '%s', already parsed.", typeName) @@ -1033,7 +1026,7 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) parser.debug.Printf("Skipping '%s', recursion detected.", typeName) return &Schema{ - Name: refTypeName, + Name: typeName, PkgPath: typeSpecDef.PkgPath, Schema: PrimitiveSchema(OBJECT), }, @@ -1054,7 +1047,7 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) } sch := Schema{ - Name: refTypeName, + Name: typeName, PkgPath: typeSpecDef.PkgPath, Schema: definition, } @@ -1069,12 +1062,8 @@ func (parser *Parser) ParseDefinition(typeSpecDef *TypeSpecDef) (*Schema, error) return &sch, nil } -func fullTypeName(pkgName, typeName string) string { - if pkgName != "" && !ignoreNameOverride(typeName) { - return pkgName + "." + typeName - } - - return typeName +func fullTypeName(parts ...string) string { + return strings.Join(parts, ".") } func fullTypeNameFunctionScoped(pkgName, fnName, typeName string) string { diff --git a/parser_test.go b/parser_test.go index bd58d4d16..d3ee7c58c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -168,7 +168,7 @@ func TestParser_ParseDefinition(t *testing.T) { } _, err = p.ParseDefinition(definition) assert.Error(t, err) - assert.Equal(t, "model.TestFuncDecl.Test", definition.FullName()) + assert.Equal(t, "model.TestFuncDecl.Test", definition.TypeName()) } func TestParser_ParseGeneralApiInfo(t *testing.T) { diff --git a/schema.go b/schema.go index 0baee9328..5949113fb 100644 --- a/schema.go +++ b/schema.go @@ -3,8 +3,6 @@ package swag import ( "errors" "fmt" - "go/ast" - "strings" "github.com/go-openapi/spec" ) @@ -133,58 +131,10 @@ func TransToValidCollectionFormat(format string) string { return "" } -// TypeDocName get alias from comment '// @name ', otherwise the original type name to display in doc. -func TypeDocName(pkgName string, spec *ast.TypeSpec) string { - if spec != nil && !ignoreNameOverride(pkgName) { - if spec.Comment != nil { - for _, comment := range spec.Comment.List { - texts := strings.Split(strings.TrimSpace(strings.TrimLeft(comment.Text, "/")), " ") - if len(texts) > 1 && strings.ToLower(texts[0]) == "@name" { - return texts[1] - } - } - } - - if spec.Name != nil { - return fullTypeName(strings.Split(pkgName, ".")[0], spec.Name.Name) - } - } - - if ignoreNameOverride(pkgName) { - return pkgName[1:] - } - - return pkgName -} - func ignoreNameOverride(name string) bool { return len(name) != 0 && name[0] == IgnoreNameOverridePrefix } -// TypeDocNameFuncScoped get alias from comment '// @name ', otherwise the original type name to display in doc. -func TypeDocNameFuncScoped(pkgName string, spec *ast.TypeSpec, fnName string) string { - if spec != nil && !ignoreNameOverride(pkgName) { - if spec.Comment != nil { - for _, comment := range spec.Comment.List { - texts := strings.Split(strings.TrimSpace(strings.TrimLeft(comment.Text, "/")), " ") - if len(texts) > 1 && strings.ToLower(texts[0]) == "@name" { - return texts[1] - } - } - } - - if spec.Name != nil { - return fullTypeNameFunctionScoped(strings.Split(pkgName, ".")[0], fnName, spec.Name.Name) - } - } - - if ignoreNameOverride(pkgName) { - return pkgName[1:] - } - - return pkgName -} - // RefSchema build a reference schema. func RefSchema(refType string) *spec.Schema { return spec.RefSchema("#/definitions/" + refType) diff --git a/schema_test.go b/schema_test.go index d6fb3fdb2..6589e2e54 100644 --- a/schema_test.go +++ b/schema_test.go @@ -1,7 +1,6 @@ package swag import ( - "go/ast" "testing" "github.com/go-openapi/spec" @@ -150,57 +149,3 @@ func TestIsInterfaceLike(t *testing.T) { assert.Equal(t, IsInterfaceLike(STRING), false) } - -func TestTypeDocName(t *testing.T) { - t.Parallel() - - expected := "a/package" - assert.Equal(t, expected, TypeDocName(expected, nil)) - - expected = "package.Model" - assert.Equal(t, expected, TypeDocName("package", &ast.TypeSpec{Name: &ast.Ident{Name: "Model"}})) - - expected = "Model" - assert.Equal(t, expected, TypeDocName("package", &ast.TypeSpec{ - Comment: &ast.CommentGroup{ - List: []*ast.Comment{{Text: "// @name Model"}}, - }, - })) - - expected = "package.ModelName" - assert.Equal(t, expected, TypeDocName("$package.ModelName", &ast.TypeSpec{Name: &ast.Ident{Name: "Model"}})) - - expected = "Model" - assert.Equal(t, expected, TypeDocName("$Model", &ast.TypeSpec{ - Comment: &ast.CommentGroup{ - List: []*ast.Comment{{Text: "// @name ModelName"}}, - }, - })) -} - -func TestTypeDocNameFuncScoped(t *testing.T) { - t.Parallel() - - expected := "a/package" - assert.Equal(t, expected, TypeDocNameFuncScoped(expected, nil, "FnName")) - - expected = "package.FnName.Model" - assert.Equal(t, expected, TypeDocNameFuncScoped("package", &ast.TypeSpec{Name: &ast.Ident{Name: "Model"}}, "FnName")) - - expected = "Model" - assert.Equal(t, expected, TypeDocNameFuncScoped("package", &ast.TypeSpec{ - Comment: &ast.CommentGroup{ - List: []*ast.Comment{{Text: "// @name Model"}}, - }, - }, "FnName")) - - expected = "package.FnName.ModelName" - assert.Equal(t, expected, TypeDocNameFuncScoped("$package.FnName.ModelName", &ast.TypeSpec{Name: &ast.Ident{Name: "Model"}}, "FnName")) - - expected = "Model" - assert.Equal(t, expected, TypeDocNameFuncScoped("$Model", &ast.TypeSpec{ - Comment: &ast.CommentGroup{ - List: []*ast.Comment{{Text: "// @name ModelName"}}, - }, - }, "FnName")) -} diff --git a/testdata/generics_basic/expected.json b/testdata/generics_basic/expected.json index d4933fe9c..b1d362497 100644 --- a/testdata/generics_basic/expected.json +++ b/testdata/generics_basic/expected.json @@ -46,7 +46,7 @@ "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/web.GenericResponse-types_Field_string" + "$ref": "#/definitions/web.GenericResponse-types_Field-string" } }, "222": { @@ -138,25 +138,25 @@ "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/web.GenericResponse-types_Field_string" + "$ref": "#/definitions/web.GenericResponse-types_Field-string" } }, "203": { "description": "Non-Authoritative Information", "schema": { - "$ref": "#/definitions/web.GenericResponse-types_Field_int" + "$ref": "#/definitions/web.GenericResponse-types_Field-int" } }, "204": { "description": "No Content", "schema": { - "$ref": "#/definitions/api.Response-string-types_Field_int" + "$ref": "#/definitions/api.Response-string-types_Field-int" } }, "205": { "description": "Reset Content", "schema": { - "$ref": "#/definitions/api.Response-api_StringStruct-types_Field_int" + "$ref": "#/definitions/api.Response-api_StringStruct-types_Field-int" } }, "222": { @@ -182,7 +182,7 @@ } }, "definitions": { - "api.Response-api_StringStruct-types_Field_int": { + "api.Response-api_StringStruct-types_Field-int": { "type": "object", "properties": { "data": { @@ -196,7 +196,7 @@ } } }, - "api.Response-string-types_Field_int": { + "api.Response-string-types_Field-int": { "type": "object", "properties": { "data": { @@ -226,6 +226,14 @@ } } }, + "types.Field-string": { + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, "types.Hello": { "type": "object", "properties": { @@ -372,7 +380,7 @@ } } }, - "web.GenericResponse-types_Field_int": { + "web.GenericResponse-types_Field-int": { "type": "object", "properties": { "data": { @@ -383,11 +391,11 @@ } } }, - "web.GenericResponse-types_Field_string": { + "web.GenericResponse-types_Field-string": { "type": "object", "properties": { "data": { - "type": "string" + "$ref": "#/definitions/types.Field-string" }, "status": { "type": "string" diff --git a/testdata/generics_names/expected.json b/testdata/generics_names/expected.json index cd35f090d..741b2455d 100644 --- a/testdata/generics_names/expected.json +++ b/testdata/generics_names/expected.json @@ -146,7 +146,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/myweb.AliasPkgGenericResponse-Post" + "$ref": "#/definitions/web.AliasPkgGenericResponse-Post" } } } @@ -286,17 +286,6 @@ } } }, - "myweb.AliasPkgGenericResponse-Post": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/Post" - }, - "status": { - "type": "string" - } - } - }, "web.APIError": { "description": "API error with information about it", "type": "object", @@ -318,6 +307,17 @@ "type": "integer" } } + }, + "web.AliasPkgGenericResponse-Post": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/Post" + }, + "status": { + "type": "string" + } + } } } } \ No newline at end of file diff --git a/testdata/generics_nested/expected.json b/testdata/generics_nested/expected.json index 78ea43f9c..a2d005505 100644 --- a/testdata/generics_nested/expected.json +++ b/testdata/generics_nested/expected.json @@ -26,7 +26,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType-types_Post" } } ], @@ -40,25 +40,25 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericNestedResponse-web_GenericInnerType-types_Post" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-types_Post" } }, "203": { "description": "Non-Authoritative Information", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post" } }, "222": { "description": "", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_types_Post-types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType-types_Post-types_Post" } } } @@ -81,7 +81,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType_array_types_Post" + "$ref": "#/definitions/web.GenericNestedBody-web_GenericInnerType-array_types_Post" } } ], @@ -95,37 +95,37 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType-types_Post" } }, "202": { "description": "Accepted", "schema": { - "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType_array_types_Post" + "$ref": "#/definitions/web.GenericNestedResponse-array_web_GenericInnerType-array_types_Post" } }, "203": { "description": "Non-Authoritative Information", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType_array_types_Post_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType-array_types_Post-types_Post" } }, "204": { "description": "No Content", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType_array_types_Post_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType-array_types_Post-types_Post" } }, "205": { "description": "Reset Content", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_array_web_GenericInnerType_array2_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post" } }, "222": { "description": "", "schema": { - "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType_array_types_Post-array_types_Post" + "$ref": "#/definitions/web.GenericNestedResponseMulti-web_GenericInnerType-array_types_Post-array_types_Post" } } } @@ -183,7 +183,7 @@ } } }, - "web.GenericInnerMultiType-types_Post-array_web_GenericInnerType_array2_types_Post": { + "web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post": { "type": "object", "properties": { "itemOne": { @@ -218,7 +218,7 @@ } } }, - "web.GenericInnerMultiType-types_Post-web_GenericInnerType_types_Post": { + "web.GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post": { "type": "object", "properties": { "itemOne": { @@ -270,7 +270,7 @@ } } }, - "web.GenericNestedBody-web_GenericInnerType_array_types_Post": { + "web.GenericNestedBody-web_GenericInnerType-array_types_Post": { "type": "object", "properties": { "items": { @@ -283,7 +283,7 @@ } } }, - "web.GenericNestedBody-web_GenericInnerType_types_Post": { + "web.GenericNestedBody-web_GenericInnerType-types_Post": { "type": "object", "properties": { "items": { @@ -315,7 +315,7 @@ } } }, - "web.GenericNestedResponse-array_web_GenericInnerType_array_types_Post": { + "web.GenericNestedResponse-array_web_GenericInnerType-array_types_Post": { "type": "object", "properties": { "items": { @@ -334,7 +334,7 @@ } } }, - "web.GenericNestedResponse-array_web_GenericInnerType_types_Post": { + "web.GenericNestedResponse-array_web_GenericInnerType-types_Post": { "type": "object", "properties": { "items": { @@ -369,7 +369,7 @@ } } }, - "web.GenericNestedResponse-web_GenericInnerType_types_Post": { + "web.GenericNestedResponse-web_GenericInnerType-types_Post": { "type": "object", "properties": { "items": { @@ -385,7 +385,7 @@ } } }, - "web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType_array_types_Post_types_Post": { + "web.GenericNestedResponseMulti-array_types_Post-array_web_GenericInnerMultiType-array_types_Post-types_Post": { "type": "object", "properties": { "itemOne": { @@ -411,7 +411,7 @@ } } }, - "web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType_array_types_Post_types_Post": { + "web.GenericNestedResponseMulti-array_types_Post-web_GenericInnerMultiType-array_types_Post-types_Post": { "type": "object", "properties": { "itemOne": { @@ -434,7 +434,7 @@ } } }, - "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_array_web_GenericInnerType_array2_types_Post": { + "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post": { "type": "object", "properties": { "itemOne": { @@ -445,7 +445,7 @@ "description": "ItemsTwo is the second thing", "type": "array", "items": { - "$ref": "#/definitions/web.GenericInnerMultiType-types_Post-array_web_GenericInnerType_array2_types_Post" + "$ref": "#/definitions/web.GenericInnerMultiType-types_Post-array_web_GenericInnerType-array2_types_Post" } }, "status": { @@ -454,7 +454,7 @@ } } }, - "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_types_Post": { + "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-types_Post": { "type": "object", "properties": { "itemOne": { @@ -474,7 +474,7 @@ } } }, - "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType_types_Post_web_GenericInnerType_types_Post": { + "web.GenericNestedResponseMulti-types_Post-web_GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post": { "type": "object", "properties": { "itemOne": { @@ -485,7 +485,7 @@ "description": "ItemsTwo is the second thing", "type": "array", "items": { - "$ref": "#/definitions/web.GenericInnerMultiType-types_Post-web_GenericInnerType_types_Post" + "$ref": "#/definitions/web.GenericInnerMultiType-types_Post-web_GenericInnerType-types_Post" } }, "status": { @@ -494,7 +494,7 @@ } } }, - "web.GenericNestedResponseMulti-web_GenericInnerType_array_types_Post-array_types_Post": { + "web.GenericNestedResponseMulti-web_GenericInnerType-array_types_Post-array_types_Post": { "type": "object", "properties": { "itemOne": { @@ -517,7 +517,7 @@ } } }, - "web.GenericNestedResponseMulti-web_GenericInnerType_types_Post-types_Post": { + "web.GenericNestedResponseMulti-web_GenericInnerType-types_Post-types_Post": { "type": "object", "properties": { "itemOne": { diff --git a/testdata/generics_property/expected.json b/testdata/generics_property/expected.json index 36acc18ca..e0880258f 100644 --- a/testdata/generics_property/expected.json +++ b/testdata/generics_property/expected.json @@ -121,10 +121,10 @@ "$ref": "#/definitions/types.Field-array_api_Person" }, "detail1": { - "$ref": "#/definitions/types.Field-types_Field_api_Person" + "$ref": "#/definitions/types.Field-types_Field-api_Person" }, "detail2": { - "$ref": "#/definitions/types.Field-types_Field_string" + "$ref": "#/definitions/types.Field-types_Field-string" }, "directors": { "$ref": "#/definitions/types.Field-array_api_Person" @@ -298,7 +298,7 @@ } } }, - "types.Field-types_Field_api_Person": { + "types.Field-types_Field-api_Person": { "type": "object", "properties": { "value": { @@ -326,7 +326,7 @@ } } }, - "types.Field-types_Field_string": { + "types.Field-types_Field-string": { "type": "object", "properties": { "value": { diff --git a/types.go b/types.go index 505984fdb..684342368 100644 --- a/types.go +++ b/types.go @@ -2,6 +2,7 @@ package swag import ( "go/ast" + "strings" "github.com/go-openapi/spec" ) @@ -24,6 +25,8 @@ type TypeSpecDef struct { // path of package starting from under ${GOPATH}/src or from module path in go.mod PkgPath string ParentSpec ast.Decl + + IsUnique bool } // Name the name of the typeSpec. @@ -35,18 +38,40 @@ func (t *TypeSpecDef) Name() string { return "" } -// FullName full name of the typeSpec. -func (t *TypeSpecDef) FullName() string { - var fullName string - if parentFun, ok := (t.ParentSpec).(*ast.FuncDecl); ok && parentFun != nil { - fullName = fullTypeNameFunctionScoped(t.File.Name.Name, parentFun.Name.Name, t.TypeSpec.Name.Name) +// TypeName the type name of the typeSpec. +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 ' + for _, comment := range t.TypeSpec.Comment.List { + texts := strings.Split(strings.TrimSpace(strings.TrimLeft(comment.Text, "/")), " ") + if len(texts) > 1 && strings.ToLower(texts[0]) == "@name" { + return texts[1] + } + } + } + + var names []string + if t.IsUnique { + names = append(names, t.File.Name.Name) } else { - fullName = fullTypeName(t.File.Name.Name, t.TypeSpec.Name.Name) + pkgPath := strings.Map(func(r rune) rune { + if r == '\\' || r == '/' || r == '.' { + return '_' + } + return r + }, t.PkgPath) + names = append(names, pkgPath) + } + if parentFun, ok := (t.ParentSpec).(*ast.FuncDecl); ok && parentFun != nil { + names = append(names, parentFun.Name.Name) } - return fullName + names = append(names, t.TypeSpec.Name.Name) + return fullTypeName(names...) } -// FullPath of the typeSpec. +// FullPath return the full path of the typeSpec. func (t *TypeSpecDef) FullPath() string { return t.PkgPath + "." + t.Name() } From 6517d4daa79cfa1764273c9973a9139df54769a6 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Tue, 25 Oct 2022 18:54:49 +0800 Subject: [PATCH 3/8] fix issue #1353 about generics, add unit tests Signed-off-by: sdghchj --- generics_test.go | 15 ++ packages.go | 4 +- testdata/generics_package_alias/api/api1.go | 25 ++ testdata/generics_package_alias/api/api2.go | 46 ++++ testdata/generics_package_alias/expected.json | 228 ++++++++++++++++++ testdata/generics_package_alias/main.go | 5 + .../path1/v1/product.go | 17 ++ .../path2/v1/product.go | 17 ++ 8 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 testdata/generics_package_alias/api/api1.go create mode 100644 testdata/generics_package_alias/api/api2.go create mode 100644 testdata/generics_package_alias/expected.json create mode 100644 testdata/generics_package_alias/main.go create mode 100644 testdata/generics_package_alias/path1/v1/product.go create mode 100644 testdata/generics_package_alias/path2/v1/product.go diff --git a/generics_test.go b/generics_test.go index 4d82c2cfd..4a042f9b0 100644 --- a/generics_test.go +++ b/generics_test.go @@ -103,6 +103,21 @@ func TestParseGenericsNames(t *testing.T) { assert.Equal(t, string(expected), string(b)) } +func TestParseGenericsPackageAlias(t *testing.T) { + t.Parallel() + + searchDir := "testdata/generics_package_alias" + 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/packages.go b/packages.go index 9eb83431f..4719379b2 100644 --- a/packages.go +++ b/packages.go @@ -147,7 +147,7 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag anotherTypeDef.IsUnique = false typeSpecDef.IsUnique = false pkgDefs.uniqueDefinitions[fullName] = nil - pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = typeSpecDef + pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef pkgDefs.uniqueDefinitions[typeSpecDef.TypeName()] = typeSpecDef } } else { @@ -207,7 +207,7 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as anotherTypeDef.IsUnique = false typeSpecDef.IsUnique = false pkgDefs.uniqueDefinitions[fullName] = nil - pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = typeSpecDef + pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef pkgDefs.uniqueDefinitions[typeSpecDef.TypeName()] = typeSpecDef } } else { diff --git a/testdata/generics_package_alias/api/api1.go b/testdata/generics_package_alias/api/api1.go new file mode 100644 index 000000000..cf443df62 --- /dev/null +++ b/testdata/generics_package_alias/api/api1.go @@ -0,0 +1,25 @@ +package api + +import ( + myv1 "github.com/swaggo/swag/testdata/generics_package_alias/path1/v1" +) + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv1.ListResult[myv1.ProductDto] "" +// @Router /api1 [post] +func CreateMovie1() { + _ = myv1.ListResult[myv1.ProductDto]{} +} + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv1.RenamedListResult[myv1.RenamedProductDto] "" +// @Router /api2 [post] +func CreateMovie2() { + _ = myv1.ListResult[myv1.ProductDto]{} +} diff --git a/testdata/generics_package_alias/api/api2.go b/testdata/generics_package_alias/api/api2.go new file mode 100644 index 000000000..9ffdd3911 --- /dev/null +++ b/testdata/generics_package_alias/api/api2.go @@ -0,0 +1,46 @@ +package api + +import ( + myv1 "github.com/swaggo/swag/testdata/generics_package_alias/path1/v1" + myv2 "github.com/swaggo/swag/testdata/generics_package_alias/path2/v1" +) + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv2.ListResult[myv2.ProductDto] "" +// @Router /api3 [post] +func CreateMovie3() { + _ = myv2.ListResult[myv2.ProductDto]{} +} + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv2.RenamedListResult[myv2.RenamedProductDto] "" +// @Router /api4 [post] +func CreateMovie4() { + _ = myv2.ListResult[myv2.ProductDto]{} +} + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv1.ListResult[myv2.ProductDto] "" +// @Router /api5 [post] +func CreateMovie5() { + _ = myv1.ListResult[myv2.ProductDto]{} +} + +// @Summary Create movie +// @Description Create a new movie production +// @Accept json +// @Produce json +// @Success 200 {object} myv1.RenamedListResult[myv2.RenamedProductDto] "" +// @Router /api6 [post] +func CreateMovie6() { + _ = myv1.ListResult[myv2.ProductDto]{} +} diff --git a/testdata/generics_package_alias/expected.json b/testdata/generics_package_alias/expected.json new file mode 100644 index 000000000..ec2c51aa1 --- /dev/null +++ b/testdata/generics_package_alias/expected.json @@ -0,0 +1,228 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/api1": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path1_v1_ProductDto" + } + } + } + } + }, + "/api2": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ListResultV1-ProductDtoV1" + } + } + } + } + }, + "/api3": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path2_v1_ProductDto" + } + } + } + } + }, + "/api4": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ListResultV2-ProductDtoV2" + } + } + } + } + }, + "/api5": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path2_v1_ProductDto" + } + } + } + } + }, + "/api6": { + "post": { + "description": "Create a new movie production", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create movie", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/ListResultV1-ProductDtoV2" + } + } + } + } + } + }, + "definitions": { + "ListResultV1-ProductDtoV1": { + "type": "object", + "properties": { + "items11": { + "type": "array", + "items": { + "$ref": "#/definitions/ProductDtoV1" + } + } + } + }, + "ListResultV1-ProductDtoV2": { + "type": "object", + "properties": { + "items11": { + "type": "array", + "items": { + "$ref": "#/definitions/ProductDtoV2" + } + } + } + }, + "ListResultV2-ProductDtoV2": { + "type": "object", + "properties": { + "items22": { + "type": "array", + "items": { + "$ref": "#/definitions/ProductDtoV2" + } + } + } + }, + "ProductDtoV1": { + "type": "object", + "properties": { + "name11": { + "type": "string" + } + } + }, + "ProductDtoV2": { + "type": "object", + "properties": { + "name22": { + "type": "string" + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path1_v1_ProductDto": { + "type": "object", + "properties": { + "items1": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ProductDto" + } + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path2_v1_ProductDto": { + "type": "object", + "properties": { + "items1": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ProductDto" + } + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path1_v1.ProductDto": { + "type": "object", + "properties": { + "name1": { + "type": "string" + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ListResult-github_com_swaggo_swag_testdata_generics_package_alias_path2_v1_ProductDto": { + "type": "object", + "properties": { + "items2": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ProductDto" + } + } + } + }, + "github_com_swaggo_swag_testdata_generics_package_alias_path2_v1.ProductDto": { + "type": "object", + "properties": { + "name2": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/generics_package_alias/main.go b/testdata/generics_package_alias/main.go new file mode 100644 index 000000000..790580777 --- /dev/null +++ b/testdata/generics_package_alias/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/testdata/generics_package_alias/path1/v1/product.go b/testdata/generics_package_alias/path1/v1/product.go new file mode 100644 index 000000000..c0524a37b --- /dev/null +++ b/testdata/generics_package_alias/path1/v1/product.go @@ -0,0 +1,17 @@ +package v1 + +type ProductDto struct { + Name1 string `json:"name1"` +} + +type ListResult[T any] struct { + Items1 []T `json:"items1,omitempty"` +} + +type RenamedProductDto struct { + Name11 string `json:"name11"` +} // @name ProductDtoV1 + +type RenamedListResult[T any] struct { + Items11 []T `json:"items11,omitempty"` +} // @name ListResultV1 diff --git a/testdata/generics_package_alias/path2/v1/product.go b/testdata/generics_package_alias/path2/v1/product.go new file mode 100644 index 000000000..754ddb591 --- /dev/null +++ b/testdata/generics_package_alias/path2/v1/product.go @@ -0,0 +1,17 @@ +package v1 + +type ProductDto struct { + Name2 string `json:"name2"` +} + +type ListResult[T any] struct { + Items2 []T `json:"items2,omitempty"` +} + +type RenamedProductDto struct { + Name22 string `json:"name22"` +} // @name ProductDtoV2 + +type RenamedListResult[T any] struct { + Items22 []T `json:"items22,omitempty"` +} // @name ListResultV2 From f3b5311b4d3911a613803b0ecfe7c0f06bbcd379 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Wed, 26 Oct 2022 09:36:31 +0800 Subject: [PATCH 4/8] fix unit tests Signed-off-by: sdghchj --- parser_test.go | 1 - testdata/conflict_name/expected.json | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/parser_test.go b/parser_test.go index d3ee7c58c..9afebfb30 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3384,7 +3384,6 @@ func Fun() { assert.NoError(t, err) b, _ := json.MarshalIndent(p.swagger, "", " ") - t.Log(string(b)) assert.Equal(t, expected, string(b)) } diff --git a/testdata/conflict_name/expected.json b/testdata/conflict_name/expected.json index 74f046c46..0b3576dbc 100644 --- a/testdata/conflict_name/expected.json +++ b/testdata/conflict_name/expected.json @@ -24,7 +24,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse" + "$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse" } } } @@ -47,7 +47,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse" + "$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse" } } } @@ -55,7 +55,7 @@ } }, "definitions": { - "github.com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse": { + "github_com_swaggo_swag_testdata_conflict_name_model.ErrorsResponse": { "type": "object", "properties": { "newTime": { @@ -63,7 +63,7 @@ } } }, - "github.com_swaggo_swag_testdata_conflict_name_model.MyStruct": { + "github_com_swaggo_swag_testdata_conflict_name_model.MyStruct": { "type": "object", "properties": { "name": { @@ -71,7 +71,7 @@ } } }, - "github.com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse": { + "github_com_swaggo_swag_testdata_conflict_name_model2.ErrorsResponse": { "type": "object", "properties": { "newTime": { @@ -79,7 +79,7 @@ } } }, - "github.com_swaggo_swag_testdata_conflict_name_model2.MyStruct": { + "github_com_swaggo_swag_testdata_conflict_name_model2.MyStruct": { "type": "object", "properties": { "name": { @@ -91,7 +91,7 @@ "type": "object", "properties": { "my": { - "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model.MyStruct" + "$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model.MyStruct" }, "name": { "type": "string" @@ -102,7 +102,7 @@ "type": "object", "properties": { "my": { - "$ref": "#/definitions/github.com_swaggo_swag_testdata_conflict_name_model2.MyStruct" + "$ref": "#/definitions/github_com_swaggo_swag_testdata_conflict_name_model2.MyStruct" }, "name": { "type": "string" From de4daa31147ecfe9fff4ca8730f738505a09e843 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Wed, 26 Oct 2022 09:57:35 +0800 Subject: [PATCH 5/8] fix unit tests Signed-off-by: sdghchj --- generics.go | 5 ++--- generics_test.go | 3 +-- packages.go | 10 ++++------ types.go | 8 ++++---- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/generics.go b/generics.go index 7c744bb57..1049d3863 100644 --- a/generics.go +++ b/generics.go @@ -95,9 +95,8 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi } parametrizedTypeSpec := &TypeSpecDef{ - File: original.File, - PkgPath: original.PkgPath, - IsUnique: true, + File: original.File, + PkgPath: original.PkgPath, TypeSpec: &ast.TypeSpec{ Name: &ast.Ident{ Name: name, diff --git a/generics_test.go b/generics_test.go index 4a042f9b0..deb5b75f5 100644 --- a/generics_test.go +++ b/generics_test.go @@ -127,8 +127,7 @@ func TestParametrizeStruct(t *testing.T) { typeSpec := pd.parametrizeGenericType( &ast.File{Name: &ast.Ident{Name: "test2"}}, &TypeSpecDef{ - IsUnique: true, - File: &ast.File{Name: &ast.Ident{Name: "test"}}, + File: &ast.File{Name: &ast.Ident{Name: "test"}}, TypeSpec: &ast.TypeSpec{ Name: &ast.Ident{Name: "Field"}, TypeParams: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{{Name: "T"}}}, {Names: []*ast.Ident{{Name: "T2"}}}}}, diff --git a/packages.go b/packages.go index 4719379b2..6651ef2e7 100644 --- a/packages.go +++ b/packages.go @@ -122,7 +122,6 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag PkgPath: packagePath, File: astFile, TypeSpec: typeSpec, - IsUnique: true, } if idt, ok := typeSpec.Type.(*ast.Ident); ok && IsGolangPrimitiveType(idt.Name) && parsedSchemas != nil { @@ -144,8 +143,8 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag if typeSpecDef.PkgPath == anotherTypeDef.PkgPath { continue } else { - anotherTypeDef.IsUnique = false - typeSpecDef.IsUnique = false + anotherTypeDef.NotUnique = true + typeSpecDef.NotUnique = true pkgDefs.uniqueDefinitions[fullName] = nil pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef pkgDefs.uniqueDefinitions[typeSpecDef.TypeName()] = typeSpecDef @@ -182,7 +181,6 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as File: astFile, TypeSpec: typeSpec, ParentSpec: astDeclaration, - IsUnique: true, } if idt, ok := typeSpec.Type.(*ast.Ident); ok && IsGolangPrimitiveType(idt.Name) && parsedSchemas != nil { @@ -204,8 +202,8 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as if typeSpecDef.PkgPath == anotherTypeDef.PkgPath { continue } else { - anotherTypeDef.IsUnique = false - typeSpecDef.IsUnique = false + anotherTypeDef.NotUnique = true + typeSpecDef.NotUnique = true pkgDefs.uniqueDefinitions[fullName] = nil pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef pkgDefs.uniqueDefinitions[typeSpecDef.TypeName()] = typeSpecDef diff --git a/types.go b/types.go index 684342368..e149f8b28 100644 --- a/types.go +++ b/types.go @@ -26,7 +26,7 @@ type TypeSpecDef struct { PkgPath string ParentSpec ast.Decl - IsUnique bool + NotUnique bool } // Name the name of the typeSpec. @@ -53,9 +53,7 @@ func (t *TypeSpecDef) TypeName() string { } var names []string - if t.IsUnique { - names = append(names, t.File.Name.Name) - } else { + if t.NotUnique { pkgPath := strings.Map(func(r rune) rune { if r == '\\' || r == '/' || r == '.' { return '_' @@ -63,6 +61,8 @@ func (t *TypeSpecDef) TypeName() string { return r }, t.PkgPath) names = append(names, pkgPath) + } else { + names = append(names, t.File.Name.Name) } if parentFun, ok := (t.ParentSpec).(*ast.FuncDecl); ok && parentFun != nil { names = append(names, parentFun.Name.Name) From e4349ceaae195c06544161f7c37e412bd10645d4 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Wed, 26 Oct 2022 10:23:38 +0800 Subject: [PATCH 6/8] remove useless code, fix unit tests coverage Signed-off-by: sdghchj --- parser.go | 72 ------------------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/parser.go b/parser.go index 103fea510..a36ba6d2d 100644 --- a/parser.go +++ b/parser.go @@ -11,7 +11,6 @@ import ( "go/token" "log" "net/http" - "net/url" "os" "os/exec" "path/filepath" @@ -105,15 +104,6 @@ type Parser struct { // outputSchemas store schemas which will be export to swagger outputSchemas map[*TypeSpecDef]*Schema - // existSchemaNames store names of models for conflict determination - existSchemaNames map[string]*Schema - - // toBeRenamedSchemas names of models to be renamed - toBeRenamedSchemas map[string]string - - // toBeRenamedSchemas URLs of ref models to be renamed - toBeRenamedRefURLs []*url.URL - // PropNamingStrategy naming strategy PropNamingStrategy string @@ -208,8 +198,6 @@ func New(options ...func(*Parser)) *Parser { debug: log.New(os.Stdout, "", log.LstdFlags), parsedSchemas: make(map[*TypeSpecDef]*Schema), outputSchemas: make(map[*TypeSpecDef]*Schema), - existSchemaNames: make(map[string]*Schema), - toBeRenamedSchemas: make(map[string]string), excludes: make(map[string]struct{}), fieldParserFactory: newTagBaseFieldParser, Overrides: make(map[string]string), @@ -368,8 +356,6 @@ func (parser *Parser) ParseAPIMultiSearchDir(searchDirs []string, mainAPIFile st return err } - parser.renameRefSchemas() - return parser.checkOperationIDUniqueness() } @@ -933,57 +919,9 @@ func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) ( return schema.Schema, nil } -func (parser *Parser) renameRefSchemas() { - if len(parser.toBeRenamedSchemas) == 0 { - return - } - - // rename schemas in swagger.Definitions - for name, pkgPath := range parser.toBeRenamedSchemas { - if schema, ok := parser.swagger.Definitions[name]; ok { - delete(parser.swagger.Definitions, name) - name = parser.renameSchema(name, pkgPath) - parser.swagger.Definitions[name] = schema - } - } - - // rename URLs if match - for _, refURL := range parser.toBeRenamedRefURLs { - parts := strings.Split(refURL.Fragment, "/") - name := parts[len(parts)-1] - - if pkgPath, ok := parser.toBeRenamedSchemas[name]; ok { - parts[len(parts)-1] = parser.renameSchema(name, pkgPath) - - refURL.Fragment = strings.Join(parts, "/") - } - } -} - -func (parser *Parser) renameSchema(name, pkgPath string) string { - parts := strings.Split(name, ".") - name = fullTypeName(pkgPath, parts[len(parts)-1]) - name = strings.ReplaceAll(name, "/", "_") - - return name -} - func (parser *Parser) getRefTypeSchema(typeSpecDef *TypeSpecDef, schema *Schema) *spec.Schema { _, ok := parser.outputSchemas[typeSpecDef] if !ok { - existSchema, ok := parser.existSchemaNames[schema.Name] - if ok { - // store the first one to be renamed after parsing over - _, ok = parser.toBeRenamedSchemas[existSchema.Name] - if !ok { - parser.toBeRenamedSchemas[existSchema.Name] = existSchema.PkgPath - } - // rename not the first one - schema.Name = parser.renameSchema(schema.Name, schema.PkgPath) - } else { - parser.existSchemaNames[schema.Name] = schema - } - parser.swagger.Definitions[schema.Name] = spec.Schema{} if schema.Schema != nil { @@ -994,8 +932,6 @@ func (parser *Parser) getRefTypeSchema(typeSpecDef *TypeSpecDef, schema *Schema) } refSchema := RefSchema(schema.Name) - // store every URL - parser.toBeRenamedRefURLs = append(parser.toBeRenamedRefURLs, refSchema.Ref.GetURL()) return refSchema } @@ -1066,14 +1002,6 @@ func fullTypeName(parts ...string) string { return strings.Join(parts, ".") } -func fullTypeNameFunctionScoped(pkgName, fnName, typeName string) string { - if pkgName != "" { - return pkgName + "." + fnName + "." + typeName - } - - return typeName -} - // 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) { From 0af92168355cfb1902fc3ee7dd0fcb700f5a1e42 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Wed, 26 Oct 2022 10:48:22 +0800 Subject: [PATCH 7/8] optimize Signed-off-by: sdghchj --- generics.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/generics.go b/generics.go index 1049d3863..e04bc89ea 100644 --- a/generics.go +++ b/generics.go @@ -13,8 +13,6 @@ import ( "github.com/go-openapi/spec" ) -//var genericsDefinitions = map[*TypeSpecDef]map[string]*TypeSpecDef{} - type genericTypeSpec struct { ArrayDepth int TypeSpec *TypeSpecDef @@ -83,13 +81,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi } name += strings.Replace(strings.Join(nameParts, "-"), ".", "_", -1) - /*if genericsMap, ok := genericsDefinitions[original]; ok { - if parametrizedTypeSpec, ok := genericsMap[name]; ok { - return parametrizedTypeSpec - } - } else { - genericsDefinitions[original] = map[string]*TypeSpecDef{} - }*/ + if typeSpec, ok := pkgDefs.uniqueDefinitions[name]; ok { return typeSpec } @@ -109,7 +101,6 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi }, } pkgDefs.uniqueDefinitions[name] = parametrizedTypeSpec - //genericsDefinitions[original][name] = parametrizedTypeSpec return parametrizedTypeSpec } @@ -157,8 +148,7 @@ func splitGenericsTypeName(fullGenericForm string) (string, []string) { func (pkgDefs *PackagesDefinitions) getParametrizedType(genTypeSpec *genericTypeSpec) ast.Expr { if genTypeSpec.TypeSpec != nil && strings.Contains(genTypeSpec.Name, ".") { - uniqueFullTypeName := genTypeSpec.TypeName() - parts := strings.SplitN(uniqueFullTypeName, ".", 2) + parts := strings.SplitN(genTypeSpec.Name, ".", 2) return &ast.SelectorExpr{ X: &ast.Ident{Name: parts[0]}, Sel: &ast.Ident{Name: parts[1]}, From be7f3d8ef8c180dfd16903ea8d0c4150c0fb7af3 Mon Sep 17 00:00:00 2001 From: sdghchj Date: Wed, 26 Oct 2022 14:29:34 +0800 Subject: [PATCH 8/8] fix unit tests coverage Signed-off-by: sdghchj --- packages.go | 43 +++++++++++++------------------------------ 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/packages.go b/packages.go index 6651ef2e7..1883e5666 100644 --- a/packages.go +++ b/packages.go @@ -140,9 +140,7 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName] if ok { - if typeSpecDef.PkgPath == anotherTypeDef.PkgPath { - continue - } else { + if typeSpecDef.PkgPath != anotherTypeDef.PkgPath { anotherTypeDef.NotUnique = true typeSpecDef.NotUnique = true pkgDefs.uniqueDefinitions[fullName] = nil @@ -199,9 +197,7 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as anotherTypeDef, ok := pkgDefs.uniqueDefinitions[fullName] if ok { - if typeSpecDef.PkgPath == anotherTypeDef.PkgPath { - continue - } else { + if typeSpecDef.PkgPath != anotherTypeDef.PkgPath { anotherTypeDef.NotUnique = true typeSpecDef.NotUnique = true pkgDefs.uniqueDefinitions[fullName] = nil @@ -370,30 +366,11 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File return pkgDefs.uniqueDefinitions[typeName] } - typeDef, ok := pkgDefs.uniqueDefinitions[typeName] - if ok { - return typeDef - } - parts := strings.Split(strings.Split(typeName, "[")[0], ".") if len(parts) > 1 { - isAliasPkgName := func(file *ast.File, pkgName string) bool { - if file != nil && file.Imports != nil { - for _, pkg := range file.Imports { - if pkg.Name != nil && pkg.Name.Name == pkgName { - return true - } - } - } - - return false - } - - if !isAliasPkgName(file, parts[0]) { - typeDef, ok := pkgDefs.uniqueDefinitions[typeName] - if ok { - return typeDef - } + typeDef, ok := pkgDefs.uniqueDefinitions[typeName] + if ok { + return typeDef } pkgPath := pkgDefs.findPackagePathFromImports(parts[0], file, false) @@ -411,12 +388,18 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File } } - typeDef := pkgDefs.findTypeSpec(pkgPath, parts[1]) + typeDef = pkgDefs.findTypeSpec(pkgPath, parts[1]) return pkgDefs.parametrizeGenericType(file, typeDef, typeName, parseDependency) } - typeDef, ok = pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)] + typeDef, ok := pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)] + if ok { + 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 }