tests/testthat/test-lint_package.R

# When called from inside a package:
# > lint_package(".")
# .. should give the same results as when called from outside the package
# with:
# > lint_package(path_to_package)

# Template packages for use in testing are stored in
# tests/testthat/dummy_packages/<pkgName>
# These packages should not have a .lintr file:  Hardcoding a .lintr in a
# dummy package throws problems during `R CMD check` (they are flagged as
# hidden files, but can't be added to RBuildIgnore since they should be
# available during `R CMD check` tests)

test_that(
  "`lint_package` does not depend on path to pkg - no excluded files",
  {
    withr::local_options(lintr.linter_file = "lintr_test_config")

    # This dummy package does not have a .lintr file, so no files / lines should
    # be excluded from analysis
    pkg_path <- test_path("dummy_packages", "assignmentLinter")

    expected_lines <- c(
      # from abc.R
      "abc = 123",
      # from jkl.R
      "jkl = 456",
      "mno = 789",
      # from exec/script.R
      "x = 1:4"
    )

    lints_from_outside <- lint_package(
      pkg_path,
      linters = list(assignment_linter())
    )
    lints_from_pkg_root <- withr::with_dir(
      pkg_path,
      lint_package(".", linters = list(assignment_linter()), parse_settings = FALSE)
    )
    lints_from_a_subdir <- withr::with_dir(
      file.path(pkg_path, "R"),
      lint_package("..", linters = list(assignment_linter()), parse_settings = FALSE)
    )

    expect_identical(
      as.data.frame(lints_from_outside)[["line"]],
      expected_lines
    )
    expect_identical(
      as.data.frame(lints_from_outside),
      as.data.frame(lints_from_pkg_root),
      info = paste(
        "lint_package() finds the same lints from pkg-root as from outside a pkg",
        "(no .lintr config present)"
      )
    )
    expect_identical(
      as.data.frame(lints_from_outside),
      as.data.frame(lints_from_a_subdir),
      info = paste(
        "lint_package() finds the same lints from a subdir as from outside a pkg",
        "(no .lintr config present)"
      )
    )
  }
)

test_that(
  "`lint_package` does not depend on path to pkg - with excluded files",
  {
    # Since excluded regions can be specified in two ways
    # list(
    #   filename = line_numbers, # approach 1
    #   filename                 # approach 2
    # ),
    # the test checks both approaches

    pkg_path <- test_path("dummy_packages", "assignmentLinter")

    # Add a .lintr that excludes the whole of `abc.R` and the first line of
    # `jkl.R` (and remove it on finishing this test)
    local_config(pkg_path, "exclusions: list('R/abc.R', 'R/jkl.R' = 1)")

    expected_lines <- c("mno = 789", "x = 1:4")
    lints_from_outside <- lint_package(
      pkg_path,
      linters = list(assignment_linter())
    )
    lints_from_pkg_root <- withr::with_dir(
      pkg_path,
      lint_package(".", linters = list(assignment_linter()))
    )
    lints_from_a_subdir <- withr::with_dir(
      file.path(pkg_path, "R"),
      lint_package(".", linters = list(assignment_linter()))
    )
    lints_from_a_subsubdir <- withr::with_dir(
      file.path(pkg_path, "tests", "testthat"),
      lint_package(".", linters = list(assignment_linter()))
    )

    expect_identical(
      as.data.frame(lints_from_outside)[["line"]],
      expected_lines
    )
    expect_identical(
      as.data.frame(lints_from_outside),
      as.data.frame(lints_from_pkg_root),
      info = paste(
        "lint_package() finds the same lints from pkg-root as from outside a pkg",
        "(.lintr config present)"
      )
    )
    expect_identical(
      as.data.frame(lints_from_outside),
      as.data.frame(lints_from_a_subdir),
      info = paste(
        "lint_package() finds the same lints from a subdir as from outside a pkg",
        "(.lintr config present)"
      )
    )
    expect_identical(
      as.data.frame(lints_from_outside),
      as.data.frame(lints_from_a_subsubdir),
      info = paste(
        "lint_package() finds the same lints from a sub-subdir as from outside a pkg",
        "(.lintr config present)"
      )
    )
  }
)

