module_namespace_linter {SIAtools} | R Documentation |
Require usage of ns()
in inputId
and outputId
arguments of UI functions
in {shiny}
modules
Description
A custom linter to be used by {lintr}
package. Checks the functions in a
module's UI part for any missing ns()
calls. These are often omitted when
working with the plain {shiny}
or SIA modules. More details follows below.
Usage
module_namespace_linter(
io_funs = default_shiny_io_functions,
io_args = c("inputId", "outputId"),
ns_funs = c("ns", "NS")
)
Arguments
io_funs |
character, |
io_args |
character, arguments of UI functions to check. |
ns_funs |
character, function names that are considered valid in order
to "namespace" inputs' or outputs' IDs. Defaults to both |
Details
How to use this linter
The easiest way is to call lint_ns()
which is essentially a wrapper around:
lintr::lint_package(linters = module_namespace_linter())
Both calls use our linter for the whole package. However, note that only
module_namespace_linter
is considered. Using this custom linter with the
native ones is somewhat complicated, but not impossible. To the best of our
knowledge, the only place where the {lintr}
documentation mentions the
actual usage of external linters, is in
linters_with_tags() help page. According to that,
you can pass the following call to linters
argument in any supported
lintr::lint_*
function:
lintr::linters_with_tags( tags = NULL, packages = c("lintr", "SIAtools") )
That should select all linters available in both packages.
It is also possible to set up a configuration file that enables you to
shorten calls to {lintr}
functions, use RStudio Addins to lint an active
file, or even apply linters during continuous integration workflows, e.g., in
GitHub Actions or in Travis. To opt for that, create .lintr
file at your
package's root and fill in the following line:
linters: linters_with_tags(tags = NULL, packages = "SIAtools")
Then, you can use the provided addins or call lintr::lint_package()
to get
your modules checked.
What the linter does
By default, the linter looks for any inputId
or outputId
arguments of
{shiny}
's UI functions (such as numericInput or
plotOutput, respectively), and tests if the values
assigned to the arguments are all "namespaced", i.e., wrapped in ns()
function. This is crucial for inputs and outputs in the UI portion of a
module to match their counterparts in the server logic chunk.
Only {shiny}
UI calls that are inside of a tagList in a
function ("lambda" shorthand, \( )
, applies as well) are inspected. This is
because we don't want to cause false alarms for any "ordinary" {shiny}
apps
that aren't modules. All UI portions of modules are usually defined as
functions, and all input/output UI functions are inside a
tagList, so we opted for the this strategy to minimalize
false positive matches outside {shiny}
modules.
We look for any inputId
or outputId
arguments that are named as such. On
top of that, the ns()
omission is detected even if you call the function
without named arguments that would be evaluated as input or output IDs.
However, if you use partial matching (numericInput(inp = "input")
), the
actual input won't get linted, even though it should, as it is eventually
evaluated as inputId
. The same applies for arguments defined outside the
call and passed as a variable, e.g., inp <- "input"; numericInput(inputId = inp)
. That is tricky to catch in a static code analysis, which is employed
in this linter.
Value
A linter
closure. To be used by {lintr}
only. See the first
example below.
See Also
linters for a complete list of linters available in lintr.
Other linter-related functions:
lint_ns()
Examples
# will produce lints
lintr::lint(
text =
"module_ui <- function(id, imports, ...) {
tagList(
numericInput(inputId = \"input_id_without_ns\", ...)
)
}",
linter = module_namespace_linter()
)
# is OK
lintr::lint(
text =
"module_ui <- function(id, imports, ...) {
tagList(
numericInput(inputId = ns(\"input_id_with_ns\"), ...)
)
}",
linter = module_namespace_linter()
)