Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find missing ns #1159

Draft
wants to merge 16 commits into
base: dev
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Imports:
here,
htmltools,
rlang (>= 1.0.0),
roxygen2,
shiny (>= 1.5.0),
utils,
yaml
Expand All @@ -55,7 +56,6 @@ Suggests:
remotes,
renv,
rmarkdown,
roxygen2,
rsconnect,
rstudioapi,
sass,
Expand All @@ -71,4 +71,4 @@ Config/testthat/edition: 3
Encoding: UTF-8
Language: en-US
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.1
RoxygenNote: 7.3.2
13 changes: 13 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Generated by roxygen2: do not edit by hand

S3method(format,rd_section_shinyModule)
S3method(roxy_tag_parse,roxy_tag_shinyModule)
S3method(roxy_tag_rd,roxy_tag_shinyModule)
export(activate_js)
export(add_css_file)
export(add_dockerfile)
Expand Down Expand Up @@ -34,6 +37,7 @@ export(browser_button)
export(browser_dev)
export(bundle_resources)
export(cat_dev)
export(check_namespace_sanity)
export(create_golem)
export(css_template)
export(detach_all_attached)
Expand Down Expand Up @@ -76,6 +80,7 @@ export(set_golem_name)
export(set_golem_options)
export(set_golem_version)
export(set_golem_wd)
export(shinyModule_roclet)
export(use_external_css_file)
export(use_external_file)
export(use_external_html_template)
Expand All @@ -102,6 +107,13 @@ importFrom(attempt,stop_if_not)
importFrom(attempt,without_warning)
importFrom(config,get)
importFrom(htmltools,htmlDependency)
importFrom(roxygen2,block_get_tag)
importFrom(roxygen2,parse_package)
importFrom(roxygen2,rd_section)
importFrom(roxygen2,roclet)
importFrom(roxygen2,roxy_tag_parse)
importFrom(roxygen2,roxy_tag_rd)
importFrom(roxygen2,tag_markdown)
importFrom(shiny,addResourcePath)
importFrom(shiny,getShinyOption)
importFrom(shiny,htmlTemplate)
Expand All @@ -112,6 +124,7 @@ importFrom(tools,file_ext)
importFrom(utils,capture.output)
importFrom(utils,file.edit)
importFrom(utils,getFromNamespace)
importFrom(utils,getParseData)
importFrom(utils,menu)
importFrom(utils,modifyList)
importFrom(utils,person)
Expand Down
66 changes: 66 additions & 0 deletions R/find_invalid_url.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#' @noRd
extract_urls_from_file <- function(file) {
content <- readLines(file, warn = FALSE)
url_pattern <- "(http|https)://[a-zA-Z0-9./?=_-]*"
urls <- unique(unlist(regmatches(content, gregexpr(url_pattern, content))))
names(urls) <- rep(file, length(urls))
return(urls)
}

#' @noRd
check_url <- function(url) {
response <- try(httr::GET(url), silent = TRUE)
if (inherits(response, "try-error")) {
res <- FALSE
}

res <- httr::status_code(response) == 200
res <- stats::setNames(res, url)

return(res)
}

#' Check for the validity of the URLs in the R folder
#'
#' @param exclude a character vector of urls to exclude
#'
#' @return a message if some URLs are invalid
#' @export
check_url_validity <- function(exclude = NA_character_) {
if (!curl::has_internet()) {
cli::cli_alert_info("No internet connection.")
return(invisible(FALSE))
}

if (!dir.exists("R")) {
cli::cli_alert_info("No R folder found.")
return(invisible(FALSE))
}

urls <- purrr::map(
files,
~ extract_urls_from_file(file = .x)
) |>
unlist() |>
purrr::discard(
~ .x %in% exclude
) |>
purrr::map(
check_url
)

invalid_urls <- urls |>
purrr::keep(
~ .x == FALSE
)

if (length(invalid_urls) > 0) {
cli::cli_alert_info("Some URLs are invalid.")
purrr::walk(
invalid_urls,
~ cli::cli_alert_danger(sprintf("URL %s is invalid in file {.file %s}", names(.x), names(invalid_urls)))
)
} else {
cli::cli_alert_success("All URLs are valid.")
}
}
206 changes: 206 additions & 0 deletions R/find_missing_ns.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#' @noRd
is_ns <- function(text) {
text == "ns"
}

#' @noRd
is_shiny_input_output_funmodule <- function(
text,
extend_input_output_funmodule = NA_character_) {
stopifnot(is.character(extend_input_output_funmodule))

input_output_knew <- c("Input|Output|actionButton|radioButtons")
ui_funmodule_pattern <- c("mod_\\w+_ui")

patterns <- paste(
input_output_knew,
ui_funmodule_pattern,
sep = "|"
)

if (
!is.null(extend_input_output_funmodule) ||
!is.na(extend_input_output_funmodule) ||
extend_input_output_funmodule == ""
) {
patterns <- paste(
patterns,
extend_input_output_funmodule,
sep = "|"
)
}

grepl(
pattern = patterns,
x = text
)
}

