Last active
September 30, 2023 18:31
-
-
Save yjunechoe/9913a70e4904f449c358e1351f0d0617 to your computer and use it in GitHub Desktop.
Linter to check that the `fmt` argument of `sprintf()` comes last
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sprintf_fmt_bottom_linter <- function() { | |
xpath <- " | |
//SYMBOL_FUNCTION_CALL[text() = 'sprintf'] | |
/parent::expr/following-sibling::expr[last()] | |
/preceding-sibling::*[2][not( | |
self::SYMBOL_SUB[text() = 'f' or text() = 'fm' or text() = 'fmt'] | |
)] | |
/preceding-sibling::expr[ | |
preceding-sibling::*[2][ | |
self::SYMBOL_SUB[text() = 'f' or text() = 'fm' or text() = 'fmt'] | |
] | |
or | |
preceding-sibling::*[not( | |
self::SYMBOL_SUB[text() = 'f' or text() = 'fm' or text() = 'fmt'] | |
)] | |
][last()] | |
" | |
lintr::Linter(function(source_expression) { | |
if (!lintr::is_lint_level(source_expression, "expression")) { | |
return(list()) | |
} | |
xml <- source_expression$xml_parsed_content | |
bad_expr <- xml2::xml_find_all(xml, xpath) | |
lintr::xml_nodes_to_lints( | |
bad_expr, | |
source_expression = source_expression, | |
lint_message = "Consider putting the `fmt` argument last for readability", | |
type = "style" | |
) | |
}) | |
} |
Author
yjunechoe
commented
Sep 29, 2023
Addin:
sprintf_fmt_bottom_transform <- function(x) {
sprintf_args <- as.list(parse(text = x)[[1]])[-1]
if (!is.null(names(sprintf_args))) {
fmt_arg <- which.max(pmatch(names(sprintf_args), "fmt"))
} else {
fmt_arg <- 1
}
pos_arg <- sprintf_args[-fmt_arg]
fmt_str <- sprintf_args[[fmt_arg]]
matches <- regmatches(fmt_str, gregexpr("%.*?[aAdifeEgGosxX%]", fmt_str))[[1]]
matches_stripped <- gsub("%(\\d+\\$)?", "", matches)
matches_positioned <- paste0("%", seq_along(pos_arg), "$", matches_stripped)
fmt_template <- strsplit(fmt_str, "%.*?[aAdifeEgGosxX%]")[[1]]
offset <- length(fmt_template) - length(matches_positioned)
fmt_new <- paste(t(cbind(fmt_template, c(matches_positioned, rep("", offset)))), collapse = "")
sprintf_args <- c(setNames(pos_arg, matches_positioned), list(fmt = fmt_new))
sprintf_new_expr <- as.call(c(list(quote(sprintf)), sprintf_args))
sprintf_new_expr
}
addin_fmt_bottom_transform <- function() {
context <- rstudioapi::getActiveDocumentContext()
sel <- context$selection[[1]]$range
txt <- context$selection[[1]]$text
tryCatch(
{
expr <- sprintf_fmt_bottom_transform(txt)
# styling
new <- deparse(expr)
new <- gsub("\\(", "\\(\n ", new)
new <- gsub("\\)", "\n\\)", new)
new <- gsub(",", ",\n ", new)
new <- gsub("`", '"', new)
rstudioapi::modifyRange(sel, new)
},
error = function(...) NULL
)
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment