From bdc2a9469d48bf9dc634b1021db5388f34dd5953 Mon Sep 17 00:00:00 2001 From: faga Date: Thu, 16 Mar 2023 17:48:02 +0800 Subject: [PATCH 1/5] feat: slice api support --- core/src/magic_string.rs | 44 +++++++++++++++++ core/tests/slice.rs | 104 +++++++++++++++++++++++++++++++++++++++ node/src/lib.rs | 6 +++ 3 files changed, 154 insertions(+) create mode 100644 core/tests/slice.rs diff --git a/core/src/magic_string.rs b/core/src/magic_string.rs index 325afde..bdbadfb 100644 --- a/core/src/magic_string.rs +++ b/core/src/magic_string.rs @@ -525,6 +525,50 @@ impl MagicString { Ok(self) } + /// ## Slice + /// Get a slice of the modified string. + /// + /// Example: + /// ``` + /// use magic_string::MagicString; + /// + /// let mut s = MagicString::new("abcdefghijkl"); + /// assert_eq!(s.slice(1,5)?, "bcde"); + /// assert_eq!(s.slice(1, -1)?, "bcdefghijk"); + /// + /// ``` + /// + pub fn slice(&mut self, start: i64, end: i64) -> Result { + let start = normalize_index(self.original_str.as_str(), start)?; + let end = normalize_index(self.original_str.as_str(), end)?; + + let start = start as u32; + let end = end as u32; + + if start > end { + return Err(Error::new_with_reason( + MagicStringErrorType::MagicStringOutOfRangeError, + "Start must be greater than end.", + )); + } + + self._split_at_index(start)?; + self._split_at_index(end)?; + + let first = self.chunk_by_start.get(&start); + let last = self.chunk_by_end.get(&end); + + let mut str = String::new(); + + if first.is_some() && last.is_some() { + Chunk::try_each_next(Rc::clone(first.unwrap()), |chunk| { + str.push_str(chunk.borrow().to_string().as_str()); + Ok(chunk == Rc::clone(last.unwrap())) + })?; + } + + Ok(str) + } /// ## Is empty /// /// Returns `true` if the resulting source is empty (disregarding white space). diff --git a/core/tests/slice.rs b/core/tests/slice.rs new file mode 100644 index 0000000..5fbd644 --- /dev/null +++ b/core/tests/slice.rs @@ -0,0 +1,104 @@ +#[cfg(test)] + +mod slice { + use magic_string::{MagicString, OverwriteOptions, Result}; + + #[test] + fn should_return_the_generated_content_between_the_specified_original_characters() -> Result { + let mut s = MagicString::new("abcdefghijkl"); + + assert_eq!(s.slice(3, 9)?, "defghi"); + s.overwrite(4, 8, "XX", OverwriteOptions::default())?; + assert_eq!(s.slice(3, 9)?, "dXXi"); + s.overwrite(2, 10, "ZZ", OverwriteOptions::default())?; + assert_eq!(s.slice(1, 11)?, "bZZk"); + assert_eq!(s.slice(2, 10)?, "ZZ"); + + Ok(()) + } + + // #[test] + // fn defaults_end_to_the_original_string_length() -> Result { + // let mut s = MagicString::new("abcdefghijkl"); + // assert_eq!(s.slice(3)?, "defghijkl"); + // } + + #[test] + fn allow_negative_numbers_as_params() -> Result { + let mut s = MagicString::new("abcdefghijkl"); + + assert_eq!(s.slice(0, -3)?, "abcdefghi"); + // assert_eq!(s.slice(-3)?, "jkl"); + Ok(()) + } + #[test] + fn includes_inserted_characters_respecting_insertion_direction() -> Result { + let mut s = MagicString::new("abefij"); + + s.prepend_right(2, "cd")?; + s.append_left(4, "gh")?; + + // assert_eq!(s.slice(), "abcdefghij"); + assert_eq!(s.slice(1, 5)?, "bcdefghi"); + assert_eq!(s.slice(2, 4)?, "cdefgh"); + assert_eq!(s.slice(3, 4)?, "fgh"); + assert_eq!(s.slice(0, 2)?, "ab"); + assert_eq!(s.slice(0, 3)?, "abcde"); + assert_eq!(s.slice(4, 6)?, "ij"); + assert_eq!(s.slice(3, 6)?, "fghij"); + Ok(()) + } + + // wating for move to be implemented + // #[test] + // fn supports_characters_moved_outward() -> Result { + // let mut s = MagicString::new("abcdEFghIJklmn"); + + // s._move(4, 6, 2)?; + // s._move(8, 10, 12)?; + // assert_eq!(s.to_string(), "abEFcdghklIJmn"); + + // assert_eq!(s.slice(1, -1)?, "bEFcdghklIJm"); + // assert_eq!(s.slice(2, -2)?, "cdghkl"); + // assert_eq!(s.slice(3, -3)?, "dghk"); + // assert_eq!(s.slice(4, -4)?, "EFcdghklIJ"); + // assert_eq!(s.slice(5, -5)?, "FcdghklI"); + // assert_eq!(s.slice(6, -6)?, "gh"); + // Ok(()) + // } + + // #[test] + // fn supports_characters_moved_inward() -> Result { + // let mut s = MagicString::new("abCDefghijKLmn"); + // s._move(2, 4, 6)?; + // s._move(10, 12, 8)?; + // assert_eq!(s.to_string(), "abefCDghKLijmn"); + + // assert_eq!(s.slice(1, -1)?, "befCDghKLijm"); + // assert_eq!(s.slice(2, -2)?, "CDghKL"); + // assert_eq!(s.slice(3, -3)?, "DghK"); + // assert_eq!(s.slice(4, -4)?, "efCDghKLij"); + // assert_eq!(s.slice(5, -5)?, "fCDghKLi"); + // assert_eq!(s.slice(6, -6)?, "gh"); + // Ok(()) + // } + + // #[test] + // fn supports_characters_moved_inward() -> Result { + // let mut s = MagicString::new("abCDefghIJkl"); + // // s._move(2, 4, 8)?; + // // s._move(8, 10, 4)?; + // assert_eq!(s.to_string(), "abIJefghCDkl"); + + // assert_eq!(s.slice(1, -1)?, "bIJefghCDk"); + // assert_eq!(s.slice(2, -2)?, ""); + // assert_eq!(s.slice(3, -3)?, ""); + // assert_eq!(s.slice(-3, 3)?, "JefghC"); + // assert_eq!(s.slice(4, -4)?, "efgh"); + // assert_eq!(s.slice(0, 3)?, "abIJefghC"); + // // assert_eq!(s.slice(3)?, "Dkl"); + // assert_eq!(s.slice(0, -3)?, "abI"); + // // assert_eq!(s.slice(-3)?, "JefghCDkl"); + // Ok(()) + // } +} diff --git a/node/src/lib.rs b/node/src/lib.rs index 224f16d..5d52fbc 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -104,6 +104,12 @@ impl MagicString { Ok(self) } + #[napi] + pub fn slice(&mut self, start: i64, end: i64) -> Result<&Self> { + self.0.slice(start, end)?; + Ok(self) + } + #[napi] pub fn is_empty(&self) -> Result { Ok(self.0.is_empty()) From 8debfd0152a8d03403b68c3268d650d75a597210 Mon Sep 17 00:00:00 2001 From: faga Date: Thu, 16 Mar 2023 19:37:49 +0800 Subject: [PATCH 2/5] feat(slice): throw err if chunk has edited --- core/src/chunk.rs | 4 + core/src/magic_string.rs | 7 ++ node/index.d.ts | 1 + node/src/lib.rs | 5 +- node/tests/MagicString.spec.ts | 178 ++++++++++++++++----------------- 5 files changed, 103 insertions(+), 92 deletions(-) diff --git a/core/src/chunk.rs b/core/src/chunk.rs index 6dc5a6c..cd2e46f 100644 --- a/core/src/chunk.rs +++ b/core/src/chunk.rs @@ -16,6 +16,8 @@ pub struct Chunk { pub next: Option>>, pub prev: Option>>, + + pub edited: bool, } impl Chunk { @@ -31,6 +33,8 @@ impl Chunk { next: None, prev: None, + + edited: false, } } diff --git a/core/src/magic_string.rs b/core/src/magic_string.rs index bdbadfb..b3f2ee9 100644 --- a/core/src/magic_string.rs +++ b/core/src/magic_string.rs @@ -301,6 +301,7 @@ impl MagicString { } chunk.borrow_mut().content = String::default(); + chunk.borrow_mut().edited = true; if !content_only { chunk.borrow_mut().intro = String::default(); chunk.borrow_mut().outro = String::default(); @@ -561,6 +562,12 @@ impl MagicString { let mut str = String::new(); if first.is_some() && last.is_some() { + if first.clone().unwrap().borrow().edited { + return Err(Error::new_with_reason( + MagicStringErrorType::MagicStringOutOfRangeError, + "Cannot slice a string that has been edited.", + )); + } Chunk::try_each_next(Rc::clone(first.unwrap()), |chunk| { str.push_str(chunk.borrow().to_string().as_str()); Ok(chunk == Rc::clone(last.unwrap())) diff --git a/node/index.d.ts b/node/index.d.ts index 0c37f8c..6e23ea1 100644 --- a/node/index.d.ts +++ b/node/index.d.ts @@ -49,6 +49,7 @@ export class MagicString { trimEnd(pattern?: string | undefined | null): this trimLines(): this remove(start: number, end: number): this + slice(start: number, end: number): string isEmpty(): boolean generateMap(options?: Partial): { toString: () => string diff --git a/node/src/lib.rs b/node/src/lib.rs index 5d52fbc..369f78f 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -105,9 +105,8 @@ impl MagicString { } #[napi] - pub fn slice(&mut self, start: i64, end: i64) -> Result<&Self> { - self.0.slice(start, end)?; - Ok(self) + pub fn slice(&mut self, start: i64, end: i64) -> Result { + Ok(self.0.slice(start, end)?) } #[napi] diff --git a/node/tests/MagicString.spec.ts b/node/tests/MagicString.spec.ts index 989d99c..b44d6e3 100644 --- a/node/tests/MagicString.spec.ts +++ b/node/tests/MagicString.spec.ts @@ -1094,116 +1094,116 @@ describe('remove', () => { // }) }) -// describe('slice', () => { -// it('should return the generated content between the specified original characters', () => { -// const s = new MagicString('abcdefghijkl') +describe('slice', () => { + it('should return the generated content between the specified original characters', () => { + const s = new MagicString('abcdefghijkl') -// assert.equal(s.slice(3, 9), 'defghi') -// s.overwrite(4, 8, 'XX') -// assert.equal(s.slice(3, 9), 'dXXi') -// s.overwrite(2, 10, 'ZZ') -// assert.equal(s.slice(1, 11), 'bZZk') -// assert.equal(s.slice(2, 10), 'ZZ') + assert.equal(s.slice(3, 9), 'defghi') + s.overwrite(4, 8, 'XX') + assert.equal(s.slice(3, 9), 'dXXi') + s.overwrite(2, 10, 'ZZ') + assert.equal(s.slice(1, 11), 'bZZk') + assert.equal(s.slice(2, 10), 'ZZ') -// assert.throws(() => s.slice(3, 9)) -// }) + assert.throws(() => s.slice(3, 9)) + }) -// it('defaults `end` to the original string length', () => { -// const s = new MagicString('abcdefghijkl') -// assert.equal(s.slice(3), 'defghijkl') -// }) + // it('defaults `end` to the original string length', () => { + // const s = new MagicString('abcdefghijkl') + // assert.equal(s.slice(3), 'defghijkl') + // }) -// it('allows negative numbers as arguments', () => { -// const s = new MagicString('abcdefghijkl') -// assert.equal(s.slice(-3), 'jkl') -// assert.equal(s.slice(0, -3), 'abcdefghi') -// }) + it('allows negative numbers as arguments', () => { + const s = new MagicString('abcdefghijkl') + // assert.equal(s.slice(-3), 'jkl') + assert.equal(s.slice(0, -3), 'abcdefghi') + }) -// it('includes inserted characters, respecting insertion direction', () => { -// const s = new MagicString('abefij') + it('includes inserted characters, respecting insertion direction', () => { + const s = new MagicString('abefij') -// s.prependRight(2, 'cd') -// s.appendLeft(4, 'gh') + s.prependRight(2, 'cd') + s.appendLeft(4, 'gh') -// assert.equal(s.slice(), 'abcdefghij') -// assert.equal(s.slice(1, 5), 'bcdefghi') -// assert.equal(s.slice(2, 4), 'cdefgh') -// assert.equal(s.slice(3, 4), 'fgh') -// assert.equal(s.slice(0, 2), 'ab') -// assert.equal(s.slice(0, 3), 'abcde') -// assert.equal(s.slice(4, 6), 'ij') -// assert.equal(s.slice(3, 6), 'fghij') -// }) + // assert.equal(s.slice(), 'abcdefghij') + assert.equal(s.slice(1, 5), 'bcdefghi') + assert.equal(s.slice(2, 4), 'cdefgh') + assert.equal(s.slice(3, 4), 'fgh') + assert.equal(s.slice(0, 2), 'ab') + assert.equal(s.slice(0, 3), 'abcde') + assert.equal(s.slice(4, 6), 'ij') + assert.equal(s.slice(3, 6), 'fghij') + }) -// it('supports characters moved outward', () => { -// const s = new MagicString('abcdEFghIJklmn') + // it('supports characters moved outward', () => { + // const s = new MagicString('abcdEFghIJklmn') -// s.move(4, 6, 2) -// s.move(8, 10, 12) -// assert.equal(s.toString(), 'abEFcdghklIJmn') + // s.move(4, 6, 2) + // s.move(8, 10, 12) + // assert.equal(s.toString(), 'abEFcdghklIJmn') -// assert.equal(s.slice(1, -1), 'bEFcdghklIJm') -// assert.equal(s.slice(2, -2), 'cdghkl') -// assert.equal(s.slice(3, -3), 'dghk') -// assert.equal(s.slice(4, -4), 'EFcdghklIJ') -// assert.equal(s.slice(5, -5), 'FcdghklI') -// assert.equal(s.slice(6, -6), 'gh') -// }) + // assert.equal(s.slice(1, -1), 'bEFcdghklIJm') + // assert.equal(s.slice(2, -2), 'cdghkl') + // assert.equal(s.slice(3, -3), 'dghk') + // assert.equal(s.slice(4, -4), 'EFcdghklIJ') + // assert.equal(s.slice(5, -5), 'FcdghklI') + // assert.equal(s.slice(6, -6), 'gh') + // }) -// it('supports characters moved inward', () => { -// const s = new MagicString('abCDefghijKLmn') + // it('supports characters moved inward', () => { + // const s = new MagicString('abCDefghijKLmn') -// s.move(2, 4, 6) -// s.move(10, 12, 8) -// assert.equal(s.toString(), 'abefCDghKLijmn') - -// assert.equal(s.slice(1, -1), 'befCDghKLijm') -// assert.equal(s.slice(2, -2), 'CDghKL') -// assert.equal(s.slice(3, -3), 'DghK') -// assert.equal(s.slice(4, -4), 'efCDghKLij') -// assert.equal(s.slice(5, -5), 'fCDghKLi') -// assert.equal(s.slice(6, -6), 'gh') -// }) + // s.move(2, 4, 6) + // s.move(10, 12, 8) + // assert.equal(s.toString(), 'abefCDghKLijmn') -// it('supports characters moved opposing', () => { -// const s = new MagicString('abCDefghIJkl') - -// s.move(2, 4, 8) -// s.move(8, 10, 4) -// assert.equal(s.toString(), 'abIJefghCDkl') - -// assert.equal(s.slice(1, -1), 'bIJefghCDk') -// assert.equal(s.slice(2, -2), '') -// assert.equal(s.slice(3, -3), '') -// assert.equal(s.slice(-3, 3), 'JefghC') -// assert.equal(s.slice(4, -4), 'efgh') -// assert.equal(s.slice(0, 3), 'abIJefghC') -// assert.equal(s.slice(3), 'Dkl') -// assert.equal(s.slice(0, -3), 'abI') -// assert.equal(s.slice(-3), 'JefghCDkl') -// }) + // assert.equal(s.slice(1, -1), 'befCDghKLijm') + // assert.equal(s.slice(2, -2), 'CDghKL') + // assert.equal(s.slice(3, -3), 'DghK') + // assert.equal(s.slice(4, -4), 'efCDghKLij') + // assert.equal(s.slice(5, -5), 'fCDghKLi') + // assert.equal(s.slice(6, -6), 'gh') + // }) + + // it('supports characters moved opposing', () => { + // const s = new MagicString('abCDefghIJkl') + + // s.move(2, 4, 8) + // s.move(8, 10, 4) + // assert.equal(s.toString(), 'abIJefghCDkl') + + // assert.equal(s.slice(1, -1), 'bIJefghCDk') + // assert.equal(s.slice(2, -2), '') + // assert.equal(s.slice(3, -3), '') + // assert.equal(s.slice(-3, 3), 'JefghC') + // assert.equal(s.slice(4, -4), 'efgh') + // assert.equal(s.slice(0, 3), 'abIJefghC') + // assert.equal(s.slice(3), 'Dkl') + // assert.equal(s.slice(0, -3), 'abI') + // assert.equal(s.slice(-3), 'JefghCDkl') + // }) -// it('errors if replaced characters are used as slice anchors', () => { -// const s = new MagicString('abcdef') -// s.overwrite(2, 4, 'CD') + // it('errors if replaced characters are used as slice anchors', () => { + // const s = new MagicString('abcdef') + // s.overwrite(2, 4, 'CD') -// assert.throws(() => s.slice(2, 3), /slice end anchor/) + // assert.throws(() => s.slice(2, 3), /slice end anchor/) -// assert.throws(() => s.slice(3, 4), /slice start anchor/) + // assert.throws(() => s.slice(3, 4), /slice start anchor/) -// assert.throws(() => s.slice(3, 5), /slice start anchor/) + // assert.throws(() => s.slice(3, 5), /slice start anchor/) -// assert.equal(s.slice(1, 5), 'bCDe') -// }) + // assert.equal(s.slice(1, 5), 'bCDe') + // }) -// it('does not error if slice is after removed characters', () => { -// const s = new MagicString('abcdef') + it('does not error if slice is after removed characters', () => { + const s = new MagicString('abcdef') -// s.remove(0, 2) + s.remove(0, 2) -// assert.equal(s.slice(2, 4), 'cd') -// }) -// }) + assert.equal(s.slice(2, 4), 'cd') + }) +}) // describe('snip', () => { // it('should return a clone with content outside `start` and `end` removed', () => { From 406913283b24afc14416f26f358e2d8e3ca978a5 Mon Sep 17 00:00:00 2001 From: faga Date: Fri, 17 Mar 2023 20:44:10 +0800 Subject: [PATCH 3/5] refactor: slice --- core/src/chunk.rs | 4 --- core/src/magic_string.rs | 72 +++++++++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/core/src/chunk.rs b/core/src/chunk.rs index cd2e46f..6dc5a6c 100644 --- a/core/src/chunk.rs +++ b/core/src/chunk.rs @@ -16,8 +16,6 @@ pub struct Chunk { pub next: Option>>, pub prev: Option>>, - - pub edited: bool, } impl Chunk { @@ -33,8 +31,6 @@ impl Chunk { next: None, prev: None, - - edited: false, } } diff --git a/core/src/magic_string.rs b/core/src/magic_string.rs index b3f2ee9..eddadc6 100644 --- a/core/src/magic_string.rs +++ b/core/src/magic_string.rs @@ -301,7 +301,6 @@ impl MagicString { } chunk.borrow_mut().content = String::default(); - chunk.borrow_mut().edited = true; if !content_only { chunk.borrow_mut().intro = String::default(); chunk.borrow_mut().outro = String::default(); @@ -528,14 +527,11 @@ impl MagicString { /// ## Slice /// Get a slice of the modified string. - /// /// Example: /// ``` /// use magic_string::MagicString; - /// /// let mut s = MagicString::new("abcdefghijkl"); - /// assert_eq!(s.slice(1,5)?, "bcde"); - /// assert_eq!(s.slice(1, -1)?, "bcdefghijk"); + /// /// /// ``` /// @@ -553,28 +549,64 @@ impl MagicString { )); } - self._split_at_index(start)?; - self._split_at_index(end)?; + let mut result = String::new(); + let mut chunk = Some(Rc::clone(&self.first_chunk)); + while let Some(c) = chunk.clone() { + if c.borrow().start > start || c.borrow().end <= start { + chunk = c.borrow().clone().next; + } else { + break; + } + } + if let Some(c) = chunk.clone() { + if c.borrow().is_content_edited() && c.borrow().start != start { + return Err(Error::new_with_reason( + MagicStringErrorType::MagicStringUnknownError, + "Cannot move a selection inside itself", + )); + } + } + let start_chunk = chunk.clone().unwrap(); + Chunk::try_each_next(Rc::clone(&chunk.unwrap()), |chunk| { + let str: &str; - let first = self.chunk_by_start.get(&start); - let last = self.chunk_by_end.get(&end); + if chunk.borrow().intro.len() != 0 && (start_chunk != chunk || chunk.borrow().start == start) + { + result.push_str(chunk.borrow().intro.as_str()); + }; + + let contain_end = chunk.borrow().end >= end; + + let slice_start = if chunk.borrow().start < start { + start - chunk.borrow().start + } else { + 0 + }; + let slice_end = if contain_end { + chunk.borrow().content.len() as u32 + end - chunk.borrow().end + } else { + chunk.borrow().content.len() as u32 + }; - let mut str = String::new(); + let chunk_str = chunk.borrow().content.clone(); - if first.is_some() && last.is_some() { - if first.clone().unwrap().borrow().edited { + if contain_end && chunk.borrow().is_content_edited() && chunk.borrow().end != end { return Err(Error::new_with_reason( - MagicStringErrorType::MagicStringOutOfRangeError, - "Cannot slice a string that has been edited.", + MagicStringErrorType::MagicStringUnknownError, + "Cannot use replaced character ${end} as slice end anchor.", )); } - Chunk::try_each_next(Rc::clone(first.unwrap()), |chunk| { - str.push_str(chunk.borrow().to_string().as_str()); - Ok(chunk == Rc::clone(last.unwrap())) - })?; - } - Ok(str) + str = &chunk_str.as_str()[slice_start as usize..slice_end as usize]; + result.push_str(str); + if chunk.borrow().outro.len() != 0 && (!contain_end || chunk.borrow().end == end) { + result.push_str(chunk.borrow().outro.as_str()) + } + + Ok(chunk.borrow().end >= end) + })?; + + Ok(result) } /// ## Is empty /// From 51704d356a44dd796e3beed68b3f12c262118445 Mon Sep 17 00:00:00 2001 From: "M. Bagher Abiat" Date: Sun, 9 Apr 2023 15:48:54 +0330 Subject: [PATCH 4/5] add clone --- core/src/magic_string.rs | 197 +++++++++++++++++++++++++-------- core/tests/clone.rs | 31 ++++++ node/index.d.ts | 11 +- node/src/lib.rs | 7 ++ node/tests/MagicString.spec.ts | 130 +++++++++++----------- 5 files changed, 258 insertions(+), 118 deletions(-) create mode 100644 core/tests/clone.rs diff --git a/core/src/magic_string.rs b/core/src/magic_string.rs index eddadc6..6a02f8f 100644 --- a/core/src/magic_string.rs +++ b/core/src/magic_string.rs @@ -1,4 +1,11 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc, string::ToString}; +use std::{ + borrow::{Borrow, BorrowMut}, + cell::RefCell, + collections::HashMap, + ops::Deref, + rc::Rc, + string::ToString, +}; use crate::utils::{normalize_index, trim}; @@ -156,7 +163,8 @@ impl MagicString { self._split_at_index(index)?; if let Some(chunk) = self.chunk_by_end.get(&index) { - chunk.borrow_mut().prepend_outro(str); + // chunk.borrow_mut().prepend_outro(str); + chunk.deref().borrow_mut().prepend_outro(str); } else { self.intro = format!("{}{}", str, self.intro) }; @@ -171,7 +179,7 @@ impl MagicString { self._split_at_index(index)?; if let Some(chunk) = self.chunk_by_start.get(&index) { - chunk.borrow_mut().prepend_intro(str); + chunk.deref().borrow_mut().prepend_intro(str); } else { self.outro = format!("{}{}", str, self.outro) }; @@ -187,7 +195,7 @@ impl MagicString { self._split_at_index(index)?; if let Some(chunk) = self.chunk_by_end.get(&index) { - chunk.borrow_mut().append_outro(str); + chunk.deref().borrow_mut().append_outro(str); } else { self.intro = format!("{}{}", self.intro, str); }; @@ -203,7 +211,7 @@ impl MagicString { self._split_at_index(index)?; if let Some(chunk) = self.chunk_by_start.get(&index) { - chunk.borrow_mut().append_intro(str); + chunk.deref().borrow_mut().append_intro(str); } else { self.append(str)?; }; @@ -211,6 +219,65 @@ impl MagicString { Ok(self) } + pub fn clone(&self) -> Result { + let mut cloned = MagicString::new(self.original_str.borrow()); + + let mut original_chunk = Rc::clone(&self.first_chunk); + let mut cloned_chunk = Rc::new(RefCell::new(original_chunk.deref().borrow().clone())); + cloned.last_searched_chunk = Rc::clone(&cloned_chunk); + cloned.first_chunk = Rc::clone(&cloned_chunk); + + while let Some(c) = Some(Rc::clone(&original_chunk)) { + cloned + .chunk_by_start + .remove(&cloned_chunk.deref().borrow().start); + cloned + .chunk_by_end + .remove(&cloned_chunk.deref().borrow().end); + + cloned.chunk_by_start.insert( + cloned_chunk.deref().borrow().start, + Rc::clone(&cloned_chunk), + ); + cloned + .chunk_by_end + .insert(cloned_chunk.deref().borrow().end, Rc::clone(&cloned_chunk)); + // cloned.chunk_by_start[&cloned_chunk.deref().borrow().start] = Rc::clone(&cloned_chunk) ; + // cloned.chunk_by_end[&cloned_chunk.deref().borrow().end] = Rc::clone(&cloned_chunk); + + let original_chunk_clone = original_chunk.clone(); + let next_original_chunk = &original_chunk_clone.deref().borrow().next; + let next_cloned_chunk = match next_original_chunk { + None => None, + Some(chunk) => Some(Rc::new(RefCell::new(chunk.deref().borrow().clone()))) + }; + + match next_cloned_chunk { + None => { + break; + } + Some(chunk) => { + cloned_chunk.deref().borrow_mut().next = Some(Rc::clone(&chunk)); + chunk.deref().borrow_mut().prev = Some(Rc::clone(&cloned_chunk)); + cloned_chunk = chunk; + original_chunk = Rc::clone(next_original_chunk.as_ref().unwrap()); + } + } + } + + cloned.last_chunk = cloned_chunk; + // TODO: + /* if (this.indentExclusionRanges) { + cloned.indentExclusionRanges = this.indentExclusionRanges.slice(); + } + + cloned.sourcemapLocations = new BitSet(this.sourcemapLocations); */ + cloned.intro = self.intro.clone(); + cloned.outro = self.outro.clone(); + + Ok(cloned) + } + /// ## Overwrite /// /// Replaces the characters from start to end with content. Returns `self`. @@ -236,6 +303,7 @@ impl MagicString { content: &str, options: OverwriteOptions, ) -> Result<&mut Self> { + let content_only = options.content_only; let start = normalize_index(self.original_str.as_str(), start)?; let end = normalize_index(self.original_str.as_str(), end)?; @@ -243,6 +311,7 @@ impl MagicString { let start = start as u32; let end = end as u32; + if start == end { return Err(Error::new_with_reason( MagicStringErrorType::MagicStringOutOfRangeError, @@ -257,21 +326,25 @@ impl MagicString { )); } + self._split_at_index(start)?; self._split_at_index(end)?; + + let start_chunk: Option>> = self.chunk_by_start.get(&start).map(Rc::clone); let end_chunk: Option>> = self.chunk_by_end.get(&end).map(Rc::clone); if let Some(start_chunk) = start_chunk { + // Note: This original implementation looks a little bit weird to me. // It should check whether the latter chunks had been edited(not only for content-wise, but also for intro and outro) or not, // then we could return the Error. But for now, It's been doing just fine. - if start_chunk.borrow().end < end - && (start_chunk.borrow().next + if start_chunk.deref().borrow().end < end + && (start_chunk.deref().borrow().next != self .chunk_by_start - .get(&start_chunk.borrow().end) + .get(&start_chunk.deref().borrow().end) .map(Rc::clone)) { return Err(Error::new_with_reason( @@ -282,28 +355,29 @@ impl MagicString { Chunk::try_each_next(Rc::clone(&start_chunk), |chunk| { if start_chunk == chunk { - start_chunk.borrow_mut().content = content.to_owned(); + start_chunk.deref().borrow_mut().content = content.to_owned(); if !content_only { - start_chunk.borrow_mut().intro = String::default(); - start_chunk.borrow_mut().outro = String::default(); + start_chunk.deref().borrow_mut().intro = String::default(); + start_chunk.deref().borrow_mut().outro = String::default(); } return Ok(false); } if end_chunk.is_some() - && chunk.borrow().start + && chunk.deref().borrow().start >= (end_chunk.as_ref().map(Rc::clone).unwrap() as Rc>) + .deref() .borrow() .end { return Ok(true); } - chunk.borrow_mut().content = String::default(); + chunk.deref().borrow_mut().content = String::default(); if !content_only { - chunk.borrow_mut().intro = String::default(); - chunk.borrow_mut().outro = String::default(); + chunk.deref().borrow_mut().intro = String::default(); + chunk.deref().borrow_mut().outro = String::default(); } Ok(false) @@ -379,11 +453,11 @@ impl MagicString { Chunk::try_each_next(Rc::clone(&self.first_chunk), |chunk| { self.last_searched_chunk = Rc::clone(&chunk); - if let Err(e) = chunk.borrow_mut().trim_start_regexp(pattern) { + if let Err(e) = chunk.deref().borrow_mut().trim_start_regexp(pattern) { return Err(e); } - Ok(!chunk.borrow().to_string().is_empty()) + Ok(!chunk.deref().borrow().to_string().is_empty()) })?; if error != Error::default() { @@ -391,7 +465,13 @@ impl MagicString { } if self.last_searched_chunk == self.last_chunk - && self.last_chunk.borrow().content.to_string().is_empty() + && self + .last_chunk + .deref() + .borrow() + .content + .to_string() + .is_empty() { self.outro = trim::trim_start_regexp(self.outro.as_str(), pattern)?.to_owned() } @@ -434,15 +514,21 @@ impl MagicString { Chunk::try_each_prev(Rc::clone(&self.last_chunk), |chunk| { self.last_searched_chunk = Rc::clone(&chunk); - if let Err(e) = chunk.borrow_mut().trim_end_regexp(pattern) { + if let Err(e) = chunk.deref().borrow_mut().trim_end_regexp(pattern) { return Err(e); } - Ok(!chunk.borrow().to_string().is_empty()) + Ok(!chunk.deref().borrow().to_string().is_empty()) })?; if self.last_searched_chunk == self.first_chunk - && self.first_chunk.borrow().content.to_string().is_empty() + && self + .first_chunk + .deref() + .borrow() + .content + .to_string() + .is_empty() { self.intro = trim::trim_end_regexp(self.intro.as_str(), pattern)?.to_owned() } @@ -514,9 +600,9 @@ impl MagicString { if start_chunk.is_some() { Chunk::try_each_next(start_chunk.map(Rc::clone).unwrap(), |chunk| { - chunk.borrow_mut().content = String::default(); - chunk.borrow_mut().intro = String::default(); - chunk.borrow_mut().outro = String::default(); + chunk.deref().borrow_mut().content = String::default(); + chunk.deref().borrow_mut().intro = String::default(); + chunk.deref().borrow_mut().outro = String::default(); Ok(chunk == Rc::clone(end_chunk.unwrap())) })?; @@ -552,14 +638,14 @@ impl MagicString { let mut result = String::new(); let mut chunk = Some(Rc::clone(&self.first_chunk)); while let Some(c) = chunk.clone() { - if c.borrow().start > start || c.borrow().end <= start { - chunk = c.borrow().clone().next; + if c.deref().borrow().start > start || c.deref().borrow().end <= start { + chunk = c.deref().borrow().clone().next; } else { break; } } if let Some(c) = chunk.clone() { - if c.borrow().is_content_edited() && c.borrow().start != start { + if c.deref().borrow().is_content_edited() && c.deref().borrow().start != start { return Err(Error::new_with_reason( MagicStringErrorType::MagicStringUnknownError, "Cannot move a selection inside itself", @@ -570,27 +656,31 @@ impl MagicString { Chunk::try_each_next(Rc::clone(&chunk.unwrap()), |chunk| { let str: &str; - if chunk.borrow().intro.len() != 0 && (start_chunk != chunk || chunk.borrow().start == start) + if chunk.deref().borrow().intro.len() != 0 + && (start_chunk != chunk || chunk.deref().borrow().start == start) { - result.push_str(chunk.borrow().intro.as_str()); + result.push_str(chunk.deref().borrow().intro.as_str()); }; - let contain_end = chunk.borrow().end >= end; + let contain_end = chunk.deref().borrow().end >= end; - let slice_start = if chunk.borrow().start < start { - start - chunk.borrow().start + let slice_start = if chunk.deref().borrow().start < start { + start - chunk.deref().borrow().start } else { 0 }; let slice_end = if contain_end { - chunk.borrow().content.len() as u32 + end - chunk.borrow().end + chunk.deref().borrow().content.len() as u32 + end - chunk.deref().borrow().end } else { - chunk.borrow().content.len() as u32 + chunk.deref().borrow().content.len() as u32 }; - let chunk_str = chunk.borrow().content.clone(); + let chunk_str = chunk.deref().borrow().content.clone(); - if contain_end && chunk.borrow().is_content_edited() && chunk.borrow().end != end { + if contain_end + && chunk.deref().borrow().is_content_edited() + && chunk.deref().borrow().end != end + { return Err(Error::new_with_reason( MagicStringErrorType::MagicStringUnknownError, "Cannot use replaced character ${end} as slice end anchor.", @@ -599,11 +689,13 @@ impl MagicString { str = &chunk_str.as_str()[slice_start as usize..slice_end as usize]; result.push_str(str); - if chunk.borrow().outro.len() != 0 && (!contain_end || chunk.borrow().end == end) { - result.push_str(chunk.borrow().outro.as_str()) + if chunk.deref().borrow().outro.len() != 0 + && (!contain_end || chunk.deref().borrow().end == end) + { + result.push_str(chunk.deref().borrow().outro.as_str()) } - Ok(chunk.borrow().end >= end) + Ok(chunk.deref().borrow().end >= end) })?; Ok(result) @@ -665,7 +757,7 @@ impl MagicString { map.advance(self.intro.as_str()); Chunk::try_each_next(Rc::clone(&self.first_chunk), |chunk| { - let loc = locator.locate(chunk.borrow().start); + let loc = locator.locate(chunk.deref().borrow().start); map.add_chunk(Rc::clone(&chunk), loc); Ok(false) })?; @@ -722,21 +814,30 @@ impl MagicString { let chunk = Rc::clone(&self.last_searched_chunk); - let search_forward = index > chunk.borrow().start; + let search_forward = index > chunk.deref().borrow().start; let mut curr = Some(chunk); while let Some(c) = curr { - if c.borrow().contains(index) { + + if c.deref().borrow().contains(index) { + self._split_chunk_at_index(c, index)?; return Ok(()); } else { + curr = { if search_forward { - self.chunk_by_start.get(&c.borrow().end).map(Rc::clone) + self + .chunk_by_start + .get(&c.deref().borrow().end) + .map(Rc::clone) } else { - self.chunk_by_end.get(&c.borrow().start).map(Rc::clone) + self + .chunk_by_end + .get(&c.deref().borrow().start) + .map(Rc::clone) } - } + }; } } @@ -745,14 +846,14 @@ impl MagicString { fn _split_chunk_at_index(&mut self, chunk: Rc>, index: u32) -> Result { // Zero-length edited chunks can be split into different chunks, cause split chunks are the same. - if chunk.borrow().is_content_edited() && !chunk.borrow().content.is_empty() { + if chunk.deref().borrow().is_content_edited() && !chunk.deref().borrow().content.is_empty() { return Err(Error::new( MagicStringErrorType::MagicStringDoubleSplitError, )); } let new_chunk = Chunk::split(Rc::clone(&chunk), index); - let new_chunk_original = new_chunk.borrow(); + let new_chunk_original = new_chunk.deref().borrow(); self.chunk_by_end.insert(index, Rc::clone(&chunk)); self.chunk_by_start.insert(index, Rc::clone(&new_chunk)); @@ -787,7 +888,7 @@ impl ToString for MagicString { let mut str = self.intro.to_owned(); Chunk::try_each_next(Rc::clone(&self.first_chunk), |chunk| { - str = format!("{}{}", str, chunk.borrow().to_string()); + str = format!("{}{}", str, chunk.deref().borrow().to_string()); Ok(false) }) .unwrap(); diff --git a/core/tests/clone.rs b/core/tests/clone.rs new file mode 100644 index 0000000..1aa8906 --- /dev/null +++ b/core/tests/clone.rs @@ -0,0 +1,31 @@ +#[cfg(test)] +mod remove { + use magic_string::{MagicString, OverwriteOptions, Result}; + + #[test] + fn should_clone_a_magic_string() -> Result { + + let mut s = MagicString::new("abcdefghijkl"); + s.overwrite(3, 9, "XYZ", OverwriteOptions::default()); + let mut c = s.clone().unwrap(); + c.overwrite(3, 9, "XYZB", OverwriteOptions::default()); + + assert_eq!(s.to_string(), "abcXYZjkl"); + assert_eq!(c.to_string(), "abcXYZBjkl"); + + Ok(()) + } + + #[test] + fn should_clone_intro_and_outro() -> Result { + + let mut s = MagicString::new("defghi"); + s.prepend("abc"); + s.append("jkl"); + let c = s.clone().unwrap(); + + assert_eq!(s.to_string(), c.to_string()); + + Ok(()) + } +} diff --git a/node/index.d.ts b/node/index.d.ts index 6e23ea1..1c42279 100644 --- a/node/index.d.ts +++ b/node/index.d.ts @@ -11,18 +11,18 @@ export class ExternalObject { } /** Only for .d.ts type generation */ export interface DecodedMap { - file?: string | undefined | null + file?: string sources: Array - sourceRoot?: string | undefined | null + sourceRoot?: string sourcesContent: Array names: Array mappings: Array>> } /** Only for .d.ts generation */ export interface GenerateDecodedMapOptions { - file?: string | undefined | null - sourceRoot?: string | undefined | null - source?: string | undefined | null + file?: string + sourceRoot?: string + source?: string includeContent: boolean hires: boolean } @@ -38,6 +38,7 @@ export class MagicString { appendRight(index: number, input: string): this prependLeft(index: number, input: string): this prependRight(index: number, input: string): this + clone(): MagicString overwrite( start: number, end: number, diff --git a/node/src/lib.rs b/node/src/lib.rs index 369f78f..8370352 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -57,6 +57,13 @@ impl MagicString { Ok(self) } + #[napi] + pub fn clone(&mut self) -> Result { + // Ok(self.0.clone().unwrap()) + // self.0.clone() + Ok(MagicString(self.0.clone().unwrap())) + } + #[napi(ts_args_type = r" start: number, end: number, diff --git a/node/tests/MagicString.spec.ts b/node/tests/MagicString.spec.ts index b44d6e3..013c854 100644 --- a/node/tests/MagicString.spec.ts +++ b/node/tests/MagicString.spec.ts @@ -110,89 +110,89 @@ describe('MagicString', () => { }) }) - // describe('clone', () => { - // it('should clone a magic string', () => { - // const s = new MagicString('abcdefghijkl') + describe('clone', () => { + it('should clone a magic string', () => { + const s = new MagicString('abcdefghijkl') - // s.overwrite(3, 9, 'XYZ') - // const c = s.clone() + s.overwrite(3, 9, 'XYZ') + const c = s.clone() - // assert.notEqual(s, c) - // assert.equal(c.toString(), 'abcXYZjkl') - // }) + assert.notEqual(s, c) + assert.equal(c.toString(), 'abcXYZjkl') + }) - // it('should clone filename info', () => { - // const s = new MagicString('abcdefghijkl', { filename: 'foo.js' }) - // const c = s.clone() + /* it('should clone filename info', () => { + const s = new MagicString('abcdefghijkl', { filename: 'foo.js' }) + const c = s.clone() - // assert.equal(c.filename, 'foo.js') - // }) + assert.equal(c.filename, 'foo.js') + }) - // it('should clone indentExclusionRanges', () => { - // const array = [3, 6] - // const source = new MagicString('abcdefghijkl', { - // filename: 'foo.js', - // indentExclusionRanges: array, - // }) + it('should clone indentExclusionRanges', () => { + const array = [3, 6] + const source = new MagicString('abcdefghijkl', { + filename: 'foo.js', + indentExclusionRanges: array, + }) - // const clone = source.clone() + const clone = source.clone() - // assert.notStrictEqual( - // source.indentExclusionRanges, - // clone.indentExclusionRanges, - // ) - // assert.deepEqual( - // source.indentExclusionRanges, - // clone.indentExclusionRanges, - // ) - // }) + assert.notStrictEqual( + source.indentExclusionRanges, + clone.indentExclusionRanges, + ) + assert.deepEqual( + source.indentExclusionRanges, + clone.indentExclusionRanges, + ) + }) - // it('should clone complex indentExclusionRanges', () => { - // const array = [ - // [3, 6], - // [7, 9], - // ] - // const source = new MagicString('abcdefghijkl', { - // filename: 'foo.js', - // indentExclusionRanges: array, - // }) + it('should clone complex indentExclusionRanges', () => { + const array = [ + [3, 6], + [7, 9], + ] + const source = new MagicString('abcdefghijkl', { + filename: 'foo.js', + indentExclusionRanges: array, + }) - // const clone = source.clone() + const clone = source.clone() - // assert.notStrictEqual( - // source.indentExclusionRanges, - // clone.indentExclusionRanges, - // ) - // assert.deepEqual( - // source.indentExclusionRanges, - // clone.indentExclusionRanges, - // ) - // }) + assert.notStrictEqual( + source.indentExclusionRanges, + clone.indentExclusionRanges, + ) + assert.deepEqual( + source.indentExclusionRanges, + clone.indentExclusionRanges, + ) + }) - // it('should clone sourcemapLocations', () => { - // const source = new MagicString('abcdefghijkl', { - // filename: 'foo.js', - // }) + it('should clone sourcemapLocations', () => { + const source = new MagicString('abcdefghijkl', { + filename: 'foo.js', + }) - // source.addSourcemapLocation(3) + source.addSourcemapLocation(3) - // const clone = source.clone() + const clone = source.clone() - // assert.notStrictEqual(source.sourcemapLocations, clone.sourcemapLocations) - // assert.deepEqual(source.sourcemapLocations, clone.sourcemapLocations) - // }) + assert.notStrictEqual(source.sourcemapLocations, clone.sourcemapLocations) + assert.deepEqual(source.sourcemapLocations, clone.sourcemapLocations) + }) */ - // it('should clone intro and outro', () => { - // const source = new MagicString('defghi') + it('should clone intro and outro', () => { + const source = new MagicString('defghi') - // source.prepend('abc') - // source.append('jkl') + source.prepend('abc') + source.append('jkl') - // const clone = source.clone() + const clone = source.clone() - // assert.equal(source.toString(), clone.toString()) - // }) - // }) + assert.equal(source.toString(), clone.toString()) + }) + }) describe('generateMap', () => { it('should generate a sourcemap', () => { From d31eb567fa29ef13bdd0bbbaaf2896d51aafad15 Mon Sep 17 00:00:00 2001 From: "M. Bagher Abiat" Date: Wed, 12 Apr 2023 18:34:37 +0330 Subject: [PATCH 5/5] add snip, clone and some benchmarks --- benchmark/bench.ts | 30 +++++++++++- core/src/magic_string.rs | 30 ++++++++---- core/tests/clone.rs | 8 ++-- core/tests/snip.rs | 50 +++++++++++++++++++ node/index.d.ts | 2 + node/index.js | 88 ++++++++++++++++------------------ node/src/lib.rs | 14 ++++-- node/tests/MagicString.spec.ts | 55 ++++++++++----------- 8 files changed, 186 insertions(+), 91 deletions(-) create mode 100644 core/tests/snip.rs diff --git a/benchmark/bench.ts b/benchmark/bench.ts index 2bcd066..a2959b9 100644 --- a/benchmark/bench.ts +++ b/benchmark/bench.ts @@ -160,4 +160,32 @@ b.suite( }), b.cycle(), b.complete(), -) \ No newline at end of file +) + +b.suite( + 'clone', + b.add('MagicString', () => { + const m = new MagicString(`export const foo = 'bar'`) + m.clone() + }), + b.add('MagicStringRust', () => { + const m = new MagicStringRust(`export const foo = 'bar'`) + m.clone() + }), + b.cycle(), + b.complete(), +) + +b.suite( + 'snip', + b.add('MagicString', () => { + const m = new MagicString(`export const foo = 'bar'`) + m.snip(3, 9) + }), + b.add('MagicStringRust', () => { + const m = new MagicStringRust(`export const foo = 'bar'`) + m.snip(3, 9) + }), + b.cycle(), + b.complete(), +) diff --git a/core/src/magic_string.rs b/core/src/magic_string.rs index 6a02f8f..8def03c 100644 --- a/core/src/magic_string.rs +++ b/core/src/magic_string.rs @@ -67,7 +67,7 @@ pub struct DecodedMap { #[derive(Debug, Clone)] pub struct MagicString { - original_str: String, + pub original: String, original_str_locator: Locator, intro: String, @@ -98,7 +98,7 @@ impl MagicString { let original_chunk = Rc::new(RefCell::new(Chunk::new(0u32, str.len() as u32, str))); MagicString { - original_str: String::from(str), + original: String::from(str), intro: String::default(), outro: String::default(), @@ -220,7 +220,7 @@ impl MagicString { } pub fn clone(&self) -> Result { - let mut cloned = MagicString::new(self.original_str.borrow()); + let mut cloned = MagicString::new(self.original.borrow()); let mut original_chunk = Rc::clone(&self.first_chunk); let mut cloned_chunk = Rc::new(RefCell::new(original_chunk.deref().borrow().clone())); @@ -305,8 +305,8 @@ impl MagicString { ) -> Result<&mut Self> { let content_only = options.content_only; - let start = normalize_index(self.original_str.as_str(), start)?; - let end = normalize_index(self.original_str.as_str(), end)?; + let start = normalize_index(self.original.as_str(), start)?; + let end = normalize_index(self.original.as_str(), end)?; let start = start as u32; let end = end as u32; @@ -574,8 +574,8 @@ impl MagicString { /// /// ``` pub fn remove(&mut self, start: i64, end: i64) -> Result<&mut Self> { - let start = normalize_index(self.original_str.as_str(), start)?; - let end = normalize_index(self.original_str.as_str(), end)?; + let start = normalize_index(self.original.as_str(), start)?; + let end = normalize_index(self.original.as_str(), end)?; let start = start as u32; let end = end as u32; @@ -622,8 +622,8 @@ impl MagicString { /// ``` /// pub fn slice(&mut self, start: i64, end: i64) -> Result { - let start = normalize_index(self.original_str.as_str(), start)?; - let end = normalize_index(self.original_str.as_str(), end)?; + let start = normalize_index(self.original.as_str(), start)?; + let end = normalize_index(self.original.as_str(), end)?; let start = start as u32; let end = end as u32; @@ -700,6 +700,16 @@ impl MagicString { Ok(result) } + + pub fn snip(&mut self, start: i64, end: i64) -> Result { + let mut clone = self.clone()?; + let length = clone.original.len(); + clone.remove(0, start)?; + clone.remove(end, length as i64)?; + + Ok(clone) + } + /// ## Is empty /// /// Returns `true` if the resulting source is empty (disregarding white space). @@ -772,7 +782,7 @@ impl MagicString { names: Vec::default(), sources_content: { if options.include_content { - vec![Some(self.original_str.to_owned())] + vec![Some(self.original.to_owned())] } else { Default::default() } diff --git a/core/tests/clone.rs b/core/tests/clone.rs index 1aa8906..c3d6457 100644 --- a/core/tests/clone.rs +++ b/core/tests/clone.rs @@ -6,9 +6,9 @@ mod remove { fn should_clone_a_magic_string() -> Result { let mut s = MagicString::new("abcdefghijkl"); - s.overwrite(3, 9, "XYZ", OverwriteOptions::default()); + s.overwrite(3, 9, "XYZ", OverwriteOptions::default())?; let mut c = s.clone().unwrap(); - c.overwrite(3, 9, "XYZB", OverwriteOptions::default()); + c.overwrite(3, 9, "XYZB", OverwriteOptions::default())?; assert_eq!(s.to_string(), "abcXYZjkl"); assert_eq!(c.to_string(), "abcXYZBjkl"); @@ -20,8 +20,8 @@ mod remove { fn should_clone_intro_and_outro() -> Result { let mut s = MagicString::new("defghi"); - s.prepend("abc"); - s.append("jkl"); + s.prepend("abc")?; + s.append("jkl")?; let c = s.clone().unwrap(); assert_eq!(s.to_string(), c.to_string()); diff --git a/core/tests/snip.rs b/core/tests/snip.rs new file mode 100644 index 0000000..ee7dc95 --- /dev/null +++ b/core/tests/snip.rs @@ -0,0 +1,50 @@ +#[cfg(test)] +mod remove { + use magic_string::{MagicString, OverwriteOptions, Result}; + + #[test] + fn should_return_a_clone_with_content_outside_start_and_end_removed() -> Result { + + let mut s = MagicString::new("abcdefghijkl"); + s.overwrite(6, 9, "GHI", OverwriteOptions::default())?; + let snippet = s.snip(3, 9)?; + + assert_eq!(snippet.to_string(), "defGHI"); + + Ok(()) + } + + #[test] + fn should_snip_from_the_start() -> Result { + + let mut s = MagicString::new("abcdefghijkl"); + let snippet = s.snip(0, 6)?; + + assert_eq!(snippet.to_string(), "abcdef"); + + Ok(()) + } + + #[test] + fn should_snip_from_the_end() -> Result { + + let mut s = MagicString::new("abcdefghijkl"); + let snippet = s.snip(6, 12)?; + + assert_eq!(snippet.to_string(), "ghijkl"); + + Ok(()) + } + + #[test] + fn should_respect_original_indices() -> Result { + + let mut s = MagicString::new("abcdefghijkl"); + let mut snippet = s.snip(3, 9)?; + + snippet.overwrite(6, 9, "GHI", OverwriteOptions::default())?; + assert_eq!(snippet.to_string(), "defGHI"); + + Ok(()) + } +} diff --git a/node/index.d.ts b/node/index.d.ts index 1c42279..6e29563 100644 --- a/node/index.d.ts +++ b/node/index.d.ts @@ -32,6 +32,7 @@ export interface OverwriteOptions { } export class MagicString { constructor(originalStr: string) + get original(): string append(input: string): this prepend(input: string): this appendLeft(index: number, input: string): this @@ -39,6 +40,7 @@ export class MagicString { prependLeft(index: number, input: string): this prependRight(index: number, input: string): this clone(): MagicString + snip(start: number, end: number): MagicString overwrite( start: number, end: number, diff --git a/node/index.js b/node/index.js index d9481a5..940d7ff 100644 --- a/node/index.js +++ b/node/index.js @@ -1,55 +1,51 @@ const { MagicString: MagicStringNative } = require('./binding') -module.exports.MagicString = class MagicString extends MagicStringNative { - overwrite(start, end, content, options) { - options = { - contentOnly: false, - ...options - } - return super.overwrite(start, end, content, options) - } - generateMap(options) { - options = { - file: null, - source: null, - sourceRoot: null, - includeContent: false, - hires: false, - ...options, - } - - const toString = () => super.toSourcemapString(options) - const toUrl = () => super.toSourcemapUrl(options) - const toMap = () => JSON.parse(toString(options)) - - return { - toString, - toUrl, - toMap, - } +const nativeOverwrite = MagicStringNative.prototype.overwrite +MagicStringNative.prototype.overwrite = function overwrite(start, end, content, options) { + options = { + contentOnly: false, + ...options } - generateDecodedMap(options) { - options = { - file: null, - source: null, - sourceRoot: null, - includeContent: false, - hires: false, - ...options, - } - - return JSON.parse(super.generateDecodedMap(options)) + nativeOverwrite.call(this, start, end, content, options) + return this +} + +MagicStringNative.prototype.generateMap = function generateMap(options) { + options = { + file: null, + source: null, + sourceRoot: null, + includeContent: false, + hires: false, + ...options, } - toSourcemapString() { - throw new Error( - '[magic-string] This is an internal API, you may refer to `generateMap`', - ) + + const toString = () => this.toSourcemapString(options) + const toUrl = () => this.toSourcemapUrl(options) + const toMap = () => JSON.parse(toString(options)) + + return { + toString, + toUrl, + toMap, } - toSourcemapUrl() { - throw new Error( - '[magic-string] This is an internal API, you may refer to `generateMap`', - ) +} + +const nativeGenerateDecodedMap = MagicStringNative.prototype.generateDecodedMap +MagicStringNative.prototype.generateDecodedMap = function generateDecodedMap(options) { + options = { + file: null, + source: null, + sourceRoot: null, + includeContent: false, + hires: false, + ...options, } + + return JSON.parse(nativeGenerateDecodedMap.call(this, options)) +} + +module.exports.MagicString = class MagicString extends MagicStringNative { } Object.assign(exports, '__esModule', { diff --git a/node/src/lib.rs b/node/src/lib.rs index 8370352..dcaa5b3 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -21,6 +21,11 @@ impl MagicString { MagicString(magic_string::MagicString::new(original_str.as_str())) } + #[napi(getter)] + pub fn original(&self) -> Result { + Ok(self.0.original.clone()) + } + #[napi] pub fn append(&mut self, input: String) -> Result<&Self> { self.0.append(input.as_str())?; @@ -59,9 +64,12 @@ impl MagicString { #[napi] pub fn clone(&mut self) -> Result { - // Ok(self.0.clone().unwrap()) - // self.0.clone() - Ok(MagicString(self.0.clone().unwrap())) + Ok(MagicString(self.0.clone()?)) + } + + #[napi] + pub fn snip(&mut self, start: i64, end: i64) -> Result { + Ok(MagicString(self.0.snip(start, end)?)) } #[napi(ts_args_type = r" diff --git a/node/tests/MagicString.spec.ts b/node/tests/MagicString.spec.ts index 013c854..5399330 100644 --- a/node/tests/MagicString.spec.ts +++ b/node/tests/MagicString.spec.ts @@ -1205,41 +1205,42 @@ describe('slice', () => { }) }) -// describe('snip', () => { -// it('should return a clone with content outside `start` and `end` removed', () => { -// const s = new MagicString('abcdefghijkl', { -// filename: 'foo.js', -// }) +describe('snip', () => { + it('should return a clone with content outside `start` and `end` removed', () => { + /* const s = new MagicString('abcdefghijkl', { + filename: 'foo.js', + }) */ + const s = new MagicString('abcdefghijkl') -// s.overwrite(6, 9, 'GHI') + s.overwrite(6, 9, 'GHI') -// const snippet = s.snip(3, 9) -// assert.equal(snippet.toString(), 'defGHI') -// assert.equal(snippet.filename, 'foo.js') -// }) + const snippet = s.snip(3, 9) + assert.equal(snippet.toString(), 'defGHI') + // assert.equal(snippet.filename, 'foo.js') + }) -// it('should snip from the start', () => { -// const s = new MagicString('abcdefghijkl') -// const snippet = s.snip(0, 6) + it('should snip from the start', () => { + const s = new MagicString('abcdefghijkl') + const snippet = s.snip(0, 6) -// assert.equal(snippet.toString(), 'abcdef') -// }) + assert.equal(snippet.toString(), 'abcdef') + }) -// it('should snip from the end', () => { -// const s = new MagicString('abcdefghijkl') -// const snippet = s.snip(6, 12) + it('should snip from the end', () => { + const s = new MagicString('abcdefghijkl') + const snippet = s.snip(6, 12) -// assert.equal(snippet.toString(), 'ghijkl') -// }) + assert.equal(snippet.toString(), 'ghijkl') + }) -// it('should respect original indices', () => { -// const s = new MagicString('abcdefghijkl') -// const snippet = s.snip(3, 9) + it('should respect original indices', () => { + const s = new MagicString('abcdefghijkl') + const snippet = s.snip(3, 9) -// snippet.overwrite(6, 9, 'GHI') -// assert.equal(snippet.toString(), 'defGHI') -// }) -// }) + snippet.overwrite(6, 9, 'GHI') + assert.equal(snippet.toString(), 'defGHI') + }) +}) describe('trim', () => { it('should trim original content', () => {