Ruleguard by example: Custom locations

package gorules

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

// When analyzer is integrated into the text editor (or IDE),
// it's very important to provide good issue locations so
// the editor can highlight the offending part correctly.

func mismatchingUnlock(m dsl.Matcher) {
	// By default, an entire match position is used as a location.
	// This can be changed by the At() method that binds the location
	// to the provided named submatch.
	//
	// In the rules below text editor would get mismatching method
	// name locations:
	//
	//   defer mu.RUnlock()
	//            ^^^^^^^

	m.Match(`$mu.Lock(); defer $mu.$unlock()`).
		Where(m["unlock"].Text == "RUnlock").
		At(m["unlock"]).
		Report(`maybe $mu.Unlock() was intended?`)

	m.Match(`$mu.RLock(); defer $mu.$unlock()`).
		Where(m["unlock"].Text == "Unlock").
		At(m["unlock"]).
		Report(`maybe $mu.RUnlock() was intended?`)
}
package main

import "sync"

func main() {
	var mu sync.RWMutex
	f1(&mu)
	f2(&mu)
	f3(&mu)
}

func f1(mu *sync.RWMutex) {
	mu.Lock()
	defer mu.RUnlock()
}

func f2(mu *sync.RWMutex) {
	mu.RLock()
	defer mu.Unlock()
}

func f3(mu *sync.RWMutex) {
	mu.Lock()
	defer mu.Unlock()
}

Notes: