From 5f5926e0bad3d2cc0d3426c1028f79a4db33bbe1 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 17 Nov 2023 12:44:17 +0000 Subject: [PATCH] feat(prettier) Use minimum amount of escapes in strings (#1376) ```ts [ "foo", 'bar', "bar'", 'bar\'', 'bar"', "bar\"", "bar\"\"\"''", "bar\"\"\"'''", ] ``` Becomes: ```ts ["foo", "bar", "bar'", "bar'", 'bar"', 'bar"', 'bar"""\'\'', "bar\"\"\"'''"]; ``` --- crates/oxc_prettier/src/format/mod.rs | 4 +- crates/oxc_prettier/src/format/string.rs | 57 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 crates/oxc_prettier/src/format/string.rs diff --git a/crates/oxc_prettier/src/format/mod.rs b/crates/oxc_prettier/src/format/mod.rs index 0abbb2bd3..8bdcd5b93 100644 --- a/crates/oxc_prettier/src/format/mod.rs +++ b/crates/oxc_prettier/src/format/mod.rs @@ -21,6 +21,7 @@ mod function_parameters; mod module; mod object; mod statement; +mod string; mod ternary; use crate::{ @@ -720,8 +721,7 @@ impl<'a> Format<'a> for RegExpLiteral { impl<'a> Format<'a> for StringLiteral { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let quote = if p.options.single_quote { "'" } else { "\"" }; - array!(p, ss!(quote), p.str(&self.value), ss!(quote)) + array!(p, p.str(&string::print_string(self.value.as_str(), p.options.single_quote))) } } diff --git a/crates/oxc_prettier/src/format/string.rs b/crates/oxc_prettier/src/format/string.rs new file mode 100644 index 000000000..4c3c80a56 --- /dev/null +++ b/crates/oxc_prettier/src/format/string.rs @@ -0,0 +1,57 @@ +fn get_preferred_quote(raw: &str, prefer_single_quote: bool) -> char { + let (preferred_quote_char, alternate_quote_char) = + if prefer_single_quote { ('\'', '"') } else { ('"', '\'') }; + + let mut preferred_quote_count = 0; + let mut alternate_quote_count = 0; + + for character in raw.chars() { + if character == preferred_quote_char { + preferred_quote_count += 1; + } else if character == alternate_quote_char { + alternate_quote_count += 1; + } + } + + if preferred_quote_count > alternate_quote_count { + alternate_quote_char + } else { + preferred_quote_char + } +} + +fn make_string(raw_text: &str, enclosing_quote: char) -> String { + let other_quote = if enclosing_quote == '"' { '\'' } else { '"' }; + let mut result = String::new(); + result.push(enclosing_quote); + + let mut chars = raw_text.chars().peekable(); + while let Some(c) = chars.next() { + match c { + '\\' => { + if let Some(&next_char) = chars.peek() { + if next_char != other_quote { + result.push('\\'); + } + result.push(next_char); + chars.next(); + } else { + result.push('\\'); + } + } + _ if c == enclosing_quote => { + result.push('\\'); + result.push(c); + } + _ => result.push(c), + } + } + + result.push(enclosing_quote); + result +} + +pub(super) fn print_string(raw_text: &str, prefer_single_quote: bool) -> String { + let enclosing_quote = get_preferred_quote(raw_text, prefer_single_quote); + make_string(raw_text, enclosing_quote) +}