# flir (pronounced “fleer” as in “beer”) `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()`](https://flir.etiennebacher.com/reference/setup_flir.md): 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()`](https://flir.etiennebacher.com/reference/setup_flir.md) 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()`](https://flir.etiennebacher.com/reference/lint.md) looks for rule violations in R files; - [`fix()`](https://flir.etiennebacher.com/reference/fix.md) looks for rule violations in R files and automatically applies their replacement (if any). One can also experiment with [`flir::lint_text()`](https://flir.etiennebacher.com/reference/lint.md) and [`flir::fix_text()`](https://flir.etiennebacher.com/reference/fix.md): ``` 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()`](https://flir.etiennebacher.com/reference/fix.md) or `flir::fix_dir()`. ## 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` #> #> 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: . # Package index ## Find and fix lints - [`fix()`](https://flir.etiennebacher.com/reference/fix.md) [`fix_dir()`](https://flir.etiennebacher.com/reference/fix.md) [`fix_package()`](https://flir.etiennebacher.com/reference/fix.md) [`fix_text()`](https://flir.etiennebacher.com/reference/fix.md) : Automatically replace lints - [`lint()`](https://flir.etiennebacher.com/reference/lint.md) [`lint_dir()`](https://flir.etiennebacher.com/reference/lint.md) [`lint_package()`](https://flir.etiennebacher.com/reference/lint.md) [`lint_text()`](https://flir.etiennebacher.com/reference/lint.md) : List all lints in a file or a directory - [`list_linters()`](https://flir.etiennebacher.com/reference/list_linters.md) : Get the list of linters in `flir` ## Setup `flir` - [`add_new_rule()`](https://flir.etiennebacher.com/reference/add_new_rule.md) : Create a custom rule for internal use - [`export_new_rule()`](https://flir.etiennebacher.com/reference/export_new_rule.md) : Create a custom rule for external use - [`setup_flir()`](https://flir.etiennebacher.com/reference/setup_flir.md) : Setup flir - [`setup_flir_gha()`](https://flir.etiennebacher.com/reference/setup_flir_gha.md) : Create a Github Actions workflow for `flir` ## Linters - [`T_and_F_symbol_linter`](https://flir.etiennebacher.com/reference/T_and_F_symbol_linter.md) : `T` and `F` symbol linter - [`any_duplicated_linter`](https://flir.etiennebacher.com/reference/any_duplicated_linter.md) : Require usage of `anyDuplicated(x) > 0` over `any(duplicated(x))` - [`any_is_na_linter`](https://flir.etiennebacher.com/reference/any_is_na_linter.md) : Require usage of `anyNA(x)` over `any(is.na(x))` - [`class_equals_linter`](https://flir.etiennebacher.com/reference/class_equals_linter.md) : Block comparison of class with `==` - [`condition_message_linter`](https://flir.etiennebacher.com/reference/condition_message_linter.md) : Block usage of [`paste()`](https://rdrr.io/r/base/paste.html) and [`paste0()`](https://rdrr.io/r/base/paste.html) with messaging functions using `...` - [`double_assignment_linter`](https://flir.etiennebacher.com/reference/double_assignment_linter.md) : double_assignment - [`duplicate_argument_linter`](https://flir.etiennebacher.com/reference/duplicate_argument_linter.md) : Duplicate argument linter - [`empty_assignment_linter`](https://flir.etiennebacher.com/reference/empty_assignment_linter.md) : empty_assignment - [`equal_assignment_linter`](https://flir.etiennebacher.com/reference/equal_assignment_linter.md) : equal_assignment - [`equals_na_linter`](https://flir.etiennebacher.com/reference/equals_na_linter.md) : Equality check with NA linter - [`expect_comparison_linter`](https://flir.etiennebacher.com/reference/expect_comparison_linter.md) : Require usage of `expect_gt(x, y)` over `expect_true(x > y)` (and similar) - [`expect_identical_linter`](https://flir.etiennebacher.com/reference/expect_identical_linter.md) : Require usage of `expect_identical(x, y)` where appropriate - [`expect_length_linter`](https://flir.etiennebacher.com/reference/expect_length_linter.md) : Require usage of `expect_length(x, n)` over `expect_equal(length(x), n)` - [`expect_named_linter`](https://flir.etiennebacher.com/reference/expect_named_linter.md) : Require usage of `expect_named(x, n)` over `expect_equal(names(x), n)` - [`expect_not_linter`](https://flir.etiennebacher.com/reference/expect_not_linter.md) : Require usage of `expect_false(x)` over `expect_true(!x)` - [`expect_null_linter`](https://flir.etiennebacher.com/reference/expect_null_linter.md) : Require usage of `expect_null` for checking `NULL` - [`expect_s3_class_linter`](https://flir.etiennebacher.com/reference/expect_s3_class_linter.md) : Require usage of `expect_s3_class()` - [`expect_s4_class_linter`](https://flir.etiennebacher.com/reference/expect_s4_class_linter.md) : Require usage of `expect_s4_class(x, k)` over `expect_true(is(x, k))` - [`expect_true_false_linter`](https://flir.etiennebacher.com/reference/expect_true_false_linter.md) : Require usage of `expect_true(x)` over `expect_equal(x, TRUE)` - [`expect_type_linter`](https://flir.etiennebacher.com/reference/expect_type_linter.md) : Require usage of `expect_type(x, type)` over `expect_equal(typeof(x), type)` - [`for_loop_index_linter`](https://flir.etiennebacher.com/reference/for_loop_index_linter.md) : Block usage of for loops directly overwriting the indexing variable - [`function_return_linter`](https://flir.etiennebacher.com/reference/function_return_linter.md) : Lint common mistakes/style issues cropping up from return statements - [`implicit_assignment_linter`](https://flir.etiennebacher.com/reference/implicit_assignment_linter.md) : implicit_assignment - [`is_numeric_linter`](https://flir.etiennebacher.com/reference/is_numeric_linter.md) : Redirect `is.numeric(x) || is.integer(x)` to just use `is.numeric(x)` - [`length_levels_linter`](https://flir.etiennebacher.com/reference/length_levels_linter.md) : Require usage of nlevels over length(levels(.)) - [`length_test_linter`](https://flir.etiennebacher.com/reference/length_test_linter.md) : Check for a common mistake where length is applied in the wrong place - [`lengths_linter`](https://flir.etiennebacher.com/reference/lengths_linter.md) : Require usage of [`lengths()`](https://rdrr.io/r/base/lengths.html) where possible - [`library_call_linter`](https://flir.etiennebacher.com/reference/library_call_linter.md) : Library call linter - [`list_comparison_linter`](https://flir.etiennebacher.com/reference/list_comparison_linter.md) : Block usage of comparison operators with known-list() functions like lapply - [`literal_coercion_linter`](https://flir.etiennebacher.com/reference/literal_coercion_linter.md) : Require usage of correctly-typed literals over literal coercions - [`matrix_apply_linter`](https://flir.etiennebacher.com/reference/matrix_apply_linter.md) : Require usage of `colSums(x)` or `rowSums(x)` over `apply(x, ., sum)` - [`missing_argument_linter`](https://flir.etiennebacher.com/reference/missing_argument_linter.md) : Missing argument linter - [`nested_ifelse_linter`](https://flir.etiennebacher.com/reference/nested_ifelse_linter.md) : Block usage of nested [`ifelse()`](https://rdrr.io/r/base/ifelse.html) calls - [`numeric_leading_zero_linter`](https://flir.etiennebacher.com/reference/numeric_leading_zero_linter.md) : Require usage of a leading zero in all fractional numerics - [`nzchar_linter`](https://flir.etiennebacher.com/reference/nzchar_linter.md) : Require usage of nzchar where appropriate - [`outer_negation_linter`](https://flir.etiennebacher.com/reference/outer_negation_linter.md) : Require usage of `!any(x)` over `all(!x)`, `!all(x)` over `any(!x)` - [`package_hooks_linter`](https://flir.etiennebacher.com/reference/package_hooks_linter.md) : Package hooks linter - [`paste_linter`](https://flir.etiennebacher.com/reference/paste_linter.md) : Raise lints for several common poor usages of [`paste()`](https://rdrr.io/r/base/paste.html) - [`redundant_equals_linter`](https://flir.etiennebacher.com/reference/redundant_equals_linter.md) : Block usage of `==`, `!=` on logical vectors - [`redundant_ifelse_linter`](https://flir.etiennebacher.com/reference/redundant_ifelse_linter.md) : Prevent [`ifelse()`](https://rdrr.io/r/base/ifelse.html) from being used to produce `TRUE`/`FALSE` or `1`/`0` - [`rep_len_linter`](https://flir.etiennebacher.com/reference/rep_len_linter.md) : Require usage of rep_len(x, n) over rep(x, length.out = n) - [`right_assignment_linter`](https://flir.etiennebacher.com/reference/right_assignment_linter.md) : right_assignment - [`sample_int_linter`](https://flir.etiennebacher.com/reference/sample_int_linter.md) : Require usage of sample.int(n, m, ...) over sample(1:n, m, ...) - [`seq_linter`](https://flir.etiennebacher.com/reference/seq_linter.md) : Sequence linter - [`sort_linter`](https://flir.etiennebacher.com/reference/sort_linter.md) : Check for common mistakes around sorting vectors - [`stopifnot_all_linter`](https://flir.etiennebacher.com/reference/stopifnot_all_linter.md) : Block usage of all() within stopifnot() - [`todo_comment_linter`](https://flir.etiennebacher.com/reference/todo_comment_linter.md) : TODO comment linter - [`undesirable_function_linter`](https://flir.etiennebacher.com/reference/undesirable_function_linter.md) : Undesirable function linter - [`undesirable_operator_linter`](https://flir.etiennebacher.com/reference/undesirable_operator_linter.md) : Undesirable operator linter - [`unnecessary_nesting_linter`](https://flir.etiennebacher.com/reference/unnecessary_nesting_linter.md) : Block instances of unnecessary nesting - [`vector_logic_linter`](https://flir.etiennebacher.com/reference/vector_logic_linter.md) : Enforce usage of scalar logical operators in conditional statements - [`which_grepl_linter`](https://flir.etiennebacher.com/reference/which_grepl_linter.md) : Require usage of grep over which(grepl(.)) # Articles ### All vignettes - [Adding new rules](https://flir.etiennebacher.com/articles/adding_rules.md): - [Automatic fixes](https://flir.etiennebacher.com/articles/automatic_fixes.md): - [Sharing rules across packages](https://flir.etiennebacher.com/articles/sharing_rules.md): - [Tips and tricks](https://flir.etiennebacher.com/articles/tips-and-tricks.md):