flint
comes with an extensive set of rules taken from
lintr
, but if necessary one can also extend it relatively
easily. This will require some knowledge of the Rust crate
ast-grep
to write. This crate has great documentation on creating new
rules so you should start there.
In this vignette, our objective is to replace calls to
stop()
by rlang::abort()
, for instance because
we prefer the formatting of the output with the latter.
stop("this is an error")
#> Error: this is an error
rlang::abort("this is an error")
#> Error:
#> ! this is an error
Step 1: setup
First, we need to set up flint
using
setup_flint()
. This will create a flint
folder
that contains (among other things) a rules
folder where all
rules are stored. This is divided between builtin
rules
(that shouldn’t modified manually) and custom
rules (where
we will store our custom rule).
Then, we can create the structure of a new rule by copying an
existing rule, say flint/rules/builtin/any_is_na.yml
for
instance. After removing the stuff that is specific to this rule, we end
up with this structure:
Step 2: start exploring
When we use stop()
, we can pass multiple elements, like
in paste0()
:
n <- 10
stop("Got ", n, " values instead of 1.")
#> Error: Got 10 values instead of 1.
This is not possible with rlang::abort()
, which needs
everything to be in the argument message
, meaning that we
need to manually put all those elements in paste0()
:
n <- 10
rlang::abort(paste0("Got ", n, " values instead of 1."))
#> Error:
#> ! Got 10 values instead of 1.
Therefore, we can look for the pattern stop(...)
and
replace it by rlang::abort(paste0(...))
. Capturing all
elements in a pattern is done with $$$
and those elements
can be used in the fix
or message
arguments
using by wrapping them in ~~
:
id: stop_abort-1
language: r
severity: warning
rule:
pattern: stop($$$ELEMS)
fix: rlang::abort(paste0(~~ELEMS~~))
message: Use `rlang::abort()` instead of `stop()`.
After storing this rule in
flint/rules/custom/stop_abort.yml
, we can call:
flint::lint_text(
'stop("Got ", n, " values instead of 1.")',
linters = "stop_abort"
)
Running our example with lint_text()
now shows the
message:
flint::lint_text(
'stop("Got ", n, " values instead of 1.")',
linters = "stop_abort"
)
#> Original code: stop("Got ", n, " values instead of 1.")
#> Suggestion: Use `rlang::abort()` instead of `stop()`.
#> Rule ID: stop_abort-1
And fix_text()
correctly applies the fix:
flint::fix_text(
'stop("Got ", n, " values instead of 1.")',
linters = "stop_abort"
)
#> Old code: stop("Got ", n, " values instead of 1.")
#> New code: rlang::abort(paste0("Got ", n, " values instead of 1."))
Note that there are still some corner cases to address, such as
ignoring the arguments call.
and domain
of
stop()
if they are specified, but this is not in the scope
of this vignette.
Step 3 (optional): add to config
To automatically use this new linter without having to specify it
manually, we can add it to flint/config.yaml
:
Running lint_text()
or fix_text()
without
linters
now works:
flint::fix_text('stop("Got ", n, " values instead of 1.")')
#> Old code: stop("Got ", n, " values instead of 1.")
#> New code: rlang::abort(paste0("Got ", n, " values instead of 1."))