tests/testthat/test-lint.R

# The lints for a given file should be the same regardless of the working
# directory

test_that("lint() results do not depend on the working directory", {
  # Helper function: run assignment_linter on a given file
  lint_assignments <- function(filename) {
    lint(filename, linters = list(assignment_linter()))
  }

  # a dummy package for use in the test
  pkg_path <- test_path("dummy_packages", "assignmentLinter")

  # put a .lintr in the package root that excludes the first line of `R/jkl.R`
  local_config(pkg_path, "exclusions: list('R/jkl.R' = 1)")

  # linting the `R/jkl.R` should identify the following assignment lint on the
  # second line of the file
  expected_lines <- "mno = 789"

  # lint the file from:
  # - outside the package
  # - at the package root
  # - in the package's R/ directory

  lints_from_outside <- lint_assignments(
    file.path(pkg_path, "R", "jkl.R")
  )
  lints_from_pkg_root <- withr::with_dir(
    pkg_path,
    lint_assignments(file.path("R", "jkl.R"))
  )
  lints_from_a_subdir <- withr::with_dir(
    file.path(pkg_path, "R"),
    lint_assignments("jkl.R")
  )

  expect_identical(
    as.data.frame(lints_from_pkg_root)[["line"]],
    expected_lines
  )
  expect_identical(
    as.data.frame(lints_from_outside),
    as.data.frame(lints_from_pkg_root)
  )
  expect_identical(
    as.data.frame(lints_from_a_subdir),
    as.data.frame(lints_from_pkg_root)
  )
})

# The lints for a given file should be the same regardless of where the .lintr
# file is positioned (file-exclusions in the .lintr should be relative to the
# directory containing the .lintr)

test_that("lint() results do not depend on the position of the .lintr", {
  # .lintr config files for lint(filepath) are looked for in:
  # - the same directory as filepath
  # - the project directory
  # - the user's home directory
  lint_with_config <- function(config_dir, config_string, filename) {
    local_config(config_dir, config_string)
    lint(filename, linters = assignment_linter())
  }

  # a dummy package for use in the test
  pkg_path <- test_path("dummy_packages", "assignmentLinter")

  # we lint the file <pkg-root>/R/jkl.R using the pkg-root as working directory
  # and
  # - 1) a .lintr config in the package root,
  # - 2) a .lintr config in the source directory R/

  # The second line of jkl.R contains the following assignment lint:
  expected_lines <- "mno = 789"

  lints_with_config_at_pkg_root <- withr::with_dir(
    pkg_path,
    lint_with_config(
      config_dir = ".",
      config_string = "exclusions: list('R/jkl.R' = 1)",
      filename = file.path("R", "jkl.R")
    )
  )

  lints_with_config_in_r_dir <- withr::with_dir(
    pkg_path,
    lint_with_config(
      config_dir = "R",
      config_string = "exclusions: list('jkl.R' = 1)",
      filename = file.path("R", "jkl.R")
    )
  )

  expect_identical(
    as.data.frame(lints_with_config_at_pkg_root)[["line"]], expected_lines
  )
  expect_identical(
    as.data.frame(lints_with_config_at_pkg_root),
    as.data.frame(lints_with_config_in_r_dir),
    info = paste(
      "lints for a source file should be independent of whether the .lintr",
      "file is in the project-root or the source-file-directory"
    )
  )
})

test_that("lint uses linter names", {
  expect_lint("a = 2", list(linter = "bla"), linters = list(bla = assignment_linter()), parse_settings = FALSE)
})

