Skip to content

Commit 0b92259

Browse files
committed
feat: update MagicString module with new features and
bug fixes
1 parent bc59f43 commit 0b92259

File tree

7 files changed

+120
-113
lines changed

7 files changed

+120
-113
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "string_wizard"
3-
version = "0.0.11"
3+
version = "0.0.12"
44
edition = "2021"
55
license = "MIT"
66
description = "manipulate string like wizards"
@@ -13,6 +13,7 @@ rustc-hash = { version = "1.1.0" }
1313
regex = "1.10.2"
1414
serde = { version = "1.0", features = ["derive"], optional = true }
1515
serde_json = { version = "1.0", optional = true }
16+
once_cell = "1.18.0"
1617

1718
[features]
1819
# Enable source map functionality

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::borrow::Cow;
1616

1717
pub use crate::{
1818
joiner::{Joiner, JoinerOptions},
19-
magic_string::{mutation::UpdateOptions, MagicString, MagicStringOptions, indent::IndentOptions},
19+
magic_string::{update::UpdateOptions, MagicString, MagicStringOptions, indent::IndentOptions},
2020
};
2121

2222
#[cfg(feature = "source_map")]

src/magic_string/indent.rs

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustc_hash::FxHashSet;
22

33
use crate::{CowStr, MagicString, TextSize};
44

