From 4cc661f95608d05a1de25716cd6a72dddbffe822 Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Mon, 16 Dec 2024 11:12:04 +0900 Subject: [PATCH 1/9] Rename print/string > print/literal --- .../src/format/print/{string.rs => literal.rs} | 10 +++++----- crates/oxc_prettier/src/format/print/mod.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename crates/oxc_prettier/src/format/print/{string.rs => literal.rs} (100%) diff --git a/crates/oxc_prettier/src/format/print/string.rs b/crates/oxc_prettier/src/format/print/literal.rs similarity index 100% rename from crates/oxc_prettier/src/format/print/string.rs rename to crates/oxc_prettier/src/format/print/literal.rs index c48ba8ff36279..147aabec15074 100644 --- a/crates/oxc_prettier/src/format/print/string.rs +++ b/crates/oxc_prettier/src/format/print/literal.rs @@ -2,6 +2,11 @@ use oxc_allocator::String; use crate::Prettier; +pub fn print_string<'a>(p: &Prettier<'a>, raw_text: &str, prefer_single_quote: bool) -> &'a str { + let enclosing_quote = get_preferred_quote(raw_text, prefer_single_quote); + make_string(p, raw_text, enclosing_quote).into_bump_str() +} + fn get_preferred_quote(raw: &str, prefer_single_quote: bool) -> char { let (preferred_quote_char, alternate_quote_char) = if prefer_single_quote { ('\'', '"') } else { ('"', '\'') }; @@ -54,8 +59,3 @@ fn make_string<'a>(p: &Prettier<'a>, raw_text: &str, enclosing_quote: char) -> S result.push(enclosing_quote); result } - -pub fn print_string<'a>(p: &Prettier<'a>, raw_text: &str, prefer_single_quote: bool) -> &'a str { - let enclosing_quote = get_preferred_quote(raw_text, prefer_single_quote); - make_string(p, raw_text, enclosing_quote).into_bump_str() -} diff --git a/crates/oxc_prettier/src/format/print/mod.rs b/crates/oxc_prettier/src/format/print/mod.rs index 411902c3e0b46..119fd93f70d9a 100644 --- a/crates/oxc_prettier/src/format/print/mod.rs +++ b/crates/oxc_prettier/src/format/print/mod.rs @@ -13,6 +13,6 @@ pub mod module; pub mod object; pub mod property; pub mod statement; -pub mod string; +pub mod literal; pub mod template_literal; pub mod ternary; From 5604342dc2623e886365dec87f88c341ac3b4bb9 Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Mon, 16 Dec 2024 11:12:24 +0900 Subject: [PATCH 2/9] Refactor printing regexp literal --- crates/oxc_prettier/src/format/js.rs | 56 ++++++---------------------- 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/crates/oxc_prettier/src/format/js.rs b/crates/oxc_prettier/src/format/js.rs index c64ea002a27d7..49f548056ee28 100644 --- a/crates/oxc_prettier/src/format/js.rs +++ b/crates/oxc_prettier/src/format/js.rs @@ -11,7 +11,8 @@ use crate::{ format::{ print::{ array, arrow_function, assignment, binaryish, block, call_expression, class, function, - function_parameters, misc, module, object, property, string, template_literal, ternary, + function_parameters, literal, misc, module, object, property, template_literal, + ternary, }, Format, }, @@ -61,7 +62,7 @@ impl<'a> Format<'a> for Directive<'a> { let mut parts = Vec::new_in(p.allocator); parts.push(dynamic_text!( p, - string::print_string(p, self.directive.as_str(), p.options.single_quote,) + literal::print_string(p, self.directive.as_str(), p.options.single_quote,) )); if let Some(semi) = p.semi() { parts.push(semi); @@ -977,12 +978,7 @@ impl<'a> Format<'a> for BigIntLiteral<'a> { impl<'a> Format<'a> for RegExpLiteral<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let mut parts = Vec::new_in(p.allocator); - parts.push(text!("/")); - parts.push(dynamic_text!(p, self.regex.pattern.source_text(p.source_text).as_ref())); - parts.push(text!("/")); - parts.push(self.regex.flags.format(p)); - array!(p, parts) + dynamic_text!(p, &self.regex.to_string()) } } @@ -991,7 +987,7 @@ impl<'a> Format<'a> for StringLiteral<'a> { wrap!(p, self, StringLiteral, { let raw = &p.source_text[(self.span.start + 1) as usize..(self.span.end - 1) as usize]; // TODO: implement `makeString` from prettier/src/utils/print-string.js - dynamic_text!(p, string::print_string(p, raw, p.options.single_quote)) + dynamic_text!(p, literal::print_string(p, raw, p.options.single_quote)) }) } } @@ -1216,7 +1212,7 @@ impl<'a> Format<'a> for PropertyKey<'a> { if need_quote { dynamic_text!( p, - string::print_string(p, &ident.name, p.options.single_quote) + literal::print_string(p, &ident.name, p.options.single_quote) ) } else { ident.format(p) @@ -1235,7 +1231,11 @@ impl<'a> Format<'a> for PropertyKey<'a> { } else { dynamic_text!( p, - string::print_string(p, literal.value.as_str(), p.options.single_quote,) + literal::print_string( + p, + literal.value.as_str(), + p.options.single_quote, + ) ) } } @@ -1243,7 +1243,7 @@ impl<'a> Format<'a> for PropertyKey<'a> { if need_quote { dynamic_text!( p, - string::print_string(p, &literal.raw_str(), p.options.single_quote) + literal::print_string(p, &literal.raw_str(), p.options.single_quote) ) } else { literal.format(p) @@ -1723,35 +1723,3 @@ impl<'a> Format<'a> for AssignmentPattern<'a> { }) } } - -impl<'a> Format<'a> for RegExpFlags { - fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let mut string = std::vec::Vec::with_capacity(self.iter().count()); - if self.contains(Self::D) { - string.push('d'); - } - if self.contains(Self::G) { - string.push('g'); - } - if self.contains(Self::I) { - string.push('i'); - } - if self.contains(Self::M) { - string.push('m'); - } - if self.contains(Self::S) { - string.push('s'); - } - if self.contains(Self::U) { - string.push('u'); - } - if self.contains(Self::V) { - string.push('v'); - } - if self.contains(Self::Y) { - string.push('y'); - } - let sorted = string.iter().collect::(); - dynamic_text!(p, &sorted) - } -} From ac607b7b59418a175a6e2008422a9ee6a9ea4441 Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Mon, 16 Dec 2024 12:28:17 +0900 Subject: [PATCH 3/9] Fix numeric literal --- crates/oxc_prettier/src/format/js.rs | 59 +---------------- .../oxc_prettier/src/format/print/literal.rs | 65 ++++++++++++++++++- 2 files changed, 65 insertions(+), 59 deletions(-) diff --git a/crates/oxc_prettier/src/format/js.rs b/crates/oxc_prettier/src/format/js.rs index 49f548056ee28..71bd7523b7503 100644 --- a/crates/oxc_prettier/src/format/js.rs +++ b/crates/oxc_prettier/src/format/js.rs @@ -906,64 +906,7 @@ impl<'a> Format<'a> for NullLiteral { impl<'a> Format<'a> for NumericLiteral<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - wrap!(p, self, NumericLiteral, { - // See https://github.com/prettier/prettier/blob/3.3.3/src/utils/print-number.js - // Perf: the regexes from prettier code above are ported to manual search for performance reasons. - let mut string = self.span.source_text(p.source_text).cow_to_ascii_lowercase(); - - // Remove unnecessary plus and zeroes from scientific notation. - if let Some((head, tail)) = string.split_once('e') { - let negative = if tail.starts_with('-') { "-" } else { "" }; - let trimmed = tail.trim_start_matches(['+', '-']).trim_start_matches('0'); - if trimmed.starts_with(|c: char| c.is_ascii_digit()) { - string = Cow::Owned(std::format!("{head}e{negative}{trimmed}")); - } - } - - // Remove unnecessary scientific notation (1e0). - if let Some((head, tail)) = string.split_once('e') { - if tail.trim_start_matches(['+', '-']).trim_start_matches('0').is_empty() { - string = Cow::Owned(head.to_string()); - } - } - - // Make sure numbers always start with a digit. - if string.starts_with('.') { - string = Cow::Owned(std::format!("0{string}")); - } - - // Remove extraneous trailing decimal zeroes. - if let Some((head, tail)) = string.split_once('.') { - if let Some((head_e, tail_e)) = tail.split_once('e') { - if !head_e.is_empty() { - let trimmed = head_e.trim_end_matches('0'); - if trimmed.is_empty() { - string = Cow::Owned(std::format!("{head}.0e{tail_e}")); - } else { - string = Cow::Owned(std::format!("{head}.{trimmed}e{tail_e}")); - } - } - } else if !tail.is_empty() { - let trimmed = tail.trim_end_matches('0'); - if trimmed.is_empty() { - string = Cow::Owned(std::format!("{head}.0")); - } else { - string = Cow::Owned(std::format!("{head}.{trimmed}")); - } - } - } - - // Remove trailing dot. - if let Some((head, tail)) = string.split_once('.') { - if tail.is_empty() { - string = Cow::Owned(head.to_string()); - } else if tail.starts_with('e') { - string = Cow::Owned(std::format!("{head}{tail}")); - } - } - - dynamic_text!(p, &string) - }) + literal::print_number(p, self.span.source_text(p.source_text)) } } diff --git a/crates/oxc_prettier/src/format/print/literal.rs b/crates/oxc_prettier/src/format/print/literal.rs index 147aabec15074..7e5a2aefdebbd 100644 --- a/crates/oxc_prettier/src/format/print/literal.rs +++ b/crates/oxc_prettier/src/format/print/literal.rs @@ -1,12 +1,75 @@ +use std::borrow::Cow; + +use cow_utils::CowUtils; use oxc_allocator::String; +use oxc_span::Span; -use crate::Prettier; +use crate::{Prettier, ir::Doc, dynamic_text}; pub fn print_string<'a>(p: &Prettier<'a>, raw_text: &str, prefer_single_quote: bool) -> &'a str { let enclosing_quote = get_preferred_quote(raw_text, prefer_single_quote); make_string(p, raw_text, enclosing_quote).into_bump_str() } +// See https://github.com/prettier/prettier/blob/3.3.3/src/utils/print-number.js +// Perf: the regexes from prettier code above are ported to manual search for performance reasons. +pub fn print_number<'a>(p: &Prettier<'a>, raw_text: &str) -> Doc<'a> { + let mut string = raw_text.cow_to_ascii_lowercase(); + + // Remove unnecessary plus and zeroes from scientific notation. + if let Some((head, tail)) = string.split_once('e') { + let negative = if tail.starts_with('-') { "-" } else { "" }; + let trimmed = tail.trim_start_matches(['+', '-']).trim_start_matches('0'); + if trimmed.starts_with(|c: char| c.is_ascii_digit()) { + string = Cow::Owned(std::format!("{head}e{negative}{trimmed}")); + } + } + + // Remove unnecessary scientific notation (1e0). + if let Some((head, tail)) = string.split_once('e') { + if tail.trim_start_matches(['+', '-']).trim_start_matches('0').is_empty() { + string = Cow::Owned(head.to_string()); + } + } + + // Make sure numbers always start with a digit. + if string.starts_with('.') { + string = Cow::Owned(std::format!("0{string}")); + } + + // Remove extraneous trailing decimal zeroes. + if let Some((head, tail)) = string.split_once('.') { + if let Some((head_e, tail_e)) = tail.split_once('e') { + if !head_e.is_empty() { + let trimmed = head_e.trim_end_matches('0'); + if trimmed.is_empty() { + string = Cow::Owned(std::format!("{head}.0e{tail_e}")); + } else { + string = Cow::Owned(std::format!("{head}.{trimmed}e{tail_e}")); + } + } + } else if !tail.is_empty() { + let trimmed = tail.trim_end_matches('0'); + if trimmed.is_empty() { + string = Cow::Owned(std::format!("{head}.0")); + } else { + string = Cow::Owned(std::format!("{head}.{trimmed}")); + } + } + } + + // Remove trailing dot. + if let Some((head, tail)) = string.split_once('.') { + if tail.is_empty() { + string = Cow::Owned(head.to_string()); + } else if tail.starts_with('e') { + string = Cow::Owned(std::format!("{head}{tail}")); + } + } + + dynamic_text!(p, &string) +} + fn get_preferred_quote(raw: &str, prefer_single_quote: bool) -> char { let (preferred_quote_char, alternate_quote_char) = if prefer_single_quote { ('\'', '"') } else { ('"', '\'') }; From 780a7a477f66b6a8467781dfa21a6f1a4a7c695d Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Mon, 16 Dec 2024 15:21:30 +0900 Subject: [PATCH 4/9] Fix string related --- crates/oxc_prettier/src/format/print/literal.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/oxc_prettier/src/format/print/literal.rs b/crates/oxc_prettier/src/format/print/literal.rs index 7e5a2aefdebbd..5c3a1a998feec 100644 --- a/crates/oxc_prettier/src/format/print/literal.rs +++ b/crates/oxc_prettier/src/format/print/literal.rs @@ -4,7 +4,7 @@ use cow_utils::CowUtils; use oxc_allocator::String; use oxc_span::Span; -use crate::{Prettier, ir::Doc, dynamic_text}; +use crate::{dynamic_text, ir::Doc, Prettier}; pub fn print_string<'a>(p: &Prettier<'a>, raw_text: &str, prefer_single_quote: bool) -> &'a str { let enclosing_quote = get_preferred_quote(raw_text, prefer_single_quote); @@ -70,14 +70,14 @@ pub fn print_number<'a>(p: &Prettier<'a>, raw_text: &str) -> Doc<'a> { dynamic_text!(p, &string) } -fn get_preferred_quote(raw: &str, prefer_single_quote: bool) -> char { +pub fn get_preferred_quote(not_quoted_raw_text: &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() { + for character in not_quoted_raw_text.chars() { if character == preferred_quote_char { preferred_quote_count += 1; } else if character == alternate_quote_char { @@ -92,12 +92,17 @@ fn get_preferred_quote(raw: &str, prefer_single_quote: bool) -> char { } } -fn make_string<'a>(p: &Prettier<'a>, raw_text: &str, enclosing_quote: char) -> String<'a> { +fn make_string<'a>( + p: &Prettier<'a>, + not_quoted_raw_text: &str, + enclosing_quote: char, +) -> String<'a> { let other_quote = if enclosing_quote == '"' { '\'' } else { '"' }; let mut result = String::new_in(p.allocator); result.push(enclosing_quote); - let mut chars = raw_text.chars().peekable(); + // TODO: Need to handle useless escape + let mut chars = not_quoted_raw_text.chars().peekable(); while let Some(c) = chars.next() { match c { '\\' => { From c53149b3164f806b2ab538efb9fc9f5e13164fec Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Mon, 16 Dec 2024 15:58:21 +0900 Subject: [PATCH 5/9] Wip print_string --- crates/oxc_prettier/src/format/js.rs | 32 ++++++++++++++---- .../oxc_prettier/src/format/print/literal.rs | 33 +++++++++++++++++-- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/crates/oxc_prettier/src/format/js.rs b/crates/oxc_prettier/src/format/js.rs index 71bd7523b7503..2634695977aa0 100644 --- a/crates/oxc_prettier/src/format/js.rs +++ b/crates/oxc_prettier/src/format/js.rs @@ -62,7 +62,11 @@ impl<'a> Format<'a> for Directive<'a> { let mut parts = Vec::new_in(p.allocator); parts.push(dynamic_text!( p, - literal::print_string(p, self.directive.as_str(), p.options.single_quote,) + literal::print_string_from_not_quoted_raw_text( + p, + self.directive.as_str(), + p.options.single_quote, + ) )); if let Some(semi) = p.semi() { parts.push(semi); @@ -928,9 +932,15 @@ impl<'a> Format<'a> for RegExpLiteral<'a> { impl<'a> Format<'a> for StringLiteral<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { wrap!(p, self, StringLiteral, { - let raw = &p.source_text[(self.span.start + 1) as usize..(self.span.end - 1) as usize]; - // TODO: implement `makeString` from prettier/src/utils/print-string.js - dynamic_text!(p, literal::print_string(p, raw, p.options.single_quote)) + dynamic_text!( + p, + // TODO: `replaceEndOfLine()` to handle line continuation + literal::print_string( + p, + self.span.source_text(p.source_text), + p.options.single_quote, + ) + ) }) } } @@ -1155,7 +1165,11 @@ impl<'a> Format<'a> for PropertyKey<'a> { if need_quote { dynamic_text!( p, - literal::print_string(p, &ident.name, p.options.single_quote) + literal::print_string_from_not_quoted_raw_text( + p, + &ident.name, + p.options.single_quote + ) ) } else { ident.format(p) @@ -1174,7 +1188,7 @@ impl<'a> Format<'a> for PropertyKey<'a> { } else { dynamic_text!( p, - literal::print_string( + literal::print_string_from_not_quoted_raw_text( p, literal.value.as_str(), p.options.single_quote, @@ -1186,7 +1200,11 @@ impl<'a> Format<'a> for PropertyKey<'a> { if need_quote { dynamic_text!( p, - literal::print_string(p, &literal.raw_str(), p.options.single_quote) + literal::print_string_from_not_quoted_raw_text( + p, + &literal.raw_str(), + p.options.single_quote + ) ) } else { literal.format(p) diff --git a/crates/oxc_prettier/src/format/print/literal.rs b/crates/oxc_prettier/src/format/print/literal.rs index 5c3a1a998feec..96fd36abd252d 100644 --- a/crates/oxc_prettier/src/format/print/literal.rs +++ b/crates/oxc_prettier/src/format/print/literal.rs @@ -6,9 +6,36 @@ use oxc_span::Span; use crate::{dynamic_text, ir::Doc, Prettier}; -pub fn print_string<'a>(p: &Prettier<'a>, raw_text: &str, prefer_single_quote: bool) -> &'a str { - let enclosing_quote = get_preferred_quote(raw_text, prefer_single_quote); - make_string(p, raw_text, enclosing_quote).into_bump_str() +pub fn print_string<'a>( + p: &Prettier<'a>, + quoted_raw_text: &'a str, + prefer_single_quote: bool, +) -> &'a str { + debug_assert!( + quoted_raw_text.starts_with("'") && quoted_raw_text.ends_with("'") + || quoted_raw_text.starts_with("\"") && quoted_raw_text.ends_with("\"") + ); + let original_quote = quoted_raw_text.chars().next().unwrap(); + let not_quoted_raw_text = "ed_raw_text[1..quoted_raw_text.len() - 1]; + + let enclosing_quote = get_preferred_quote(not_quoted_raw_text, prefer_single_quote); + + // This keeps useless escape as-is + if original_quote == enclosing_quote { + return quoted_raw_text; + } + + make_string(p, not_quoted_raw_text, enclosing_quote).into_bump_str() +} + +// TODO: Can this be removed? +pub fn print_string_from_not_quoted_raw_text<'a>( + p: &Prettier<'a>, + not_quoted_raw_text: &str, + prefer_single_quote: bool, +) -> &'a str { + let enclosing_quote = get_preferred_quote(not_quoted_raw_text, prefer_single_quote); + make_string(p, not_quoted_raw_text, enclosing_quote).into_bump_str() } // See https://github.com/prettier/prettier/blob/3.3.3/src/utils/print-number.js From a4ef80b8adb35a0e9edc0897a623207ad3392638 Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Mon, 16 Dec 2024 17:46:52 +0900 Subject: [PATCH 6/9] Implement make_string --- .../oxc_prettier/src/format/print/literal.rs | 31 ++++++++++--------- crates/oxc_prettier/src/format/print/mod.rs | 2 +- .../snapshots/prettier.js.snap.md | 3 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/crates/oxc_prettier/src/format/print/literal.rs b/crates/oxc_prettier/src/format/print/literal.rs index 96fd36abd252d..8d5d6eaac42a8 100644 --- a/crates/oxc_prettier/src/format/print/literal.rs +++ b/crates/oxc_prettier/src/format/print/literal.rs @@ -12,8 +12,8 @@ pub fn print_string<'a>( prefer_single_quote: bool, ) -> &'a str { debug_assert!( - quoted_raw_text.starts_with("'") && quoted_raw_text.ends_with("'") - || quoted_raw_text.starts_with("\"") && quoted_raw_text.ends_with("\"") + quoted_raw_text.starts_with('\'') && quoted_raw_text.ends_with('\'') + || quoted_raw_text.starts_with('"') && quoted_raw_text.ends_with('"') ); let original_quote = quoted_raw_text.chars().next().unwrap(); let not_quoted_raw_text = "ed_raw_text[1..quoted_raw_text.len() - 1]; @@ -125,29 +125,32 @@ fn make_string<'a>( enclosing_quote: char, ) -> String<'a> { let other_quote = if enclosing_quote == '"' { '\'' } else { '"' }; + let mut result = String::new_in(p.allocator); result.push(enclosing_quote); - // TODO: Need to handle useless escape let mut chars = not_quoted_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); + if c == '\\' { + if let Some(&nc) = chars.peek() { + if nc == other_quote { + // Skip(remove) useless escape chars.next(); + result.push(nc); } else { result.push('\\'); + if let Some(nc) = chars.next() { + result.push(nc); + } } - } - _ if c == enclosing_quote => { + } else { result.push('\\'); - result.push(c); } - _ => result.push(c), + } else if c == enclosing_quote { + result.push('\\'); + result.push(c); + } else { + result.push(c); } } diff --git a/crates/oxc_prettier/src/format/print/mod.rs b/crates/oxc_prettier/src/format/print/mod.rs index 119fd93f70d9a..8870118a2aba8 100644 --- a/crates/oxc_prettier/src/format/print/mod.rs +++ b/crates/oxc_prettier/src/format/print/mod.rs @@ -8,11 +8,11 @@ pub mod call_expression; pub mod class; pub mod function; pub mod function_parameters; +pub mod literal; pub mod misc; pub mod module; pub mod object; pub mod property; pub mod statement; -pub mod literal; pub mod template_literal; pub mod ternary; diff --git a/tasks/prettier_conformance/snapshots/prettier.js.snap.md b/tasks/prettier_conformance/snapshots/prettier.js.snap.md index f4923de8023b3..677a139bea498 100644 --- a/tasks/prettier_conformance/snapshots/prettier.js.snap.md +++ b/tasks/prettier_conformance/snapshots/prettier.js.snap.md @@ -1,4 +1,4 @@ -js compatibility: 241/641 (37.60%) +js compatibility: 242/641 (37.75%) # Failed @@ -442,7 +442,6 @@ js compatibility: 241/641 (37.60%) * js/strings/escaped.js * js/strings/multiline-literal.js * js/strings/non-octal-eight-and-nine.js -* js/strings/strings.js * js/strings/template-literals.js ### js/switch From 06f9c74dfdcaa08268da90a7c4128ddb96073043 Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Tue, 17 Dec 2024 10:46:48 +0900 Subject: [PATCH 7/9] Return doc --- crates/oxc_prettier/src/format/js.rs | 47 ++++++------------- .../oxc_prettier/src/format/print/literal.rs | 17 ++++--- 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/crates/oxc_prettier/src/format/js.rs b/crates/oxc_prettier/src/format/js.rs index 2634695977aa0..e7f9598d9d302 100644 --- a/crates/oxc_prettier/src/format/js.rs +++ b/crates/oxc_prettier/src/format/js.rs @@ -60,13 +60,10 @@ impl<'a> Format<'a> for Hashbang<'a> { impl<'a> Format<'a> for Directive<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { let mut parts = Vec::new_in(p.allocator); - parts.push(dynamic_text!( + parts.push(literal::print_string_from_not_quoted_raw_text( p, - literal::print_string_from_not_quoted_raw_text( - p, - self.directive.as_str(), - p.options.single_quote, - ) + self.directive.as_str(), + p.options.single_quote, )); if let Some(semi) = p.semi() { parts.push(semi); @@ -932,15 +929,8 @@ impl<'a> Format<'a> for RegExpLiteral<'a> { impl<'a> Format<'a> for StringLiteral<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { wrap!(p, self, StringLiteral, { - dynamic_text!( - p, - // TODO: `replaceEndOfLine()` to handle line continuation - literal::print_string( - p, - self.span.source_text(p.source_text), - p.options.single_quote, - ) - ) + // TODO: `replaceEndOfLine()` to handle line continuation + literal::print_string(p, self.span.source_text(p.source_text), p.options.single_quote) }) } } @@ -1163,13 +1153,10 @@ impl<'a> Format<'a> for PropertyKey<'a> { match self { PropertyKey::StaticIdentifier(ident) => { if need_quote { - dynamic_text!( + literal::print_string_from_not_quoted_raw_text( p, - literal::print_string_from_not_quoted_raw_text( - p, - &ident.name, - p.options.single_quote - ) + &ident.name, + p.options.single_quote, ) } else { ident.format(p) @@ -1186,25 +1173,19 @@ impl<'a> Format<'a> for PropertyKey<'a> { { dynamic_text!(p, literal.value.as_str()) } else { - dynamic_text!( + literal::print_string_from_not_quoted_raw_text( p, - literal::print_string_from_not_quoted_raw_text( - p, - literal.value.as_str(), - p.options.single_quote, - ) + literal.value.as_str(), + p.options.single_quote, ) } } PropertyKey::NumericLiteral(literal) => { if need_quote { - dynamic_text!( + literal::print_string_from_not_quoted_raw_text( p, - literal::print_string_from_not_quoted_raw_text( - p, - &literal.raw_str(), - p.options.single_quote - ) + &literal.raw_str(), + p.options.single_quote, ) } else { literal.format(p) diff --git a/crates/oxc_prettier/src/format/print/literal.rs b/crates/oxc_prettier/src/format/print/literal.rs index 8d5d6eaac42a8..f5f7daacb3d7b 100644 --- a/crates/oxc_prettier/src/format/print/literal.rs +++ b/crates/oxc_prettier/src/format/print/literal.rs @@ -6,15 +6,18 @@ use oxc_span::Span; use crate::{dynamic_text, ir::Doc, Prettier}; +/// Print quoted string. +/// Quotes are automatically chosen based on the content of the string and option. pub fn print_string<'a>( p: &Prettier<'a>, quoted_raw_text: &'a str, prefer_single_quote: bool, -) -> &'a str { +) -> Doc<'a> { debug_assert!( quoted_raw_text.starts_with('\'') && quoted_raw_text.ends_with('\'') || quoted_raw_text.starts_with('"') && quoted_raw_text.ends_with('"') ); + let original_quote = quoted_raw_text.chars().next().unwrap(); let not_quoted_raw_text = "ed_raw_text[1..quoted_raw_text.len() - 1]; @@ -22,20 +25,22 @@ pub fn print_string<'a>( // This keeps useless escape as-is if original_quote == enclosing_quote { - return quoted_raw_text; + return dynamic_text!(p, quoted_raw_text); } - make_string(p, not_quoted_raw_text, enclosing_quote).into_bump_str() + dynamic_text!(p, make_string(p, not_quoted_raw_text, enclosing_quote).into_bump_str()) } -// TODO: Can this be removed? +// TODO: Can this be removed? It does not exist in Prettier +/// Print quoted string from not quoted text. +/// Mainly this is used to add quotes for object property keys. pub fn print_string_from_not_quoted_raw_text<'a>( p: &Prettier<'a>, not_quoted_raw_text: &str, prefer_single_quote: bool, -) -> &'a str { +) -> Doc<'a> { let enclosing_quote = get_preferred_quote(not_quoted_raw_text, prefer_single_quote); - make_string(p, not_quoted_raw_text, enclosing_quote).into_bump_str() + dynamic_text!(p, make_string(p, not_quoted_raw_text, enclosing_quote).into_bump_str()) } // See https://github.com/prettier/prettier/blob/3.3.3/src/utils/print-number.js From 86c9802f7884c9983641418f5942e48f438762ed Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Tue, 17 Dec 2024 12:29:17 +0900 Subject: [PATCH 8/9] Implement replaceEndOfLine --- crates/oxc_prettier/src/format/js.rs | 11 ++++++++-- .../oxc_prettier/src/format/print/literal.rs | 21 ++++++++++++++++++- crates/oxc_prettier/src/ir/doc.rs | 1 + crates/oxc_prettier/src/macros.rs | 20 ++++++++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/crates/oxc_prettier/src/format/js.rs b/crates/oxc_prettier/src/format/js.rs index e7f9598d9d302..2c78ad2bcd3dd 100644 --- a/crates/oxc_prettier/src/format/js.rs +++ b/crates/oxc_prettier/src/format/js.rs @@ -929,8 +929,15 @@ impl<'a> Format<'a> for RegExpLiteral<'a> { impl<'a> Format<'a> for StringLiteral<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { wrap!(p, self, StringLiteral, { - // TODO: `replaceEndOfLine()` to handle line continuation - literal::print_string(p, self.span.source_text(p.source_text), p.options.single_quote) + literal::replace_end_of_line( + p, + literal::print_string( + p, + self.span.source_text(p.source_text), + p.options.single_quote, + ), + JoinSeparator::Literalline, + ) }) } } diff --git a/crates/oxc_prettier/src/format/print/literal.rs b/crates/oxc_prettier/src/format/print/literal.rs index f5f7daacb3d7b..aa1e56e512617 100644 --- a/crates/oxc_prettier/src/format/print/literal.rs +++ b/crates/oxc_prettier/src/format/print/literal.rs @@ -4,7 +4,11 @@ use cow_utils::CowUtils; use oxc_allocator::String; use oxc_span::Span; -use crate::{dynamic_text, ir::Doc, Prettier}; +use crate::{ + dynamic_text, + ir::{Doc, JoinSeparator}, + join, Prettier, +}; /// Print quoted string. /// Quotes are automatically chosen based on the content of the string and option. @@ -162,3 +166,18 @@ fn make_string<'a>( result.push(enclosing_quote); result } + +/// Handle line continuation. +/// This does not recursively handle the doc, expects single `Doc::Str`. +pub fn replace_end_of_line<'a>( + p: &Prettier<'a>, + doc: Doc<'a>, + replacement: JoinSeparator, +) -> Doc<'a> { + let Doc::Str(text) = doc else { + return doc; + }; + + let lines = text.split('\n').map(|line| dynamic_text!(p, line)).collect::>(); + join!(p, replacement, lines) +} diff --git a/crates/oxc_prettier/src/ir/doc.rs b/crates/oxc_prettier/src/ir/doc.rs index 6338e8ae938e3..c6998b8dea7e9 100644 --- a/crates/oxc_prettier/src/ir/doc.rs +++ b/crates/oxc_prettier/src/ir/doc.rs @@ -87,4 +87,5 @@ pub enum JoinSeparator { Softline, Hardline, CommaLine, // [",", line] + Literalline, } diff --git a/crates/oxc_prettier/src/macros.rs b/crates/oxc_prettier/src/macros.rs index ced992902c83d..8cedddc5d679f 100644 --- a/crates/oxc_prettier/src/macros.rs +++ b/crates/oxc_prettier/src/macros.rs @@ -210,6 +210,7 @@ macro_rules! join { $crate::ir::JoinSeparator::CommaLine => { parts.extend([$crate::text!(","), $crate::line!()]); } + $crate::ir::JoinSeparator::Literalline => parts.extend($crate::literalline!()), } } parts.push(doc); @@ -258,6 +259,25 @@ macro_rules! hardline { }}; } +/// Specify a line break that is always included in the output and doesn't indent the next line. +/// Also, unlike hardline, this kind of line break preserves trailing whitespace on the line it ends. +/// This is used for template literals. +/// +/// ``` +/// literalline!(); +/// ``` +#[macro_export] +macro_rules! literalline { + () => {{ + let literalline = $crate::ir::Doc::Line($crate::ir::Line { + hard: true, + literal: true, + ..Default::default() + }); + [literalline, $crate::ir::Doc::BreakParent] + }}; +} + /// Increase the level of indentation. /// /// ``` From 94fc3df5781f5c3eb5b7e622679b439af905fdbf Mon Sep 17 00:00:00 2001 From: Yuji Sugiura Date: Tue, 17 Dec 2024 12:31:33 +0900 Subject: [PATCH 9/9] Print stringliteral --- crates/oxc_prettier/src/format/js.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/oxc_prettier/src/format/js.rs b/crates/oxc_prettier/src/format/js.rs index 2c78ad2bcd3dd..ab20031f348dc 100644 --- a/crates/oxc_prettier/src/format/js.rs +++ b/crates/oxc_prettier/src/format/js.rs @@ -928,17 +928,11 @@ impl<'a> Format<'a> for RegExpLiteral<'a> { impl<'a> Format<'a> for StringLiteral<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - wrap!(p, self, StringLiteral, { - literal::replace_end_of_line( - p, - literal::print_string( - p, - self.span.source_text(p.source_text), - p.options.single_quote, - ), - JoinSeparator::Literalline, - ) - }) + literal::replace_end_of_line( + p, + literal::print_string(p, self.span.source_text(p.source_text), p.options.single_quote), + JoinSeparator::Literalline, + ) } }