/go-ruleguard

Define and run pattern-based custom linting rules

Primary LanguageGoBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

go-ruleguard

Build Status GoDoc Go Report Card

Logo

Overview

analysis-based Go linter that runs dynamically loaded rules.

You write the rules, ruleguard checks whether they are satisfied.

ruleguard has some similarities with GitHub CodeQL, but only focuses on Go code queries.

Features:

  • Custom linting rules without re-compilation and Go plugins.
  • Diagnostics are written in a declarative way.
  • Quickfix actions support.
  • Powerful match filtering features, like expression type pattern matching.

ruleguard comes with rules.go file that can be used as a foundation to write your own rules file.

Quick start

To install ruleguard binary under your $(go env GOPATH)/bin:

$ go get -v -u github.com/quasilyte/go-ruleguard/...

If $GOPATH/bin is under your system $PATH, ruleguard command should be available after that.

$ ruleguard -help
ruleguard: execute dynamic gogrep-based rules

Usage: ruleguard [-flag] [package]

Flags:
  -rules string
    	path to a rules.go file
  -e string
    	execute a single rule from a given string
  -fix
    	apply all suggested fixes
  -c int
    	display offending line with this many lines of context (default -1)
  -json
    	emit JSON output

Create a test example.rules.go file:

// +build ignore

package gorules

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

func _(m fluent.Matcher) {
	m.Match(`$x || $x`,
		`$x && $x`).
		Where(m["x"].Pure).
		Report(`suspicious identical LHS and RHS`)

	m.Match(`!($x != $y)`).Suggest(`$x == $y`)
	m.Match(`!($x == $y)`).Suggest(`$x != $y`)
}

Create a test example.go target file:

package main

func main() {
	var v1, v2 int
	println(!(v1 != v2))
	println(!(v1 == v2))
	if v1 == 0 && v1 == 0 {
		println("hello, world!")
	}
}

Run ruleguard on that target file:

$ ruleguard -rules example.rules.go -fix example.go
example.go:5:10: hint: suggested: v1 == v2
example.go:6:10: hint: suggested: v1 != v2
example.go:7:5: error: suspicious identical LHS and RHS

Since we ran ruleguard with -fix argument, both suggested changes are applied to example.go.

There is also a -e mode that is useful during pattern debugging:

$ ruleguard -e 'm.Match(`!($x != $y)`)' example.go
example.go:5:10: !(v1 != v2)

It automatically inserts Report("$$") into the specified pattern.

How does it work?

ruleguard parses gorules (e.g. rules.go) during the start to load the rule set.
Loaded rules are then used to check the specified targets (Go files, packages).
The rules.go file itself is never compiled, nor executed.

A rules.go file, as interpreted by a dsl/fluent API, is a set of functions that serve as a rule groups. Every function accepts a single fluent.Matcher argument that is then used to define and configure rules inside the group.

A rule definition always starts from a Match(patterns...) method call and ends with a Report(message) method call.

There can be additional calls in between these two. For example, a Where(cond) call applies constraints to a match to decide whether its accepted or rejected. So even if there is a match for a pattern, it won't produce a report message unless it satisfies a Where() condition.

To learn more, check out the documentation and/or the source code.

Documentation

Extra references