Skip to content

Commit

Permalink
feat: add comments to generated methods
Browse files Browse the repository at this point in the history
Updated the generated methods to include any doc comments on the original definitions in the interface.
  • Loading branch information
adamconnelly committed Dec 2, 2023
1 parent 35a3444 commit 26a431a
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 2 deletions.
13 changes: 12 additions & 1 deletion cmd/kelpie/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"text/template"

"github.com/alecthomas/kong"
"github.com/pkg/errors"

"github.com/adamconnelly/kelpie/parser"
"github.com/adamconnelly/kelpie/slices"
)

//go:embed "mock.go.tmpl"
Expand All @@ -38,7 +40,16 @@ func (g *generateCmd) Run() error {
return errors.Wrap(err, "could not parse file")
}

template := template.Must(template.New("mock").Parse(mockTemplate))
template := template.Must(template.New("mock").
Funcs(template.FuncMap{
"CommentBlock": func(comment string) string {
lines := strings.Split(comment, "\n")
return strings.Join(slices.Map(lines, func(line string) string {
return "// " + line
}), "\n")
},
}).
Parse(mockTemplate))

for _, i := range mockedInterfaces {
err := func() error {
Expand Down
4 changes: 4 additions & 0 deletions cmd/kelpie/mock.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ type Instance struct {

{{- range $method := .Methods }}

{{ if $method.Comment }}{{ CommentBlock $method.Comment }}
{{ end -}}
func (m *Instance) {{ $method.Name }}({{ template "parameterWithTypeList" $method.Parameters }}){{ if $method.Results }} ({{ template "resultWithTypeList" $method.Results }}){{ end }} {
expectation := m.mock.Call("{{ $method.Name }}"{{ if $method.Parameters }}, {{ template "parameterList" $method.Parameters }}{{ end }})
if expectation != nil {
Expand Down Expand Up @@ -101,6 +103,8 @@ func (m *{{ $method.Name }}MethodMatcher) CreateMethodMatcher() *mocking.MethodM
return &m.matcher
}

{{ if $method.Comment }}{{ CommentBlock $method.Comment }}
{{ end -}}
func {{ $method.Name }}{{ if $method.Parameters }}[{{ template "matcherTypeParams" $method.Parameters }}]{{ end }}({{ template "matcherParams" $method.Parameters }}) *{{ $method.Name }}MethodMatcher {
result := {{ $method.Name }}MethodMatcher{
matcher: mocking.MethodMatcher{
Expand Down
21 changes: 21 additions & 0 deletions examples/argument_matching_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,28 @@ import (

//go:generate go run ../cmd/kelpie generate --interfaces Maths
type Maths interface {
// Add adds a and b together and returns the result.
Add(a, b int) int

// ParseInt interprets a string s in the given base (0, 2 to 36) and bit size (0 to 64)
// and returns the corresponding value i.
//
// The string may begin with a leading sign: "+" or "-".
//
// If the base argument is 0, the true base is implied by the string's prefix following
// the sign (if present): 2 for "0b", 8 for "0" or "0o", 16 for "0x", and 10 otherwise.
// Also, for argument base 0 only, underscore characters are permitted as defined by the
// Go syntax for integer literals.
//
// The bitSize argument specifies the integer type that the result must fit into. Bit
// sizes 0, 8, 16, 32, and 64 correspond to int, int8, int16, int32, and int64. If bitSize
// is below 0 or above 64, an error is returned.
//
// The errors that ParseInt returns have concrete type *NumError and include err.Num = s.
// If s is empty or contains invalid digits, err.Err = ErrSyntax and the returned value is
// 0; if the value corresponding to s cannot be represented by a signed integer of the given
// size, err.Err = ErrRange and the returned value is the maximum magnitude integer of the
// appropriate bitSize and sign.
ParseInt(input string) (int, error)
}

Expand Down
1 change: 1 addition & 0 deletions examples/called_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

//go:generate go run ../cmd/kelpie generate --interfaces RegistrationService
type RegistrationService interface {
// Register registers the item with the specified name.
Register(name string) error
}

Expand Down
40 changes: 40 additions & 0 deletions examples/mocks/maths/maths.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions examples/mocks/registrationservice/registrationservice.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions parser/mocks/interfacefilter/interfacefilter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type MethodDefinition struct {

// Results contains the method results.
Results []ResultDefinition

// Comment contains any comments added to the method.
Comment string
}

// ParameterDefinition contains information about a method parameter.
Expand Down Expand Up @@ -99,7 +102,8 @@ func Parse(reader io.Reader, filter InterfaceFilter) ([]MockedInterface, error)
for _, method := range typeSpecType.Methods.List {
methodDefinition := MethodDefinition{
// When are there multiple names?
Name: method.Names[0].Name,
Name: method.Names[0].Name,
Comment: strings.TrimSuffix(method.Doc.Text(), "\n"),
}

getTypeName := func(e ast.Expr) string {
Expand Down
32 changes: 32 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,38 @@ type AlarmService interface {
t.Equal("[]int", addAlarms.Results[0].Type)
}

func (t *ParserTests) Test_Parse_IncludesComments() {
// Arrange
input := `package test
// AlarmService can be used to create and manage various alarms.
type AlarmService interface {
// AddAlarms adds new alarms, returning the alarm IDs.
//
// Here's some super-exciting information about this method.
AddAlarms(names []string) []int
}`

// Act
result, err := parser.Parse(strings.NewReader(input), t.interfaceFilter.Instance())

// Assert
t.NoError(err)
t.Len(result, 1)

alarmService := slices.FirstOrPanic(result, func(mock parser.MockedInterface) bool {
return mock.Name == "AlarmService"
})

addAlarms := slices.FirstOrPanic(alarmService.Methods, func(method parser.MethodDefinition) bool {
return method.Name == "AddAlarms"
})
t.Equal(
`AddAlarms adds new alarms, returning the alarm IDs.
Here's some super-exciting information about this method.`, addAlarms.Comment)
}

// TODO: what about empty interfaces? Return a warning?

func TestParser(t *testing.T) {
Expand Down

0 comments on commit 26a431a

Please sign in to comment.