Deferror is a Go linter that suggests a customly made defer
function call when the function has a return named error and the function does not already start with a defer
call.
go install github.com/Jiang-Gianni/deferror/cmd/deferror@latest
A template file for the deferred function call is needed.
To generate a template file like deferror.tmpl with a custom name in the current directory:
deferror init -o "yourDeferror.tmpl"
If the -o
flag is not specified then the file will be generated as deferror.tmpl
.
To run the linter on a go
file:
DFRR_FILE=yourDeferror.tmpl deferror yourGoFile.go
If the environment variable DFRR_FILE
is not specified, the linter will look for deferror.tmpl
as the template.
Add the -fix
flag to make the linter write the suggested defer function call:
DFRR_FILE=yourDeferror.tmpl deferror -fix yourGoFile.go
The template must uses text/template package. The functions in the strings package are made available (see funcmap.go).
The structure of the input data map is the following (type F
):
type F struct {
PkgName string
PkgPath string
FnName string
RecvName string
RecvType string
RecvPointer bool
Params []P
ErrName string
}
type P struct {
Name string
Type string
Pointer bool
}
package example
import (
"time"
)
type R struct{}
func (r *R) MyExample(now time.Duration, i *int) (a int, err error) {
return 0, nil
}
The input data map for the template is:
&{PkgName:example PkgPath:github.com/Jiang-Gianni/deferror/example FnName:MyExample RecvName:r RecvType:*R RecvPointer:true Params:[{Name:now Type:time.Duration Pointer:false} {Name:i Type:*int Pointer:true}] ErrName: err}
The following defer call is suggested with the default template:
defer func(now time.Duration, i *int) {
if err != nil {
err = fmt.Errorf("r.MyExample(%v, %v, ): %w", now, i, err)
}
}(now, i)
Wrap function wrap.go
With a Wrap
function like this:
package dfrr
import "fmt"
func Wrap(errp *error, format string, args ...any) {
if *errp != nil {
*errp = fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), *errp)
}
}
the template can be simplified to:
{{define "main"}} defer dfrr.Wrap(&err, "{{if .RecvName}}{{.RecvName}}.{{end}}{{.FnName}}({{template "percV" .}})", {{template "plist" .}})
{{end}}
{{define "plist"}}{{range $item := .Params}}{{$item.Name}}, {{end}}{{end}}
{{define "percV"}}{{range $item := .Params}}%v, {{end}}{{end}}
which suggests (dfrr
module will need to be imported):
defer dfrr.Wrap(&err, "r.MyExample(%v, %v, )", now, i)
You can clone this repository and try it yourself on the example
folder.
By using the strings package functions it is possible to determine if the analyzed function package name or path contains a substring which means it allows to define subpackage specific templates.
For example you may want to add the complete input list values to the wrapping error message only inside critical package.
{{if Contains .PkgPath "/api/"}}
{{template "apiDefer" .}}
{{end}}