Skip to content

Deferror is a Go linter that suggests a customly made defer function call

License

Notifications You must be signed in to change notification settings

Jiang-Gianni/deferror

Repository files navigation

deferror

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.

Install

go install github.com/Jiang-Gianni/deferror/cmd/deferror@latest

How to use

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

Template

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
}

Example

readme.go

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)

Tips

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.

Subpackage templates

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}}

About

Deferror is a Go linter that suggests a customly made defer function call

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages