Skip to content

Commit

Permalink
feat(go): Improve file layout for types (#5173)
Browse files Browse the repository at this point in the history
  • Loading branch information
amckinney authored Nov 14, 2024
1 parent ed573cc commit 1208e0a
Show file tree
Hide file tree
Showing 275 changed files with 21,264 additions and 16,991 deletions.
Binary file added .package.json.swp
Binary file not shown.
5 changes: 5 additions & 0 deletions fern/pages/changelogs/cli/2024-11-14.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 0.45.0-rc44
**`(fix):`** Update the IR's `ServiceTypeReferenceInfo` to include all transitive types
referenced by a service.


4 changes: 4 additions & 0 deletions fern/pages/changelogs/go-sdk/2024-11-14.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 0.31.0
**`(feat):`** Improves type file layout with zero impact on backwards compatibility.
Shared types are now more accurately placed in the `types.go` file, whereas types referenced by a single service are now placed in a file that matches the service's filename (e.g. user.go).

137 changes: 74 additions & 63 deletions generators/go/internal/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import (
)

const (
// sharedTypesFilename is the filename for the shared types file.
sharedTypesFilename = "types.go"

// packageDocsFilename represents the standard package documentation filename.
packageDocsFilename = "doc.go"

Expand Down Expand Up @@ -1357,43 +1360,46 @@ func fileInfoToTypes(
continue
}
fileInfo := fileInfoForType(rootPackageName, irService.Name.FernFilepath)
result[fileInfo] = append(result[fileInfo], &typeToGenerate{ID: irEndpoint.Name.OriginalName, FernFilepath: irService.Name.FernFilepath, Endpoint: irEndpoint, Service: irService})
result[fileInfo] = append(
result[fileInfo],
&typeToGenerate{
ID: irEndpoint.Name.OriginalName,
FernFilepath: irService.Name.FernFilepath,
Endpoint: irEndpoint,
Service: irService,
},
)
}
}
if irServiceTypeReferenceInfo == nil {
// If the service type reference info isn't provided, default
// to the file-per-type naming convention.
for _, irType := range irTypes {
fileInfo := fileInfoForType(rootPackageName, irType.Name.FernFilepath)
result[fileInfo] = append(result[fileInfo], &typeToGenerate{ID: irType.Name.TypeId, FernFilepath: irType.Name.FernFilepath, TypeDeclaration: irType})
result[fileInfo] = append(
result[fileInfo],
&typeToGenerate{
ID: irType.Name.TypeId,
FernFilepath: irType.Name.FernFilepath,
TypeDeclaration: irType,
},
)
}
} else {
directories := make(map[fernir.TypeId][]string)
for irTypeId, irType := range irTypes {
var elements []string
for _, packageName := range irType.Name.FernFilepath.PackagePath {
elements = append(elements, strings.ToLower(packageName.CamelCase.SafeName))
}
directories[irTypeId] = elements
}
sharedTypes := irServiceTypeReferenceInfo.SharedTypes
if typeIds, ok := irServiceTypeReferenceInfo.TypesReferencedOnlyByService["service_"]; ok {
// The root service types should be included alongside the other shared types.
sharedTypes = append(sharedTypes, typeIds...)
}
for _, sharedTypeId := range sharedTypes {
typeDeclaration, ok := irTypes[sharedTypeId]
for _, typeId := range sharedTypes {
typeDeclaration, ok := irTypes[typeId]
if !ok {
// Should be unreachable.
return nil, fmt.Errorf("IR ServiceTypeReferenceInfo referenced type %q which doesn't exist", sharedTypeId)
return nil, fmt.Errorf("IR ServiceTypeReferenceInfo referenced type %q which doesn't exist", typeId)
}
fileInfo := fileInfo{
filename: "types.go",
packageName: rootPackageName,
}
if directory := directories[sharedTypeId]; len(directory) > 0 {
fileInfo.filename = filepath.Join(append(directory, fileInfo.filename)...)
fileInfo.packageName = directory[len(directory)-1]
fileInfo := fileInfoForType(rootPackageName, typeDeclaration.Name.FernFilepath)
if isReservedFilename(filepath.Base(fileInfo.filename)) {
fileInfo.filename = filepath.Join(filepath.Dir(fileInfo.filename), sharedTypesFilename)
}
result[fileInfo] = append(
result[fileInfo],
Expand All @@ -1414,56 +1420,18 @@ func fileInfoToTypes(
// Should be unreachable.
return nil, fmt.Errorf("IR ServiceTypeReferenceInfo referenced service %q which doesn't exist", serviceId)
}
fernFilepath := service.Name.FernFilepath
var basename string
if service.Name.FernFilepath.File != nil {
basename = fernFilepath.File.SnakeCase.UnsafeName
} else {
basename = fernFilepath.PackagePath[len(fernFilepath.PackagePath)-1].SnakeCase.UnsafeName
}
var packages []string
for _, packageName := range fernFilepath.PackagePath {
packages = append(packages, strings.ToLower(packageName.CamelCase.SafeName))
}
servicePackageName := rootPackageName
if len(packages) > 0 {
servicePackageName = packages[len(packages)-1]
}
serviceFileInfo := fileInfo{
filename: filepath.Join(append(packages, fmt.Sprintf("%s.go", basename))...),
packageName: servicePackageName,
}
for _, typeId := range typeIds {
typeDeclaration, ok := irTypes[typeId]
if !ok {
// Should be unreachable.
return nil, fmt.Errorf("IR ServiceTypeReferenceInfo referenced type %q which doesn't exist", typeId)
}
typeFilename := "types.go"
typePackageName := rootPackageName
if directory := directories[typeId]; len(directory) > 0 {
typeFilename = filepath.Join(append(directory, typeFilename)...)
typePackageName = directory[len(directory)-1]
fileInfo := fileInfoForType(rootPackageName, typeDeclaration.Name.FernFilepath)
if shouldSetFileInfoToMatchService(typeDeclaration.Name.FernFilepath, service.Name.FernFilepath) {
fileInfo.filename = filepath.Join(filepath.Dir(fileInfo.filename), service.Name.FernFilepath.File.SnakeCase.UnsafeName+".go")
}
if servicePackageName != typePackageName {
// There is only one service referencing this type, but it still
// belongs in the package where it was defined.
typeFileInfo := fileInfo{
filename: typeFilename,
packageName: typePackageName,
}
result[typeFileInfo] = append(
result[typeFileInfo],
&typeToGenerate{
ID: typeId,
FernFilepath: typeDeclaration.Name.FernFilepath,
TypeDeclaration: typeDeclaration,
},
)
continue
}
result[serviceFileInfo] = append(
result[serviceFileInfo],
result[fileInfo] = append(
result[fileInfo],
&typeToGenerate{
ID: typeDeclaration.Name.TypeId,
FernFilepath: typeDeclaration.Name.FernFilepath,
Expand All @@ -1480,6 +1448,36 @@ func fileInfoToTypes(
return result, nil
}

func shouldSetFileInfoToMatchService(
typeFernFilepath *fernir.FernFilepath,
serviceFernFilepath *fernir.FernFilepath,
) bool {
if serviceFernFilepath.File == nil || typeFernFilepath.File != nil {
// If the service is a root client or the type is already defined
// in a particular non-root package, we can leave it as-is.
return false
}
if !packagePathIsEqual(typeFernFilepath, serviceFernFilepath) {
// We only want to set the file info if the type is defined in the
// same package as the service.
return false
}
filename := serviceFernFilepath.File.SnakeCase.UnsafeName
return !isReservedFilename(filename)
}

func packagePathIsEqual(a, b *fernir.FernFilepath) bool {
if len(a.PackagePath) != len(b.PackagePath) {
return false
}
for i := range a.PackagePath {
if a.PackagePath[i].CamelCase.SafeName != b.PackagePath[i].CamelCase.SafeName {
return false
}
}
return true
}

func fileInfoToErrors(
rootPackageName string,
irErrorDeclarations map[fernir.ErrorId]*fernir.ErrorDeclaration,
Expand Down Expand Up @@ -1615,6 +1613,19 @@ func needsPaginationHelpers(ir *fernir.IntermediateRepresentation) bool {
return false
}

func isReservedFilename(filename string) bool {
_, ok := reservedFilenames[filename]
return ok
}

var reservedFilenames = map[string]struct{}{
"environments.go": struct{}{},
"errors.go": struct{}{},
"file_param.go": struct{}{},
"optional.go": struct{}{},
"pointer.go": struct{}{},
}

// pointerFunctionNames enumerates all of the pointer function names.
var pointerFunctionNames = map[string]struct{}{
"Bool": struct{}{},
Expand Down
Loading

0 comments on commit 1208e0a

Please sign in to comment.