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

Support for curly-curly syntactic sugar #528

Merged
merged 7 commits into from Jun 30, 2019
@@ -71,6 +71,7 @@ Collate:
'set-assert-args.R'
'style-guides.R'
'styler.R'
'testing-mocks.R'
'testing-public-api.R'
'testing.R'
'token-create.R'
@@ -1,7 +1,14 @@
# A { should never go on its own line
remove_line_break_before_curly_opening <- function(pd) {
rm_break <- (pd$token_after == "'{'") & (pd$token != "COMMENT")
pd$lag_newlines[lag(rm_break)] <- 0L
rm_break_idx <- which((pd$token_after == "'{'") & (pd$token != "COMMENT"))
rm_break_idx <- setdiff(rm_break_idx, nrow(pd))
if (length(rm_break_idx) > 0) {
is_not_curly_curly <- map_chr(rm_break_idx + 1L,
~next_terminal(pd[.x,], vars = "token_after")$token_after
) != "'{'"
is_not_curly_curly_idx <- rm_break_idx[is_not_curly_curly]
pd$lag_newlines[1 + is_not_curly_curly_idx] <- 0L
}
pd
}

@@ -30,6 +37,54 @@ style_line_break_around_curly <- function(strict, pd) {
pd
}

#' Styling around `\{\{`
#'
#' With {rlang} version 0.4, a new syntactic sugar is introduced, the
#' curly-curly operator. It interprets this code in a special way:
#' `call({{ x }})`. See this
#' [blog post](https://www.tidyverse.org/articles/2019/06/rlang-0-4-0/)
#' on the topic. Here, the
#' curly-curly sugar is understood as two opening curly braces, followed by
#' an expression followed by two closing curly braces, e.g. `{{1}}`. `{{1} + 1}`
#' does not contain the curly-curly syntactic sugar according to the above
#' definition. On the other hand
#' `{{ x + y }}` is
#' recognized by styler as containing it (and is parsable code)
#' but will most likely give an error at runtime because the way the syntactic
#' suggar is defined in rlang is to use a
#' single token within curly-curly. In addition, because rlang parses `{{` in
#' a special way (just as `!!`), the expression `{{ x }}` will give a runtime
#' error when used outside of a context that is capable of handling it, e.g.
#' on the top level (that is, not within function call like
#' `rlang_fun({{ x }})`) or within a base R function such as [c()].
#' However, these differences are assumed to be irrelevant for
#' styling curly-curly, as much as they were for styling `!!`.
#' @details
#' curly-curly affects styling of line break and spaces, namely:
#'
#' * No line break after first or second `\{`, before third and fourth `\{`.
#' * No space after first and third `\{`, one space after second and before third
#' `\}`.
#' @param pd A parse table.
#' @keywords internal
#' @seealso style_text_without_curly_curly
set_line_break_around_curly_curly <- function(pd) {
if (is_curly_expr(pd)) {
# none after {
opening_before <- (pd$token == "'{'") &
(pd$token_before == "'{'" | pd$token_after == "'{'")

# none before }
closing_before <- (pd$token == "'}'") &
(pd$token_after == "'}'" | pd$token_before == "'}'")
if (any(opening_before) && any(closing_before)) {
pd$lag_newlines[lag(opening_before, default = FALSE)] <- 0L
pd$lag_newlines[closing_before] <- 0L
}
}
pd
}

