Skip to contents

Introduction

lintrhelper makes it easy to create custom linters for the lintr package. The best part? You don’t need to know XPath or understand XML parse trees! This vignette will show you how to create powerful linters using simple, intuitive functions.

Installation

# Install from GitHub
devtools::install_github("fabiandistler/lintrhelper")

Why Create Custom Linters?

While lintr comes with many built-in linters, you might want to create custom linters for:

  • Team-specific style guides: Enforce conventions unique to your team or organization
  • Domain-specific rules: Check for patterns specific to your field (e.g., statistical best practices)
  • Package-specific warnings: Flag deprecated functions or suggest package-specific alternatives
  • Project conventions: Enforce naming conventions, file structure rules, etc.

Your First Linter - No XPath!

Let’s create a linter that warns against using T and F instead of TRUE and FALSE:

# Just list the symbols you want to forbid!
no_t_f <- forbid_symbols(
  c("T", "F"),
  "Use TRUE/FALSE instead of {symbol}."
)

# Test it
test_linter(no_t_f, "x <- T", should_lint = TRUE)
test_linter(no_t_f, "x <- TRUE", should_lint = FALSE)

That’s it! No XPath, no XML, just plain R.

Breaking it Down

  • symbols: A character vector of variable names to forbid
  • message: The message to show users. {symbol} gets replaced with the actual symbol found
  • type: (optional) Severity level - “style”, “warning”, or “error”

Understanding XPath

XPath is a query language for XML. R code is parsed into an XML tree, and linters use XPath to find problematic patterns.

Common XPath Patterns

# View all common patterns
xpath_patterns()

# View specific category
xpath_patterns("functions")

Some examples:

  • //SYMBOL - All symbols (variable names)
  • //SYMBOL_FUNCTION_CALL - Function calls
  • //SYMBOL[text() = 'my_var'] - Specific symbol
  • //NUM_CONST - Numeric constants
  • //STR_CONST - String constants

Testing XPath Expressions

Use quick_test() to rapidly test XPath expressions:

quick_test("//SYMBOL[text() = 'T']", "x <- T")
# Shows if the XPath matches and where

Function Call Linters

A very common use case is flagging specific function calls:

no_sapply <- create_function_call_linter(
  function_names = "sapply",
  message = "Use vapply() instead of {function} for type-safe code.",
  linter_name = "no_sapply_linter"
)

# Test it
test_linter(no_sapply, "result <- sapply(1:10, sqrt)", should_lint = TRUE)

You can flag multiple functions:

deprecated_funcs <- create_function_call_linter(
  function_names = c("sapply", "mapply"),
  message = "Function {function} is deprecated in our style guide.",
  linter_name = "deprecated_functions"
)

Note the {function} placeholder - it will be replaced with the actual function name found.

Assignment Linters

Another common pattern is enforcing assignment operator style:

prefer_arrow <- create_assignment_linter(
  forbidden_operators = "=",
  message = "Use <- for assignment, not =.",
  linter_name = "prefer_arrow_assignment",
  type = "style"
)

test_linter(prefer_arrow, "x = 5", should_lint = TRUE)
test_linter(prefer_arrow, "x <- 5", should_lint = FALSE)

Testing Your Linters

The test_linter() function provides several testing modes:

# Basic: does it lint?
test_linter(my_linter, "x <- T", should_lint = TRUE)

# Expect no lints
test_linter(my_linter, "x <- TRUE", should_lint = FALSE)

# Check exact number of lints
test_linter(my_linter, "a <- T; b <- T", n_lints = 2)

# Verify message content
test_linter(
  my_linter,
  "x <- T",
  message_pattern = "TRUE/FALSE"
)

Using Templates

Get started quickly with templates:

# View all templates
linter_template("all")

# Get specific template
linter_template("simple")
linter_template("function_call")
linter_template("assignment")
linter_template("advanced")

Example Linters

The package includes several example linters you can use or learn from:

# Ready-to-use linters
lintr::lint("my_script.R", linters = no_t_f_linter())
lintr::lint("my_script.R", linters = no_attach_linter())
lintr::lint("my_script.R", linters = prefer_arrow_assignment_linter())
lintr::lint("my_script.R", linters = no_sapply_linter())

Advanced Custom Linters

For more complex logic, you can build linters using the full lintr API:

library(lintr)
library(xml2)

advanced_linter <- function() {
  lintr::Linter(function(source_expression) {
    if (!lintr::is_lint_level(source_expression, "file")) {
      return(list())
    }

    xml <- source_expression$full_xml_parsed_content

    # Find all function calls
    nodes <- xml2::xml_find_all(xml, "//SYMBOL_FUNCTION_CALL")

    # Custom filtering logic
    bad_nodes <- Filter(function(node) {
      func_name <- xml2::xml_text(node)
      # Flag functions starting with "old_"
      grepl("^old_", func_name)
    }, nodes)

    # Generate custom lints
    lints <- lapply(bad_nodes, function(node) {
      func_name <- xml2::xml_text(node)
      new_name <- sub("^old_", "new_", func_name)

      lintr::xml_nodes_to_lints(
        node,
        source_expression = source_expression,
        lint_message = sprintf(
          "Function %s is deprecated. Use %s instead.",
          func_name, new_name
        ),
        type = "warning"
      )
    })

    unlist(lints, recursive = FALSE)
  })
}

Using Your Linters with lintr

Once you’ve created your linters, use them with lintr:

# Single file
lintr::lint("my_script.R", linters = my_linter())

# Multiple linters
lintr::lint(
  "my_script.R",
  linters = lintr::linters_with_defaults(
    my_linter = my_linter(),
    another_linter = another_linter()
  )
)

# Entire package
lintr::lint_package(
  linters = lintr::linters_with_defaults(
    my_linter = my_linter()
  )
)

Best Practices

  1. Start simple: Begin with create_simple_linter() and XPath
  2. Test thoroughly: Use test_linter() with various code examples
  3. Use quick_test(): Iterate quickly when developing XPath expressions
  4. Clear messages: Write helpful, actionable lint messages
  5. Appropriate severity: Use “style” for preferences, “warning” for potential issues, “error” for serious problems
  6. Document your linters: Explain why a pattern is problematic

Next Steps

Getting Help

If you encounter issues or have questions: