test_that("input validation for available_linters works as expected", { expect_error(available_linters(1L), "`packages` must be a character vector.") expect_error(available_linters(tags = 1L), "`tags` must be a character vector.") expect_error(available_linters(exclude_tags = 1L), "`exclude_tags` must be a character vector.") }) test_that("validate_linter_db works as expected", { df_empty <- data.frame() expect_warning( lintr:::validate_linter_db(df_empty, "mypkg"), "`linters.csv` must contain the columns 'linter' and 'tags'.", fixed = TRUE ) expect_false(suppressWarnings(lintr:::validate_linter_db(df_empty, "mypkg"))) df <- data.frame( linter = "absolute_path_linter", tags = "robustness", stringsAsFactors = FALSE ) expect_true(lintr:::validate_linter_db(df, "mypkg")) }) test_that("available_linters returns a data frame", { avail <- available_linters() avail2 <- available_linters(c("lintr", "not-a-package")) empty <- available_linters("not-a-package") expect_s3_class(avail, "data.frame") expect_identical(avail, avail2) expect_identical(nrow(empty), 0L) expect_named(avail, c("linter", "package", "tags")) expect_type(avail[["linter"]], "character") expect_type(avail[["package"]], "character") expect_type(avail[["tags"]], "list") expect_type(avail[["tags"]][[1L]], "character") }) test_that("available_tags returns a character vector", { tags <- available_tags() tags2 <- available_tags(c("lintr", "not-a-package")) empty <- available_tags("not-a-package") expect_type(tags, "character") expect_identical(tags, tags2) expect_length(empty, 0L) expect_true(all(available_linters()$tags[[1L]] %in% tags)) expect_true(all(unlist(available_linters()$tags) %in% tags)) expect_identical(anyDuplicated(tags), 0L) expect_identical(lintr:::platform_independent_order(tags), seq_along(tags)) }) test_that("default_linters and default tag match up", { avail <- available_linters() tagged_default <- avail[["linter"]][vapply(avail[["tags"]], function(tags) "default" %in% tags, logical(1L))] expect_identical(tagged_default, names(default_linters)) }) test_that("warnings occur only for deprecated linters", { expect_silent(linters_with_tags(tags = NULL)) num_deprecated_linters <- nrow(available_linters(tags = "deprecated", exclude_tags = NULL)) deprecation_warns_seen <- 0L outer_env <- environment() expect_silent({ withCallingHandlers( linters_with_tags(tags = "deprecated", exclude_tags = NULL), warning = function(w) { if (grepl("was deprecated", conditionMessage(w), fixed = TRUE)) { outer_env$deprecation_warns_seen <- outer_env$deprecation_warns_seen + 1L invokeRestart("muffleWarning") } else { w } } ) }) expect_identical(deprecation_warns_seen, num_deprecated_linters) }) test_that("available_linters matches the set of linters available from lintr", { lintr_db <- available_linters(exclude_tags = NULL) linters_in_namespace <- setdiff(ls(asNamespace("lintr"), pattern = "_linter$"), "is_linter") # ensure that the contents of inst/lintr/linters.csv covers all _linter objects in our namespace expect_identical(sort(lintr_db$linter), sort(linters_in_namespace)) # ensure that all _linter objects in our namespace are also exported exported_linters <- setdiff(grep("_linter$", getNamespaceExports("lintr"), value = TRUE), "is_linter") expect_identical(sort(linters_in_namespace), sort(exported_linters)) }) test_that("rownames for available_linters data frame doesn't have missing entries", { lintr_db <- available_linters() expect_identical( tail(rownames(lintr_db), 1L), as.character(nrow(lintr_db)) ) lintr_db2 <- available_linters(exclude_tags = NULL) expect_identical( tail(rownames(lintr_db2), 1L), as.character(nrow(lintr_db2)) ) }) # See the roxygen helpers in R/linter_tags.R for the code used to generate the docs. # This test helps ensure the documentation is up to date with the available_linters() database test_that("lintr help files are up to date", { help_db <- tools::Rd_db("lintr") # e.g. in dev under pkgload::load_all() if (length(help_db) == 0L) { help_db <- tools::Rd_db(dir = test_path("..", "..")) skip_if_not(length(help_db) > 0L, message = "Package help not installed or corrupted") } lintr_db <- available_linters(exclude_tags = NULL) lintr_db$package <- NULL lintr_db$tags <- lapply(lintr_db$tags, function(x) if ("deprecated" %in% x) "deprecated" else sort(x)) lintr_db <- lintr_db[order(lintr_db$linter), ] expect_true("linters.Rd" %in% names(help_db), info = "?linters exists") # NB: objects in help_env are class Rd, see ?as.character.Rd (part of 'tools') rd_as_string <- function(rd) paste(as.character(rd), collapse = "") # Get a character string with the contents of ?linters linter_help_text <- rd_as_string(help_db$linters.Rd) # Test three things about ?linters # (1) the complete list of linters and tags matches that in available_linters() # (2) the tabulation of tags & corresponding count of linters matches that in available_linters() # (3) the 'configurable' tag applies if and only if the linter has parameters # Extract linters & associated tags from ?linters text # Rd markup for items looks like \item{\code{\link{...}} (tags: ...)} # [1] [2] # [1]: linter name; [2]: associated tags help_linters <- rex::re_matches( linter_help_text, rex::rex( "\\item{\\code{\\link{", capture(some_of(letter, number, "_", "."), name = "linter"), "}} (tags: ", capture(some_of(letter, "_", ",", " "), name = "tags"), ")}" ), global = TRUE )[[1L]] help_linters$tags <- lapply(strsplit(help_linters$tags, ", ", fixed = TRUE), sort) help_linters <- help_linters[order(help_linters$linter), ] # (1) the complete list of linters and tags matches that in available_linters() expect_identical( help_linters, # deprecated linters are excluded from this page lintr_db[!vapply(lintr_db$tags, identical, "deprecated", FUN.VALUE = NA), ], info = "Database implied by ?linters is the same as is available_linters()", ignore_attr = "row.names" ) # Counts of tags from available_linters() db_tag_table <- as.data.frame( table(tag = unlist(lintr_db$tags)), responseName = "n_linters", stringsAsFactors = FALSE ) # In ?linters, entries in the enumeration of tags look like # \item{\link[=${TAG}_linters]{${TAG}} (${N_LINTERS_WITH_TAG} linters)} help_tag_table <- rex::re_matches( linter_help_text, rex::rex( "\\item{\\link[=", capture(some_of(letter, "_"), name = "tag_page"), "_linters]{", capture(some_of(letter, "_"), name = "tag"), "} (", capture(numbers, name = "n_linters"), " linters)}" ), global = TRUE )[[1L]] # consistency/sanity check expect_identical(help_tag_table$tag_page, help_tag_table$tag) help_tag_table$tag_page <- NULL help_tag_table$n_linters <- as.integer(help_tag_table$n_linters) # (2) the tabulation of tags & corresponding count of linters matches that in available_linters() expect_identical( help_tag_table[order(help_tag_table$tag), ], db_tag_table[order(db_tag_table$tag), ], ignore_attr = "row.names", info = "Tags and corresponding counts in ?linters is the same as in available_linters()" ) # Now test an analogue to (1) from above for each tag's help page for (tag in db_tag_table$tag) { expect_true(paste0(tag, "_linters.Rd") %in% names(help_db), info = paste0("?", tag, "_linters exists")) tag_help <- help_db[[paste0(tag, "_linters.Rd")]] tag_help_text <- rd_as_string(tag_help) # linters listed in ?${TAG}_linter help_tag_linters <- rex::re_matches( tag_help_text, rex::rex("\\item{\\code{\\link{", capture(some_of(letter, number, "_", "."), name = "linter"), "}}}"), global = TRUE )[[1L]] # those entries in available_linters() with the current tag db_linter_has_tag <- vapply(lintr_db$tags, function(linter_tag) any(tag %in% linter_tag), logical(1L)) expected <- lintr_db$linter[db_linter_has_tag] expect_identical( sort(help_tag_linters$linter), sort(expected), info = paste0("?", tag, "_linters lists all linters with that tag in available_linters()") ) } # (3) the 'configurable' tag applies if and only if the linter has parameters has_args <- 0L < lengths(Map( function(linter, tags) if ("deprecated" %in% tags) NULL else formals(match.fun(linter)), lintr_db$linter, lintr_db$tags )) has_configurable_tag <- vapply(lintr_db$tags, function(tags) "configurable" %in% tags, logical(1L)) expect_identical(has_configurable_tag, unname(has_args)) }) test_that("available_linters gives precedence to included tags", { expect_true("style" %in% unlist(available_linters(tags = "style", exclude_tags = "style")$tags)) # also for the default case expect_identical( available_linters(tags = "deprecated"), available_linters(tags = "deprecated", exclude_tags = NULL) ) })