Boost Your Shiny App’s Code Quality with {box.linters} in {rhino} 1.8.0

This article was first published on Appsilon | Enterprise R Shiny Dashboards , and kindly contributed to python-bloggers. (You can report issue about the content on this page here)
Want to share your content on python-bloggers? click here.

With the new {rhino} 1.8.0, we’re happy to introduce {box.linters}. This new package helps check code for projects using {box}.

Looking to upgrade your enterprise dashboard to Rhino? Check out how it’s done with the World Bank’s Carbon Pricing dashboard. 

In this article, we’ll show you how {box.linters} works with {rhino} 1.8.0 and how it improves code quality and efficiency with linter functions compatible with {box} modules.

Code Quality Practices

At Appsilon, we take code quality seriously; here’s how:

  1. We implement code testing using {testthat} for unit testing, and {shinytest2} and Cypress for end-to-end testing of Shiny applications.
  2. We write modular code using the {box} package to manage namespace conflicts in enterprise-scale applications.
  3. We include {lintr} in our automated CI pipelines to check that the code we write follows a standard style.

Adding end-to-end tests to your application can ensure quality and help catch bugs early. Read our blog post to learn an easy way to automate testing using Cypress and Rhino.

Challenges with Existing Linters and {box} Modules:

These are evident in our {rhino} framework for Shiny. However, {lintr} does not know how to handle modules and packages/libraries imported using {box}. The following is a simple example:

We have to disable lintr::object_usage_linter() in {rhino} to work around this. This means, however, that we lose valuable source code checking provided by lintr::object_usage_linter().

Some of the actions performed by lintr::object_usage_linter() are checking if all of the objects (functions, variables, function parameters) you define inside closures (typically a function scope) are later used within the closure. It also performs checks in the opposite direction, checking that all of the objects (functions, variables, data, parameters) you use or call in a closure were previously defined or imported.

With a small project, leaving such code may not have any real impact. With a larger project, however, logical errors can error as the code misbehaves due to misplaced and orphaned lines.

Introducing {box.linters}: Features and Benefits

Over the past few months, we have worked to provide compatibility between {box} and {lintr}. Some of these were already included in {rhino} v1.7.x.

Since then, we have moved the linter functions to their own package and are happy to announce that {box.linters} is now available for use with{rhino} and any projects that use {box}.

Linter Extensions for {lintr}

{box.linters} default box_default_linters extends lintr::linters_with_defaults().

object_usage_linter is still disabled and in place are {box}-compatible linter functions which:

All linter functions work with {box} aliases for both modules/packages and functions.

{rhino} comes with additional box::use() checks described in the Rhino Style Guide.

How to Use {box.linters}

{rhino} version 1.8.0 comes with {box.linters} support. New {rhino} projects created with rhino::init() come with a .lintr file configured for {box.linters}. As before, rhino::lint_r() will run static code analysis for the project.

Existing {rhino} projects need to be migrated to 1.8.0 to take advantage of {box.linters}.

For non-{rhino} projects, we provide a helper function to set up the .lintr config file.


This will create a .lintr file:

    defaults = box.linters::box_default_linters
encoding: "UTF-8"

Then, use the lint() or lint_dir() functions of {lintr} to check your source code.

Here’s an Example:

Revisiting the box module example at the start of this article:

Notice that the original lint on str_trim() is now resolved, but we have a new lint on dplyr[...]. This is an expected and desired lint.

It is good practice to attach or import only those that you need. This is to avoid namespace conflicts of two or more functions or variables having the same name. R library packages export and attach many function names.

With a larger project with many packages imported, these function names can be duplicates of function names in another package. It becomes difficult to track down from which package a function comes from. {box} keeps imported packages, modules, and the functions contained within a project under control.

{rhino} 1.8.0 GitHub Workflow Update

Included in the 1.8.0 release of {rhino} are streamlined GitHub Actions workflow pipelines. These remove unnecessary, duplicated workflow runs. Run the following to update your existing {rhino} project’s CI workflow.

      system.file("templates", "github_ci", "dot.github", "workflows", "rhino-test.yml", package = "rhino"),
      file.path(".github", "workflows", "rhino-test.yml")


The {box.linters} hex logo depicts two oxpeckers (Genus Buphagus). Oxpeckers are found in sub-Saharan Africa. They eat insects and ticks and are often seen perched on large wild mammals, such as rhinoceros. Actually, one of the species is known in Swahili as Askari wa kifaru which means “the rhino’s guard”.

Summing Up  {box.linters} in {rhino} 1.8.0

In conclusion, the {box.linters} package offers robust solutions for enhancing code quality in projects using the {box} package. We encourage you to integrate {box.linters} into your projects and experience the improvements firsthand.

For any feedback or support, reach out to our team on our community Slack channel or visit our GitHub repository.

Enjoyed this blog post on {box.linters}? Sign up for our weekly newsletter to stay up-to-date on the latest in Shiny and data science.

The post appeared first on

To leave a comment for the author, please follow the link and comment on their blog: Appsilon | Enterprise R Shiny Dashboards .

Want to share your content on python-bloggers? click here.