Ruleguard by example: Node predicates

package gorules

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

// When ruleguard inspects the program and tries to match your rules,
// it walks over the AST created from the target files source code.
// The AST consists of the nodes that are defined in "go/ast" package.
//
// These node types are not connected to the "expression types" directly,
// but they're useful when you want to assert the shape of the match.

func formatLiteral(m dsl.Matcher) {
	// The following rule tries to find the formatting calls
	// that use non-literal arguments for the formatting string.
	// So, variables, named constants and everything else is a no-no.
	m.Match(`fmt.Sprintf($format, $*_)`,
		`fmt.Fprintf($_, $format, $*_)`,
		`fmt.Printf($format, $*_)`).
		Where(!m["format"].Node.Is(`BasicLit`)).
		Report("non-literal formatting string is used")
}
package main

import "fmt"

func main() {
	formatStringVar := "%d"
	const formatStringConst = "%d"

	_ = fmt.Sprintf(formatStringVar, 1)
	_ = fmt.Sprintf(formatStringConst, 2)
	_ = fmt.Sprintf("%s: %d", "the answer is", 42) // OK

	fmt.Printf("%"+"d", -5)

	fmt.Printf("%d", 10) // OK
}

Notes: