diff --git a/core/src/magic_string.rs b/core/src/magic_string.rs index 325afde..eddadc6 100644 --- a/core/src/magic_string.rs +++ b/core/src/magic_string.rs @@ -525,6 +525,89 @@ impl MagicString { Ok(self) } + /// ## Slice + /// Get a slice of the modified string. + /// Example: + /// ``` + /// use magic_string::MagicString; + /// let mut s = MagicString::new("abcdefghijkl"); + /// + /// + /// ``` + /// + 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.", + )); + } + + 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; + + 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 chunk_str = chunk.borrow().content.clone(); + + if contain_end && chunk.borrow().is_content_edited() && chunk.borrow().end != end { + return Err(Error::new_with_reason( + MagicStringErrorType::MagicStringUnknownError, + "Cannot use replaced character ${end} as slice end anchor.", + )); + } + + 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 /// /// 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/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 224f16d..369f78f 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -104,6 +104,11 @@ impl MagicString { Ok(self) } + #[napi] + pub fn slice(&mut self, start: i64, end: i64) -> Result { + Ok(self.0.slice(start, end)?) + } + #[napi] pub fn is_empty(&self) -> Result { Ok(self.0.is_empty()) 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', () => {