go-ruleguard
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 it's dedicated to Go only.
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
- Rules can be installed as Go modules
- Integrated into golangci-lint
It can also be easily embedded into other static analyzers. go-critic can be used as an example.
Quick start
It's advised that you get a binary from the latest release {linux/amd64, linux/arm64, darwin/amd64, windows/amd64}.
If you want to install the ruleguard from source, it's as simple as:
# Installs a `ruleguard` binary under your `$(go env GOPATH)/bin`
$ GO111MODULE=on go get -v -u github.com/quasilyte/go-ruleguard/cmd/ruleguard
# Get the DSL package (needed to execute the ruleguard files)
$ go get -v -u github.com/quasilyte/go-ruleguard/dsl
If inside a Go module, the dsl
package will be installed for the current module,
otherwise it installs the package into the $GOPATH and it will be globally available.
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
comma-separated list of ruleguard file paths
-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 rules.go
file:
package gorules
import "github.com/quasilyte/go-ruleguard/dsl"
func dupSubExpr(m dsl.Matcher) {
m.Match(`$x || $x`,
`$x && $x`,
`$x | $x`,
`$x & $x`).
Where(m["x"].Pure).
Report(`suspicious identical LHS and RHS`)
}
func boolExprSimplify(m dsl.Matcher) {
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 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 the pattern debugging:
$ ruleguard -e 'm.Match(`!($x != $y)`)' example.go
example.go:5:10: !(v1 != v2)
It automatically inserts Report("$$")
into the specified pattern.
You can use -debug-group <name>
flag to see explanations
on why some rules rejected the match (e.g. which Where()
condition failed).
The -e
generated rule will have e
name, so it can be debugged as well.
How does it work?
First, it parses ruleguard files (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 is written in terms of dsl
API. Ruleguard files contain a set of functions that serve as a rule groups. Every such function accepts a single dsl.Matcher
argument that is then used to define and configure rules inside the group.
A rule definition always starts with Match(patterns...)
method call and ends with 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.
Documentation
Rule set examples