tests/testthat/test-keyword_quote_linter.R

test_that("keyword_quote_linter skips allowed usages", {
  linter <- keyword_quote_linter()

  # main use case: c()
  expect_lint("x <- c(1, 2, 4, 5)", NULL, linter)
  expect_lint("x <- c(a = 1, 2)", NULL, linter)
  expect_lint("x <- c(a = 1, b = 2)", NULL, linter)
  expect_lint("y <- c(`a b` = 1, `c d` = 2)", NULL, linter)
  expect_lint('y <- c("a b" = 1, "c d" = 2)', NULL, linter)
  expect_lint("z <- c('a b' = 1, c = 2)", NULL, linter)

  # don't catch strings as arguments
  expect_lint('c(A = "a")', NULL, linter)
  # don't catch unnamed arguments
  expect_lint('c(1, 2, "a")', NULL, linter)
  # don't get thrown off by missing arguments
  expect_lint("alist(`a b` =)", NULL, linter)

  # other use cases: switch() and list()
  expect_lint("list(a = 1, b = list(c = 2))", NULL, linter)
  expect_lint("list(`a b` = 1, c = 2:6)", NULL, linter)

  expect_lint("switch(x, a = 1, b = 2)", NULL, linter)
  expect_lint(
    "switch(x, `a b` = 1, c = 2:6)",
    NULL,
    linter
  )
})

test_that("keyword_quote_linter blocks simple disallowed usages", {
  linter <- keyword_quote_linter()
  lint_msg <- "Only quote named arguments to functions"

  expect_lint('c("a" = 1, b = 2)', lint_msg, linter)

  expect_lint(
    "c('a' = 1, 'b' = 2)",
    list(lint_msg, lint_msg),
    linter
  )

  expect_lint(
    "c(`a` = 1, b = list(`c` = 2))",
    list(lint_msg, lint_msg),
    linter
  )

  # missing argument is caught
  expect_lint("alist(`a` = )", lint_msg, linter)

  expect_lint(
    "switch(x, `a` = c('b' = list(\"c\" = 1)))",
    list(lint_msg, lint_msg, lint_msg),
    linter
  )
})

test_that("keyword_quote_linter skips quoting on reserved words", {
  linter <- keyword_quote_linter()

  expect_lint("c(`next` = 1, `while` = 2)", NULL, linter)
  expect_lint("switch(x, `for` = 3, `TRUE` = 4)", NULL, linter)
  expect_lint("list('NA' = 5, 'Inf' = 6)", NULL, linter)
})

test_that("keyword_quote_linter works on more common functions", {
  linter <- keyword_quote_linter()
  lint_msg <- "Only quote named arguments to functions"

  expect_lint("data.frame('a' = 1)", lint_msg, linter)
  expect_lint("data.table('a' = 1)", lint_msg, linter)
  expect_lint("data.table::data.table('a' = 1)", lint_msg, linter)
  expect_lint("rbind('a' = 1)", lint_msg, linter)
  expect_lint("cbind('a' = 1)", lint_msg, linter)
})

test_that("keyword_quote_linter finds blocked usages in any function call", {
  expect_lint(
    "foo('a' = 1)",
    rex::rex("Only quote named arguments to functions"),
    keyword_quote_linter()
  )
})

test_that("keyword_quote_linter blocks quoted assignment targets", {
  linter <- keyword_quote_linter()
  backtick_msg <- rex::rex("Use backticks to create non-syntactic names, not quotes.")
  assign_msg <- "Only quote targets of assignment if necessary"

  expect_lint('"foo bar" <- 1', backtick_msg, linter)
  expect_lint("'foo bar' = 1", backtick_msg, linter)
  # valid choice: use backticks
  expect_lint("`foo bar` = 1", NULL, linter)

  expect_lint('"foo" <- 1', assign_msg, linter)
  expect_lint("'foo' = 1", assign_msg, linter)
  expect_lint("`foo` = 1", assign_msg, linter)

  # don't include data.table assignments
  expect_lint('DT[, "a" := 1]', NULL, linter)
  expect_lint("DT[, 'a' := 1]", NULL, linter)
  expect_lint("DT[, `a` := 1]", NULL, linter)

  # include common use cases: [<-/$ methods and infixes
  expect_lint('"$.my_class" <- function(x, key) { }', backtick_msg, linter)
  expect_lint("'Setter[<-.my_class' = function(x, idx, value) { }", backtick_msg, linter)
  expect_lint('"%nin%" <- function(x, table) !x %in% table', backtick_msg, linter)

  # right assignment
  expect_lint('1 -> "foo"', assign_msg, linter)
  expect_lint("1 -> foo", NULL, linter)
  expect_lint('1 -> "a b"', backtick_msg, linter)
})

test_that("keyword_quote_linter blocks quoted $, @ extractions", {
  linter <- keyword_quote_linter()
  backtick_msg <- rex::rex("Use backticks to create non-syntactic names, not quotes.")
  dollar_msg <- rex::rex("Only quote targets of extraction with $ if necessary")
  at_msg <- rex::rex("Only quote targets of extraction with @ if necessary")

  expect_lint('x$"foo bar" <- 1', backtick_msg, linter)
  expect_lint("x$'foo bar' = 1", backtick_msg, linter)
  expect_lint('x@"foo bar" <- 1', backtick_msg, linter)
  expect_lint("x@'foo bar' = 1", backtick_msg, linter)
  # valid choice: non-syntactic name with backticks
  expect_lint("x@`foo bar` <- 1", NULL, linter)
  expect_lint("x@`foo bar` = 1", NULL, linter)

  expect_lint('x$"foo" <- 1', dollar_msg, linter)
  expect_lint("x$'foo' = 1", dollar_msg, linter)
  expect_lint('x@"foo" <- 1', at_msg, linter)
  expect_lint("x@'foo' = 1", at_msg, linter)
  expect_lint("x@`foo` <- 1", at_msg, linter)
  expect_lint("x@`foo` = 1", at_msg, linter)
})

test_that("multiple lints are generated correctly", {
  linter <- keyword_quote_linter()

  expect_lint(
    trim_some('{
      foo("a" = 1)
      "b" <- 2
      x$"c"
      y@"d"
    }'),
    list(
      "Only quote named arguments",
      "Only quote targets of assignment",
      "Only quote targets of extraction with \\$",
      "Only quote targets of extraction with @"
    ),
    linter
  )

  # multiple flavors of assignment lints
  expect_lint(
    trim_some('{
      "a" <- 1
      "a b" <- 1
      `a` <- 1
      `a b` <- 1
    }'),
    list(
      "Only quote targets of assignment if necessary",
      "Use backticks to create non-syntactic names, not quotes",
      "Only quote targets of assignment if necessary"
    ),
    linter
  )

  # multiple flavors of extraction lints
  expect_lint(
    trim_some('{
      x$"a"
      x$"a b" <- 1
      x$`a` <- 1
      x$`a b` <- 1
      y@"a"
      y@"a b" <- 1
      y@`a` <- 1
      y@`a b` <- 1
    }'),
    list(
      rex::rex("Only quote targets of extraction with $ if necessary"),
      rex::rex("Use backticks to create non-syntactic names, not quotes."),
      rex::rex("Only quote targets of extraction with $ if necessary"),
      rex::rex("Only quote targets of extraction with @ if necessary"),
      rex::rex("Use backticks to create non-syntactic names, not quotes."),
      rex::rex("Only quote targets of extraction with @ if necessary")
    ),
    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.