test_that("lint_package returns early if no package is found", {
  temp_pkg <- withr::local_tempdir("dir")

  expect_warning(
    {
      l <- lint_package(temp_pkg)
    },
    "Didn't find any R package",
    fixed = TRUE
  )
  expect_null(l)

  # ignore a folder named DESCRIPTION, #702
  file.copy(test_path("dummy_packages", "desc_dir_pkg"), temp_pkg, recursive = TRUE)

  expect_warning(
    lint_package(file.path(temp_pkg, "desc_dir_pkg", "DESCRIPTION", "R")),
    "Didn't find any R package",
    fixed = TRUE
  )
})

test_that("length(path)>1 is not supported", {
  expect_error(lint_package(letters), "one package at a time", fixed = TRUE)
})

test_that(
  "`lint_package` will use a `.lintr` file in `.github/linters/` directory the same as the package root",
  {
    withr::local_options(lintr.linter_file = "lintr_test_config")

    pkg_path <- test_path("dummy_packages", "github_lintr_file")

    # First, ensure that the package has lint messages in the absence of a
    # custom configuration:

    pkg_lints_before <- withr::with_dir(
      pkg_path,
      lint_package(".", linters = list(quotes_linter()))
    )

    expect_identical(
      as.data.frame(pkg_lints_before)[["line"]],
      c("'abc'", "'abc'"),
      "linting the `github_lintr_file` package should fail"
    )

    # In `github/linters`add a `.lintr` file
    dir.create(
      path = file.path(pkg_path, ".github", "linters/"),
      recursive = TRUE
    )
    on.exit(unlink(file.path(pkg_path, ".github"), recursive = TRUE), add = TRUE)

    local_config(
      file.path(pkg_path, ".github", "linters"),
      "linters: linters_with_defaults(quotes_linter(\"'\"))",
      filename = "lintr_test_config"
    )

    pkg_lints <- withr::with_dir(pkg_path, lint_package("."))
    expect_length(pkg_lints, 0L)

    subdir_lints <- withr::with_dir(pkg_path, lint_dir("tests/testthat"))
    expect_length(subdir_lints, 0L)
  }
)

test_that("package using .lintr.R config lints correctly", {
  withr::local_options(lintr.linter_file = "lintr_test_config")

  r_config_pkg <- test_path("dummy_packages", "RConfig")

  lints <- as.data.frame(lint_package(r_config_pkg))
  expect_identical(unique(basename(lints$filename)), "lint_me.R")
  expect_identical(lints$linter, c("infix_spaces_linter", "any_duplicated_linter"))

  # config has bad R syntax
  expect_error(
    lint_package(test_path("dummy_packages", "RConfigInvalid")),
    "Malformed config file, ensure it is valid R syntax",
    fixed = TRUE
  )

  # config produces unused variables
  withr::local_options(lintr.linter_file = "lintr_test_config_extraneous")
  expect_warning(
    expect_length(lint_package(r_config_pkg), 2L),
    "Found unused settings in config",
    fixed = TRUE
  )

  # R is preferred if multiple matched configs
  withr::local_options(lintr.linter_file = "lintr_test_config_conflict")
  lints <- as.data.frame(lint_package(r_config_pkg))
  expect_identical(unique(basename(lints$filename)), "testthat.R")
  expect_identical(lints$linter, c("expect_null_linter", "trailing_blank_lines_linter"))
})

test_that("lintr need not be attached for .lintr.R configs to use lintr functions", {
  skip_if_not_r_version("3.6.0") # unclear error message
  exprs <- paste(
    'options(lintr.linter_file = "lintr_test_config")',
    sprintf('lints <- lintr::lint_package("%s")', test_path("dummy_packages", "RConfig")),
    # simplify output to be read from stdout
    'cat(paste(as.data.frame(lints)$linter, collapse = "|"), "\n", sep = "")',
    sep = "; "
  )
  if (tolower(Sys.info()[["sysname"]]) == "windows") {
    rscript <- file.path(R.home("bin"), "Rscript.exe")
  } else {
    rscript <- file.path(R.home("bin"), "Rscript")
  }
  expect_identical(
    system2(rscript, c("-e", shQuote(exprs)), stdout = TRUE),
    "infix_spaces_linter|any_duplicated_linter"
  )
})

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.