# if ) follows on }, don't break line
remove_line_break_before_round_closing_after_curly <- function(pd) {
round_after_curly <- pd$token == "')'" & (pd$token_before == "'}'")
@@ -158,7 +158,36 @@ add_space_after_for_if_while <- function(pd_flat) {
pd_flat
}

#' @rdname set_line_break_around_curly_curly
#' @keywords internal
set_space_in_curly_curly <- function(pd) {
if (is_curly_expr(pd)) {

after_inner_opening <- pd$token == "'{'" & pd$token_before == "'{'"
before_inner_closing <- lead(pd$token == "'}'" & pd$token_after == "'}'")
is_curly_curly_inner <- any(after_inner_opening, na.rm = TRUE) &&
any(before_inner_closing, na.rm = TRUE)
if (is_curly_curly_inner) {
pd$spaces[after_inner_opening] <- 1L
pd$spaces[before_inner_closing] <- 1L
}

after_outer_opening <- pd$token == "'{'" & pd$token_after == "'{'"
before_outer_closing <- lead(pd$token == "'}'" & pd$token_before == "'}'")
is_curly_curly_outer <- any(after_outer_opening, na.rm = TRUE) &&
any(before_outer_closing, nna.rm = TRUE)
if (is_curly_curly_outer) {
pd$spaces[after_outer_opening] <- 0L
pd$spaces[before_outer_closing] <- 0L
}
}
pd
}

add_space_before_brace <- function(pd_flat) {
# TODO remove this, it has no effect since { can only appear in the first
# position of the nest and taking lead(op_after, default = FALSE) will always
# yield a vector of FALSE only.
op_after <- pd_flat$token %in% "'{'"
if (!any(op_after)) {
return(pd_flat)
@@ -111,7 +111,8 @@ tidyverse_style <- function(scope = "tokens",
add_space_before_comments
},
set_space_between_levels,
set_space_between_eq_sub_and_comma
set_space_between_eq_sub_and_comma,
set_space_in_curly_curly
)
}

@@ -128,6 +129,9 @@ tidyverse_style <- function(scope = "tokens",
style_line_break_around_curly = partial(style_line_break_around_curly,
strict
),
# must be after style_line_break_around_curly as it remove line
# breaks again for {{.
set_line_break_around_curly_curly,
set_line_break_after_opening_if_call_is_multi_line = if (strict)
partial(
set_line_break_after_opening_if_call_is_multi_line,
@@ -0,0 +1,35 @@
#'`style_text()` without rules for `\{\{`
#'
#' This function mocks [style_text()], but without taking into consideration the
#' rules for the curly-curly syntactic sugar (introduced in rlang 0.4).
#' This function (`style_text_without_curly_curly()`) is needed for testing
#' only, namely to test indention
#' with multiple curly braces in a sequence. It is important to maintain testing
#' for indention rules even as the curly-curly expression is always kept on the
#' same line in the tidyverse style guide because we should
#' ensure the underlaying mechanics for indention work correctly. When
#' indention mechanisms are changed later, e.g. by simplifying
#' [compute_indent_indices()], we must have
#' a way of testing this without the interaction of `\{\{`.
#' @examples
#' styler:::style_text_without_curly_curly("rlang::list2({{ x }} := 2)")
#' styler:::style_text("rlang::list2({{ x }} := 3)")
#' @keywords internal
#' @seealso set_line_break_around_curly_curly
style_text_without_curly_curly <- function(text,
...,
style = tidyverse_style,
transformers = style(...),
include_roxygen_examples = TRUE) {
dots <- list(...)
if ("strict" %in% names(dots)) {
strict <- dots$strict
} else {
strict <- TRUE
}
transformers$line_break$set_line_break_around_curly_curly <- NULL
style_text(text, ...,
style = NULL, transformers = transformers,
include_roxygen_examples = include_roxygen_examples
)
}

Some generated files are not rendered by default. Learn more.

Some generated files are not rendered by default. Learn more.

@@ -0,0 +1,82 @@

## ............................................................................
## line breaks ####
# not inserting line breaks
call({{ x }})

# removing line breaks
call({{
x
}})

call({
{x
}})

call({
{x}}
)

call({
{x}
})

call(
{
{x
}
}
)

## ............................................................................
## spaces ####

# not inserting spaces between braces
call({{ x }})

# removing spaces between braces
call({ { x }})
call({ { x }} )
call( { { x }})
call( { { x } })

# inserting spaces within {{
call({{x }})
call({{x}})
call({{ x}})

# not removing spaces within {{
call({{ x }})


# combine spaces and line breaks
call({{ x}
})

call({
{ x}})

# not applicable when only one curly brace
{
y
}
{ 1 + 1}
{{1 + a} + 1} # not curly-culry!


## ............................................................................
## multiple ####
call({
1
}, a + b, { 33 / f(c)})

call({{ x }}, {{ y}})
call({{ x }}, {{ y}
})
call(
{{ x }}, {{ y}})

call(
{{ x }},
{{ y}} := 3, f(bk)
)
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.