etiennebacher / flint

Find and Fix Lints in R Code
https://flint.etiennebacher.com
Other
42 stars 0 forks source link

flint

R-CMD-check

flint is a small R package to find and replace lints in R code.

flint is powered by astgrepr, which is itself built on the Rust crate ast-grep.

Installation

install.packages('flint', repos = c('https://etiennebacher.r-universe.dev'))

Usage

Optional setup:

You can use flint as-is, without any setup. However, running setup_flint() enables the use of caching, meaning that the subsequent runs will be faster. It is also gives you a place where you can store custom rules for your project/package.

The everyday usage consists of two functions:

One can also experiment with flint::lint_text() and flint::fix_text():

flint::lint_text("
any(is.na(x))
any(duplicated(y))
")
#> Original code: any(is.na(x)) 
#> Suggestion: anyNA(x) is better than any(is.na(x)). 
#> Rule ID: any_na-1 
#> 
#> Original code: any(duplicated(y)) 
#> Suggestion: anyDuplicated(x, ...) > 0 is better than any(duplicated(x), ...). 
#> Rule ID: any_duplicated-1
flint::fix_text("
any(is.na(x))
any(duplicated(y))
")
#> Old code:
#> any(is.na(x))
#> any(duplicated(y))
#> 
#> New code:
#> anyNA(x)
#> anyDuplicated(y) > 0

Real-life examples

I tested flint on several packages while developing it. I proposed some pull requests for those packages. Here are a few:

Except for some manual tweaks when the replacement was wrong (I was testing flint after all), all changes were generated by flint::fix_package() or flint::fix_dir(<dirname>).

Comparison with existing tools

The most used tool for lints detection in R is lintr. However, lintr’s performance is not optimal when it is applied on medium to large packages. Also, lintr cannot perform automatic replacement of lints.

styler is a package to clean code by fixing indentation and other things, but doesn’t perform code replacement based on lints.

flint is quite performant. This is a small benchmark on 3.5k lines of code with a few linters:

file <- system.file("bench/test.R", package = "flint")

bench::mark(
  lintr = lintr::lint(
    file, linters = list(lintr::any_duplicated_linter(), lintr::any_is_na_linter(),
                         lintr::matrix_apply_linter(), lintr::function_return_linter(),
                         lintr::lengths_linter(), lintr::T_and_F_symbol_linter(),
                         lintr::undesirable_function_linter(), lintr::expect_length_linter())
  ),
  flint = flint::lint(
    file, linters = list(flint::any_duplicated_linter(), flint::any_is_na_linter(),
                         flint::matrix_apply_linter(), flint::function_return_linter(),
                         flint::lengths_linter(), flint::T_and_F_symbol_linter(),
                         flint::undesirable_function_linter(), flint::expect_length_linter()),
    verbose = FALSE,
    open = FALSE
  ),
  check = FALSE
)
#> Warning: Some expressions had a GC in every
#> iteration; so filtering is disabled.
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>
#> 1 lintr         1.89s    1.89s     0.528  308.57MB
#> 2 flint      101.64ms  102.4ms     9.71     1.67MB
#> # ℹ 1 more variable: `gc/sec` <dbl>

Contributing

Did you find some bugs or some errors in the documentation? Do you want flint to support more rules?

Take a look at the contributing guide for instructions on bug report and pull requests.

Acknowledgements

The website theme was heavily inspired by Matthew Kay’s ggblend package: https://mjskay.github.io/ggblend/.