Ruleguard by example: Custom filters

package gorules

import (
	"github.com/quasilyte/go-ruleguard/dsl"
	"github.com/quasilyte/go-ruleguard/dsl/types"
)

// If you need to apply a filter that can't be expressed via Matcher API,
// it's possible to use a lower-level mechanism and resort to `go/types`.

// implementsStringer is a custom filter function.
// It tries both T and *T to see whether a type implements `fmt.Stringer`.
func implementsStringer(ctx *dsl.VarFilterContext) bool {
	// ctx.Type mapped to m["x"].Type;
	// ctx.GetInterface finds the `types.Type` object for the `fmt.Stringer`.
	stringer := ctx.GetInterface(`fmt.Stringer`)
	return types.Implements(ctx.Type, stringer) ||
		types.Implements(types.NewPointer(ctx.Type), stringer)
}

func sprintStringer(m dsl.Matcher) {
	// If we used m["x"].Type.Implements(`fmt.Stringer`), then we
	// wouldn't get all results we wanted: if $x type implements
	// stringer by pointer, then non-pointer value will not "implement" it.
	// Our custom filter will try both pointer and non-pointer versions.
	m.Match(`fmt.Sprint($x)`).
		Where(m["x"].Filter(implementsStringer) && m["x"].Addressable).
		Report(`can use $x.String() directly`)
}
package main

import "fmt"

func main() {
	fooPtr := &Foo{}
	foo := Foo{}

	println(fmt.Sprint(foo))
	println(fmt.Sprint(fooPtr))

	println(fmt.Sprint(0))    // Not fmt.Stringer
	println(fmt.Sprint(&foo)) // Not addressable
}

type Foo struct{}

func (*Foo) String() string { return "Foo" }

Notes: