//go:build gorules

package gorules

// https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module
// to apply changes in this file, please do: ./build/bin/golangci-lint cache clean
import (
	"github.com/quasilyte/go-ruleguard/dsl"
	//quasilyterules "github.com/quasilyte/ruleguard-rules-test"
)

func init() {
	//dsl.ImportRules("qrules", quasilyterules.Bundle)
}

func txDeferRollback(m dsl.Matcher) {
	// Common pattern for long-living transactions:
	//	tx, err := db.Begin()
	//	if err != nil {
	//		return err
	//	}
	//	defer tx.Rollback()
	//
	//	... code which uses database in transaction
	//
	//	err := tx.Commit()
	//	if err != nil {
	//		return err
	//	}

	m.Match(
		`$tx, $err := $db.BeginRw($ctx); $chk; $rollback`,
		`$tx, $err = $db.BeginRw($ctx); $chk; $rollback`,
		`$tx, $err := $db.Begin($ctx); $chk; $rollback`,
		`$tx, $err = $db.Begin($ctx); $chk; $rollback`,
		`$tx, $err := $db.BeginRo($ctx); $chk; $rollback`,
		`$tx, $err = $db.BeginRo($ctx); $chk; $rollback`,
	).
		Where(!m["rollback"].Text.Matches(`defer .*\.Rollback()`)).
		//At(m["rollback"]).
		Report(`Add "defer $tx.Rollback()" right after transaction creation error check. 
			If you are in the loop - consider use "$db.View" or "$db.Update" or extract whole transaction to function.
			Without rollback in defer - app can deadlock on error or panic.
			Rules are in ./rules.go file.
			`)
}

func closeCollector(m dsl.Matcher) {
	m.Match(`$c := etl.NewCollector($*_); $close`).
		Where(!m["close"].Text.Matches(`defer .*\.Close()`)).
		Report(`Add "defer $c.Close()" right after collector creation`)
}

func closeLockedDir(m dsl.Matcher) {
	m.Match(`$c := dir.OpenRw($*_); $close`).
		Where(!m["close"].Text.Matches(`defer .*\.Close()`)).
		Report(`Add "defer $c.Close()" after locked.OpenDir`)
}

func passValuesByContext(m dsl.Matcher) {
	m.Match(`ctx.WithValue($*_)`).Report(`Don't pass app-level parameters by context, pass them as-is or as typed objects`)
}

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?
			Rules are in ./rules.go file.`)

	m.Match(`$mu.RLock(); defer $mu.$unlock()`).
		Where(m["unlock"].Text == "Unlock").
		At(m["unlock"]).
		Report(`maybe $mu.RUnlock() was intended?
			Rules are in ./rules.go file.`)
}
