diff --git a/NEWS.md b/NEWS.md index 85b6bcf23..e02530497 100644 --- a/NEWS.md +++ b/NEWS.md @@ -122,6 +122,9 @@ * `available_linters()` gives priority to `tags` over `exclude_tags` in the case of overlap. In particular, this means that `available_linters(tags = "deprecated")` will work to return deprecated linters without needing to specify `exclude_tags` (#1959, @MichaelChirico). +* The {lintr} configuration file is now searched in the system's user configuration path; the lintr config filename can + also be configured explicitly by setting the environment variable `R_LINTR_LINTER_FILE` (#460, @klmr) + ### New linters * `matrix_apply_linter()` recommends use of dedicated `rowSums()`, `colSums()`, `colMeans()`, `rowMeans()` over `apply(., MARGIN, sum)` or `apply(., MARGIN, mean)`. The recommended alternative is much more efficient and more readable (#1869, @Bisaloo). diff --git a/R/settings.R b/R/settings.R index 801cf596d..7cc279c4e 100644 --- a/R/settings.R +++ b/R/settings.R @@ -8,7 +8,8 @@ #' 4. `linter_file` in the user home directory #' 5. [default_settings()] #' -#' The default linter_file name is `.lintr` but it can be changed with option `lintr.linter_file`. +#' The default linter_file name is `.lintr` but it can be changed with option `lintr.linter_file` +#' or the environment variable `R_LINTR_LINTER_FILE` #' This file is a dcf file, see [base::read.dcf()] for details. #' @param filename source file to be linted read_settings <- function(filename) { diff --git a/R/settings_utils.R b/R/settings_utils.R index 2eef16fae..f5f024ae0 100644 --- a/R/settings_utils.R +++ b/R/settings_utils.R @@ -69,7 +69,9 @@ find_config <- function(filename) { find_config2(path), # User directory # cf: rstudio@bc9b6a5 SessionRSConnect.R#L32 - file.path(Sys.getenv("HOME", unset = "~"), linter_file) + file.path(Sys.getenv("HOME", unset = "~"), linter_file), + # Next check for a global config file + file.path(R_user_dir("lintr", which = "config"), "config") ) # Search through locations, return first valid result diff --git a/R/zzz.R b/R/zzz.R index 8198a6477..ff5d3ae39 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -279,7 +279,7 @@ settings <- NULL .onLoad <- function(libname, pkgname) { op <- options() op_lintr <- list( - lintr.linter_file = ".lintr" + lintr.linter_file = Sys.getenv("R_LINTR_LINTER_FILE", ".lintr") ) toset <- !(names(op_lintr) %in% names(op)) if (any(toset)) options(op_lintr[toset]) diff --git a/man/read_settings.Rd b/man/read_settings.Rd index 3a568b4dd..bff73b52c 100644 --- a/man/read_settings.Rd +++ b/man/read_settings.Rd @@ -20,6 +20,7 @@ Lintr searches for settings for a given source file in the following order. } } \details{ -The default linter_file name is \code{.lintr} but it can be changed with option \code{lintr.linter_file}. +The default linter_file name is \code{.lintr} but it can be changed with option \code{lintr.linter_file} +or the environment variable \code{R_LINTR_LINTER_FILE} This file is a dcf file, see \code{\link[base:dcf]{base::read.dcf()}} for details. } diff --git a/tests/testthat/test-settings.R b/tests/testthat/test-settings.R index 0a0d87ca1..0ecdda807 100644 --- a/tests/testthat/test-settings.R +++ b/tests/testthat/test-settings.R @@ -43,6 +43,23 @@ test_that("it uses config home directory settings if provided", { expect_identical(settings$exclude, "test") }) +test_that("it uses system config directory settings if provided", { + path <- withr::local_tempdir() + config_parent_path <- withr::local_tempdir("config") + config_path <- file.path(config_parent_path, "R", "lintr") + dir.create(config_path, recursive = TRUE) + file <- withr::local_tempfile(tmpdir = path) + local_config(config_path, 'exclude: "test"', filename = "config") + + withr::with_envvar(c(R_USER_CONFIG_DIR = config_parent_path), lintr:::read_settings(file)) + + lapply(setdiff(ls(settings), "exclude"), function(setting) { + expect_identical(settings[[setting]], default_settings[[setting]]) + }) + + expect_identical(settings$exclude, "test") +}) + test_that("it errors if the config file does not end in a newline", { f <- withr::local_tempfile() cat("linters: linters_with_defaults(closed_curly_linter = NULL)", file = f) diff --git a/vignettes/lintr.Rmd b/vignettes/lintr.Rmd index d176419ba..aae715718 100644 --- a/vignettes/lintr.Rmd +++ b/vignettes/lintr.Rmd @@ -91,12 +91,13 @@ exclusions: list( More generally, `lintr` searches for a settings file according to following prioritized list. The first one found, if any, will be used: -1. If `options("lintr.linter_file")` is an absolute path, this file will be used. The default for this option is `".lintr"`. +1. If `options("lintr.linter_file")` is an absolute path, this file will be used. The default for this option is `".lintr"` or the value of the environment variable `R_LINTR_LINTER_FILE`, if set. 2. A project-local linter file; that is, either 1. a linter file (that is, a file named like `lintr.linter_file`) in the currently-searched directory, i.e. the directory of the file passed to `lint()`; or 2. a linter file in the `.github/linters` child directory of the currently-searched directory. 3. A project-local linter file in the closest parent directory of the currently-searched directory, starting from the deepest path, moving upwards one level at a time. When run from `lint_package()`, this directory can differ for each linted file. 4. A linter file in the user's `HOME` directory. +5. A linter file called `config` in the user's configuration path (given by `tools::R_user_dir("lintr", which = "config")`). If no linter file is found, only default settings take effect (see [defaults](#defaults)).