diff --git a/CHANGELOG.md b/CHANGELOG.md index 217aa30..05cd196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v0.9.1] - 2024-09-09 + +### Changed + +* Added improved testability for the `TemplateModelBuilder` by refactoring its constructor function to accept an `AstFileAccessor` function instead of a filename. This allows for greater flexibility in testing, as the AST can now be sourced either from a file or directly from a string, making it easier to test different code inputs without relying on file I/O. + +### Fixed + +* Fixed an issue in the `type_model_builder.go` module where parameters and result fields of type array were not correctly handled. This update ensures that array types are properly represented in the generated template models, allowing for accurate code generation in cases involving arrays. + + ## [v0.9.0] - 2024-09-08 Starting with this release, the project's license has been changed from AGPLv3 to Apache License 2.0. The move to the Apache 2.0 license reflects my desire to make the library more accessible and easier to adopt, especially in commercial and proprietary projects. diff --git a/internal/commands/init_command.go b/internal/commands/init_command.go index 98dd5d5..1c0a949 100644 --- a/internal/commands/init_command.go +++ b/internal/commands/init_command.go @@ -2,11 +2,12 @@ package commands import ( "fmt" + "os" + "github.com/matzefriedrich/cobra-extensions/pkg" "github.com/matzefriedrich/cobra-extensions/pkg/abstractions" "github.com/matzefriedrich/parsley/internal/generator" "github.com/spf13/cobra" - "os" ) type initCommand struct { @@ -22,7 +23,7 @@ func (g *initCommand) Execute() { return } - const minVersion = "v0.8.1" + const minVersion = "v0.9.1" const packageName = "github.com/matzefriedrich/parsley" dependencyErr := p.AddDependency(packageName, minVersion) if dependencyErr != nil { diff --git a/internal/generator/ast_file_accessor.go b/internal/generator/ast_file_accessor.go new file mode 100644 index 0000000..6780168 --- /dev/null +++ b/internal/generator/ast_file_accessor.go @@ -0,0 +1,25 @@ +package generator + +import ( + "go/ast" + "go/parser" + "go/token" +) + +type AstFileAccessor func() (*ast.File, error) + +// AstFromFile Creates an AstFileAccessor object for the given Golang source file. +func AstFromFile(sourceFilePath string) AstFileAccessor { + return func() (*ast.File, error) { + fileSet := token.NewFileSet() + return parser.ParseFile(fileSet, sourceFilePath, nil, parser.ParseComments) + } +} + +// AstFromSource Creates an AstFileAccessor object for the given source code. +func AstFromSource(code []byte) AstFileAccessor { + return func() (*ast.File, error) { + fileSet := token.NewFileSet() + return parser.ParseFile(fileSet, "", code, parser.ParseComments) + } +} diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 55d3caa..04f6597 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -50,7 +50,7 @@ func (g *codeFileGenerator) GenerateCode() error { return err } - builder, err := NewTemplateModelBuilder(goFilePath) + builder, err := NewTemplateModelBuilder(AstFromFile(goFilePath)) if err != nil { return err } diff --git a/internal/generator/type_model_builder.go b/internal/generator/type_model_builder.go index 48928c6..92ddf48 100644 --- a/internal/generator/type_model_builder.go +++ b/internal/generator/type_model_builder.go @@ -3,21 +3,17 @@ package generator import ( "fmt" "go/ast" - "go/parser" - "go/token" ) type TemplateModelBuilder struct { node *ast.File } -func NewTemplateModelBuilder(sourceFilePath string) (*TemplateModelBuilder, error) { - fileSet := token.NewFileSet() - node, err := parser.ParseFile(fileSet, sourceFilePath, nil, parser.ParseComments) +func NewTemplateModelBuilder(accessor AstFileAccessor) (*TemplateModelBuilder, error) { + node, err := accessor() if err != nil { return nil, err } - return &TemplateModelBuilder{ node: node, }, nil @@ -72,11 +68,16 @@ func (b *TemplateModelBuilder) collectParametersFor(funcType *ast.FuncType) []Pa parameters := make([]Parameter, 0) for _, param := range funcType.Params.List { paramType := param.Type + paramArrayType, isArrayType := paramType.(*ast.ArrayType) + if isArrayType { + paramType = paramArrayType.Elt + } paramTypeIdentifier, _ := paramType.(*ast.Ident) for _, paramName := range param.Names { parameters = append(parameters, Parameter{ Name: paramName.Name, TypeName: paramTypeIdentifier.Name, + IsArray: isArrayType, }) } } @@ -90,11 +91,16 @@ func (b *TemplateModelBuilder) collectResultFieldsFor(funcType *ast.FuncType) [] } for index, field := range funcType.Results.List { fieldType := field.Type + fieldArrayType, isArrayType := fieldType.(*ast.ArrayType) + if isArrayType { + fieldType = fieldArrayType.Elt + } fieldTypeIdentifier, ok := fieldType.(*ast.Ident) if ok { parameters = append(parameters, Parameter{ Name: fmt.Sprintf("result%d", index), TypeName: fieldTypeIdentifier.Name, + IsArray: isArrayType, }) } } diff --git a/internal/generator/type_model_functions.go b/internal/generator/type_model_functions.go index 751bf67..be0cd14 100644 --- a/internal/generator/type_model_functions.go +++ b/internal/generator/type_model_functions.go @@ -22,7 +22,11 @@ func HasResults(m Method) bool { func FormattedParameters(m Method) string { formattedParameters := make([]string, len(m.Parameters)) for i, parameter := range m.Parameters { - formattedParameters[i] = fmt.Sprintf("%s %s", parameter.Name, parameter.TypeName) + typeName := parameter.TypeName + if parameter.IsArray { + typeName = "[]" + typeName + } + formattedParameters[i] = fmt.Sprintf("%s %s", parameter.Name, typeName) } return strings.Join(formattedParameters, ", ") } @@ -46,7 +50,11 @@ func FormattedResultParameters(m Method) string { func FormattedResultTypes(m Method) string { formattedResults := make([]string, len(m.Results)) for i, result := range m.Results { - formattedResults[i] = fmt.Sprintf("%s", result.TypeName) + typeName := result.TypeName + if result.IsArray { + typeName = "[]" + typeName + } + formattedResults[i] = fmt.Sprintf("%s", typeName) } if len(formattedResults) == 0 { return "" diff --git a/internal/generator/types.go b/internal/generator/types.go index d391f79..4b458f9 100644 --- a/internal/generator/types.go +++ b/internal/generator/types.go @@ -7,6 +7,7 @@ import ( type Parameter struct { Name string TypeName string + IsArray bool } func (p Parameter) MatchesType(name string) bool { diff --git a/internal/tests/generator/ast_file_accessor_test.go b/internal/tests/generator/ast_file_accessor_test.go new file mode 100644 index 0000000..39bf633 --- /dev/null +++ b/internal/tests/generator/ast_file_accessor_test.go @@ -0,0 +1,31 @@ +package generator + +import ( + "github.com/matzefriedrich/parsley/internal/generator" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_AstFileAccessor_create_accessor_from_source(t *testing.T) { + + // Arrange + source := []byte("package types\n" + + "\n" + + "type Service0 interface {\n" + + " Method0()\n" + + "}\n" + + "\n" + + "type Service1 interface {\n" + + " Method1() string\n" + + " Method3() (string, error)\n" + + "}\n") + + accessor := generator.AstFromSource(source) + + // Act + actual, err := accessor() + + // Assert + assert.NoError(t, err) + assert.NotNil(t, actual) +} diff --git a/internal/tests/generator/type_model_builder_test.go b/internal/tests/generator/type_model_builder_test.go new file mode 100644 index 0000000..c9e185c --- /dev/null +++ b/internal/tests/generator/type_model_builder_test.go @@ -0,0 +1,64 @@ +package generator + +import ( + "github.com/matzefriedrich/parsley/internal/generator" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_NewTemplateModelBuilder_from_empty_source_file_returns_error(t *testing.T) { + + // Arrange + source := []byte("") + + // Act + sut, err := generator.NewTemplateModelBuilder(generator.AstFromSource(source)) + + // Assert + assert.Error(t, err) + assert.Nil(t, sut) +} + +func Test_NewTemplateModelBuilder_from_minimal_source_file(t *testing.T) { + + // Arrange + source := []byte("package main") + + // Act + sut, err := generator.NewTemplateModelBuilder(generator.AstFromSource(source)) + + // Assert + assert.NoError(t, err) + assert.NotNil(t, sut) +} + +func Test_NewTemplateModelBuilder_Build_multiple_interface_definitions(t *testing.T) { + + // Arrange + source := []byte("package types\n" + "\n" + + "type Service0 interface {\n" + " Method0(s string)\n" + "}\n" + "\n" + + "type Service1 interface {\n" + " Method1() string\n" + " Method2(data []bytes) (string, error)\n" + + "}\n") + + accessor := generator.AstFromSource(source) + sut, _ := generator.NewTemplateModelBuilder(accessor) + + // Act + actual, err := sut.Build() + + // Assert + assert.NoError(t, err) + assert.NotNil(t, actual) + + serviceInterface0 := actual.Interfaces[0] + assert.Equal(t, "Service0", serviceInterface0.Name) + + serviceInterface1 := actual.Interfaces[1] + assert.Equal(t, "Service1", serviceInterface1.Name) + + method1 := serviceInterface1.Methods[0] + assert.Equal(t, "Method1", method1.Name) + + method2 := serviceInterface1.Methods[1] + assert.Equal(t, "Method2", method2.Name) +}