5-
fn guess_indent_str(source: &str) -> Option<String> {
5+
pub fn guess_indentor(source: &str) -> Option<String> {
66
let mut tabbed_count = 0;
77
let mut spaced_line = vec![];
88
for line in source.lines() {
@@ -36,37 +36,27 @@ fn guess_indent_str(source: &str) -> Option<String> {
3636

3737
#[derive(Debug, Default)]
3838
pub struct IndentOptions<'a> {
39-
/// MagicString will guess the `indent_str`` from lines of the source if passed `None`.
40-
pub indent_str: Option<&'a str>,
39+
/// MagicString will guess the `indentor`` from lines of the source if passed `None`.
40+
pub indentor: Option<&'a str>,
4141
pub exclude: Vec<TextSize>,
4242
}
4343

4444
impl<'text> MagicString<'text> {
45-
fn ensure_indent_str(&mut self) -> &str {
46-
if self.indent_str.is_none() {
47-
self.indent_str = guess_indent_str(&self.source);
48-
}
49-
50-
self.indent_str.as_deref().unwrap_or(&"\t")
45+
pub fn guessed_indentor(&mut self) -> &str {
46+
let guessed_indentor = self.guessed_indentor.get_or_init(|| guess_indentor(&self.source).unwrap_or_else(|| "\t".to_string()));
47+
guessed_indentor
5148
}
5249

5350
pub fn indent(&mut self) -> &mut Self {
5451
self.indent_with(IndentOptions {
55-
indent_str: None,
56-
..Default::default()
57-
})
58-
}
59-
60-
/// Shortcut for `indent_with(IndentOptions { indent_str: Some(indent_str), ..Default::default() })`
61-
pub fn indent_str(&mut self, indent_str: &str) -> &mut Self {
62-
self.indent_with(IndentOptions {
63-
indent_str: Some(indent_str),
52+
indentor: None,
6453
..Default::default()
6554
})
6655
}
6756

57+
6858
pub fn indent_with(&mut self, opts: IndentOptions<'_>) -> &mut Self {
69-
if opts.indent_str.map_or(false, |s| s.is_empty()) {
59+
if opts.indentor.map_or(false, |s| s.is_empty()) {
7060
return self;
7161
}
7262
struct IndentReplacer {
@@ -111,13 +101,13 @@ impl<'text> MagicString<'text> {
111101
}
112102
}
113103

114-
let indent_str = opts.indent_str.unwrap_or_else(|| self.ensure_indent_str());
104+
let indentor = opts.indentor.unwrap_or_else(|| self.guessed_indentor());
115105

116106
let pattern = regex::Regex::new(r"(?m)^[^\r\n]").unwrap();
117107

118108
let mut indent_replacer = IndentReplacer {
119109
should_indent_next_char: true,
120-
indent_str: indent_str.to_string(),
110+
indent_str: indentor.to_string(),
121111
};
122112

123113
for intro_frag in self.intro.iter_mut() {

src/magic_string/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ pub mod indent;
22
pub mod mutation;
33
#[cfg(feature = "source_map")]
44
pub mod source_map;
5+
pub mod update;
56

67
use std::collections::VecDeque;
78

9+
use once_cell::sync::OnceCell;
810
use rustc_hash::FxHashMap;
911

1012
use crate::{
@@ -24,14 +26,14 @@ pub struct MagicString<'s> {
2426
pub filename: Option<String>,
2527
intro: VecDeque<CowStr<'s>>,
2628
outro: VecDeque<CowStr<'s>>,
27-
indent_str: Option<String>,
2829
source: CowStr<'s>,
2930
source_len: TextSize,
3031
chunks: ChunkVec<'s>,
3132
first_chunk_idx: ChunkIdx,
3233
last_chunk_idx: ChunkIdx,
3334
chunk_by_start: FxHashMap<TextSize, ChunkIdx>,
3435
chunk_by_end: FxHashMap<TextSize, ChunkIdx>,
36+
guessed_indentor: OnceCell<String>,
3537
}
3638

3739
impl<'text> MagicString<'text> {
@@ -59,7 +61,7 @@ impl<'text> MagicString<'text> {
5961
chunk_by_end: Default::default(),
6062
// setup options
6163
filename: options.filename,
62-
indent_str: None,
64+
guessed_indentor: OnceCell::default(),
6365
};
6466

6567
magic_string.chunk_by_start.insert(0, initial_chunk_idx);
@@ -301,4 +303,3 @@ impl<'a> Iterator for ChunkIter<'a> {
301303
})
302304
}
303305
}
304-

src/magic_string/mutation.rs

Lines changed: 3 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,8 @@
1-
use crate::{basic_types::AssertIntoU32, chunk::EditOptions, CowStr, MagicString};
1+
use crate::{basic_types::AssertIntoU32, MagicString};
22

3-
#[derive(Debug, Default, Clone)]
4-
pub struct UpdateOptions {
5-
/// `true` will store the original content in the `name` field of the generated sourcemap.
6-
pub keep_original: bool,
7-
8-
/// `true` will clear the `intro` and `outro` for the corresponding range.
9-
pub overwrite: bool,
10-
}
3+
use super::update::UpdateOptions;
114

125
impl<'text> MagicString<'text> {
13-
/// A shorthand for `update_with(start, end, content, Default::default())`;
14-
pub fn update(
15-
&mut self,
16-
start: impl AssertIntoU32,
17-
end: impl AssertIntoU32,
18-
content: impl Into<CowStr<'text>>,
19-
) -> &mut Self {
20-
self.update_with(start, end, content, Default::default())
21-
}
22-
23-
pub fn update_with(
24-
&mut self,
25-
start: impl AssertIntoU32,
26-
end: impl AssertIntoU32,
27-
content: impl Into<CowStr<'text>>,
28-
opts: UpdateOptions,
29-
) -> &mut Self {
30-
self.update_with_inner(
31-
start.assert_into_u32(),
32-
end.assert_into_u32(),
33-
content.into(),
34-
opts,
35-
true,
36-
);
37-
self
38-
}
39-
406
pub fn remove(&mut self, start: impl AssertIntoU32, end: impl AssertIntoU32) -> &mut Self {
417
self.update_with_inner(
428
start.assert_into_u32(),
@@ -115,7 +81,7 @@ impl<'text> MagicString<'text> {
11581
self.last_chunk_idx = self.chunks[first_idx].prev.unwrap();
11682
self.chunks[last_idx].next = None;
11783
}
118-
84+
11985
if new_left_idx.is_none() {
12086
self.first_chunk_idx = first_idx;
12187
}
@@ -126,54 +92,6 @@ impl<'text> MagicString<'text> {
12692
self.chunks[first_idx].prev = new_left_idx;
12793
self.chunks[last_idx].next = new_right_idx;
12894

129-
130-
self
131-
}
132-
133-
// --- private
134-
135-
fn update_with_inner(
136-
&mut self,
137-
start: u32,
138-
end: u32,
139-
content: CowStr<'text>,
140-
opts: UpdateOptions,
141-
panic_if_start_equal_end: bool,
142-
) -> &mut Self {
143-
let start = start as u32;
144-
let end = end as u32;
145-
if panic_if_start_equal_end && start == end {
146-
panic!(
147-
"Cannot overwrite a zero-length range – use append_left or prepend_right instead"
148-
)
149-
}
150-
assert!(start < end);
151-
self.split_at(start);
152-
self.split_at(end);
153-
154-
let start_idx = self.chunk_by_start.get(&start).copied().unwrap();
155-
let end_idx = self.chunk_by_end.get(&end).copied().unwrap();
156-
157-
let start_chunk = &mut self.chunks[start_idx];
158-
start_chunk.edit(
159-
content.into(),
160-
EditOptions {
161-
overwrite: opts.overwrite,
162-
store_name: opts.keep_original,
163-
},
164-
);
165-
166-
let mut rest_chunk_idx = if start_idx != end_idx {
167-
start_chunk.next.unwrap()
168-
} else {
169-
return self;
170-
};
171-
172-
while rest_chunk_idx != end_idx {
173-
let rest_chunk = &mut self.chunks[rest_chunk_idx];
174-
rest_chunk.edit("".into(), Default::default());
175-
rest_chunk_idx = rest_chunk.next.unwrap();
176-
}
17795
self
17896
}
17997
}

src/magic_string/update.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use crate::{basic_types::AssertIntoU32, chunk::EditOptions, CowStr, MagicString};
2+
3+
#[derive(Debug, Default, Clone)]
4+
pub struct UpdateOptions {
5+
/// `true` will store the original content in the `name` field of the generated sourcemap.
6+
pub keep_original: bool,
7+
8+
/// `true` will clear the `intro` and `outro` for the corresponding range.
9+
pub overwrite: bool,
10+
}
11+
12+
impl<'text> MagicString<'text> {
13+
/// A shorthand for `update_with(start, end, content, Default::default())`;
14+
pub fn update(
15+
&mut self,
16+
start: impl AssertIntoU32,
17+
end: impl AssertIntoU32,
18+
content: impl Into<CowStr<'text>>,
19+
) -> &mut Self {
20+
self.update_with(start, end, content, Default::default())
21+
}
22+
23+
pub fn update_with(
24+
&mut self,
25+
start: impl AssertIntoU32,
26+
end: impl AssertIntoU32,
27+
content: impl Into<CowStr<'text>>,
28+
opts: UpdateOptions,
29+
) -> &mut Self {
30+
self.update_with_inner(
31+
start.assert_into_u32(),
32+
end.assert_into_u32(),
33+
content.into(),
34+
opts,
35+
true,
36+
);
37+
self
38+
}
39+
40+
// --- private
41+
42+
pub(super) fn update_with_inner(
43+
&mut self,
44+
start: u32,
45+
end: u32,
46+
content: CowStr<'text>,
47+
opts: UpdateOptions,
48+
panic_if_start_equal_end: bool,
49+
) -> &mut Self {
50+
let start = start as u32;
51+
let end = end as u32;
52+
if panic_if_start_equal_end && start == end {
53+
panic!(
54+
"Cannot overwrite a zero-length range – use append_left or prepend_right instead"
55+
)
56+
}
57+
assert!(start < end);
58+
self.split_at(start);
59+
self.split_at(end);
60+
61+
let start_idx = self.chunk_by_start.get(&start).copied().unwrap();
62+
let end_idx = self.chunk_by_end.get(&end).copied().unwrap();
63+
64+
let start_chunk = &mut self.chunks[start_idx];
65+
start_chunk.edit(
66+
content.into(),
67+
EditOptions {
68+
overwrite: opts.overwrite,
69+
store_name: opts.keep_original,
70+
},
71+
);
72+
73+
let mut rest_chunk_idx = if start_idx != end_idx {
74+
start_chunk.next.unwrap()
75+
} else {
76+
return self;
77+
};
78+
79+
while rest_chunk_idx != end_idx {
80+
let rest_chunk = &mut self.chunks[rest_chunk_idx];
81+
rest_chunk.edit("".into(), Default::default());
82+
rest_chunk_idx = rest_chunk.next.unwrap();
83+
}
84+
self
85+
}
86+
}

tests/magic_string.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22

3+
use string_wizard::IndentOptions;
34
use string_wizard::MagicString;
45
use string_wizard::MagicStringOptions;
56
use string_wizard::UpdateOptions;
@@ -11,6 +12,8 @@ trait MagicStringExt<'text> {
1112
end: usize,
1213
content: impl Into<Cow<'text, str>>,
1314
) -> &mut Self;
15+
16+
fn indent_str(&mut self, indent_str: &str) -> &mut Self;
1417
}
1518

1619
impl<'text> MagicStringExt<'text> for MagicString<'text> {
@@ -30,6 +33,14 @@ impl<'text> MagicStringExt<'text> for MagicString<'text> {
3033
},
3134
)
3235
}
36+
/// Shortcut for `indent_with(IndentOptions { indent_str: Some(indent_str), ..Default::default() })`
37+
fn indent_str(&mut self, indent_str: &str) -> &mut Self {
38+
self.indent_with(IndentOptions {
39+
indentor: Some(indent_str),
40+
..Default::default()
41+
})
42+
}
43+
3344
}
3445

3546
mod options {
@@ -329,12 +340,12 @@ mod indent {
329340
fn should_prevent_excluded_characters_from_being_indented() {
330341
let mut s = MagicString::new("abc\ndef\nghi\njkl");
331342
s.indent_with(IndentOptions {
332-
indent_str: Some(" "),
343+
indentor: Some(" "),
333344
exclude: vec![7, 15],
334345
});
335346
assert_eq!(s.to_string(), " abc\n def\nghi\njkl");
336347
s.indent_with(IndentOptions {
337-
indent_str: Some(">>"),
348+
indentor: Some(">>"),
338349
exclude: vec![7, 15],
339350
});
340351
assert_eq!(s.to_string(), ">> abc\n>> def\nghi\njkl");

0 commit comments

Comments
 (0)