Skip to content

Commit 45f4d49

Browse files
CopilotDunqing
andcommitted
Optimize get_minified_number method by removing candidates vec and reducing format! usage
Co-authored-by: Dunqing <29533304+Dunqing@users.noreply.github.com>
1 parent c79e302 commit 45f4d49

File tree

1 file changed

+54
-28
lines changed

1 file changed

+54
-28
lines changed

crates/oxc_codegen/src/lib.rs

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -745,11 +745,7 @@ impl<'a> Codegen<'a> {
745745
self.print_str(buffer.format(num));
746746
self.need_space_before_dot = self.code_len();
747747
} else {
748-
let s = Self::get_minified_number(num, &mut buffer);
749-
self.print_str(&s);
750-
if !s.bytes().any(|b| matches!(b, b'.' | b'e' | b'x')) {
751-
self.need_space_before_dot = self.code_len();
752-
}
748+
self.print_minified_number(num, &mut buffer);
753749
}
754750
}
755751

@@ -760,14 +756,18 @@ impl<'a> Codegen<'a> {
760756
}
761757
}
762758

763-
// `get_minified_number` from terser
759+
// Optimized version of `get_minified_number` from terser
764760
// https://github.com/terser/terser/blob/c5315c3fd6321d6b2e076af35a70ef532f498505/lib/output.js#L2418
761+
// Instead of building all candidates and finding the shortest, we track the shortest as we go
762+
// and use self.print_str directly instead of returning intermediate strings
765763
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)]
766-
fn get_minified_number(num: f64, buffer: &mut dragonbox_ecma::Buffer) -> Cow<'_, str> {
764+
fn print_minified_number(&mut self, num: f64, buffer: &mut dragonbox_ecma::Buffer) {
767765
use cow_utils::CowUtils;
768766

769767
if num < 1000.0 && num.fract() == 0.0 {
770-
return Cow::Borrowed(buffer.format(num));
768+
self.print_str(buffer.format(num));
769+
self.need_space_before_dot = self.code_len();
770+
return;
771771
}
772772

773773
let mut s = buffer.format(num);
@@ -778,40 +778,66 @@ impl<'a> Codegen<'a> {
778778

779779
let s = s.cow_replacen("e+", "e", 1);
780780

781-
let mut candidates = vec![s.clone()];
781+
// Track the best candidate found so far
782+
let mut best_candidate = s.clone();
783+
let mut best_len = best_candidate.len();
782784

785+
// Check hex format for integers
783786
if num.fract() == 0.0 {
784-
candidates.push(Cow::Owned(format!("0x{:x}", num as u128)));
787+
let hex_candidate = format!("0x{:x}", num as u128);
788+
if hex_candidate.len() < best_len {
789+
best_candidate = hex_candidate.into();
790+
best_len = best_candidate.len();
791+
}
785792
}
786793

787-
// create `1e-2`
788-
if s.starts_with(".0") {
789-
if let Some((i, _)) = s[1..].bytes().enumerate().find(|(_, c)| *c != b'0') {
794+
// Check for scientific notation optimizations for numbers starting with ".0"
795+
if best_candidate.starts_with(".0") {
796+
if let Some((i, _)) = best_candidate[1..].bytes().enumerate().find(|(_, c)| *c != b'0')
797+
{
790798
let len = i + 1; // `+1` to include the dot.
791-
let digits = &s[len..];
792-
candidates.push(Cow::Owned(format!("{digits}e-{}", digits.len() + len - 1)));
799+
let digits = &best_candidate[len..];
800+
let scientific_candidate = format!("{digits}e-{}", digits.len() + len - 1);
801+
if scientific_candidate.len() < best_len {
802+
best_candidate = scientific_candidate.into();
803+
best_len = best_candidate.len();
804+
}
793805
}
794806
}
795807

796-
// create 1e2
797-
if s.ends_with('0') {
798-
if let Some((len, _)) = s.bytes().rev().enumerate().find(|(_, c)| *c != b'0') {
799-
candidates.push(Cow::Owned(format!("{}e{len}", &s[0..s.len() - len])));
808+
// Check for numbers ending with zeros (but not hex numbers)
809+
if best_candidate.ends_with('0') && !best_candidate.starts_with("0x") {
810+
if let Some((len, _)) =
811+
best_candidate.bytes().rev().enumerate().find(|(_, c)| *c != b'0')
812+
{
813+
let base = &best_candidate[0..best_candidate.len() - len];
814+
let scientific_candidate = format!("{base}e{len}");
815+
if scientific_candidate.len() < best_len {
816+
best_candidate = scientific_candidate.into();
817+
best_len = best_candidate.len();
818+
}
800819
}
801820
}
802821

803-
// `1.2e101` -> ("1", "2", "101")
804-
// `1.3415205933077406e300` -> `13415205933077406e284;`
805-
if let Some((integer, point, exponent)) =
806-
s.split_once('.').and_then(|(a, b)| b.split_once('e').map(|e| (a, e.0, e.1)))
822+
// Check for scientific notation optimization: `1.2e101` -> `12e100`
823+
if let Some((integer, point, exponent)) = best_candidate
824+
.split_once('.')
825+
.and_then(|(a, b)| b.split_once('e').map(|e| (a, e.0, e.1)))
807826
{
808-
candidates.push(Cow::Owned(format!(
809-
"{integer}{point}e{}",
810-
exponent.parse::<isize>().unwrap() - point.len() as isize
811-
)));
827+
let new_exp = exponent.parse::<isize>().unwrap() - point.len() as isize;
828+
let optimized_candidate = format!("{integer}{point}e{new_exp}");
829+
if optimized_candidate.len() < best_len {
830+
best_candidate = optimized_candidate.into();
831+
}
812832
}
813833

814-
candidates.into_iter().min_by_key(|c| c.len()).unwrap()
834+
// Print the best candidate
835+
self.print_str(&best_candidate);
836+
837+
// Update need_space_before_dot based on the final output
838+
if !best_candidate.bytes().any(|b| matches!(b, b'.' | b'e' | b'x')) {
839+
self.need_space_before_dot = self.code_len();
840+
}
815841
}
816842

817843
fn add_source_mapping(&mut self, span: Span) {

0 commit comments

Comments
 (0)