Ruleguard by example: Custom filters

package gorules

import (

// 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.
		Where(m["x"].Filter(implementsStringer) && m["x"].Addressable).
		Report(`can use $x.String() directly`)
$ ruleguard -rules rules.go main.go
main.go:9:10: can use foo.String() directly
main.go:10:10: can use fooPtr.String() directly
package main

import "fmt"

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


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

type Foo struct{}

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


To index Next: Matching comments Edit this page