Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .lintr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ linters: linters_with_defaults(
object_length_linter = NULL,
object_name_linter = NULL,
commented_code_linter = NULL,
cyclocomp_linter = NULL,
pipe_consistency_linter = NULL,
trailing_whitespace_linter = NULL,
indentation_linter(indent = 4, hanging_indent_style = "never"))
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ Description: An implementation of the Language Server Protocol
License: MIT + file LICENSE
URL: https://github.com/REditorSupport/languageserver/
Depends:
R (>= 3.4.0)
R (>= 4.0.0)
Imports:
callr (>= 3.0.0),
collections (>= 0.3.0),
digest,
fs (>= 1.3.1),
jsonlite (>= 1.6),
lintr (>= 3.0.0),
lintr (>= 3.3.0-1),
parallel,
R6 (>= 2.4.1),
roxygen2 (>= 7.0.0),
Expand Down
93 changes: 21 additions & 72 deletions R/diagnostics.R
Original file line number Diff line number Diff line change
Expand Up @@ -61,46 +61,6 @@ diagnostic_from_lint <- function(result, content) {
)
}

#' Find the lintr config file
#' @noRd
find_config <- function(filename) {
# instead of calling `lintr:::find_config` directly
# since CRAN doesn't like :::.
asNamespace("lintr")$find_config(filename)
}

#' Lint R text using settings from its source file or working directory
#' @noRd
lint_document_text <- function(path, content, linters = NULL, cache = FALSE) {
# lintr 3.0 interprets text according to the filename's extension, even
# when it has already been extracted. It also resolves settings relative
# to a temporary file when no filename is supplied. Load settings first
# so literate and pathless documents use the correct configuration.
lintr_namespace <- asNamespace("lintr")
settings <- lintr_namespace$settings
previous_settings <- as.list(settings, all.names = TRUE)
on.exit({
rm(list = ls(settings, all.names = TRUE), envir = settings)
list2env(previous_settings, envir = settings)
})
lintr_namespace$read_settings(if (is.null(path)) getwd() else path)
lints <- lintr::lint(
text = content, linters = linters, cache = cache,
parse_settings = FALSE
)
if (is.null(path)) return(lints)

# Inline linting uses a temporary filename. Restore the real filename
# before applying the config's file and line exclusions. Inline nolint
# comments have already been applied by lint().
lints[] <- lapply(lints, function(lint) {
# lintr normalizes exclusion paths to forward slashes on Windows too.
lint$filename <- normalizePath(path, winslash = "/", mustWork = FALSE)
lint
})
lintr_namespace$exclude(lints, lines = character())
}