#' @noRd
fix_ns_in_data <- function(data) {
for (i in 1:nrow(data)) {
line_index <- data$next_line1[i]
col_start <- data$next_col1[i]
col_end <- data$next_col2[i]
file <- data$path[i]

file_content <- readLines(file)

line_to_modify <- file_content[line_index]
modified_line <- paste0(
substr(line_to_modify, 1, col_start - 1),
"ns(",
substr(line_to_modify, col_start, col_end),
")",
substr(line_to_modify, col_end + 1, nchar(line_to_modify))
)
file_content[line_index] <- modified_line
writeLines(file_content, file)
}
}

#' @noRd
#' @importFrom utils getParseData
check_namespace_in_file <- function(
path,
extend_input_output_funmodule = NA_character_) {
getParseData(
parse(
file = path,
keep.source = TRUE
)
) |>
dplyr::mutate(
path = path
) |>
dplyr::filter(
token %in% c(
"SYMBOL_FUNCTION_CALL",
"STR_CONST"
)
) |>
dplyr::mutate(
is_input_output_funmodule = is_shiny_input_output_funmodule(
text = text,
extend_input_output_funmodule = extend_input_output_funmodule
),
dplyr::across(
dplyr::starts_with("line"),
~ dplyr::lead(.x),
.names = "next_{.col}"
),
dplyr::across(
dplyr::starts_with("col"),
~ dplyr::lead(.x),
.names = "next_{.col}"
),
next_text = dplyr::lead(text),
is_followed_by_ns = is_ns(next_text)
) |>
dplyr::filter(
is_input_output_funmodule
)
}

#' check namespace sanity
#' Will check if the namespace (NS) are correctly set in the shiny modules
#'
#' @param pkg Character. The package path
#' @param extend_input_output_funmodule Character. Extend the input, output or function module to check
#' @param auto_fix Logical. Fix the missing namespace automatically. Default is TRUE
#'
#' @importFrom roxygen2 parse_package block_get_tag
#'
#' @return Logical. TRUE if the namespace are correctly set, FALSE otherwise
#'
#' @export
check_namespace_sanity <- function(
pkg = golem::get_golem_wd(),
extend_input_output_funmodule = NA_character_,
auto_fix = TRUE) {
check_desc_installed()
check_cli_installed()

base_path <- normalizePath(
path = pkg,
mustWork = TRUE
)

encoding <- desc::desc_get("Encoding", file = base_path)[[1]]

if (!identical(encoding, "UTF-8")) {
warning("roxygen2 requires Encoding: UTF-8", call. = FALSE)
}

blocks <- roxygen2::parse_package(
path = base_path,
env = NULL
)

shinymodule_blocks <- blocks |>
purrr::map(
.f = \(x) {
return <- roxygen2::block_get_tag(x, tag = "shinyModule")
if (is.null(return)) {
NULL
} else {
return
}
}
) |>
purrr::compact()

if (length(shinymodule_blocks) == 0) {
cli::cli_alert_info("No shiny module found")
return(invisible(FALSE))
}

shinymodule_links <- shinymodule_blocks |>
purrr::map_chr(
.f = ~ .x[["file"]]
) |>
unique()

data <- shinymodule_links |>
purrr::map_df(
.f = ~ check_namespace_in_file(
path = .x,
extend_input_output_funmodule = extend_input_output_funmodule
)
) |>
dplyr::filter(
!is_followed_by_ns
) |>
dplyr::mutate(
message = sprintf("... see line %d in {.file %s:%d:%d}.", line1, path, line1, col1)
)

missing_ns_detected <- nrow(data)

if (missing_ns_detected == 0) {
cli::cli_alert_success("NS check passed")
return(invisible(TRUE))
}

cli::cli_text(
"It seems that ",
cli::bg_br_yellow(
"{missing_ns_detected} namespace{?s} (NS) {?is/are} missing..."
)
)

cli::cli_alert_info("We recommand to fix {?this/these} {missing_ns_detected} missing namespace{?s} before to continue...")

purrr::walk(data$message, cli::cli_alert_danger)


if (isTRUE(auto_fix)) {
cli::cli_process_start("`auto_fix` is TRUE so we will fix the missing namespace")
fix_ns_in_data(data = data)
cli::cli_process_done()
} else {
return(invisible(FALSE))
}

return(invisible(TRUE))
}
36 changes: 36 additions & 0 deletions R/module_roclet.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#' @importFrom roxygen2 roxy_tag_parse
#' @importFrom roxygen2 roxy_tag_rd
NULL

#' @importFrom roxygen2 tag_markdown
#' @export
roxy_tag_parse.roxy_tag_shinyModule <- function(x) {
tag_markdown(
x = x
)
}

#' @importFrom roxygen2 rd_section
#' @export
roxy_tag_rd.roxy_tag_shinyModule <- function(x, base_path, env) {
rd_section(
type = "shinyModule",
value = x$val
)
}

#' @export
format.rd_section_shinyModule <- function(x, ...) {
paste0(
"\\section{Shiny module}{\n",
x$value,
"\n}"
)
}

#' shinyModule tag
#' @importFrom roxygen2 roclet
#' @export
shinyModule_roclet <- function() {
roclet("shinyModule")
}
Loading
Loading