diff --git a/.Rbuildignore b/.Rbuildignore index ea5f8885..76727e35 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -23,4 +23,7 @@ create_dev_env.R ^\.pre-commit-config\.yaml$ ^data-raw$ .envrc -.direnv \ No newline at end of file +.direnv + +# flint files +^flint$ \ No newline at end of file diff --git a/.github/workflows/flint-code-formatter.yaml b/.github/workflows/flint-code-formatter.yaml new file mode 100644 index 00000000..9af5998e --- /dev/null +++ b/.github/workflows/flint-code-formatter.yaml @@ -0,0 +1,55 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + release: + types: [published] + workflow_dispatch: + +name: flint-code-formatter + +jobs: + flint: + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: rix-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/pr-fetch@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - uses: r-lib/actions/setup-r@v2 + + - name: Install flint + run: install.packages("flint", repos = c("https://etiennebacher.r-universe.dev/", getOption("repos"))) + shell: Rscript {0} + + - name: Run flint + run: flint::lint_package(exclude_path = "inst") + shell: Rscript {0} + + - name: config bot user and check for changes + run: | + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" + git diff-index --quiet HEAD || echo "has_changes=true" >> $GITHUB_ENV + + - name: commit if changes + if: env.has_changes == 'true' + run: | + git add \*.R + git commit -m 'Check and format code with {flint}' + + - uses: r-lib/actions/pr-push@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml deleted file mode 100644 index 974c286e..00000000 --- a/.github/workflows/lint.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples -# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - -name: lint - -permissions: read-all - -jobs: - lint: - runs-on: ubuntu-latest - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@main - - - uses: cachix/cachix-action@v15 - with: - name: rstats-on-nix - # If you chose signing key for write access - # signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' - # If you chose API tokens for write access OR if you have a private cache - authToken: '${{ secrets.CACHIX_AUTH }}' - - - name: Build dev env - run: nix-build - - - name: Run lintr - run: nix-shell --run "Rscript -e 'lintr::lint_package()'" - env: - LINTR_ERROR_ON_LINT: false diff --git a/.github/workflows/run_rhub.yaml b/.github/workflows/run_rhub.yaml index bbcf973f..266e6700 100644 --- a/.github/workflows/run_rhub.yaml +++ b/.github/workflows/run_rhub.yaml @@ -2,8 +2,6 @@ on: push: branches: [main, master] - pull_request: - branches: [main, master] name: run-rhub-checks @@ -28,6 +26,8 @@ jobs: # signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' # If you chose API tokens for write access OR if you have a private cache authToken: '${{ secrets.CACHIX_AUTH }}' - + - name: Run checks - run: nix-shell --run "Rscript -e \"rhub::rhub_check(platforms = c('linux','macos','macos-arm64','windows','ubuntu-next','ubuntu-release'), branch = 'main')\"" + run: | + nix-shell --run "Rscript -e \"rhub::rhub_check(platforms = c('linux','macos','macos-arm64','windows','ubuntu-next','ubuntu-release'))\"" + diff --git a/.github/workflows/styler.yaml b/.github/workflows/style-and-lint.yaml similarity index 53% rename from .github/workflows/styler.yaml rename to .github/workflows/style-and-lint.yaml index 5b46d29d..2fb82d0b 100644 --- a/.github/workflows/styler.yaml +++ b/.github/workflows/style-and-lint.yaml @@ -6,7 +6,7 @@ on: pull_request: branches: [main, master] -name: styler +name: style-and-lint permissions: write-all @@ -20,6 +20,10 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: r-lib/actions/pr-fetch@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install Nix uses: DeterminateSystems/nix-installer-action@main @@ -33,31 +37,23 @@ jobs: - name: Run styler::style_pkg run: nix-shell --run "Rscript -e 'styler::style_pkg()'" - - name: Check if PR exists - id: check_pr - run: | - PR=$(gh pr list -S 'Style package' --json number --jq '.[0].number') - echo "PR_NUMBER=$PR" >> $GITHUB_ENV + - name: Run lintr + run: nix-shell --run "Rscript -e 'lintr::lint_package()'" + env: + LINTR_ERROR_ON_LINT: true - - name: Configure user and check for changes + - name: config bot user and check for changes run: | - git config --global user.email "ga-ci@no-reply.com" - git config --global user.name "CI Robot" + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" git diff-index --quiet HEAD || echo "has_changes=true" >> $GITHUB_ENV - - - name: Commit and push changes + + - name: commit if changes if: env.has_changes == 'true' run: | - git add . - git commit -m "Styled package" - git push origin main:style_pkg --force + git add \*.R + git commit -m 'Style via {styler}' - - name: Create Pull Request - if: env.PR_NUMBER == '' - uses: peter-evans/create-pull-request@v6 + - uses: r-lib/actions/pr-push@v2 with: - branch: style_pkg - title: 'Style package' - body: 'Automated PR to style package using `styler:style_pkg()`' - base: main - branch-suffix: '' + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 630f4210..0b98b66d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .httr-oauth inst/doc docs +flint/cache_file_state.rds result /doc/ /Meta/ diff --git a/.lintr b/.lintr index 1375c994..3c29bc69 100644 --- a/.lintr +++ b/.lintr @@ -1,5 +1,11 @@ linters: linters_with_defaults( - line_length_linter(100), - commented_code_linter = NULL + line_length_linter = line_length_linter(100), + commented_code_linter = NULL, + object_usage_linter = NULL, + cyclocomp_linter = NULL + ) +exclusions: list( + "tests/testthat/test-fetchers.R", + "vignettes/" ) encoding: "UTF-8" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..5322d988 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +# All available hooks: https://pre-commit.com/hooks.html +# R specific hooks: https://github.com/lorenzwalthert/precommit +repos: +- repo: https://github.com/lorenzwalthert/precommit + rev: v0.4.3.9001 + hooks: + - id: style-files + args: [--style_pkg=styler, --style_fun=tidyverse_style, --cache-root=styler-perm] + require_serial: true + - id: lintr + exclude: > + (?x)^( + tests/testthat/test-fetchers\.R| + vignettes/.* + )$ + - id: parsable-R + exclude: > + (?x)^( + tests/testthat/in/.*| + )$ + - id: no-browser-statement + +default_stages: ["commit"] + +ci: + # skip: [consistent-release-tag, spell-check-ordered-exclude, pkgdown] + autoupdate_schedule: monthly diff --git a/R/detect_os.R b/R/detect_os.R index ca98e349..d6adcb32 100644 --- a/R/detect_os.R +++ b/R/detect_os.R @@ -21,7 +21,10 @@ detect_os <- function() { #' @noRd generate_locale_archive <- function(os) { if (os == "Linux" || os == "Darwin") { - 'LOCALE_ARCHIVE = if pkgs.system == \"x86_64-linux\" then \"${pkgs.glibcLocales}/lib/locale/locale-archive\" else \"\";' + paste0( + 'LOCALE_ARCHIVE = if pkgs.system == \"x86_64-linux\" then ', + '\"${pkgs.glibcLocales}/lib/locale/locale-archive\" else \"\";' + ) } else { stop("Operating System unsupported") } diff --git a/R/fetchers.R b/R/fetchers.R index 6917756f..06e040a2 100644 --- a/R/fetchers.R +++ b/R/fetchers.R @@ -96,7 +96,11 @@ fetchzip <- function(archive_pkg, sri_hash = NULL) { #' @noRd remove_base <- function(list_imports) { imports_nobase <- gsub( - "(^base$)|(^compiler$)|(^datasets$)|(^grDevices$)|(^graphics$)|(^grid$)|(^methods$)|(^parallel$)|(^profile$)|(^splines$)|(^stats$)|(^stats4$)|(^tcltk$)|(^tools$)|(^translations$)|(^utils$)", + paste0( + "(^base$)|(^compiler$)|(^datasets$)|(^grDevices$)|(^graphics$)|(^grid$)|", + "(^methods$)|(^parallel$)|(^profile$)|(^splines$)|(^stats$)|", + "(^stats4$)|(^tcltk$)|(^tools$)|(^translations$)|(^utils$)" + ), NA_character_, list_imports ) @@ -233,7 +237,14 @@ fetchgits <- function(git_pkgs) { paste(lapply(git_pkgs, fetchgit), collapse = "\n") } else { - stop("There is something wrong with the input. Make sure it is either a list of three elements 'package_name', 'repo_url' and 'commit' or a list of lists with these three elements") + stop( + paste0( + "There is something wrong with the input. ", + "Make sure it is either a list of three elements ", + "'package_name', 'repo_url' and 'commit', or ", + "a list of lists with these three elements" + ) + ) } } diff --git a/R/get_latest.R b/R/get_latest.R index e396978d..2edb2c49 100644 --- a/R/get_latest.R +++ b/R/get_latest.R @@ -1,5 +1,6 @@ #' Get the latest R version and packages -#' @param r_version Character. R version to look for, for example, "4.2.0". If a nixpkgs revision is provided instead, this gets returned. +#' @param r_version Character. R version to look for, for example, "4.2.0". If a +#' nixpkgs revision is provided instead, this gets returned. #' @return A character. The commit hash of the latest nixpkgs-unstable revision #' @importFrom curl new_handle curl_fetch_memory handle_reset #' @importFrom jsonlite fromJSON @@ -18,7 +19,13 @@ get_latest <- function(r_version) { } else if ( !(r_version %in% c("bleeding_edge", "frozen_edge", available_r())) ) { - stop("The provided R version is likely wrong.\nPlease check that you provided a correct R version.\nYou can list available versions using `available_r()`.\nYou can also directly provide a commit, but you need to make sure it points to the right repo used by `rix()`.\nYou can also use 'bleeding_edge' and 'frozen_edge'.") + stop( + "The provided R version is likely wrong.\nPlease check that you ", + "provided a correct R version.\nYou can list available versions using ", + "`available_r()`.\nYou can also directly provide a commit, but you need ", + "to make sure it points to the right repo used by `rix()`.\nYou can ", + "also use 'bleeding_edge' and 'frozen_edge'." + ) } else if (!is_online) { stop("ERROR! You don't seem to be connected to the internet.") } else if (r_version == "bleeding_edge") { @@ -33,11 +40,14 @@ get_latest <- function(r_version) { #' @noRd get_right_commit <- function(r_version) { if (r_version == "frozen_edge") { + # nolint next: line_length_linter api_url <- "https://api.github.com/repos/rstats-on-nix/nixpkgs/commits?sha=r-daily" - } else if (r_version %in% Filter(function(x) `!=`(x, "latest"), available_r())) { # all but latest - + } else if ( + r_version %in% Filter(function(x) `!=`(x, "latest"), available_r()) + ) { # all but latest return(sysdata$revision[sysdata$version == r_version]) } else { + # nolint next: line_length_linter api_url <- "https://api.github.com/repos/NixOS/nixpkgs/commits?sha=nixpkgs-unstable" } diff --git a/R/make_nixpkgs_url.R b/R/make_nixpkgs_url.R index a41dd6ee..9bba1887 100644 --- a/R/make_nixpkgs_url.R +++ b/R/make_nixpkgs_url.R @@ -1,5 +1,6 @@ #' make_nixpkgs_url Find the right Nix revision -#' @param r_version Character. R version to look for, for example, "4.2.0". If a nixpkgs revision is provided instead, this gets returned. +#' @param r_version Character. R version to look for, for example, "4.2.0". If a +#' nixpkgs revision is provided instead, this gets returned. #' @return A character. The url to use #' #' @examples @@ -15,7 +16,9 @@ make_nixpkgs_url <- function(r_ver) { latest_commit <- get_latest(r_ver) list( - "url" = paste0("https://github.com/", github_repo, "archive/", latest_commit, ".tar.gz"), + "url" = paste0( + "https://github.com/", github_repo, "archive/", latest_commit, ".tar.gz" + ), "latest_commit" = latest_commit, "r_ver" = r_ver ) diff --git a/R/nix_build.R b/R/nix_build.R index 51ae9347..3c24c9e2 100644 --- a/R/nix_build.R +++ b/R/nix_build.R @@ -28,18 +28,19 @@ nix_build <- function(project_path = ".", choices = c("simple", "quiet", "verbose") ) # if nix store is not PATH variable; e.g. on macOS (system's) RStudio - PATH <- set_nix_path() + PATH <- set_nix_path() # nolint: object_name_linter if (isTRUE(nzchar(Sys.getenv("NIX_STORE")))) { # for Nix R sessions, guarantee that the system's user library # (R_LIBS_USER) is not in the search path for packages => run-time purity current_libpaths <- .libPaths() # don't do this in covr test environment, because this sets R_LIBS_USER # to multiple paths - R_LIBS_USER <- Sys.getenv("R_LIBS_USER") + R_LIBS_USER <- Sys.getenv("R_LIBS_USER") # nolint: object_name_linter if (isFALSE(nzchar(Sys.getenv("R_COVR")))) { remove_r_libs_user() } } else { + # nolint next: object_name_linter LD_LIBRARY_PATH_default <- Sys.getenv("LD_LIBRARY_PATH") if (nzchar(LD_LIBRARY_PATH_default)) { # On some systems, like Ubuntu 22.04, we found that a preset @@ -56,7 +57,7 @@ nix_build <- function(project_path = ".", fix_ld_library_path() cat( "* Current LD_LIBRARY_PATH in system R session is:", - LD_LIBRARY_PATH_default + LD_LIBRARY_PATH_default # nolint: object_name_linter ) cat("\n", "Setting `LD_LIBRARY_PATH` to `''` during `nix_build()`") } @@ -65,6 +66,7 @@ nix_build <- function(project_path = ".", nix_dir <- normalizePath(project_path) nix_file <- file.path(nix_dir, "default.nix") + # nolint start: line_length_linter stopifnot( "`project_path` must be character of length 1." = is.character(project_path) && length(project_path) == 1L, @@ -73,6 +75,7 @@ nix_build <- function(project_path = ".", "`nix-build` not available. To install, we suggest you follow https://zero-to-nix.com/start/install ." = isTRUE(has_nix_build) ) + # nolint end max_jobs <- getOption("rix.nix_build_max_jobs", default = 1L) stopifnot( diff --git a/R/nix_hash.R b/R/nix_hash.R index 53fe5929..c48568f2 100644 --- a/R/nix_hash.R +++ b/R/nix_hash.R @@ -104,16 +104,21 @@ nix_sri_hash <- function(path) { # not needed for Nix R sessions, workaround on Debian and Debian-based # systems with nix installed + # nolint start: object_name_linter LD_LIBRARY_PATH_default <- Sys.getenv("LD_LIBRARY_PATH") needs_ld_fix <- isFALSE(nzchar(Sys.getenv("NIX_STORE"))) && nzchar(LD_LIBRARY_PATH_default) + # nolint end if (isTRUE(needs_ld_fix)) { # On Debian and Debian-based systems, like Ubuntu 22.04, we found that a # preset `LD_LIBRARY_PATH` environment variable in the system's R session # leads to errors like - # nix-hash: /usr/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by nix-hash) - # nix-hash: /usr/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by /nix/store/4z754a0vzl98asv0pa95i5d9szw5jqbs-lowdown-1.0.2-lib/lib/liblowdown.so.3) + # nix-hash: /usr/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' + # not found (required by nix-hash) + # nix-hash: /usr/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' + # not found (required by # nolint next: line_length_linter + # /nix/store/4z754a0vzl98asv0pa95i5d9szw5jqbs-lowdown-1.0.2-lib/lib/liblowdown.so.3) # etc... # for both `nix-hash`; it occurs via # `sys::exec_internal`, `base::system()` or `base::system2()` from R. diff --git a/R/rix.R b/R/rix.R index 9ab8ba1b..16f929dc 100644 --- a/R/rix.R +++ b/R/rix.R @@ -19,7 +19,7 @@ #' @param system_pkgs Vector of characters. List further software you wish to #' install that are not R packages such as command line applications for #' example. You can look for available software on the NixOS website -#' \url{https://search.nixos.org/packages?channel=unstable&from=0&size=50&sort=relevance&type=packages&query=} +#' \url{https://search.nixos.org/packages?channel=unstable&from=0&size=50&sort=relevance&type=packages&query=} # nolint #' @param git_pkgs List. A list of packages to install from Git. See details for #' more information. #' @param local_r_pkgs List. A list of local packages to install. These packages @@ -129,7 +129,8 @@ #' environment will be up-to-date on the date that the `default.nix` will be #' generated, and then each subsequent call to `nix-build` will result in the #' same environment. We highly recommend you read the vignette titled -#' "z - Advanced topic: Understanding the rPackages set release cycle and using bleeding edge packages". +#' "z - Advanced topic: Understanding the rPackages set release cycle and +#' using bleeding edge packages". #' @export #' @examples #' \dontrun{ @@ -165,7 +166,10 @@ rix <- function(r_ver = "latest", choices = c("quiet", "simple", "verbose") ) - if (!(message_type %in% c("simple", "quiet")) && r_ver %in% c("bleeding_edge", "frozen_edge")) { + if ( + !(message_type %in% c("simple", "quiet")) && + r_ver %in% c("bleeding_edge", "frozen_edge") + ) { warning( "You chose 'bleeding_edge' or 'frozen_edge' as the value for `r_ver`. Please read the vignette @@ -174,8 +178,10 @@ before continuing." ) } - if (message_type != "quiet" && r_ver %in% available_r() && - r_ver != "latest" && r_ver <= "4.1.1") { + if ( + message_type != "quiet" && r_ver %in% available_r() && + r_ver != "latest" && r_ver <= "4.1.1" + ) { warning( "You are generating an expression for an older version of R.\n", "To use this environment, you should directly use `nix-shell` and not ", @@ -195,8 +201,10 @@ before continuing." ide <- match.arg(ide, c("other", "code", "radian", "rstudio", "rserver")) - if (identical(ide, "rstudio") && is.null(r_pkgs) && is.null(git_pkgs) && - is.null(local_r_pkgs)) { + if ( + identical(ide, "rstudio") && is.null(r_pkgs) && is.null(git_pkgs) && + is.null(local_r_pkgs) + ) { stop( paste0( "You chose 'rstudio' as the IDE, but didn't add any R packages", @@ -213,7 +221,10 @@ before continuing." rserver = "rstudioServerWrapper" ) - if (message_type != "quiet" && Sys.info()["sysname"] == "Darwin" && ide == "rstudio") { + if ( + message_type != "quiet" && Sys.info()["sysname"] == "Darwin" && + ide == "rstudio" + ) { warning( "Your detected operating system is macOS, and you chose 'rstudio' as the IDE. Please note that 'rstudio' is not @@ -231,8 +242,10 @@ for more details." project_path <- normalizePath(path = project_path) } + # nolint start: object_name_linter default.nix_path <- file.path(project_path, "default.nix") .Rprofile_path <- file.path(project_path, ".Rprofile") + # nolint end # Find url to use # In case of bleeding or frozen edge, the rstats-on-nix/nixpkgs @@ -246,7 +259,9 @@ for more details." cran_pkgs <- get_rpkgs(r_pkgs, ide) # If there are R packages, passes the string "rpkgs" to buildInputs - flag_rpkgs <- if (is.null(cran_pkgs$rPackages) | cran_pkgs$rPackages == "") { + flag_rpkgs <- if ( + is.null(cran_pkgs$rPackages) || cran_pkgs$rPackages == "" + ) { "" } else { "rpkgs" @@ -260,7 +275,9 @@ for more details." } # If there are R packages from Git, passes the string "git_archive_pkgs" to buildInputs - flag_git_archive <- if (!is.null(cran_pkgs$archive_pkgs) | !is.null(git_pkgs)) { + flag_git_archive <- if ( + !is.null(cran_pkgs$archive_pkgs) || !is.null(git_pkgs) + ) { "git_archive_pkgs" } else { "" @@ -284,7 +301,7 @@ for more details." "" } - # Generate default.nix file + # Generate default.nix file # nolint next: object_name_linter default.nix <- paste( generate_header( nix_repo, @@ -304,6 +321,7 @@ for more details." collapse = "\n" ) + # nolint next: object_name_linter default.nix <- readLines(textConnection(default.nix)) if (print) { @@ -318,7 +336,7 @@ for more details." writeLines(default.nix, default.nix_path) if (file.exists(.Rprofile_path)) { - if (all(!grepl( + if (!any(grepl( "File generated by `rix::rix_init()", readLines(.Rprofile_path) ))) { diff --git a/R/rix_helpers.R b/R/rix_helpers.R index d21708ea..0d0409dc 100644 --- a/R/rix_helpers.R +++ b/R/rix_helpers.R @@ -1,6 +1,9 @@ -#' generate_header Internal function used to generate the header of the `default.nix` file. -#' @param nix_repo Character. nixpkgs reop to use (upstream or rstats-on-nix fork) with latest commit hash. -#' @param r_version Character. R version to look for, for example, "4.2.0". If a nixpkgs revision is provided instead, this gets returned. +#' generate_header Internal function used to generate the header of the +#' `default.nix` file. +#' @param nix_repo Character. nixpkgs reop to use (upstream or rstats-on-nix +#' fork) with latest commit hash. +#' @param r_version Character. R version to look for, for example, "4.2.0". If a +#' nixpkgs revision is provided instead, this gets returned. #' @param rix_call Character, call to rix(). #' @noRd generate_header <- function(nix_repo, @@ -70,9 +73,11 @@ let } } -#' generate_rix_call Internal function used to generate the call to `rix()` as shown in `default.nix` +#' generate_rix_call Internal function used to generate the call to `rix()` as +#' shown in `default.nix` #' @param rix_call Character, call to rix(). -#' @param nix_repo Character. nixpkgs reop to use (upstream or rstats-on-nix fork) with latest commit hash. +#' @param nix_repo Character. nixpkgs reop to use (upstream or rstats-on-nix +#' fork) with latest commit hash. #' @noRd generate_rix_call <- function(rix_call, nix_repo) { if (grepl("NixOS", nix_repo$url)) { @@ -110,9 +115,11 @@ get_rpkgs <- function(r_pkgs, ide) { r_pkgs <- sort(r_pkgs) + # nolint start: object_name_linter rPackages <- paste(c("", r_pkgs), collapse = "\n ") rPackages <- gsub("\\.", "_", rPackages) + # nolint end list( "rPackages" = rPackages, @@ -120,10 +127,12 @@ get_rpkgs <- function(r_pkgs, ide) { ) } -#' generate_rpkgs Internal function that generates the string containing the correct Nix expression to get R packages. +#' generate_rpkgs Internal function that generates the string containing the +#' correct Nix expression to get R packages. #' @param rPackages Character, list of R packages to install. #' @param flag_rpkgs Character, are there any R packages at all? #' @noRd +#' # nolint start: object_name_linter generate_rpkgs <- function(rPackages, flag_rpkgs) { if (flag_rpkgs == "") { NULL @@ -138,8 +147,10 @@ generate_rpkgs <- function(rPackages, flag_rpkgs) { ) } } +# nolint end: object_name_linter -#' generate_local_r_pkgs Internal function that generates the string containing the correct Nix expression for installing local packages +#' generate_local_r_pkgs Internal function that generates the string containing +#' the correct Nix expression for installing local packages #' @param local_r_pkgs Character, list of local R packages to install. #' @param flag_local_r_pkgs Character, are there any local R packages at all? #' @noRd @@ -158,7 +169,8 @@ generate_local_r_pkgs <- function(local_r_pkgs, flag_local_r_pkgs) { } } -#' generate_tex_pkgs Internal function that generates the string containing the correct Nix expression to get LaTeX packages. +#' generate_tex_pkgs Internal function that generates the string containing the +#' correct Nix expression to get LaTeX packages. #' @param tex_pkgs Character, list of LaTeX packages to install. #' @noRd generate_tex_pkgs <- function(tex_pkgs) { @@ -178,7 +190,8 @@ generate_tex_pkgs <- function(tex_pkgs) { } } -#' generate_system_pkgs Internal function that formats the system package names correctly for Nix. +#' generate_system_pkgs Internal function that formats the system package names +#' correctly for Nix. #' @param system_pkgs Character, list of LaTeX packages to install. #' @param r_pkgs Character, list of LaTeX packages to install. #' @noRd @@ -187,7 +200,8 @@ get_system_pkgs <- function(system_pkgs, r_pkgs) { system_pkgs <- c(system_pkgs, "R", "glibcLocales", "nix") |> sort() - # If the user wants the R {quarto} package, then the quarto software needs to be installed + # If the user wants the R {quarto} package, then the quarto software needs to + # be installed system_pkgs <- if (any(grepl("quarto", r_pkgs))) { unique(c(system_pkgs, "quarto")) } else { @@ -197,7 +211,8 @@ get_system_pkgs <- function(system_pkgs, r_pkgs) { } -#' generate_system_pkgs Internal function that generates the string containing the correct Nix expression to get system packages. +#' generate_system_pkgs Internal function that generates the string containing +#' the correct Nix expression to get system packages. #' @param system_pkgs Character, list of LaTeX packages to install. #' @param r_pkgs Character, list of LaTeX packages to install. #' @noRd @@ -213,12 +228,16 @@ generate_system_pkgs <- function(system_pkgs, r_pkgs) { } -#' generate_git_archived_pkgs Internal function that generates the string containing the correct Nix expression to get system packages. +#' generate_git_archived_pkgs Internal function that generates the string +#' containing the correct Nix expression to get system packages. #' @param git_pkgs Character, list of R packages to install from Github. -#' @param archive_pkgs Character, list of R packages to install from the CRAN archives. +#' @param archive_pkgs Character, list of R packages to install from the CRAN +#' archives. #' @param flag_git_archive Character, are there R packages from Github at all? #' @noRd -generate_git_archived_pkgs <- function(git_pkgs, archive_pkgs, flag_git_archive) { +generate_git_archived_pkgs <- function(git_pkgs, + archive_pkgs, + flag_git_archive) { if (flag_git_archive == "") { NULL } else { @@ -228,7 +247,8 @@ generate_git_archived_pkgs <- function(git_pkgs, archive_pkgs, flag_git_archive) } -#' generate_locale_variables Internal function that generates the string containing the correct Nix expression to set locales. +#' generate_locale_variables Internal function that generates the string +#' containing the correct Nix expression to set locales. #' @noRd generate_locale_variables <- function() { locale_defaults <- list( @@ -266,7 +286,8 @@ generate_locale_variables <- function() { } -#' generate_wrapped_pkgs Internal function that generates the string containing the correct Nix expression to get wrapped packages. +#' generate_wrapped_pkgs Internal function that generates the string containing +#' the correct Nix expression to get wrapped packages. #' @param ide Character, defaults to "other". If you wish to use RStudio to work #' interactively use "rstudio" or "rserver" for the server version. Use "code" #' for Visual Studio Code. You can also use "radian", an interactive REPL. For @@ -302,7 +323,8 @@ generate_wrapped_pkgs <- function(ide, } -#' generate_wrapped_pkgs Internal function that generates the string containing the correct Nix expression to get wrapped packages. +#' generate_wrapped_pkgs Internal function that generates the string containing +#' the correct Nix expression to get wrapped packages. #' @param flag_git_archive Character, are there R packages from Github at all? #' @param flag_rpkgs Character, are there any R packages at all? #' @param flag_tex_pkgs Character, are there any LaTex packages at all? diff --git a/R/rix_init.R b/R/rix_init.R index 97bee94a..7f84f776 100644 --- a/R/rix_init.R +++ b/R/rix_init.R @@ -1,13 +1,13 @@ #' Initiate and maintain an isolated, project-specific, and runtime-pure R #' setup via Nix. #' -#' Creates an isolated project folder for a Nix-R configuration. `rix::rix_init()` -#' also adds, appends, or updates with or without backup a custom `.Rprofile` -#' file with code that initializes a startup R environment without system's user -#' libraries within a Nix software environment. Instead, it restricts search -#' paths to load R packages exclusively from the Nix store. Additionally, it -#' makes Nix utilities like `nix-shell` available to run system commands from -#' the system's RStudio R session, for both Linux and macOS. +#' Creates an isolated project folder for a Nix-R configuration. +#' `rix::rix_init()` also adds, appends, or updates with or without backup a +#' custom `.Rprofile` file with code that initializes a startup R environment +#' without system's user libraries within a Nix software environment. Instead, +#' it restricts search paths to load R packages exclusively from the Nix store. +#' Additionally, it makes Nix utilities like `nix-shell` available to run system +#' commands from the system's RStudio R session, for both Linux and macOS. #' #' **Enhancement of computational reproducibility for Nix-R environments:** #' @@ -75,12 +75,14 @@ #' does exist; `"append"` appends the existing file with code that is tailored #' to an isolated Nix-R project setup. #' @param message_type Character. Message type, defaults to `"simple"`, which -#' gives minimal but sufficient feedback. Other values are currently -#' `"quiet`, which writes `.Rprofile` without message, and `"verbose"`, -#' which displays the mechanisms implemented to achieve fully controlled R project environments in Nix. +#' gives minimal but sufficient feedback. Other values are currently `"quiet`, +#' which writes `.Rprofile` without message, and `"verbose"`, which displays +#' the mechanisms implemented to achieve fully controlled R project +#' environments in Nix. #' @export #' @seealso [with_nix()] -#' @return Nothing, this function only has the side-effect of writing a file called ".Rprofile" to the specified path. +#' @return Nothing, this function only has the side-effect of writing a file +#' called ".Rprofile" to the specified path. #' @examples #' \dontrun{ #' # create an isolated, runtime-pure R setup via Nix @@ -221,7 +223,9 @@ rix_init <- function(project_path = ".", cat(readLines(con = file(rprofile_file)), sep = "\n") } - on.exit(close(file(rprofile_file))) + on.exit({ + close(file(rprofile_file)) + }) } #' Get character vector of length two with comment and code write `.Rprofile` @@ -272,6 +276,7 @@ message_rprofile <- function(action_string = "Added", #' @return Character vector that lists `PATH` entries after modification, which #' are separated by `":"`. #' @noRd +# nolint start: object_name_linter set_message_session_PATH <- function(message_type = c("simple", "quiet", "verbose")) { message_type <- match.arg(message_type, @@ -293,6 +298,7 @@ set_message_session_PATH <- function(message_type = cat("\n\n* Updated `PATH` variable is:\n\n", PATH) } } +# nolint end: object_name_linter #' Report whether the current R session is running in Nix and RStudio, or not. @@ -392,6 +398,7 @@ set_nix_path <- function() { #' @return language object with parsed expression #' @noRd nix_rprofile <- function() { + # nolint start: object_name_linter quote({ is_rstudio <- Sys.getenv("RSTUDIO") == "1" is_nix_r <- nzchar(Sys.getenv("NIX_STORE")) @@ -417,19 +424,38 @@ nix_rprofile <- function() { if (isTRUE(is_nix_r)) { install.packages <- function(...) { - stop("You are currently in an R session running from Nix.\nDon't install packages using install.packages(),\nadd them to the default.nix file instead.") + stop( + "You are currently in an R session running from Nix.\n", + "Don't install packages using install.packages(),\nadd them to ", + "the default.nix file instead." + ) } update.packages <- function(...) { - stop("You are currently in an R session running from Nix.\nDon't update packages using update.packages(),\ngenerate a new default.nix with a more recent version of R. If you need bleeding edge packages, read the 'Understanding the rPackages set release cycle and using bleeding edge packages' vignette.") + stop( + "You are currently in an R session running from Nix.\n", + "Don't update packages using update.packages(),\n", + "generate a new default.nix with a more recent version of R. ", + "If you need bleeding edge packages, read the", + "'Understanding the rPackages set release cycle and using ", + "bleeding edge packages' vignette." + ) } remove.packages <- function(...) { - stop("You are currently in an R session running from Nix.\nDon't remove packages using remove.packages(),\ndelete them from the default.nix file instead.") + stop( + "You are currently in an R session running from Nix.\n", + "Don't remove packages using `remove.packages()``,\ndelete them ", + "from the default.nix file instead." + ) } current_paths <- .libPaths() userlib_paths <- Sys.getenv("R_LIBS_USER") - user_dir <- grep(paste(userlib_paths, collapse = "|"), current_paths, fixed = TRUE) + user_dir <- grep( + paste(userlib_paths, collapse = "|"), + current_paths, + fixed = TRUE + ) new_paths <- current_paths[-user_dir] # sets new library path without user library, making nix-R pure at # run-time @@ -439,4 +465,5 @@ nix_rprofile <- function() { rm(is_rstudio, is_nix_r) }) + # nolint end: object_name } diff --git a/R/with_nix.R b/R/with_nix.R index c6a93cd2..a275c362 100644 --- a/R/with_nix.R +++ b/R/with_nix.R @@ -134,6 +134,7 @@ with_nix <- function(expr, project_path = ".", message_type = c("simple", "quiet", "verbose")) { nix_file <- file.path(project_path, "default.nix") + # nolint start: line_length_linter stopifnot( "`project_path` must be character of length 1." = is.character(project_path) && length(project_path) == 1L, @@ -143,6 +144,7 @@ with_nix <- function(expr, "`expr` needs to be a call or function for `program = R`, and character of length 1 for `program = shell`" = is.function(expr) || is.call(expr) || (is.character(expr) && length(expr) == 1L) ) + # nolint end program <- match.arg(program, choices = c("R", "shell")) message_type <- match.arg(message_type, @@ -170,11 +172,12 @@ with_nix <- function(expr, current_libpaths <- .libPaths() # don't do this in covr test environment, because this sets R_LIBS_USER # to multiple paths - R_LIBS_USER <- Sys.getenv("R_LIBS_USER") + R_LIBS_USER <- Sys.getenv("R_LIBS_USER") # nolint: object_name_linter if (isFALSE(nzchar(Sys.getenv("R_COVR")))) { remove_r_libs_user() } } else { + # nolint start: object_name_linter LD_LIBRARY_PATH_default <- Sys.getenv("LD_LIBRARY_PATH") if (nzchar(LD_LIBRARY_PATH_default)) { # On some systems, like Ubuntu 22.04, we found that a preset @@ -195,6 +198,7 @@ with_nix <- function(expr, ) cat("\n", "Setting `LD_LIBRARY_PATH` to `''` during `nix_build()`") } + # nolint end } has_nix_shell <- nix_shell_available() # TRUE if yes, FALSE if no @@ -322,7 +326,9 @@ with_nix <- function(expr, if (nzchar(LD_LIBRARY_PATH_default)) { # set old LD_LIBRARY_PATH (only if system's R session and if it wasn't # `""`) - on.exit(Sys.setenv(LD_LIBRARY_PATH = LD_LIBRARY_PATH_default)) + on.exit({ + Sys.setenv(LD_LIBRARY_PATH = LD_LIBRARY_PATH_default) + }) } } diff --git a/R/with_nix_helpers.R b/R/with_nix_helpers.R index 4f8051fd..37c4e18f 100644 --- a/R/with_nix_helpers.R +++ b/R/with_nix_helpers.R @@ -47,7 +47,7 @@ serialize_lobjs <- function(lobjs, temp_dir) { # for unnamed arguments like `expr = function(x) print(x)` # x would be an empty symbol, see also ; i.e. arguments without # default expressions; i.e. tagged arguments with no value - # https://stackoverflow.com/questions/3892580/create-missing-objects-aka-empty-symbols-empty-objects-needed-for-f + # https://stackoverflow.com/questions/3892580/create-missing-objects-aka-empty-symbols-empty-objects-needed-for-f # nolint lobjs[[i]] <- as.symbol(names(lobjs)[i]) } saveRDS( @@ -73,6 +73,7 @@ serialize_args <- function(args, temp_dir) { # for unnamed arguments like `expr = function(x) print(x)` # x would be an empty symbol, see also ; i.e. arguments without # default expressions; i.e., tagged arguments with no value + # nolint next: line_length_linter # https://stackoverflow.com/questions/3892580/create-missing-objects-aka-empty-symbols-empty-objects-needed-for-f args[[i]] <- as.symbol(names(args)[i]) } @@ -280,8 +281,11 @@ classify_globals <- function(globals_expr, args_vec) { if (length(globs_empty) == 0L) { globs_empty <- NULL } - globs_other <- vec_envs_check[!names(vec_envs_check) %in% - names(c(globs_pkg, globs_globalenv, globs_empty, globs_base))] + globs_other <- vec_envs_check[ + !names(vec_envs_check) %in% names( + c(globs_pkg, globs_globalenv, globs_empty, globs_base) + ) + ] if (length(globs_other) == 0L) { globs_other <- NULL } @@ -612,8 +616,8 @@ with_assign_vec_call <- function(vec) { #' @return representation of `expr` as character vector of length 1 #' @author R Core Team #' @noRd -deparse_chr1 <- function(expr, width.cutoff = 500L, collapse = " ", ...) { - paste(deparse(expr, width.cutoff, ...), collapse = collapse) +deparse_chr1 <- function(expr, width_cutoff = 500L, collapse = " ", ...) { + paste(deparse(expr, width_cutoff, ...), collapse = collapse) } diff --git a/create_dev_env.R b/create_dev_env.R index 7562fe5b..5ab7075e 100644 --- a/create_dev_env.R +++ b/create_dev_env.R @@ -1,13 +1,17 @@ library(rix) -rix(r_ver = "bleeding_edge", - r_pkgs = c("devtools", "diffviewer", "fledge", "lintr", "styler", - "codetools", "jsonlite", "httr", "sys", "testthat", "knitr", - "rmarkdown", "rhub"), - system_pkgs = c("R", "glibcLocalesUtf8", "pandoc", "nix"), - tex_pkgs = "scheme-small", - ide = "other", - project_path = ".", - overwrite = TRUE, - print = FALSE, - shell_hook = NULL) +rix( + r_ver = "bleeding_edge", + r_pkgs = c( + "devtools", "diffviewer", "fledge", "lintr", "styler", + "codetools", "jsonlite", "httr", "sys", "testthat", "knitr", + "rmarkdown", "rhub", "docopt", "precommit" + ), + system_pkgs = c("R", "glibcLocalesUtf8", "pandoc", "nix"), + tex_pkgs = "scheme-small", + ide = "other", + project_path = ".", + overwrite = TRUE, + print = FALSE, + shell_hook = NULL +) diff --git a/data-raw/DATASET.R b/data-raw/DATASET.R index 0a3589ea..78f79c0e 100644 --- a/data-raw/DATASET.R +++ b/data-raw/DATASET.R @@ -1,6 +1,5 @@ ## code to prepare `DATASET` dataset goes here - library(rix) # This script is only needed for the developers of `{rix}`. @@ -16,8 +15,11 @@ library(rix) # library(rvest) # library(dplyr) # library(janitor) -# -# r_nix_revs <- read_html("https://lazamar.co.uk/nix-versions/?channel=nixpkgs-unstable&package=r") |> + +# r_nix_revs <- +# read_html( +# "https://lazamar.co.uk/nix-versions/?channel=nixpkgs-unstable&package=r" +# ) |> # html_element("table") |> # html_table() |> # clean_names() |> diff --git a/default.nix b/default.nix index af0a64a8..c91e11dc 100644 --- a/default.nix +++ b/default.nix @@ -1,4 +1,4 @@ -# This file was generated by the {rix} R package v0.7.1 on 2024-06-28 +# This file was generated by the {rix} R package v0.11.0 on 2024-09-16 # with following call: # >rix(r_ver = "bleeding_edge", # > r_pkgs = c("devtools", @@ -13,6 +13,8 @@ # > "testthat", # > "knitr", # > "rmarkdown", +# > "rhub", +# > "docopt", # > "precommit"), # > system_pkgs = c("R", # > "glibcLocalesUtf8", @@ -34,19 +36,21 @@ let rpkgs = builtins.attrValues { inherit (pkgs.rPackages) + codetools devtools diffviewer + docopt fledge - lintr - styler - codetools - jsonlite httr - sys - testthat + jsonlite knitr + lintr + precommit + rhub rmarkdown - rhub; + styler + sys + testthat; }; tex = (pkgs.texlive.combine { @@ -56,11 +60,11 @@ let system_packages = builtins.attrValues { inherit (pkgs) - R + glibcLocales glibcLocalesUtf8 - pandoc nix - glibcLocales; + pandoc + R; }; in diff --git a/flint/config.yml b/flint/config.yml new file mode 100644 index 00000000..6ae436e8 --- /dev/null +++ b/flint/config.yml @@ -0,0 +1,44 @@ +keep: + - any_duplicated + - any_is_na + - class_equals + - double_assignment + - duplicate_argument + - empty_assignment + - equal_assignment + - equals_na + - expect_comparison + - expect_length + - expect_named + - expect_not + - expect_null + - expect_true_false + - expect_type + - for_loop_index + - function_return + - implicit_assignment + - is_numeric + - length_levels + - length_test + - lengths + - library_call + - literal_coercion + - matrix_apply + - missing_argument + - nested_ifelse + - numeric_leading_zero + - outer_negation + - package_hooks + - paste + - redundant_equals + - redundant_ifelse + - right_assignment + - semicolon + - seq + - sort + - T_and_F_symbol + - todo_comment + - undesirable_function + - undesirable_operator + - unnecessary_nesting + - unreachable_code diff --git a/flint/rules/T_and_F_symbol.yml b/flint/rules/T_and_F_symbol.yml new file mode 100644 index 00000000..8bfd14df --- /dev/null +++ b/flint/rules/T_and_F_symbol.yml @@ -0,0 +1,97 @@ +id: true_false_symbol +language: r +severity: warning +rule: + pattern: T + kind: identifier + not: + any: + - precedes: + any: + - pattern: <- + - pattern: = + - regex: ^~$ + - follows: + any: + - pattern: $ + - regex: ^~$ + - inside: + any: + - kind: parameter + - kind: call + - kind: binary_operator + follows: + regex: ^~$ + stopBy: end + stopBy: + kind: + argument +fix: TRUE +message: Use TRUE instead of the symbol T. + +--- + +id: true_false_symbol_2 +language: r +severity: warning +rule: + pattern: F + kind: identifier + not: + any: + - precedes: + any: + - pattern: <- + - pattern: = + - regex: ^~$ + - follows: + any: + - pattern: $ + - regex: ^~$ + - inside: + any: + - kind: parameter + - kind: call + - kind: binary_operator + follows: + regex: ^~$ + stopBy: end + stopBy: + kind: + argument +fix: FALSE +message: Use FALSE instead of the symbol F. + +--- + +id: true_false_symbol_3 +language: r +severity: warning +rule: + pattern: T + kind: identifier + precedes: + any: + - pattern: <- + - pattern: = + not: + inside: + kind: argument +message: Don't use T as a variable name, as it can break code relying on T being TRUE. + +--- + +id: true_false_symbol_4 +language: r +severity: warning +rule: + pattern: F + kind: identifier + precedes: + any: + - pattern: <- + - pattern: = + not: + inside: + kind: argument +message: Don't use F as a variable name, as it can break code relying on F being FALSE. diff --git a/flint/rules/absolute_path.yml b/flint/rules/absolute_path.yml new file mode 100644 index 00000000..3c1e9345 --- /dev/null +++ b/flint/rules/absolute_path.yml @@ -0,0 +1,13 @@ +id: absolute_path-1 +language: r +severity: warning +rule: + kind: string_content + any: + - regex: '^~[[:alpha:]]' + - regex: '^~/[[:alpha:]]' + - regex: '^[[:alpha:]]:' + - regex: '^(/|~)$' + - regex: '^/[[:alpha:]]' + - regex: '^\\' +message: Do not use absolute paths. diff --git a/flint/rules/any_duplicated.yml b/flint/rules/any_duplicated.yml new file mode 100644 index 00000000..514b028f --- /dev/null +++ b/flint/rules/any_duplicated.yml @@ -0,0 +1,91 @@ +id: any_duplicated-1 +language: r +severity: warning +rule: + pattern: any($$$ duplicated($MYVAR) $$$) +fix: anyDuplicated(~~MYVAR~~) > 0 +message: anyDuplicated(x, ...) > 0 is better than any(duplicated(x), ...). + +--- + +id: any_duplicated-2 +language: r +severity: warning +rule: + any: + - pattern: length(unique($MYVAR)) == length($MYVAR) + - pattern: length($MYVAR) == length(unique($MYVAR)) +fix: anyDuplicated(~~MYVAR~~) == 0L +message: anyDuplicated(x) == 0L is better than length(unique(x)) == length(x). + +--- + +id: any_duplicated-3 +language: r +severity: warning +rule: + pattern: length(unique($MYVAR)) != length($MYVAR) +fix: anyDuplicated(~~MYVAR~~) != 0L +message: | + Use anyDuplicated(x) != 0L (or > or <) instead of length(unique(x)) != length(x) + (or > or <). + +--- + +id: any_duplicated-4 +language: r +severity: warning +rule: + any: + - pattern: nrow($DATA) != length(unique($DATA$µCOL)) + - pattern: length(unique($DATA$µCOL)) != nrow($DATA) +fix: anyDuplicated(~~DATA~~$~~COL~~) != 0L +message: | + anyDuplicated(DF$col) != 0L is better than length(unique(DF$col)) != nrow(DF) + +--- + +# id: any_duplicated-5 +# language: r +# severity: warning +# rule: +# any: +# - pattern: +# context: nrow($DATA) != length(unique($DATA[["µCOL"]])) +# strictness: ast +# - pattern: +# context: length(unique($DATA[["µCOL"]])) != nrow($DATA) +# strictness: ast +# fix: anyDuplicated(~~DATA~~[["~~COL~~"]]) != 0L +# message: | +# anyDuplicated(DF[["col"]]) != 0L is better than length(unique(DF[["col"]])) != nrow(DF) +# +# --- + +id: any_duplicated-6 +language: r +severity: warning +rule: + any: + - pattern: nrow($DATA) == length(unique($DATA$µCOL)) + - pattern: length(unique($DATA$µCOL)) == nrow($DATA) +fix: anyDuplicated(~~DATA~~$~~COL~~) == 0L +message: | + anyDuplicated(DF$col) == 0L is better than length(unique(DF$col)) == nrow(DF) + +# --- +# +# id: any_duplicated-7 +# language: r +# severity: warning +# rule: +# any: +# - pattern: +# context: nrow($DATA) == length(unique($DATA[["µCOL"]])) +# strictness: ast +# - pattern: +# context: length(unique($DATA[["µCOL"]])) == nrow($DATA) +# strictness: ast +# fix: anyDuplicated(~~DATA~~[["~~COL~~"]]) == 0L +# message: | +# anyDuplicated(DF[["col"]]) == 0L is better than length(unique(DF[["col"]])) == nrow(DF) diff --git a/flint/rules/any_is_na.yml b/flint/rules/any_is_na.yml new file mode 100644 index 00000000..7b05a75b --- /dev/null +++ b/flint/rules/any_is_na.yml @@ -0,0 +1,7 @@ +id: any_na-1 +language: r +severity: warning +rule: + pattern: any($$$ is.na($MYVAR) $$$) +fix: anyNA(~~MYVAR~~) +message: anyNA(x) is better than any(is.na(x)). diff --git a/flint/rules/class_equals.yml b/flint/rules/class_equals.yml new file mode 100644 index 00000000..9b8804f7 --- /dev/null +++ b/flint/rules/class_equals.yml @@ -0,0 +1,42 @@ +id: class_equals_1 +language: r +severity: warning +rule: + any: + - pattern: class($VAR) == $CLASSNAME + - pattern: $CLASSNAME == class($VAR) + not: + inside: + kind: argument +fix: inherits(~~VAR~~, ~~CLASSNAME~~) +message: Instead of comparing class(x) with ==, use inherits(x, 'class-name') or is. or is(x, 'class') + +--- + +id: class_equals_2 +language: r +severity: warning +rule: + any: + - pattern: class($VAR) != $CLASSNAME + - pattern: $CLASSNAME != class($VAR) + not: + inside: + kind: argument +fix: "!inherits(~~VAR~~, ~~CLASSNAME~~)" +message: "Instead of comparing class(x) with !=, use !inherits(x, 'class-name') or is. or is(x, 'class')" + +--- + +id: class_equals_3 +language: r +severity: warning +rule: + any: + - pattern: $CLASSNAME %in% class($VAR) + - pattern: class($VAR) %in% $CLASSNAME +constraints: + CLASSNAME: + kind: string +fix: inherits(~~VAR~~, ~~CLASSNAME~~) +message: Instead of comparing class(x) with %in%, use inherits(x, 'class-name') or is. or is(x, 'class') diff --git a/flint/rules/double_assignment.yml b/flint/rules/double_assignment.yml new file mode 100644 index 00000000..60a04e23 --- /dev/null +++ b/flint/rules/double_assignment.yml @@ -0,0 +1,23 @@ +id: right_double_assignment +language: r +severity: hint +rule: + pattern: $RHS ->> $LHS + has: + field: rhs + kind: identifier +message: ->> can have hard-to-predict behavior; prefer assigning to a + specific environment instead (with assign() or <-). + +--- + +id: left_double_assignment +language: r +severity: hint +rule: + pattern: $LHS <<- $RHS + has: + field: lhs + kind: identifier +message: <<- can have hard-to-predict behavior; prefer assigning to a + specific environment instead (with assign() or <-). diff --git a/flint/rules/duplicate_argument.yml b/flint/rules/duplicate_argument.yml new file mode 100644 index 00000000..415b9ff8 --- /dev/null +++ b/flint/rules/duplicate_argument.yml @@ -0,0 +1,46 @@ +id: duplicate_argument-1 +language: r +severity: warning +rule: + # Look for a function argument... + kind: argument + any: + - has: + kind: identifier + field: name + pattern: $OBJ + - has: + kind: string_content + pattern: $OBJ + stopBy: end + + # ... that follows other argument(s) with the same name... + follows: + kind: argument + stopBy: end + has: + stopBy: end + kind: identifier + field: name + pattern: $OBJ + + # ... inside a function call (or a subset environment for data.table)... + inside: + kind: arguments + follows: + any: + - kind: identifier + pattern: $FUN + - kind: string + inside: + any: + - kind: call + - kind: subset + +# ... that is not a function listed below. +constraints: + FUN: + not: + regex: ^(mutate|transmute)$ + +message: Avoid duplicate arguments in function calls. diff --git a/flint/rules/empty_assignment.yml b/flint/rules/empty_assignment.yml new file mode 100644 index 00000000..ccc995fa --- /dev/null +++ b/flint/rules/empty_assignment.yml @@ -0,0 +1,15 @@ +id: empty_assignment-1 +language: r +severity: warning +rule: + any: + - pattern: $OBJ <- {} + - pattern: $OBJ <- {$CONTENT} + - pattern: $OBJ = {} + - pattern: $OBJ = {$CONTENT} +constraints: + CONTENT: + regex: ^\s+$ +message: | + Assign NULL explicitly or, whenever possible, allocate the empty object with + the right type and size. diff --git a/flint/rules/equal_assignment.yml b/flint/rules/equal_assignment.yml new file mode 100644 index 00000000..77074fe1 --- /dev/null +++ b/flint/rules/equal_assignment.yml @@ -0,0 +1,10 @@ +id: equal_assignment +language: r +severity: hint +rule: + pattern: $LHS = $RHS + has: + field: lhs + kind: identifier +fix: ~~LHS~~ <- ~~RHS~~ +message: Use <-, not =, for assignment. diff --git a/flint/rules/equals_na.yml b/flint/rules/equals_na.yml new file mode 100644 index 00000000..d044f304 --- /dev/null +++ b/flint/rules/equals_na.yml @@ -0,0 +1,37 @@ +id: equals_na +language: r +severity: warning +rule: + any: + - pattern: $MYVAR == NA + - pattern: $MYVAR == NA_integer_ + - pattern: $MYVAR == NA_real_ + - pattern: $MYVAR == NA_complex_ + - pattern: $MYVAR == NA_character_ + - pattern: NA == $MYVAR + - pattern: NA_integer_ == $MYVAR + - pattern: NA_real_ == $MYVAR + - pattern: NA_complex_ == $MYVAR + - pattern: NA_character_ == $MYVAR +fix: is.na(~~MYVAR~~) +message: Use is.na for comparisons to NA (not == or !=). + +--- + +id: equals_na_2 +language: r +severity: warning +rule: + any: + - pattern: $MYVAR != NA + - pattern: $MYVAR != NA_integer_ + - pattern: $MYVAR != NA_real_ + - pattern: $MYVAR != NA_complex_ + - pattern: $MYVAR != NA_character_ + - pattern: NA != $MYVAR + - pattern: NA_integer_ != $MYVAR + - pattern: NA_real_ != $MYVAR + - pattern: NA_complex_ != $MYVAR + - pattern: NA_character_ != $MYVAR +fix: is.na(~~MYVAR~~) +message: Use is.na for comparisons to NA (not == or !=). diff --git a/flint/rules/expect_comparison.yml b/flint/rules/expect_comparison.yml new file mode 100644 index 00000000..6af9bb13 --- /dev/null +++ b/flint/rules/expect_comparison.yml @@ -0,0 +1,37 @@ +id: expect_comparison-1 +language: r +severity: warning +rule: + pattern: expect_true($X > $Y) +fix: expect_gt(~~X~~, ~~Y~~) +message: expect_gt(x, y) is better than expect_true(x > y). + +--- + +id: expect_comparison-2 +language: r +severity: warning +rule: + pattern: expect_true($X >= $Y) +fix: expect_gte(~~X~~, ~~Y~~) +message: expect_gte(x, y) is better than expect_true(x >= y). + +--- + +id: expect_comparison-3 +language: r +severity: warning +rule: + pattern: expect_true($X < $Y) +fix: expect_lt(~~X~~, ~~Y~~) +message: expect_lt(x, y) is better than expect_true(x < y). + +--- + +id: expect_comparison-4 +language: r +severity: warning +rule: + pattern: expect_true($X <= $Y) +fix: expect_lte(~~X~~, ~~Y~~) +message: expect_lte(x, y) is better than expect_true(x <= y). diff --git a/flint/rules/expect_length.yml b/flint/rules/expect_length.yml new file mode 100644 index 00000000..cd5f2db0 --- /dev/null +++ b/flint/rules/expect_length.yml @@ -0,0 +1,12 @@ +id: expect_length-1 +language: r +severity: warning +rule: + any: + - pattern: $FUN(length($OBJ), $VALUE) + - pattern: $FUN($VALUE, length($OBJ)) +constraints: + FUN: + regex: ^(expect_identical|expect_equal)$ +fix: expect_length(~~OBJ~~, ~~VALUE~~) +message: expect_length(x, n) is better than ~~FUN~~(length(x), n). diff --git a/flint/rules/expect_named.yml b/flint/rules/expect_named.yml new file mode 100644 index 00000000..bf88f366 --- /dev/null +++ b/flint/rules/expect_named.yml @@ -0,0 +1,79 @@ +id: expect_named-1 +language: r +severity: warning +rule: + any: + - pattern: + context: expect_identical(names($OBJ), $VALUES) + strictness: ast + - pattern: + context: expect_identical($VALUES, names($OBJ)) + strictness: ast +constraints: + VALUES: + not: + regex: colnames|rownames|dimnames|NULL + has: + kind: null +fix: expect_named(~~OBJ~~, ~~VALUES~~) +message: expect_named(x, n) is better than expect_identical(names(x), n). + +--- + +id: expect_named-2 +language: r +severity: warning +rule: + any: + - pattern: + context: expect_equal(names($OBJ), $VALUES) + strictness: ast + - pattern: + context: expect_equal($VALUES, names($OBJ)) + strictness: ast +constraints: + VALUES: + not: + regex: colnames|rownames|dimnames|NULL +fix: expect_named(~~OBJ~~, ~~VALUES~~) +message: expect_named(x, n) is better than expect_equal(names(x), n). + +--- + +id: expect_named-3 +language: r +severity: warning +rule: + any: + - pattern: + context: testthat::expect_identical(names($OBJ), $VALUES) + strictness: ast + - pattern: + context: testthat::expect_identical($VALUES, names($OBJ)) + strictness: ast +constraints: + VALUES: + not: + regex: colnames|rownames|dimnames|NULL +fix: testthat::expect_named(~~OBJ~~, ~~VALUES~~) +message: expect_named(x, n) is better than expect_identical(names(x), n). + +--- + +id: expect_named-4 +language: r +severity: warning +rule: + any: + - pattern: + context: testthat::expect_equal(names($OBJ), $VALUES) + strictness: ast + - pattern: + context: testthat::expect_equal($VALUES, names($OBJ)) + strictness: ast +constraints: + VALUES: + not: + regex: colnames|rownames|dimnames|NULL +fix: testthat::expect_named(~~OBJ~~, ~~VALUES~~) +message: expect_named(x, n) is better than expect_equal(names(x), n). diff --git a/flint/rules/expect_not.yml b/flint/rules/expect_not.yml new file mode 100644 index 00000000..3a23e9f2 --- /dev/null +++ b/flint/rules/expect_not.yml @@ -0,0 +1,23 @@ +id: expect_not-1 +language: r +severity: warning +rule: + all: + - pattern: expect_true(!$COND) + - not: + regex: '^expect_true\(!!' +fix: expect_false(~~COND~~) +message: expect_false(x) is better than expect_true(!x), and vice versa. + +--- + +id: expect_not-2 +language: r +severity: warning +rule: + all: + - pattern: expect_false(!$COND) + - not: + regex: '^expect_false\(!!' +fix: expect_true(~~COND~~) +message: expect_false(x) is better than expect_true(!x), and vice versa. diff --git a/flint/rules/expect_null.yml b/flint/rules/expect_null.yml new file mode 100644 index 00000000..7888fdb0 --- /dev/null +++ b/flint/rules/expect_null.yml @@ -0,0 +1,22 @@ +id: expect_null-1 +language: r +severity: warning +rule: + any: + - pattern: $FUN(NULL, $VALUES) + - pattern: $FUN($VALUES, NULL) +constraints: + FUN: + regex: ^(expect_identical|expect_equal)$ +fix: expect_null(~~VALUES~~) +message: expect_null(x) is better than ~~FUN~~(x, NULL). + +--- + +id: expect_null-2 +language: r +severity: warning +rule: + pattern: expect_true(is.null($VALUES)) +fix: expect_null(~~VALUES~~) +message: expect_null(x) is better than expect_true(is.null(x)). diff --git a/flint/rules/expect_true_false.yml b/flint/rules/expect_true_false.yml new file mode 100644 index 00000000..784843d8 --- /dev/null +++ b/flint/rules/expect_true_false.yml @@ -0,0 +1,28 @@ +id: expect_true_false-1 +language: r +severity: warning +rule: + any: + - pattern: $FUN(TRUE, $VALUES) + - pattern: $FUN($VALUES, TRUE) +constraints: + FUN: + regex: ^(expect_identical|expect_equal)$ +fix: expect_true(~~VALUES~~) +message: expect_true(x) is better than ~~FUN~~(x, TRUE). + +--- + +id: expect_true_false-2 +language: r +severity: warning +rule: + any: + - pattern: $FUN(FALSE, $VALUES) + - pattern: $FUN($VALUES, FALSE) +constraints: + FUN: + regex: ^(expect_identical|expect_equal)$ +fix: expect_false(~~VALUES~~) +message: expect_false(x) is better than ~~FUN~~(x, FALSE). + diff --git a/flint/rules/expect_type.yml b/flint/rules/expect_type.yml new file mode 100644 index 00000000..1ab20eca --- /dev/null +++ b/flint/rules/expect_type.yml @@ -0,0 +1,51 @@ +id: expect_type-1 +language: r +severity: warning +rule: + any: + - pattern: + context: expect_identical(typeof($OBJ), $VALUES) + strictness: ast + - pattern: + context: expect_identical($VALUES, typeof($OBJ)) + strictness: ast +constraints: + VALUES: + not: + regex: typeof +fix: expect_type(~~OBJ~~, ~~VALUES~~) +message: expect_type(x, t) is better than expect_identical(typeof(x), t). + +--- + +id: expect_type-2 +language: r +severity: warning +rule: + any: + - pattern: + context: expect_equal(typeof($OBJ), $VALUES) + strictness: ast + - pattern: + context: expect_equal($VALUES, typeof($OBJ)) + strictness: ast +constraints: + VALUES: + not: + regex: typeof +fix: expect_type(~~OBJ~~, ~~VALUES~~) +message: expect_type(x, t) is better than expect_equal(typeof(x), t). + +--- + +id: expect_type-3 +language: r +severity: warning +rule: + pattern: expect_true($FUN($OBJ)) +constraints: + FUN: + regex: ^is\. + not: + regex: data\.frame$ +message: expect_type(x, t) is better than expect_true(is.(x)). diff --git a/flint/rules/for_loop_index.yml b/flint/rules/for_loop_index.yml new file mode 100644 index 00000000..e5b28b43 --- /dev/null +++ b/flint/rules/for_loop_index.yml @@ -0,0 +1,27 @@ +id: for_loop_index-1 +language: r +severity: warning +rule: + pattern: for ($IDX in $IDX) +message: Don't re-use any sequence symbols as the index symbol in a for loop. + +--- + +id: for_loop_index-2 +language: r +severity: warning +rule: + pattern: for ($IDX in $SEQ) +constraints: + SEQ: + kind: call + has: + kind: arguments + has: + kind: argument + stopBy: end + has: + kind: identifier + field: value + pattern: $IDX +message: Don't re-use any sequence symbols as the index symbol in a for loop. diff --git a/flint/rules/function_return.yml b/flint/rules/function_return.yml new file mode 100644 index 00000000..31ba46be --- /dev/null +++ b/flint/rules/function_return.yml @@ -0,0 +1,11 @@ +id: function_return-1 +language: r +severity: warning +rule: + any: + - pattern: return($OBJ <- $VAL) + - pattern: return($OBJ <<- $VAL) + - pattern: return($VAL -> $OBJ) + - pattern: return($VAL ->> $OBJ) +message: | + Move the assignment outside of the return() clause, or skip assignment altogether. diff --git a/flint/rules/implicit_assignment.yml b/flint/rules/implicit_assignment.yml new file mode 100644 index 00000000..133a40ef --- /dev/null +++ b/flint/rules/implicit_assignment.yml @@ -0,0 +1,69 @@ +id: implicit_assignment-1 +language: r +severity: warning +rule: + any: + - pattern: $RECEIVER <- $VALUE + - pattern: $RECEIVER <<- $VALUE + - pattern: $VALUE -> $RECEIVER + - pattern: $VALUE ->> $RECEIVER + inside: + any: + - kind: if_statement + - kind: while_statement + field: condition + stopBy: end + strictness: cst +message: | + Avoid implicit assignments in function calls. For example, instead of + `if (x <- 1L) { ... }`, write `x <- 1L; if (x) { ... }`. + +--- + +id: implicit_assignment-2 +language: r +severity: warning +rule: + any: + - pattern: $RECEIVER <- $VALUE + - pattern: $RECEIVER <<- $VALUE + - pattern: $VALUE -> $RECEIVER + - pattern: $VALUE ->> $RECEIVER + inside: + kind: for_statement + field: sequence + stopBy: end + strictness: cst +message: | + Avoid implicit assignments in function calls. For example, instead of + `if (x <- 1L) { ... }`, write `x <- 1L; if (x) { ... }`. + +# --- +# +# id: implicit_assignment-3 +# language: r +# severity: warning +# rule: +# any: +# - pattern: $RECEIVER <- $VALUE +# - pattern: $RECEIVER <<- $VALUE +# - pattern: $VALUE -> $RECEIVER +# - pattern: $VALUE ->> $RECEIVER +# inside: +# kind: argument +# field: value +# strictness: cst +# stopBy: end +# not: +# inside: +# kind: call +# field: function +# has: +# kind: identifier +# regex: ^(lapply)$ +# stopBy: end +# strictness: cst +# message: | +# Avoid implicit assignments in function calls. For example, instead of +# `if (x <- 1L) { ... }`, write `x <- 1L; if (x) { ... }`. + diff --git a/flint/rules/is_numeric.yml b/flint/rules/is_numeric.yml new file mode 100644 index 00000000..6ef8e223 --- /dev/null +++ b/flint/rules/is_numeric.yml @@ -0,0 +1,25 @@ +id: is_numeric_1 +language: r +severity: warning +rule: + any: + - pattern: is.numeric($VAR) || is.integer($VAR) + - pattern: is.integer($VAR) || is.numeric($VAR) +message: is.numeric(x) || is.integer(x) can be simplified to is.numeric(x). Use + is.double(x) to test for objects stored as 64-bit floating point. + +--- + +id: is_numeric_2 +language: r +severity: warning +rule: + any: + - pattern: + context: class($VAR) %in% c("numeric", "integer") + strictness: ast + - pattern: + context: class($VAR) %in% c("integer", "numeric") + strictness: ast +message: class(x) %in% c("numeric", "integer") can be simplified to is.numeric(x). Use + is.double(x) to test for objects stored as 64-bit floating point. diff --git a/flint/rules/length_levels.yml b/flint/rules/length_levels.yml new file mode 100644 index 00000000..e1421dfc --- /dev/null +++ b/flint/rules/length_levels.yml @@ -0,0 +1,7 @@ +id: length_levels_1 +language: r +severity: warning +rule: + pattern: length(levels($VAR)) +fix: nlevels(~~VAR~~) +message: nlevels(x) is better than length(levels(x)). df diff --git a/flint/rules/length_test.yml b/flint/rules/length_test.yml new file mode 100644 index 00000000..26b77083 --- /dev/null +++ b/flint/rules/length_test.yml @@ -0,0 +1,59 @@ +# Strangely, having something like pattern: length($VAR $OP $VAR2) doesn't work + +id: length_test_1 +language: r +severity: warning +rule: + pattern: length($VAR == $VAR2) +fix: length(~~VAR~~) == ~~VAR2~~ +message: Checking the length of a logical vector is likely a mistake. + +--- + +id: length_test_2 +language: r +severity: warning +rule: + pattern: length($VAR != $VAR2) +fix: length(~~VAR~~) != ~~VAR2~~ +message: Checking the length of a logical vector is likely a mistake. + +--- + +id: length_test_3 +language: r +severity: warning +rule: + pattern: length($VAR > $VAR2) +fix: length(~~VAR~~) > ~~VAR2~~ +message: Checking the length of a logical vector is likely a mistake. + +--- + +id: length_test_4 +language: r +severity: warning +rule: + pattern: length($VAR >= $VAR2) +fix: length(~~VAR~~) >= ~~VAR2~~ +message: Checking the length of a logical vector is likely a mistake. + +--- + +id: length_test_5 +language: r +severity: warning +rule: + pattern: length($VAR < $VAR2) +fix: length(~~VAR~~) < ~~VAR2~~ +message: Checking the length of a logical vector is likely a mistake. + +--- + +id: length_test_6 +language: r +severity: warning +rule: + pattern: length($VAR <= $VAR2) +fix: length(~~VAR~~) <= ~~VAR2~~ +message: Checking the length of a logical vector is likely a mistake. diff --git a/flint/rules/lengths.yml b/flint/rules/lengths.yml new file mode 100644 index 00000000..a4164402 --- /dev/null +++ b/flint/rules/lengths.yml @@ -0,0 +1,59 @@ +id: sapply_lengths-1 +language: r +severity: warning +rule: + any: + - pattern: sapply($MYVAR, length) + - pattern: sapply(FUN = length, $MYVAR) + - pattern: sapply($MYVAR, FUN = length) + - pattern: vapply($MYVAR, length $$$) + + - pattern: map_dbl($MYVAR, length) + - pattern: map_dbl($MYVAR, .f = length) + - pattern: map_dbl(.f = length, $MYVAR) + - pattern: map_int($MYVAR, length) + - pattern: map_int($MYVAR, .f = length) + - pattern: map_int(.f = length, $MYVAR) + + - pattern: purrr::map_dbl($MYVAR, length) + - pattern: purrr::map_dbl($MYVAR, .f = length) + - pattern: purrr::map_dbl(.f = length, $MYVAR) + - pattern: purrr::map_int($MYVAR, length) + - pattern: purrr::map_int($MYVAR, .f = length) + - pattern: purrr::map_int(.f = length, $MYVAR) +fix: lengths(~~MYVAR~~) +message: Use lengths() to find the length of each element in a list. + +--- + +id: sapply_lengths-2 +language: r +severity: warning +rule: + any: + - pattern: $MYVAR |> sapply(length) + - pattern: $MYVAR |> sapply(FUN = length) + - pattern: $MYVAR |> vapply(length $$$) + - pattern: $MYVAR |> map_int(length) + - pattern: $MYVAR |> map_int(length $$$) + - pattern: $MYVAR |> purrr::map_int(length) + - pattern: $MYVAR |> purrr::map_int(length $$$) +fix: ~~MYVAR~~ |> lengths() +message: Use lengths() to find the length of each element in a list. + +--- + +id: sapply_lengths-3 +language: r +severity: warning +rule: + any: + - pattern: $MYVAR %>% sapply(length) + - pattern: $MYVAR %>% sapply(FUN = length) + - pattern: $MYVAR %>% vapply(length $$$) + - pattern: $MYVAR %>% map_int(length) + - pattern: $MYVAR %>% map_int(length $$$) + - pattern: $MYVAR %>% purrr::map_int(length) + - pattern: $MYVAR %>% purrr::map_int(length $$$) +fix: ~~MYVAR~~ %>% lengths() +message: Use lengths() to find the length of each element in a list. diff --git a/flint/rules/library_call.yml b/flint/rules/library_call.yml new file mode 100644 index 00000000..ef14d540 --- /dev/null +++ b/flint/rules/library_call.yml @@ -0,0 +1,26 @@ +id: library_call +language: r +severity: warning +rule: + kind: call + has: + regex: ^library|require$ + kind: identifier + follows: + not: + any: + - kind: call + has: + regex: ^library|require$ + kind: identifier + - kind: comment + not: + inside: + any: + - kind: function_definition + - kind: call + has: + pattern: suppressPackageStartupMessages + kind: identifier +message: Move all library/require calls to the top of the script. + diff --git a/flint/rules/literal_coercion.yml b/flint/rules/literal_coercion.yml new file mode 100644 index 00000000..e61fb24a --- /dev/null +++ b/flint/rules/literal_coercion.yml @@ -0,0 +1,89 @@ +id: literal_coercion-1 +language: r +severity: warning +rule: + pattern: $FUN($VALUE) +constraints: + VALUE: + kind: argument + has: + kind: float + not: + regex: 'e' + FUN: + regex: ^(int|as\.integer)$ +fix: ~~VALUE~~L +message: | + Use ~~VALUE~~L instead of ~~FUN~~(~~VALUE~~), i.e., use literals directly + where possible, instead of coercion. + +--- + +id: literal_coercion-2 +language: r +severity: warning +rule: + pattern: as.character(NA) +fix: NA_character_ +message: | + Use NA_character_ instead of as.character(NA), i.e., use literals directly + where possible, instead of coercion. + +--- + +id: literal_coercion-3 +language: r +severity: warning +rule: + pattern: as.logical($VAR) +constraints: + VAR: + kind: argument + has: + any: + - regex: ^1L$ + - regex: ^1$ + - regex: 'true' +fix: TRUE +message: Use TRUE instead of as.logical(~~VAR~~). + +--- + +id: literal_coercion-4 +language: r +severity: warning +rule: + pattern: $FUN($VAR) +constraints: + VAR: + kind: argument + has: + kind: float + FUN: + regex: ^(as\.numeric|as\.double)$ +fix: ~~VAR~~ +message: Use ~~VAR~~ instead of ~~FUN~~(~~VAR~~). + +--- + +id: literal_coercion-5 +language: r +severity: warning +rule: + pattern: as.integer(NA) +fix: NA_integer_ +message: Use NA_integer_ instead of as.integer(NA). + +--- + +id: literal_coercion-6 +language: r +severity: warning +rule: + pattern: $FUN(NA) +constraints: + FUN: + regex: ^(as\.numeric|as\.double)$ +fix: NA_real_ +message: Use NA_real_ instead of ~~FUN~~(NA). + diff --git a/flint/rules/matrix_apply.yml b/flint/rules/matrix_apply.yml new file mode 100644 index 00000000..bbc68505 --- /dev/null +++ b/flint/rules/matrix_apply.yml @@ -0,0 +1,110 @@ +id: matrix_apply-1 +language: r +severity: warning +rule: + any: + - pattern: apply($INPUT, 2, sum) + - pattern: apply($INPUT, MARGIN = 2, sum) + - pattern: apply($INPUT, 2, FUN = sum) + - pattern: apply($INPUT, MARGIN = 2, FUN = sum) +fix: colSums(~~INPUT~~) +message: Use colSums(x) rather than apply(x, 1, sum) + +--- + +id: matrix_apply-2 +language: r +severity: warning +rule: + any: + - pattern: apply($INPUT, 2, sum, na.rm = $NARM) + - pattern: apply($INPUT, MARGIN = 2, sum, na.rm = $NARM) + - pattern: apply($INPUT, 2, FUN = sum, na.rm = $NARM) + - pattern: apply($INPUT, MARGIN = 2, FUN = sum, na.rm = $NARM) +fix: colSums(~~INPUT~~, na.rm = ~~NARM~~) +message: Use colSums(x, na.rm = ~~NARM~~) rather than apply(x, 2, sum, na.rm = ~~NARM~~). + +--- + +id: matrix_apply-3 +language: r +severity: warning +rule: + any: + - pattern: apply($INPUT, 1, sum) + - pattern: apply($INPUT, MARGIN = 1, sum) + - pattern: apply($INPUT, 1, FUN = sum) + - pattern: apply($INPUT, MARGIN = 1, FUN = sum) +fix: rowSums(~~INPUT~~) +message: Use rowSums(x) rather than apply(x, 1, sum) + +--- + +id: matrix_apply-4 +language: r +severity: warning +rule: + any: + - pattern: apply($INPUT, 1, sum, na.rm = $NARM) + - pattern: apply($INPUT, MARGIN = 1, sum, na.rm = $NARM) + - pattern: apply($INPUT, 1, FUN = sum, na.rm = $NARM) + - pattern: apply($INPUT, MARGIN = 1, FUN = sum, na.rm = $NARM) +fix: rowSums(~~INPUT~~, na.rm = ~~NARM~~) +message: Use rowSums(x, na.rm = ~~NARM~~) rather than apply(x, 1, sum, na.rm = ~~NARM~~). + +--- + +id: matrix_apply-5 +language: r +severity: warning +rule: + any: + - pattern: apply($INPUT, 1, mean) + - pattern: apply($INPUT, MARGIN = 1, mean) + - pattern: apply($INPUT, 1, FUN = mean) + - pattern: apply($INPUT, MARGIN = 1, FUN = mean) +fix: rowMeans(~~INPUT~~) +message: Use rowMeans(x) rather than apply(x, 1, mean). + +--- + +id: matrix_apply-6 +language: r +severity: warning +rule: + any: + - pattern: apply($INPUT, 1, mean, na.rm = $NARM) + - pattern: apply($INPUT, MARGIN = 1, mean, na.rm = $NARM) + - pattern: apply($INPUT, 1, FUN = mean, na.rm = $NARM) + - pattern: apply($INPUT, MARGIN = 1, FUN = mean, na.rm = $NARM) +fix: rowMeans(~~INPUT~~, na.rm = ~~NARM~~) +message: Use rowMeans(x, na.rm = ~~NARM~~) rather than apply(x, 1, mean, na.rm = ~~NARM~~). + +--- + +id: matrix_apply-7 +language: r +severity: warning +rule: + any: + - pattern: apply($INPUT, 2, mean) + - pattern: apply($INPUT, MARGIN = 2, mean) + - pattern: apply($INPUT, 2, FUN = mean) + - pattern: apply($INPUT, MARGIN = 2, FUN = mean) +fix: colMeans(~~INPUT~~) +message: Use colMeans(x) rather than apply(x, 2, mean). + +--- + +id: matrix_apply-8 +language: r +severity: warning +rule: + any: + - pattern: apply($INPUT, 2, mean, na.rm = $NARM) + - pattern: apply($INPUT, MARGIN = 2, mean, na.rm = $NARM) + - pattern: apply($INPUT, 2, FUN = mean, na.rm = $NARM) + - pattern: apply($INPUT, MARGIN = 2, FUN = mean, na.rm = $NARM) +fix: colMeans(~~INPUT~~, na.rm = ~~NARM~~) +message: Use colMeans(x, na.rm = ~~NARM~~) rather than apply(x, 2, mean, na.rm = ~~NARM~~). + diff --git a/flint/rules/missing_argument.yml b/flint/rules/missing_argument.yml new file mode 100644 index 00000000..9f47d17a --- /dev/null +++ b/flint/rules/missing_argument.yml @@ -0,0 +1,44 @@ +id: missing_argument-1 +language: r +severity: warning +rule: + kind: arguments + has: + kind: comma + any: + - precedes: + stopBy: neighbor + any: + - regex: '^\)$' + - kind: comma + - follows: + any: + - regex: '^\($' + - kind: argument + regex: '=$' + follows: + kind: identifier + not: + regex: '^(quote|switch|alist)$' + inside: + kind: call +message: Missing argument in function call. + +--- + +id: missing_argument-2 +language: r +severity: warning +rule: + kind: arguments + regex: '=(\s+|)\)$' + follows: + any: + - kind: identifier + - kind: extract_operator + - kind: namespace_operator + not: + regex: '^(quote|switch|alist)$' + inside: + kind: call +message: Missing argument in function call. diff --git a/flint/rules/nested_ifelse.yml b/flint/rules/nested_ifelse.yml new file mode 100644 index 00000000..64dcb08e --- /dev/null +++ b/flint/rules/nested_ifelse.yml @@ -0,0 +1,29 @@ +id: nested_ifelse-1 +language: r +severity: warning +rule: + pattern: $FUN($COND, $TRUE, $FALSE) +constraints: + FALSE: + regex: ^(ifelse|if_else|fifelse) + FUN: + regex: ^(ifelse|if_else|fifelse) +message: | + Don't use nested ~~FUN~~() calls; instead, try (1) data.table::fcase; + (2) dplyr::case_when; or (3) using a lookup table. + +--- + +id: nested_ifelse-2 +language: r +severity: warning +rule: + pattern: $FUN($COND, $TRUE, $FALSE) +constraints: + TRUE: + regex: ^(ifelse|if_else|fifelse) + FUN: + regex: ^(ifelse|if_else|fifelse) +message: | + Don't use nested ~~FUN~~() calls; instead, try (1) data.table::fcase; + (2) dplyr::case_when; or (3) using a lookup table. diff --git a/flint/rules/numeric_leading_zero.yml b/flint/rules/numeric_leading_zero.yml new file mode 100644 index 00000000..51807870 --- /dev/null +++ b/flint/rules/numeric_leading_zero.yml @@ -0,0 +1,11 @@ +id: numeric_leading_zero_1 +language: r +severity: warning +rule: + pattern: $VALUE + any: + - kind: float + - kind: identifier + regex: ^\.[0-9] +fix: 0~~VALUE~~ +message: Include the leading zero for fractional numeric constants. diff --git a/flint/rules/outer_negation.yml b/flint/rules/outer_negation.yml new file mode 100644 index 00000000..ca377de3 --- /dev/null +++ b/flint/rules/outer_negation.yml @@ -0,0 +1,29 @@ +id: outer_negation-1 +language: r +severity: warning +rule: + pattern: all(!$VAR) +constraints: + VAR: + not: + regex: '^!' +fix: '!any(~~VAR~~)' +message: | + !any(x) is better than all(!x). The former applies negation only once after + aggregation instead of many times for each element of x. + +--- + +id: outer_negation-2 +language: r +severity: warning +rule: + pattern: any(! $VAR) +constraints: + VAR: + not: + regex: '^!' +fix: '!all(~~VAR~~)' +message: | + !all(x) is better than any(!x). The former applies negation only once after + aggregation instead of many times for each element of x. diff --git a/flint/rules/package_hooks.yml b/flint/rules/package_hooks.yml new file mode 100644 index 00000000..f4dfa756 --- /dev/null +++ b/flint/rules/package_hooks.yml @@ -0,0 +1,127 @@ +id: package_hooks-1 +language: r +severity: warning +rule: + pattern: packageStartupMessage($$$) + inside: + stopBy: end + kind: binary_operator + has: + stopBy: end + field: lhs + pattern: .onLoad +message: Put packageStartupMessage() calls in .onAttach(), not .onLoad(). + +--- + +id: package_hooks-2 +language: r +severity: warning +rule: + pattern: library.dynam($$$) + inside: + stopBy: end + kind: binary_operator + has: + stopBy: end + field: lhs + pattern: .onAttach +message: Put library.dynam() calls in .onLoad(), not .onAttach(). + +--- + +id: package_hooks-3 +language: r +severity: warning +rule: + pattern: $FN($$$) + inside: + stopBy: end + kind: binary_operator + has: + stopBy: end + field: lhs + pattern: .onLoad +constraints: + FN: + regex: '^(cat|installed.packages|message|packageStartupMessage|print|writeLines)$' +message: Don't use ~~FN~~() in .onLoad(). + +--- + +id: package_hooks-4 +language: r +severity: warning +rule: + pattern: $FN($$$) + inside: + stopBy: end + kind: binary_operator + has: + stopBy: end + field: lhs + pattern: .onAttach +constraints: + FN: + # library.dynam already has its own linter + regex: '^(cat|installed.packages|message|print|writeLines)$' +message: Don't use ~~FN~~() in .onAttach(). + +--- + +id: package_hooks-5 +language: r +severity: warning +rule: + pattern: $FN($$$) + inside: + stopBy: end + kind: binary_operator + has: + stopBy: end + field: lhs + pattern: $LOAD +constraints: + LOAD: + regex: '^(\.onAttach|\.onLoad)$' + FN: + regex: '^(require|library)$' +message: Don't alter the search() path in ~~LOAD~~() by calling ~~FN~~(). + +--- + +id: package_hooks-6 +language: r +severity: warning +rule: + pattern: installed.packages($$$) + inside: + stopBy: end + kind: binary_operator + has: + stopBy: end + field: lhs + pattern: $LOAD +constraints: + LOAD: + regex: '^(\.onAttach|\.onLoad)$' +message: Don't slow down package load by running installed.packages() in ~~LOAD~~(). + +--- + +id: package_hooks-7 +language: r +severity: warning +rule: + pattern: library.dynam.unload($$$) + inside: + stopBy: end + kind: binary_operator + has: + stopBy: end + field: lhs + pattern: $LOAD +constraints: + LOAD: + regex: '^(\.onDetach|\.Last\.lib)$' +message: Use library.dynam.unload() calls in .onUnload(), not ~~LOAD~~(). diff --git a/flint/rules/paste.yml b/flint/rules/paste.yml new file mode 100644 index 00000000..0d4faed3 --- /dev/null +++ b/flint/rules/paste.yml @@ -0,0 +1,75 @@ +id: paste_1 +language: r +severity: warning +rule: + pattern: + context: paste($$$CONTENT sep = "" $$$CONTENT2) + strictness: ast +# fix: paste0($$$CONTENT) +message: paste0(...) is better than paste(..., sep = ""). + +--- + +id: paste_2 +language: r +severity: warning +rule: + any: + - pattern: + context: paste($CONTENT, collapse = ", ") + strictness: ast + - pattern: + context: paste(collapse = ", ", $CONTENT) + strictness: ast +# fix: paste0($$$CONTENT) +message: toString(.) is more expressive than paste(., collapse = ", "). + +--- + +id: paste_3 +language: r +severity: warning +rule: + pattern: + context: paste0($$$CONTENT sep = $USELESS $$$CONTENT2) + strictness: ast +# fix: paste0($$$CONTENT) +message: | + sep= is not a formal argument to paste0(); did you mean to use paste(), or + collapse=? + +--- + +id: paste_4 +language: r +severity: warning +rule: + any: + - pattern: + context: paste0($CONTENT, collapse = $FOO) + strictness: ast + - pattern: + context: paste0(collapse = $FOO, $CONTENT) + strictness: ast + not: + has: + regex: sep + kind: argument +# fix: paste0($$$CONTENT) +message: | + Use paste(), not paste0(), to collapse a character vector when sep= is not used. + +# --- +# +# id: paste_5 +# language: r +# severity: warning +# rule: +# pattern: +# context: paste0(rep($VAR, $TIMES), collapse = "") +# strictness: ast +# constraints: +# VAR: +# kind: string +# fix: strrep(~~VAR~~, ~~TIMES~~) +# message: strrep(x, times) is better than paste0(rep(x, times), collapse = ""). diff --git a/flint/rules/redundant_equals.yml b/flint/rules/redundant_equals.yml new file mode 100644 index 00000000..79a6abcf --- /dev/null +++ b/flint/rules/redundant_equals.yml @@ -0,0 +1,29 @@ +id: redundant_equals-1 +language: r +severity: warning +rule: + any: + - pattern: $VAR == TRUE + - pattern: TRUE == $VAR + - pattern: $VAR == FALSE + - pattern: FALSE == $VAR +message: | + Using == on a logical vector is redundant. Well-named logical vectors can be + used directly in filtering. For data.table's `i` argument, wrap the column + name in (), like `DT[(is_treatment)]`. + +--- + +id: redundant_equals-2 +language: r +severity: warning +rule: + any: + - pattern: $VAR != TRUE + - pattern: TRUE != $VAR + - pattern: $VAR != FALSE + - pattern: FALSE != $VAR +message: | + Using != on a logical vector is redundant. Well-named logical vectors can be + used directly in filtering. For data.table's `i` argument, wrap the column + name in (), like `DT[(is_treatment)]`. diff --git a/flint/rules/redundant_ifelse.yml b/flint/rules/redundant_ifelse.yml new file mode 100644 index 00000000..e2528cfd --- /dev/null +++ b/flint/rules/redundant_ifelse.yml @@ -0,0 +1,67 @@ +id: redundant_ifelse_1 +language: r +severity: warning +rule: + pattern: $FUN($COND, $VAL1, $VAL2) +constraints: + VAL1: + regex: ^TRUE$ + VAL2: + regex: ^FALSE$ + FUN: + regex: ^(ifelse|fifelse|if_else)$ +fix: ~~COND~~ +message: | + Use ~~COND~~ directly instead of calling ~~FUN~~(~~COND~~, TRUE, FALSE). + +--- + +id: redundant_ifelse_2 +language: r +severity: warning +rule: + pattern: $FUN($COND, $VAL1, $VAL2) +constraints: + VAL1: + regex: ^FALSE$ + VAL2: + regex: ^TRUE$ + FUN: + regex: ^(ifelse|fifelse|if_else)$ +fix: !(~~COND~~) +message: | + Use !(~~COND~~) directly instead of calling ~~FUN~~(~~COND~~, FALSE, TRUE). + +--- + +id: redundant_ifelse_3 +language: r +severity: warning +rule: + pattern: $FUN($COND, $VAL1, $VAL2) +constraints: + VAL1: + regex: ^(1|1L)$ + VAL2: + regex: ^(0|0L)$ + FUN: + regex: ^(ifelse|fifelse|if_else)$ +fix: as.integer(~~COND~~) +message: Prefer as.integer(~~COND~~) to ~~FUN~~(~~COND~~, ~~VAL1~~, ~~VAL2~~). + +--- + +id: redundant_ifelse_4 +language: r +severity: warning +rule: + pattern: $FUN($COND, $VAL1, $VAL2) +constraints: + VAL1: + regex: ^(0|0L)$ + VAL2: + regex: ^(1|1L)$ + FUN: + regex: ^(ifelse|fifelse|if_else)$ +fix: as.integer(!(~~COND~~)) +message: Prefer as.integer(!(~~COND~~)) to ~~FUN~~(~~COND~~, ~~VAL1~~, ~~VAL2~~). diff --git a/flint/rules/right_assignment.yml b/flint/rules/right_assignment.yml new file mode 100644 index 00000000..76b736e9 --- /dev/null +++ b/flint/rules/right_assignment.yml @@ -0,0 +1,10 @@ +id: right_assignment +language: r +severity: hint +rule: + pattern: $RHS -> $LHS + has: + field: rhs + kind: identifier +fix: ~~LHS~~<- ~~RHS~~ +message: Use <-, not ->, for assignment. diff --git a/flint/rules/semicolon.yml b/flint/rules/semicolon.yml new file mode 100644 index 00000000..ff2e63b4 --- /dev/null +++ b/flint/rules/semicolon.yml @@ -0,0 +1,10 @@ +id: semicolon_1 +language: r +severity: warning +rule: + regex: ;\s+$ + not: + inside: + kind: string + stopBy: end +message: Trailing semicolons are not needed. diff --git a/flint/rules/seq.yml b/flint/rules/seq.yml new file mode 100644 index 00000000..87f39689 --- /dev/null +++ b/flint/rules/seq.yml @@ -0,0 +1,121 @@ +id: seq_1 +language: r +severity: warning +rule: + pattern: seq(length($VAR)) +fix: seq_along(~~VAR~~) +message: | + seq(length(...)) is likely to be wrong in the empty edge case. Use seq_along(...) instead. + +--- + +id: seq_2 +language: r +severity: warning +rule: + any: + - pattern: 1:nrow($VAR) + - pattern: 1L:nrow($VAR) + regex: ^1 +fix: seq_len(nrow(~~VAR~~)) +message: | + 1:nrow(...) is likely to be wrong in the empty edge case. Use seq_len(nrow(...)) instead. + +--- + +id: seq_3 +language: r +severity: warning +rule: + any: + - pattern: 1:n() + - pattern: 1L:n() + regex: ^1 +fix: seq_len(n()) +message: | + 1:n() is likely to be wrong in the empty edge case. Use seq_len(n()) instead. + +--- + +id: seq_4 +language: r +severity: warning +rule: + pattern: seq(nrow($VAR)) +fix: seq_len(nrow(~~VAR~~)) +message: | + seq(nrow(...)) is likely to be wrong in the empty edge case. Use seq_len(nrow(...)) instead. + +--- + +id: seq_5 +language: r +severity: warning +rule: + any: + - pattern: 1:length($VAR) + - pattern: 1L:length($VAR) + regex: ^1 +fix: seq_along(~~VAR~~) +message: | + 1:length(...) is likely to be wrong in the empty edge case. Use seq_along(...) instead. + +--- + +id: seq_6 +language: r +severity: warning +rule: + any: + - pattern: 1:ncol($VAR) + - pattern: 1L:ncol($VAR) + regex: ^1 +fix: seq_len(ncol(~~VAR~~)) +message: | + 1:ncol(...) is likely to be wrong in the empty edge case. Use seq_len(ncol(...)) instead. + +--- + +id: seq_7 +language: r +severity: warning +rule: + any: + - pattern: 1:NCOL($VAR) + - pattern: 1L:NCOL($VAR) + regex: ^1 +fix: seq_len(NCOL(~~VAR~~)) +message: | + 1:NCOL(...) is likely to be wrong in the empty edge case. Use seq_len(NCOL(...)) instead. + +--- + +id: seq_8 +language: r +severity: warning +rule: + any: + - pattern: 1:NROW($VAR) + - pattern: 1L:NROW($VAR) + regex: ^1 +fix: seq_len(NROW(~~VAR~~)) +message: | + 1:NROW(...) is likely to be wrong in the empty edge case. Use seq_len(NROW(...)) instead. + + +--- + +id: seq_9 +language: r +severity: warning +rule: + pattern: seq(1, $VAL) + not: + pattern: seq(1, 0) +constraints: + VAL: + regex: ^\d+(|L)$ +fix: seq_len(~~VAL~~) +message: seq_len(~~VAL~~) is more efficient than seq(1, ~~VAL~~). + + diff --git a/flint/rules/sort.yml b/flint/rules/sort.yml new file mode 100644 index 00000000..930f5c6d --- /dev/null +++ b/flint/rules/sort.yml @@ -0,0 +1,85 @@ +id: sort-1 +language: r +severity: warning +rule: + pattern: $OBJ[order($OBJ)] +fix: sort(~~OBJ~~, na.last = TRUE) +message: sort(~~OBJ~~, na.last = TRUE) is better than ~~OBJ~~[order(~~OBJ~~)]. + +--- + +id: sort-2 +language: r +severity: warning +rule: + any: + - pattern: $OBJ[order($OBJ, decreasing = $DECREASING)] + - pattern: $OBJ[order(decreasing = $DECREASING, $OBJ)] +constraints: + DECREASING: + regex: ^(TRUE|FALSE)$ +fix: sort(~~OBJ~~, decreasing = ~~DECREASING~~, na.last = TRUE) +message: | + sort(~~OBJ~~, decreasing = ~~DECREASING~~, na.last = TRUE) is better than + ~~OBJ~~[order(~~OBJ~~, decreasing = ~~DECREASING~~)]. + +--- + +id: sort-3 +language: r +severity: warning +rule: + any: + - pattern: $OBJ[order($OBJ, na.last = $NALAST)] + - pattern: $OBJ[order(na.last = $NALAST, $OBJ)] +constraints: + NALAST: + regex: ^(TRUE|FALSE)$ +fix: sort(~~OBJ~~, na.last = ~~NALAST~~, na.last = TRUE) +message: | + sort(~~OBJ~~, na.last = ~~NALAST~~, na.last = TRUE) is better than + ~~OBJ~~[order(~~OBJ~~, na.last = ~~NALAST~~)]. + +--- + +id: sort-4 +language: r +severity: warning +rule: + any: + - pattern: $OBJ[order($OBJ, decreasing = TRUE, na.last = FALSE)] + - pattern: $OBJ[order($OBJ, na.last = FALSE, decreasing = TRUE)] + - pattern: $OBJ[order(decreasing = TRUE, $OBJ, na.last = FALSE)] + - pattern: $OBJ[order(decreasing = TRUE, na.last = FALSE, $OBJ)] + - pattern: $OBJ[order(na.last = FALSE, decreasing = TRUE, $OBJ)] + - pattern: $OBJ[order(na.last = FALSE, $OBJ, decreasing = TRUE)] +fix: sort(~~OBJ~~, decreasing = TRUE, na.last = FALSE) +message: | + sort(~~OBJ~~, decreasing = TRUE, na.last = FALSE) is better than + ~~OBJ~~[order(~~OBJ~~, na.last = FALSE, decreasing = TRUE)]. + +--- + +id: sort-5 +language: r +severity: warning +rule: + any: + - pattern: sort($OBJ) == $OBJ + - pattern: $OBJ == sort($OBJ) +fix: !is.unsorted(~~OBJ~~) +message: | + Use !is.unsorted(~~OBJ~~) to test the sortedness of a vector. + +--- + +id: sort-6 +language: r +severity: warning +rule: + any: + - pattern: sort($OBJ) != $OBJ + - pattern: $OBJ != sort($OBJ) +fix: is.unsorted(~~OBJ~~) +message: | + Use is.unsorted(~~OBJ~~) to test the unsortedness of a vector. diff --git a/flint/rules/todo_comment.yml b/flint/rules/todo_comment.yml new file mode 100644 index 00000000..83d86edf --- /dev/null +++ b/flint/rules/todo_comment.yml @@ -0,0 +1,7 @@ +id: todo_comment-1 +language: r +severity: warning +rule: + kind: comment + regex: '(?i)#(|\s+)\b(todo|fixme)\b' +message: Remove TODO comments. diff --git a/flint/rules/undesirable_function.yml b/flint/rules/undesirable_function.yml new file mode 100644 index 00000000..c9b17562 --- /dev/null +++ b/flint/rules/undesirable_function.yml @@ -0,0 +1,13 @@ +id: undesirable_function-1 +language: r +severity: warning +rule: + pattern: $FUN + kind: identifier + not: + inside: + kind: argument +constraints: + FUN: + regex: ^(\.libPaths|attach|browser|debug|debugcall|debugonce|detach|par|setwd|structure|Sys\.setenv|Sys\.setlocale|trace|undebug|untrace)$ +message: Function "~~FUN~~()" is undesirable. diff --git a/flint/rules/undesirable_operator.yml b/flint/rules/undesirable_operator.yml new file mode 100644 index 00000000..7d635137 --- /dev/null +++ b/flint/rules/undesirable_operator.yml @@ -0,0 +1,29 @@ +id: undesirable_operator-1 +language: r +severity: warning +rule: + any: + - pattern: $X <<- $Y + - pattern: $X ->> $Y +message: | + Avoid undesirable operators `<<-` and `->>`. They assign outside the current + environment in a way that can be hard to reason about. Prefer fully-encapsulated + functions wherever possible, or, if necessary, assign to a specific environment + with assign(). Recall that you can create an environment at the desired scope + with new.env(). + +--- + +id: undesirable_operator-2 +language: r +severity: warning +rule: + kind: namespace_operator + has: + pattern: ':::' +message: | + Operator `:::` is undesirable. It accesses non-exported functions inside + packages. Code relying on these is likely to break in future versions of the + package because the functions are not part of the public interface and may be + changed or removed by the maintainers without notice. Use public functions + via :: instead. diff --git a/flint/rules/unnecessary_nesting.yml b/flint/rules/unnecessary_nesting.yml new file mode 100644 index 00000000..ac08090b --- /dev/null +++ b/flint/rules/unnecessary_nesting.yml @@ -0,0 +1,31 @@ +id: unnecessary_nesting-1 +language: r +severity: warning +rule: + kind: if_statement + any: + - has: + kind: 'braced_expression' + field: consequence + has: + kind: if_statement + stopBy: neighbor + not: + any: + - has: + nthChild: 2 + - precedes: + regex: "^else$" + - has: + kind: if_statement + field: consequence + stopBy: neighbor + # Can be in if(), but not else if() + not: + inside: + field: alternative + kind: if_statement +message: | + Don't use nested `if` statements, where a single `if` with the combined + conditional expression will do. For example, instead of `if (x) { if (y) { ... }}`, + use `if (x && y) { ... }`. diff --git a/flint/rules/unreachable_code.yml b/flint/rules/unreachable_code.yml new file mode 100644 index 00000000..56eb4974 --- /dev/null +++ b/flint/rules/unreachable_code.yml @@ -0,0 +1,64 @@ +id: unreachable_code-1 +language: r +severity: warning +rule: + regex: '[^}]+' + not: + regex: 'else' + follows: + any: + - pattern: return($$$A) + - pattern: stop($$$A) + not: + precedes: + regex: 'else' + stopBy: end +message: Code and comments coming after a return() or stop() should be removed. + +--- + +id: unreachable_code-2 +language: r +severity: warning +rule: + regex: '[^}]+' + not: + regex: 'else' + follows: + any: + - pattern: next + - pattern: break + stopBy: end +message: Remove code and comments coming after `next` or `break` + +--- + +id: unreachable_code-3 +language: r +severity: warning +rule: + inside: + any: + - kind: if_statement + pattern: if (FALSE) + - kind: while_statement + pattern: while (FALSE) + stopBy: end +message: Remove code inside a conditional loop with a deterministically false condition. + +--- + +id: unreachable_code-4 +language: r +severity: warning +rule: + inside: + any: + - kind: if_statement + pattern: if (TRUE) + - kind: while_statement + pattern: while (TRUE) + stopBy: end +message: | + One branch has a a deterministically true condition. The other branches can + be removed. diff --git a/man/rix.Rd b/man/rix.Rd index ea5e9abb..d3d92c16 100644 --- a/man/rix.Rd +++ b/man/rix.Rd @@ -114,7 +114,7 @@ command line, or from another editor (such as Emacs or Vim), set the \code{ide} argument to \code{"other"}. We recommend reading the \code{vignette("e-interactive-use")} for more details. -Packages to install from Github must be provided in a list of 3 elements: +Packages to install from Github or Gitlab must be provided in a list of 3 elements: "package_name", "repo_url" and "commit". To install several packages, provide a list of lists of these 3 elements, one per package to install. It is also possible to install old versions of packages by specifying a @@ -127,9 +127,9 @@ have looked at the time of \code{{ggplot2}}'s version 2.2.1 release, then use the Nix revision closest to that date, by setting \code{r_ver = "3.1.0"}, which was the version of R current at the time. This ensures that Nix builds a completely coherent environment. For security purposes, users that wish to -install packages from Github or from the CRAN archives must provide a +install packages from Github/Gitlab or from the CRAN archives must provide a security hash for each package. \code{{rix}} automatically precomputes this hash -for the source directory of R packages from GitHub or from the CRAN +for the source directory of R packages from GitHub/Gitlab or from the CRAN archives, to make sure the expected trusted sources that match the precomputed hashes in the \code{default.nix} are downloaded. If Nix is available, then the hash will be computed on the user's machine, however, diff --git a/tests/testthat/test-with_nix.R b/tests/testthat/test-with_nix.R index 44f17cd0..520e61c6 100644 --- a/tests/testthat/test-with_nix.R +++ b/tests/testthat/test-with_nix.R @@ -27,7 +27,7 @@ testthat::test_that("Testing `with_nix()` if Nix is installed", { out_subshell <- with_nix( expr = function() { set.seed(1234) - a <- sample(seq(1, 10), 5) + a <- sample(seq_len(10), 5) set.seed(NULL) return(a) }, diff --git a/vignettes/c-using-rix-to-build-project-specific-environments.Rmd b/vignettes/c-using-rix-to-build-project-specific-environments.Rmd index c39b1b28..066707b3 100644 --- a/vignettes/c-using-rix-to-build-project-specific-environments.Rmd +++ b/vignettes/c-using-rix-to-build-project-specific-environments.Rmd @@ -18,8 +18,6 @@ knitr::opts_chunk$set( library(rix) ``` - - ## Project-specific Nix environments Now that you have the required software installed, it’s to time learn more about @@ -50,7 +48,7 @@ file. You need to provide the following inputs to `rix()`: Run the following command to generate the right `default.nix` file: -```{r, eval = F} +```{r, eval = FALSE} path_default_nix <- tempdir() rix(