test_that("lint() results from file or text should be consistent", {
  linters <- list(assignment_linter(), infix_spaces_linter())
  lines <- c("x<-1", "x+1")
  file <- withr::local_tempfile(lines = lines)
  text <- paste0(lines, collapse = "\n")
  file <- normalizePath(file)

  lint_from_file <- lint(file, linters = linters)
  lint_from_lines <- lint(linters = linters, text = lines)
  lint_from_text <- lint(linters = linters, text = text)

  # Remove file before linting to ensure that lint works and do not
  # assume that file exists when both filename and text are supplied.
  expect_identical(unlink(file), 0L)
  lint_from_text2 <- lint(file, linters = linters, text = text)

  expect_length(lint_from_file, 2L)
  expect_length(lint_from_lines, 2L)
  expect_length(lint_from_text, 2L)
  expect_length(lint_from_text2, 2L)

  expect_identical(lint_from_file, lint_from_text2)

  for (i in seq_along(lint_from_lines)) {
    lint_from_file[[i]]$filename <- ""
    lint_from_lines[[i]]$filename <- ""
    lint_from_text[[i]]$filename <- ""
  }

  expect_identical(lint_from_file, lint_from_lines)
  expect_identical(lint_from_file, lint_from_text)
})

test_that("exclusions work with custom linter names", {
  expect_lint(
    "a = 2 # nolint: bla.",
    NULL,
    linters = list(bla = assignment_linter()),
    parse_settings = FALSE
  )
})

test_that("compatibility warnings work", {
  expect_warning(
    expect_lint(
      "a == NA",
      "Use is.na",
      linters = equals_na_linter
    ),
    regexp = "Passing linters as variables",
    fixed = TRUE
  )

  expect_warning(
    expect_lint(
      "a = 42",
      "Use <-",
      linters = assignment_linter
    ),
    regexp = "Passing linters as variables",
    fixed = TRUE
  )

  # Also within `linters_with_defaults()` (#1725)
  expect_warning(
    expect_lint(
      "a = 42",
      "Use <-",
      linters = linters_with_defaults(assignment_linter)
    ),
    regexp = "Passing linters as variables",
    fixed = TRUE
  )

  expect_warning(
    expect_lint(
      "a == NA",
      "Use is.na",
      linters = unclass(equals_na_linter())
    ),
    regexp = "The use of linters of class 'function'",
    fixed = TRUE
  )

  # Trigger compatibility in auto_names()
  expect_warning(
    expect_lint(
      "a == NA",
      "Use is.na",
      linters = list(unclass(equals_na_linter()))
    ),
    "The use of linters of class 'function'",
    fixed = TRUE
  )

  expect_error(
    expect_warning(
      lint("a <- 1\n", linters = function(two, arguments) NULL),
      regexp = "The use of linters of class 'function'",
      fixed = TRUE
    ),
    regexp = "`fun` must be a function taking exactly one argument",
    fixed = TRUE
  )

  expect_error(
    lint("a <- 1\n", linters = "equals_na_linter"),
    regexp = rex::rex("Expected '", anything, "' to be a function of class 'linter'")
  )
})

test_that("Deprecated positional usage of cache= errors", {
  expect_error(
    lint("a = 2\n", FALSE, linters = assignment_linter()),
    "'cache' is no longer available as a positional argument",
    fixed = TRUE
  )
})

test_that("Linters throwing an error give a helpful error", {
  tmp_file <- withr::local_tempfile(lines = "a <- 1")
  linter <- function() Linter(function(source_expression) stop("a broken linter"))
  # NB: Some systems/setups may use e.g. symlinked files when creating under tempfile();
  #   we don't care much about that, so just check basename()
  expect_error(
    lint(tmp_file, linter()),
    rex::rex("Linter 'linter' failed in ", anything, basename(tmp_file), ": a broken linter")
  )
  expect_error(
    lint(tmp_file, list(broken_linter = linter())),
    rex::rex("Linter 'broken_linter' failed in ", anything, basename(tmp_file), ": a broken linter")
  )
})

test_that("typo in argument name gives helpful error", {
  expect_error(lint("xxx", litners = identity), "Found unknown arguments in [.][.][.].*[?]lint ")
})

Try the lintr package in your browser

Any scripts or data that you put into this service are public.

lintr documentation built on Nov. 7, 2023, 5:07 p.m.