Ruleguard by example: Text filters

package gorules

import "github.com/quasilyte/go-ruleguard/dsl"

// Normally, all matching is done on the AST and types level.
// But sometimes it's necessary (or just easier) to treat the
// submatches as text.

func printFmt(m dsl.Matcher) {
	// Here we scan a $s string for %s, %d and %v.
	m.Match(`fmt.Println($s, $*_)`,
		`fmt.Print($s, $*_)`).
		Where(m["s"].Text.Matches(`%[sdv]`)).
		Report("found formatting directive in non-formatting call")
}

func wrongErrChecked(m dsl.Matcher) {
	// Here we check for both types and names.
	// Note that we can use `==` and `!=` with Text.
	m.Match("if $*_, $err0 := $*_; $err1 != nil { $*_ }").
		Where(m["err0"].Text == "err" && m["err0"].Type.Is("error") &&
			m["err1"].Text != "err" && m["err1"].Type.Is("error")).
		Report("maybe wrong err in error check")
}
package main

import "fmt"

func main() {
	var msg string

	fmt.Println("error: %s", msg)
	fmt.Print("Hello, %s!", "ruleguard")
	fmt.Println("no formatting directives")

	var err2 error

	if v, err := f(); err2 != nil {
		fmt.Println(v, err)
	}
	if v, err2 := f(); err2 != nil {
		fmt.Println(v, err2)
	}
	if v, err := f(); err != nil {
		fmt.Println(v, err)
	}
}

func f() (int, error) { return 0, nil }

Notes: