Skip to content
Open
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
16 changes: 16 additions & 0 deletions ci/string_literals_stress_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@
@foo = 3
puts '#@foo'
puts '#{3}'

$global = 1
puts '\#$global'
puts '"\#$global"'

@ivar = 2
puts '\#@ivar'
puts '"\#@ivar"'

local_var = 3
puts '\#{local_var}'
puts '"\#{local_var}"'
puts %q{text with \#{local_var} test}
puts '\#foo'
puts 'a\#{local_var}c'
puts '\\#{local_var}'
puts %q("")
puts %q(\"\")
puts %Q(\"\")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
puts('\#foo')
puts('a\#{b}c')
puts('\\#{x}')
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
puts("\\#foo")
puts("a\\\#{b}c")
puts("\\\#{x}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
$g = 1
puts('"\#$g"')

@g = 2
puts('"\#@g"')

x = 3
puts('"\#{x}"')
puts(%q{text with \#{x} test})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
$g = 1
puts("\"\\\#$g\"")

@g = 2
puts("\"\\\#@g\"")

x = 3
puts("\"\\\#{x}\"")
puts("text with \\\#{x} test")
63 changes: 63 additions & 0 deletions librubyfmt/src/string_escape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,17 @@ fn escape_string(content: &[u8], opening_delim: u8, closing_delim: u8) -> Vec<u8
bytes.next();
continue;
}
// '\#' is a literal backslash followed by a literal '#', not an escape
// sequence, in single-quoted strings. Don't consume the '#' here — leave
// it for the `b'#'` match arm below so it can decide (based on what
// follows) whether it needs to be escaped to avoid becoming an
// interpolation trigger (e.g. `\#{`, `\#$`, `\#@`) in the double-quoted
// output.
b'#' => {
output.push(b'\\');
output.push(b'\\');
continue;
}
// For everything else, this is not an escape sequence, so we need to
// escape the slash and then print the next character.
_ => {
Expand Down Expand Up @@ -309,6 +320,58 @@ mod tests {
);
}

/// In a single-quoted string, a backslash is only special before `\` or `'`.
/// So `\#$g`, `\#@g` and `\#{x}` are just the literal characters `\`, `#`, and
/// whatever follows — there is no interpolation. When converting to a
/// double-quoted string we must preserve that: the backslash needs to be
/// escaped (`\\`) *and* the `#` needs to be escaped (`\#`) so it doesn't turn
/// into real interpolation.
#[test]
fn single_quoted_literal_backslash_hash_dollar_not_interpolated() {
assert_eq!(
single_to_double_quoted(b"\\#$g", b"'", b"'").as_ref(),
b"\\\\\\#$g"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lot of backslashes.

);
}

#[test]
fn single_quoted_literal_backslash_hash_at_not_interpolated() {
assert_eq!(
single_to_double_quoted(b"\\#@g", b"'", b"'").as_ref(),
b"\\\\\\#@g"
);
}

#[test]
fn single_quoted_literal_backslash_hash_brace_not_interpolated() {
assert_eq!(
single_to_double_quoted(b"\\#{x}", b"'", b"'").as_ref(),
b"\\\\\\#{x}"
);
}

#[test]
fn single_quoted_literal_backslash_hash_before_regular_char() {
// \#foo — the backslash needs escaping, but `#foo` isn't an
// interpolation trigger so the `#` stays unescaped.
assert_eq!(
single_to_double_quoted(b"\\#foo", b"'", b"'").as_ref(),
b"\\\\#foo"
);
}

#[test]
fn single_quoted_escaped_backslash_then_hash_brace_is_interpolation() {
// '\\#{x}' is an *escaped* backslash followed by `#{x}`, which *is*
// interpolation-worthy in the original single-quoted source too — so
// once converted to double-quoted, the `#` must still be escaped to
// preserve the literal (non-interpolated) meaning.
assert_eq!(
single_to_double_quoted(b"\\\\#{x}", b"'", b"'").as_ref(),
b"\\\\\\#{x}"
);
}

// --- escape_string with %q(...) strings ---

#[test]
Expand Down