lintr_supports_text_terminal_newline <- function() {
lines <- asNamespace("lintr")$get_lines(
filename = "x.R",
Expand All @@ -109,15 +69,14 @@ lintr_supports_text_terminal_newline <- function() {
isFALSE(attr(lines, "terminal_newline", exact = TRUE))
}

lint_without_terminal_newline <- function(path, content, linters) {
lint_without_terminal_newline <- function(path, content, linters = NULL) {
lintr_namespace <- asNamespace("lintr")
settings <- lintr_namespace$settings
previous_settings <- as.list(settings, all.names = TRUE)
on.exit({
rm(list = ls(settings, all.names = TRUE), envir = settings)
list2env(previous_settings, envir = settings)
}, add = TRUE)
lintr_namespace$read_settings(if (nzchar(path)) path else getwd())
on.exit(lintr_namespace$reset_settings(), add = TRUE)
if (nzchar(path)) {
lintr_namespace$read_settings(path)
} else {
lintr_namespace$read_settings()
}
effective_linters <- lintr_namespace$define_linters(linters)
linter <- effective_linters[["trailing_blank_lines_linter"]]
if (is.null(linter)) {
Expand All @@ -137,7 +96,7 @@ lint_without_terminal_newline <- function(path, content, linters) {
return(list())
}
lints <- Filter(function(lint) {
grepl("terminal newline", lint$message, fixed = TRUE)
identical(lint$message, "Add a terminal newline.")
}, linter(list(
filename = filename,
file_lines = enc2utf8(content),
Expand Down Expand Up @@ -179,31 +138,21 @@ diagnose_file <- function(uri, content, is_rmarkdown = FALSE, globals = NULL, ca
on.exit(do.call("detach", list(env_name, character.only = TRUE)), add = TRUE)
}

linters <- NULL
if (nzchar(path)) {
config_path <- tryCatch(find_config(path), error = function(e) NULL)
if (is.null(config_path) || !nzchar(config_path) || !file.exists(config_path)) {
linters <- lintr::linters_with_defaults()
}
}

if (nzchar(path) && is_rmarkdown) {
lints <- lint_document_text(path, content, linters, cache)
lints <- lintr::lint(path,
cache = cache,
text = content,
parse_settings = TRUE
)
} else {
if (nzchar(path)) {
lints <- lintr::lint(path,
cache = cache,
text = content,
parse_settings = TRUE,
linters = linters
)
} else {
# There is no stable filename to cache for a pathless document.
lints <- lint_document_text(NULL, content)
}
if (!terminal_newline && (!nzchar(path) || !lintr_supports_text_terminal_newline())) {
lints <- c(lints, lint_without_terminal_newline(path, content, linters))
}
# There is no stable filename to cache for a pathless document.
lints <- lintr::lint(
text = content,
parse_settings = TRUE
)
}
if (!terminal_newline && (!nzchar(path) || !lintr_supports_text_terminal_newline())) {
lints <- c(lints, lint_without_terminal_newline(path, content))
}

diagnostics <- lapply(lints, diagnostic_from_lint, content = content)
Expand Down
38 changes: 18 additions & 20 deletions tests/testthat/test-code-action.R
Original file line number Diff line number Diff line change
Expand Up @@ -356,28 +356,26 @@ test_that("Code action filtering ignores unrelated or invalid diagnostics", {
test_that("Code action provides quickfix for missing terminal newline", {
uri <- "file:///missing-newline.R"
document <- Document$new(uri, content = "x <- 1")
for (msg in c("Add a terminal newline.", "Missing terminal newline.")) {
diagnostics <- list(
list(
range = range(position(0L, 6L), position(0L, 6L)),
severity = DiagnosticSeverity$Information,
source = "lintr",
code = "trailing_blank_lines_linter",
message = msg
)
diagnostics <- list(
list(
range = range(position(0L, 6L), position(0L, 6L)),
severity = DiagnosticSeverity$Information,
source = "lintr",
code = "trailing_blank_lines_linter",
message = "Add a terminal newline."
)
)

reply <- document_code_action_reply(
1L, uri, NULL, document, list(),
list(diagnostics = diagnostics, only = list("quickfix"))
)
titles <- vapply(reply$result, function(action) action$title, character(1L))
expect_true("Add a terminal newline" %in% titles)
action <- reply$result[[match("Add a terminal newline", titles)]]
expect_true(isTRUE(action$isPreferred))
expect_equal(action$edit$changes[[uri]][[1L]]$range, range(position(0L, 6L), position(0L, 6L)))
expect_equal(action$edit$changes[[uri]][[1L]]$newText, "\n")
}
reply <- document_code_action_reply(
1L, uri, NULL, document, list(),
list(diagnostics = diagnostics, only = list("quickfix"))
)
titles <- vapply(reply$result, function(action) action$title, character(1L))
expect_true("Add a terminal newline" %in% titles)
action <- reply$result[[match("Add a terminal newline", titles)]]
expect_true(isTRUE(action$isPreferred))
expect_equal(action$edit$changes[[uri]][[1L]]$range, range(position(0L, 6L), position(0L, 6L)))
expect_equal(action$edit$changes[[uri]][[1L]]$newText, "\n")
})

test_that("Trailing blank line with spaces and no terminal newline fixes cleanly", {
Expand Down
41 changes: 12 additions & 29 deletions tests/testthat/test-lintr.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ test_that("lintr works", {

expect_length(data_no_newline$diagnostics, 1L)
expect_equal(data_no_newline$diagnostics[[1]]$code, "trailing_blank_lines_linter")
expect_match(data_no_newline$diagnostics[[1]]$message, "terminal newline", fixed = TRUE)
expect_equal(data_no_newline$diagnostics[[1]]$message, "Add a terminal newline.")
expect_equal(
data_no_newline$diagnostics[[1]]$range,
list(
Expand Down Expand Up @@ -145,19 +145,18 @@ test_that("diagnose_file handles empty, prose-only, and untitled content", {
list()
)

diagnostics <- suppressWarnings(diagnose_file(
"untitled:1", "value=1", cache = FALSE
))
diagnostics <- diagnose_file("untitled:1", "value=1", cache = FALSE)
expect_true(length(diagnostics) >= 1L)
expect_true(all(vapply(diagnostics, function(item) {
identical(item$source, "lintr")
}, logical(1L))))

globals <- new.env(parent = emptyenv())
globals$known_global <- TRUE
expect_type(suppressWarnings(diagnose_file(
"untitled:1", "known_global", globals = globals, cache = FALSE
)), "list")
expect_type(
diagnose_file("untitled:1", "known_global", globals = globals, cache = FALSE),
"list"
)
expect_false("languageserver:globals" %in% search())
})

Expand Down Expand Up @@ -315,23 +314,23 @@ test_that("diagnose_file reports missing terminal newline for single-line and mu
diag_single_missing <- diagnose_file(uri, "x <- 1", cache = FALSE)
expect_length(diag_single_missing, 1L)
expect_equal(diag_single_missing[[1L]]$code, "trailing_blank_lines_linter")
expect_match(diag_single_missing[[1L]]$message, "terminal newline", fixed = TRUE)
expect_equal(diag_single_missing[[1L]]$message, "Add a terminal newline.")

diag_single_present <- diagnose_file(uri, c("x <- 1", ""), cache = FALSE)
expect_length(diag_single_present, 0L)

diag_multi_missing <- diagnose_file(uri, c("x <- 1", "y <- 2"), cache = FALSE)
expect_length(diag_multi_missing, 1L)
expect_equal(diag_multi_missing[[1L]]$code, "trailing_blank_lines_linter")
expect_match(diag_multi_missing[[1L]]$message, "terminal newline", fixed = TRUE)
expect_equal(diag_multi_missing[[1L]]$message, "Add a terminal newline.")

diag_multi_present <- diagnose_file(uri, c("x <- 1", "y <- 2", ""), cache = FALSE)
expect_length(diag_multi_present, 0L)

diag_untitled_missing <- diagnose_file("untitled:1", "x <- 1", cache = FALSE)
expect_length(diag_untitled_missing, 1L)
expect_equal(diag_untitled_missing[[1L]]$code, "trailing_blank_lines_linter")
expect_match(diag_untitled_missing[[1L]]$message, "terminal newline", fixed = TRUE)
expect_equal(diag_untitled_missing[[1L]]$message, "Add a terminal newline.")

diag_untitled_present <- diagnose_file("untitled:1", c("x <- 1", ""), cache = FALSE)
expect_length(diag_untitled_present, 0L)
Expand All @@ -347,7 +346,7 @@ test_that("diagnose_file reports missing terminal newline for single-line and mu
)
})

test_that("lint_without_terminal_newline respects linter configuration and lintr 3.0.0 wording", {
test_that("lint_without_terminal_newline respects linter configuration", {
dir <- withr::local_tempdir()
withr::local_dir(dir)
temp_file <- withr::local_tempfile(tmpdir = dir, fileext = ".R")
Expand All @@ -357,29 +356,13 @@ test_that("lint_without_terminal_newline respects linter configuration and lintr
temp_file, "x <- 1", lintr::linters_with_defaults()
)
expect_length(fallback_lints, 1L)
expect_match(fallback_lints[[1L]]$message, "terminal newline", fixed = TRUE)
expect_equal(fallback_lints[[1L]]$message, "Add a terminal newline.")

fallback_untitled <- lint_without_terminal_newline(
"", "x <- 1", lintr::linters_with_defaults()
)
expect_length(fallback_untitled, 1L)
expect_match(fallback_untitled[[1L]]$message, "terminal newline", fixed = TRUE)

v300_linter <- lintr::Linter(function(source_expression) {
list(lintr::Lint(
filename = source_expression$filename,
line_number = length(source_expression$file_lines),
column_number = 7L,
type = "style",
message = "Missing terminal newline.",
line = "x <- 1"
))
}, name = "trailing_blank_lines_linter")
lints_v300 <- lint_without_terminal_newline(
"", "x <- 1", list(trailing_blank_lines_linter = v300_linter)
)
expect_length(lints_v300, 1L)
expect_equal(lints_v300[[1L]]$message, "Missing terminal newline.")
expect_equal(fallback_untitled[[1L]]$message, "Add a terminal newline.")

fallback_disabled <- lint_without_terminal_newline(
temp_file, "x <- 1", list(line_length_linter = lintr::line_length_linter())
Expand Down
Loading