diff --git a/DESCRIPTION b/DESCRIPTION index 1535afc..c46d785 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: box.linters Title: Linters for 'box' Modules -Version: 0.9.0.9006 +Version: 0.9.0.9007 Authors@R: c( person("Ricardo Rodrigo", "Basa", role = c("aut", "cre"), email = "opensource+rodrigo@appsilon.com"), diff --git a/R/get_objects_in_strings.R b/R/get_objects_in_strings.R index a9f125f..7bfc215 100644 --- a/R/get_objects_in_strings.R +++ b/R/get_objects_in_strings.R @@ -8,11 +8,19 @@ #' @return A character vector of object and function names found inside `glue` string templates. #' @keywords internal get_objects_in_strings <- function(xml) { + xpath_str_consts <- " //expr/STR_CONST " all_strings <- extract_xml_and_text(xml, xpath_str_consts) + + glue_open <- getOption("glue.open", default = "\\{") + glue_close <- getOption("glue.close", default = "\\}") + + all_strings$text <- gsub(glue_open, "{", all_strings$text) + all_strings$text <- gsub(glue_close, "}", all_strings$text) + text_between_braces <- stringr::str_match_all(all_strings$text, "(\\{(?:\\{??[^\\{]*?\\}))") tryCatch({ @@ -22,7 +30,8 @@ get_objects_in_strings <- function(xml) { return(NULL) } - parsed_code <- parse(text = each_text[, 2], keep.source = TRUE) + text_to_parse <- each_text[substr(each_text[, 2], 1, 2) != "{{", 2] + parsed_code <- parse(text = text_to_parse, keep.source = TRUE) xml_parsed_code <- xml2::read_xml(xmlparsedata::xml_parse_data(parsed_code)) objects_called <- get_object_calls(xml_parsed_code) functions_calls <- get_function_calls(xml_parsed_code) diff --git a/README.md b/README.md index 49425ad..1281ba7 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,26 @@ Or, use `box.linters` from within a `{rhino}` project: rhino::lint_r() ``` +## {glue} String Interpolation + +`{box.linters}` also looks inside `{glue}` strings. The default opening and closing delimiters are "`{`" and "`}`". `{glue}` provides a way to [customize delimiters](https://glue.tidyverse.org/reference/glue.html). Support for custom `{glue}` delimiters are provided *project-wide* by setting `glue.open` and `glue.close` options in the following manner: + +```r +options( + list( + glue.open = "<<", + glue.close = ">>" + ) +) +``` + +This is consistent with [`glue::glue()`](https://glue.tidyverse.org/reference/glue.html), doubling the full delimiter escapes it. + +Because setting `glue.open` and `glue.close` will be *global* or *project-wide*, it is advised to invoke `glue` in the following manner to avoid confusion: + +```r +glue::glue(..., .open = getOption("glue.open"), .close = getOption("glue.close")) +``` ## Contribute diff --git a/tests/testthat/test-get_objects_in_strings.R b/tests/testthat/test-get_objects_in_strings.R index bed2e5b..7e8d8bd 100644 --- a/tests/testthat/test-get_objects_in_strings.R +++ b/tests/testthat/test-get_objects_in_strings.R @@ -89,6 +89,17 @@ test_that("get_objects_in_strings ignores literal glue objects {{ }}", { expect_equal(results, NULL) }) +test_that("get_objects_in_strings ignores literal glue objects {{ }} mixed in with { }", { + code <- " + string <- \"Some {{value_a + 1}} in a {value_b} string.\" + " + xml_code <- code_to_xml_expr(code) + results <- get_objects_in_strings(xml_code) + should_find <- "value_b" + + expect_equal(results, should_find) +}) + test_that("get_objects_in_strings extracts objects from multiline code", { code <- " string <- \"Some text { @@ -128,3 +139,54 @@ test_that("get_objects_in_strings handles multiple string constants in code", { expect_setequal(results, should_find) }) + +test_that("get_objects_in_strings handles custom glue .open and .close symbols", { + withr::with_options( + list( + glue.open = "<<", + glue.close = ">>" + ), + { + code <- " + string <- \"Some <> and <> in a string.\" + " + xml_code <- code_to_xml_expr(code) + results <- get_objects_in_strings(xml_code) + should_find <- c("value_a", "value_b") + + expect_setequal(results, should_find) + + code <- " + string <- \"Some <> and in a string.\" + " + xml_code <- code_to_xml_expr(code) + results <- get_objects_in_strings(xml_code) + should_find <- c("value_a") + + expect_setequal(results, should_find) + + code <- " + string <- \"Some <> and <<<>>> in a string.\" + " + xml_code <- code_to_xml_expr(code) + results <- get_objects_in_strings(xml_code) + should_find <- c("value_a") + + expect_setequal(results, should_find) + + code <- " + string <- \"Some text << + { + internal_var <- external_var + some_function_call(internal_var, another_external_var) + } + >> here.\" + " + xml_code <- code_to_xml_expr(code) + results <- get_objects_in_strings(xml_code) + should_find <- c("external_var", "some_function_call", "internal_var", "another_external_var") + + expect_setequal(results, should_find) + } + ) +})