Skip to main content

rspack_sources/
raw_source.rs

1use std::{
2  borrow::Cow,
3  hash::{Hash, Hasher},
4  sync::OnceLock,
5};
6
7use crate::{
8  helpers::{
9    get_generated_source_info, stream_chunks_of_raw_source, Chunks,
10    GeneratedInfo, StreamChunks,
11  },
12  object_pool::ObjectPool,
13  MapOptions, Source, SourceMap, SourceValue,
14};
15
16/// A string variant of [RawStringSource].
17///
18/// - [webpack-sources docs](https://github.com/webpack/webpack-sources/#rawsource).
19///
20/// ```
21/// use rspack_sources::{MapOptions, RawStringSource, Source, ObjectPool};
22///
23/// let code = "some source code";
24/// let s = RawStringSource::from(code.to_string());
25/// assert_eq!(s.source().into_string_lossy(), code);
26/// assert_eq!(s.map(&ObjectPool::default(), &MapOptions::default()), None);
27/// assert_eq!(s.size(), 16);
28/// ```
29#[derive(Clone, PartialEq, Eq)]
30pub struct RawStringSource(Cow<'static, str>);
31
32#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
33static_assertions::assert_eq_size!(RawStringSource, [u8; 24]);
34
35impl RawStringSource {
36  /// Create a new [RawStringSource] from a static &str.
37  ///
38  /// ```
39  /// use rspack_sources::{RawStringSource, Source};
40  ///
41  /// let code = "some source code";
42  /// let s = RawStringSource::from_static(code);
43  /// assert_eq!(s.source().into_string_lossy(), code);
44  /// ```
45  pub fn from_static(s: &'static str) -> Self {
46    Self(Cow::Borrowed(s))
47  }
48}
49
50impl From<String> for RawStringSource {
51  fn from(value: String) -> Self {
52    Self(Cow::Owned(value))
53  }
54}
55
56impl From<&str> for RawStringSource {
57  fn from(value: &str) -> Self {
58    Self(Cow::Owned(value.to_string()))
59  }
60}
61
62impl Source for RawStringSource {
63  fn source(&self) -> SourceValue<'_> {
64    SourceValue::String(Cow::Borrowed(&self.0))
65  }
66
67  fn rope<'a>(&'a self, on_chunk: &mut dyn FnMut(&'a str)) {
68    on_chunk(self.0.as_ref())
69  }
70
71  fn buffer(&self) -> Cow<'_, [u8]> {
72    Cow::Borrowed(self.0.as_bytes())
73  }
74
75  fn size(&self) -> usize {
76    self.0.len()
77  }
78
79  fn map(&self, _: &ObjectPool, _: &MapOptions) -> Option<SourceMap> {
80    None
81  }
82
83  fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
84    writer.write_all(self.0.as_bytes())
85  }
86}
87
88impl std::fmt::Debug for RawStringSource {
89  fn fmt(
90    &self,
91    f: &mut std::fmt::Formatter<'_>,
92  ) -> Result<(), std::fmt::Error> {
93    let indent = f.width().unwrap_or(0);
94    let indent_str = format!("{:indent$}", "", indent = indent);
95    write!(
96      f,
97      "{indent_str}RawStringSource::from_static({:?}).boxed()",
98      self.0.as_ref()
99    )
100  }
101}
102
103impl Hash for RawStringSource {
104  fn hash<H: Hasher>(&self, state: &mut H) {
105    "RawStringSource".hash(state);
106    self.buffer().hash(state);
107  }
108}
109
110struct RawStringChunks<'source>(&'source str);
111
112impl<'source> RawStringChunks<'source> {
113  pub fn new(source: &'source RawStringSource) -> Self {
114    RawStringChunks(&source.0)
115  }
116}
117
118impl Chunks for RawStringChunks<'_> {
119  fn stream<'a>(
120    &'a self,
121    _object_pool: &'a ObjectPool,
122    options: &MapOptions,
123    on_chunk: crate::helpers::OnChunk<'_, 'a>,
124    on_source: crate::helpers::OnSource<'_, 'a>,
125    on_name: crate::helpers::OnName<'_, 'a>,
126  ) -> crate::helpers::GeneratedInfo {
127    if options.final_source {
128      get_generated_source_info(self.0)
129    } else {
130      stream_chunks_of_raw_source(self.0, options, on_chunk, on_source, on_name)
131    }
132  }
133}
134
135impl StreamChunks for RawStringSource {
136  fn stream_chunks<'a>(&'a self) -> Box<dyn Chunks + 'a> {
137    Box::new(RawStringChunks::new(self))
138  }
139}
140
141/// A buffer variant of [RawBufferSource].
142///
143/// - [webpack-sources docs](https://github.com/webpack/webpack-sources/#rawsource).
144///
145/// ```
146/// use rspack_sources::{MapOptions, RawBufferSource, Source, ObjectPool};
147///
148/// let code = "some source code".as_bytes();
149/// let s = RawBufferSource::from(code);
150/// assert_eq!(s.buffer(), code);
151/// assert_eq!(s.map(&ObjectPool::default(), &MapOptions::default()), None);
152/// assert_eq!(s.size(), 16);
153/// ```
154pub struct RawBufferSource {
155  value: Vec<u8>,
156  value_as_string: OnceLock<Option<String>>,
157}
158
159impl RawBufferSource {
160  #[allow(unsafe_code)]
161  fn get_or_init_value_as_string(&self) -> &str {
162    self
163      .value_as_string
164      .get_or_init(|| match String::from_utf8_lossy(&self.value) {
165        Cow::Owned(s) => Some(s),
166        Cow::Borrowed(_) => None,
167      })
168      .as_deref()
169      .unwrap_or_else(|| unsafe { std::str::from_utf8_unchecked(&self.value) })
170  }
171}
172
173impl Clone for RawBufferSource {
174  fn clone(&self) -> Self {
175    Self {
176      value: self.value.clone(),
177      value_as_string: Default::default(),
178    }
179  }
180}
181
182impl PartialEq for RawBufferSource {
183  fn eq(&self, other: &Self) -> bool {
184    self.value == other.value
185  }
186}
187
188impl Eq for RawBufferSource {}
189
190impl From<Vec<u8>> for RawBufferSource {
191  fn from(value: Vec<u8>) -> Self {
192    Self {
193      value,
194      value_as_string: Default::default(),
195    }
196  }
197}
198
199impl From<&[u8]> for RawBufferSource {
200  fn from(value: &[u8]) -> Self {
201    Self {
202      value: value.to_vec(),
203      value_as_string: Default::default(),
204    }
205  }
206}
207
208impl Source for RawBufferSource {
209  fn source(&self) -> SourceValue<'_> {
210    SourceValue::Buffer(Cow::Borrowed(&self.value))
211  }
212
213  fn rope<'a>(&'a self, on_chunk: &mut dyn FnMut(&'a str)) {
214    on_chunk(self.get_or_init_value_as_string())
215  }
216
217  fn buffer(&self) -> Cow<'_, [u8]> {
218    Cow::Borrowed(&self.value)
219  }
220
221  fn size(&self) -> usize {
222    self.value.len()
223  }
224
225  fn map(&self, _: &ObjectPool, _: &MapOptions) -> Option<SourceMap> {
226    None
227  }
228
229  fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
230    writer.write_all(&self.value)
231  }
232}
233
234impl std::fmt::Debug for RawBufferSource {
235  fn fmt(
236    &self,
237    f: &mut std::fmt::Formatter<'_>,
238  ) -> Result<(), std::fmt::Error> {
239    let indent = f.width().unwrap_or(0);
240    let indent_str = format!("{:indent$}", "", indent = indent);
241    write!(
242      f,
243      "{indent_str}RawBufferSource::from({:?}).boxed()",
244      &self.value
245    )
246  }
247}
248
249impl Hash for RawBufferSource {
250  fn hash<H: Hasher>(&self, state: &mut H) {
251    "RawBufferSource".hash(state);
252    self.buffer().hash(state);
253  }
254}
255
256struct RawBufferSourceChunks<'a>(&'a RawBufferSource);
257
258impl Chunks for RawBufferSourceChunks<'_> {
259  fn stream<'a>(
260    &'a self,
261    _object_pool: &'a ObjectPool,
262    options: &MapOptions,
263    on_chunk: crate::helpers::OnChunk<'_, 'a>,
264    on_source: crate::helpers::OnSource<'_, 'a>,
265    on_name: crate::helpers::OnName<'_, 'a>,
266  ) -> GeneratedInfo {
267    let code = self.0.get_or_init_value_as_string();
268    if options.final_source {
269      get_generated_source_info(code)
270    } else {
271      stream_chunks_of_raw_source(code, options, on_chunk, on_source, on_name)
272    }
273  }
274}
275
276impl StreamChunks for RawBufferSource {
277  fn stream_chunks<'a>(&'a self) -> Box<dyn Chunks + 'a> {
278    Box::new(RawBufferSourceChunks(self))
279  }
280}
281
282#[cfg(test)]
283mod tests {
284  use crate::{ConcatSource, OriginalSource, ReplaceSource, SourceExt};
285
286  use super::*;
287
288  // Fix https://github.com/web-infra-dev/rspack/issues/6793
289  #[test]
290  fn fix_rspack_issue_6793() {
291    let source1 = RawStringSource::from_static("hello\n\n");
292    let source1 = ReplaceSource::new(source1);
293    let source2 = OriginalSource::new("world".to_string(), "world.txt");
294    let concat = ConcatSource::new([source1.boxed(), source2.boxed()]);
295    let map = concat
296      .map(&ObjectPool::default(), &MapOptions::new(false))
297      .unwrap();
298    assert_eq!(map.mappings(), ";;AAAA",);
299  }
300}