
<!-- README.md is generated from README.Rmd. Please edit that file -->

# flir

(pronounced “fleer” as in “beer”)

<!-- badges: start -->

[![R-CMD-check](https://github.com/etiennebacher/flir/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/etiennebacher/flir/actions/workflows/R-CMD-check.yaml)
[![CRAN
status](https://www.r-pkg.org/badges/version/flir)](https://CRAN.R-project.org/package=flir)
<!-- badges: end -->

`flir` is an R package to detect and rewrite code patterns. It was
originally created to be an R linter and an alternative to
[`lintr`](https://lintr.r-lib.org/). However, it may be better to view
it as a tool to refactor any type of code by detecting and rewriting
custom patterns (see [Adding new
rules](https://flir.etiennebacher.com/articles/adding_rules)). `flir`
comes with a list of built-in rules and therefore can still be used as a
linter (see the “Usage” section below), but I now concentrate my efforts
on a new R linter entirely written in Rust:
[Jarl](https://jarl.etiennebacher.com/). Therefore, I will not add new
rules in `flir`.

`flir` is powered by
[`astgrepr`](https://github.com/etiennebacher/astgrepr/), which is
itself built on the Rust crate
[`ast-grep`](https://ast-grep.github.io/).

## Installation

To get the CRAN version (stable):

``` r
install.packages("flir")
```

To get the development version (unstable):

``` r
# install.packages("remotes")
remotes::install_github("etiennebacher/flir")
```

## Usage

Optional setup:

- `setup_flir()`: creates the folder `flir` and populates it with
  built-in rules as well as a cache file. You can modify those rules or
  add new ones if you want more control.

You can use `flir` as-is, without any setup. However, running
`setup_flir()` 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:

- `lint()` looks for rule violations in R files;
- `fix()` looks for rule violations in R files and automatically applies
  their replacement (if any).

One can also experiment with `flir::lint_text()` and `flir::fix_text()`:

``` r
flir::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
flir::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
```

See the vignette [Automatic
fixes](https://flir.etiennebacher.com/articles/automatic_fixes) to see
how to be more confident about changes introduced by `flir`.

## Real-life examples

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

- `ggplot2`:
  [\#6050](https://github.com/tidyverse/ggplot2/pull/6050/files) and
  [\#6051](https://github.com/tidyverse/ggplot2/pull/6051/files)
- `marginaleffects`:
  [\#1171](https://github.com/vincentarelbundock/marginaleffects/pull/1171/files)
  and
  [\#1177](https://github.com/vincentarelbundock/marginaleffects/pull/1177/files)
- `targets`:
  [\#1325](https://github.com/ropensci/targets/pull/1325/files)
- `tinytable`:
  [\#325](https://github.com/vincentarelbundock/tinytable/pull/325/files)
- `usethis`: [\#2048](https://github.com/r-lib/usethis/pull/2048/files)

Except for some manual tweaks when the replacement was wrong (I was
testing `flir` after all), all changes were generated by
`flir::fix_package()` or `flir::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.

`flir` is quite fast This is a small benchmark on 3.5k lines of code
with a few linters:

``` r
file <- system.file("bench/test.R", package = "flir")

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()
    )
  ),
  flir = flir::lint(
    file,
    linters = list(
      flir::any_duplicated_linter(),
      flir::any_is_na_linter(),
      flir::matrix_apply_linter(),
      flir::function_return_linter(),
      flir::lengths_linter(),
      flir::T_and_F_symbol_linter(),
      flir::undesirable_function_linter(),
      flir::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 `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 lintr         3.44s    3.44s     0.291   313.5MB    12.2 
#> 2 flir       153.77ms 172.76ms     5.89      1.8MB     1.96
```

## Why the name “flir”?

`flir` was originally named `flint` but I had to rename it to avoid
conflicts with a package named `flint` on CRAN.

`flir` stands for “**F**ix **L**ints **I**n **R**”.

## Contributing

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

Take a look at the [contributing
guide](https://flir.etiennebacher.com/CONTRIBUTING.html) 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/>.
