rustbolt_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, OnChunk, OnName,
10    OnSource, StreamChunks,
11  },
12  MapOptions, Rope, Source, SourceMap,
13};
14
15#[derive(Clone, PartialEq, Eq)]
16enum RawValue {
17  Buffer(Vec<u8>),
18  String(Cow<'static, str>),
19}
20
21#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
22static_assertions::assert_eq_size!(RawValue, [u8; 32]);
23
24/// Represents source code without source map, it will not create source map for the source code.
25///
26/// - [webpack-sources docs](https://github.com/webpack/webpack-sources/#rawsource).
27///
28/// ```
29/// use rustbolt_sources::{MapOptions, RawSource, Source};
30///
31/// let code = "some source code";
32/// let s = RawSource::from(code.to_string());
33/// assert_eq!(s.source(), code);
34/// assert_eq!(s.map(&MapOptions::default()), None);
35/// assert_eq!(s.size(), 16);
36/// ```
37pub struct RawSource {
38  value: RawValue,
39  value_as_string: OnceLock<String>,
40}
41
42impl RawSource {
43  /// Create a new [RawSource] from a static &str.
44  ///
45  /// ```
46  /// use rustbolt_sources::{RawSource, Source};
47  ///
48  /// let code = "some source code";
49  /// let s = RawSource::from_static(code);
50  /// assert_eq!(s.source(), code);
51  /// ```
52  pub fn from_static(s: &'static str) -> Self {
53    Self {
54      value: RawValue::String(Cow::Borrowed(s)),
55      value_as_string: Default::default(),
56    }
57  }
58}
59
60impl Clone for RawSource {
61  fn clone(&self) -> Self {
62    Self {
63      value: self.value.clone(),
64      value_as_string: Default::default(),
65    }
66  }
67}
68
69impl Eq for RawSource {}
70
71impl RawSource {
72  /// Whether the [RawSource] represent a buffer.
73  pub fn is_buffer(&self) -> bool {
74    matches!(self.value, RawValue::Buffer(_))
75  }
76}
77
78impl From<String> for RawSource {
79  fn from(value: String) -> Self {
80    Self {
81      value: RawValue::String(value.into()),
82      value_as_string: Default::default(),
83    }
84  }
85}
86
87impl From<Vec<u8>> for RawSource {
88  fn from(value: Vec<u8>) -> Self {
89    Self {
90      value: RawValue::Buffer(value),
91      value_as_string: Default::default(),
92    }
93  }
94}
95
96impl From<&str> for RawSource {
97  fn from(value: &str) -> Self {
98    Self {
99      value: RawValue::String(value.to_string().into()),
100      value_as_string: Default::default(),
101    }
102  }
103}
104
105impl From<&[u8]> for RawSource {
106  fn from(value: &[u8]) -> Self {
107    Self {
108      value: RawValue::Buffer(value.to_owned()),
109      value_as_string: Default::default(),
110    }
111  }
112}
113
114impl Source for RawSource {
115  fn source(&self) -> Cow<str> {
116    match &self.value {
117      RawValue::String(v) => Cow::Borrowed(v),
118      RawValue::Buffer(v) => Cow::Borrowed(
119        self
120          .value_as_string
121          .get_or_init(|| String::from_utf8_lossy(v).to_string()),
122      ),
123    }
124  }
125
126  fn rope(&self) -> Rope<'_> {
127    match &self.value {
128      RawValue::Buffer(v) => Rope::from(
129        self
130          .value_as_string
131          .get_or_init(|| String::from_utf8_lossy(v).to_string()),
132      ),
133      RawValue::String(s) => Rope::from(s),
134    }
135  }
136
137  fn buffer(&self) -> Cow<[u8]> {
138    match &self.value {
139      RawValue::String(v) => Cow::Borrowed(v.as_bytes()),
140      RawValue::Buffer(v) => Cow::Borrowed(v),
141    }
142  }
143
144  fn size(&self) -> usize {
145    match &self.value {
146      RawValue::String(v) => v.len(),
147      RawValue::Buffer(v) => v.len(),
148    }
149  }
150
151  fn map(&self, _: &MapOptions) -> Option<SourceMap> {
152    None
153  }
154
155  fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
156    writer.write_all(match &self.value {
157      RawValue::String(v) => v.as_bytes(),
158      RawValue::Buffer(v) => v,
159    })
160  }
161}
162
163impl Hash for RawSource {
164  fn hash<H: Hasher>(&self, state: &mut H) {
165    "RawSource".hash(state);
166    self.buffer().hash(state);
167  }
168}
169
170impl PartialEq for RawSource {
171  fn eq(&self, other: &Self) -> bool {
172    match (&self.value, &other.value) {
173      (RawValue::Buffer(l0), RawValue::Buffer(r0)) => l0 == r0,
174      (RawValue::String(l0), RawValue::String(r0)) => l0 == r0,
175      _ => false,
176    }
177  }
178}
179
180impl std::fmt::Debug for RawSource {
181  fn fmt(
182    &self,
183    f: &mut std::fmt::Formatter<'_>,
184  ) -> Result<(), std::fmt::Error> {
185    let mut d = f.debug_struct("RawSource");
186    match &self.value {
187      RawValue::Buffer(buffer) => {
188        d.field(
189          "buffer",
190          &buffer.iter().take(50).copied().collect::<Vec<u8>>(),
191        );
192      }
193      RawValue::String(string) => {
194        d.field("source", &string.chars().take(50).collect::<String>());
195      }
196    }
197    d.finish()
198  }
199}
200
201impl StreamChunks for RawSource {
202  fn stream_chunks<'a>(
203    &'a self,
204    options: &MapOptions,
205    on_chunk: OnChunk<'_, 'a>,
206    on_source: OnSource<'_, 'a>,
207    on_name: OnName<'_, 'a>,
208  ) -> crate::helpers::GeneratedInfo {
209    if options.final_source {
210      match &self.value {
211        RawValue::Buffer(buffer) => {
212          let source = self
213            .value_as_string
214            .get_or_init(|| String::from_utf8_lossy(buffer).to_string());
215          get_generated_source_info(&**source)
216        }
217        RawValue::String(source) => get_generated_source_info(&**source),
218      }
219    } else {
220      match &self.value {
221        RawValue::Buffer(buffer) => {
222          let source = self
223            .value_as_string
224            .get_or_init(|| String::from_utf8_lossy(buffer).to_string());
225          stream_chunks_of_raw_source(
226            &**source, options, on_chunk, on_source, on_name,
227          )
228        }
229        RawValue::String(source) => stream_chunks_of_raw_source(
230          &**source, options, on_chunk, on_source, on_name,
231        ),
232      }
233    }
234  }
235}
236
237/// A string variant of [RawSource].
238///
239/// - [webpack-sources docs](https://github.com/webpack/webpack-sources/#rawsource).
240///
241/// ```
242/// use rustbolt_sources::{MapOptions, RawStringSource, Source};
243///
244/// let code = "some source code";
245/// let s = RawStringSource::from(code.to_string());
246/// assert_eq!(s.source(), code);
247/// assert_eq!(s.map(&MapOptions::default()), None);
248/// assert_eq!(s.size(), 16);
249/// ```
250#[derive(Clone, PartialEq, Eq)]
251pub struct RawStringSource(Cow<'static, str>);
252
253#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
254static_assertions::assert_eq_size!(RawStringSource, [u8; 24]);
255
256impl RawStringSource {
257  /// Create a new [RawStringSource] from a static &str.
258  ///
259  /// ```
260  /// use rustbolt_sources::{RawStringSource, Source};
261  ///
262  /// let code = "some source code";
263  /// let s = RawStringSource::from_static(code);
264  /// assert_eq!(s.source(), code);
265  /// ```
266  pub fn from_static(s: &'static str) -> Self {
267    Self(Cow::Borrowed(s))
268  }
269}
270
271impl From<String> for RawStringSource {
272  fn from(value: String) -> Self {
273    Self(Cow::Owned(value))
274  }
275}
276
277impl From<&str> for RawStringSource {
278  fn from(value: &str) -> Self {
279    Self(Cow::Owned(value.to_owned()))
280  }
281}
282
283impl Source for RawStringSource {
284  fn source(&self) -> Cow<str> {
285    Cow::Borrowed(&self.0)
286  }
287
288  fn rope(&self) -> Rope<'_> {
289    Rope::from(&self.0)
290  }
291
292  fn buffer(&self) -> Cow<[u8]> {
293    Cow::Borrowed(self.0.as_bytes())
294  }
295
296  fn size(&self) -> usize {
297    self.0.len()
298  }
299
300  fn map(&self, _: &MapOptions) -> Option<SourceMap> {
301    None
302  }
303
304  fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
305    writer.write_all(self.0.as_bytes())
306  }
307}
308
309impl std::fmt::Debug for RawStringSource {
310  fn fmt(
311    &self,
312    f: &mut std::fmt::Formatter<'_>,
313  ) -> Result<(), std::fmt::Error> {
314    let mut d = f.debug_tuple("RawStringSource");
315    d.field(&self.0.chars().take(50).collect::<String>());
316    d.finish()
317  }
318}
319
320impl Hash for RawStringSource {
321  fn hash<H: Hasher>(&self, state: &mut H) {
322    "RawStringSource".hash(state);
323    self.buffer().hash(state);
324  }
325}
326
327impl StreamChunks for RawStringSource {
328  fn stream_chunks<'a>(
329    &'a self,
330    options: &MapOptions,
331    on_chunk: OnChunk<'_, 'a>,
332    on_source: OnSource<'_, 'a>,
333    on_name: OnName<'_, 'a>,
334  ) -> crate::helpers::GeneratedInfo {
335    if options.final_source {
336      get_generated_source_info(&*self.0)
337    } else {
338      stream_chunks_of_raw_source(
339        &*self.0, options, on_chunk, on_source, on_name,
340      )
341    }
342  }
343}
344
345/// A buffer variant of [RawSource].
346///
347/// - [webpack-sources docs](https://github.com/webpack/webpack-sources/#rawsource).
348///
349/// ```
350/// use rustbolt_sources::{MapOptions, RawBufferSource, Source};
351///
352/// let code = "some source code".as_bytes();
353/// let s = RawBufferSource::from(code);
354/// assert_eq!(s.buffer(), code);
355/// assert_eq!(s.map(&MapOptions::default()), None);
356/// assert_eq!(s.size(), 16);
357/// ```
358#[derive(Clone, PartialEq, Eq)]
359pub struct RawBufferSource {
360  value: Vec<u8>,
361  value_as_string: OnceLock<String>,
362}
363
364impl From<Vec<u8>> for RawBufferSource {
365  fn from(value: Vec<u8>) -> Self {
366    Self {
367      value,
368      value_as_string: Default::default(),
369    }
370  }
371}
372
373impl From<&[u8]> for RawBufferSource {
374  fn from(value: &[u8]) -> Self {
375    Self {
376      value: value.to_vec(),
377      value_as_string: Default::default(),
378    }
379  }
380}
381
382impl Source for RawBufferSource {
383  fn source(&self) -> Cow<str> {
384    Cow::Borrowed(
385      self
386        .value_as_string
387        .get_or_init(|| String::from_utf8_lossy(&self.value).to_string()),
388    )
389  }
390
391  fn rope(&self) -> Rope<'_> {
392    Rope::from(
393      self
394        .value_as_string
395        .get_or_init(|| String::from_utf8_lossy(&self.value).to_string()),
396    )
397  }
398
399  fn buffer(&self) -> Cow<[u8]> {
400    Cow::Borrowed(&self.value)
401  }
402
403  fn size(&self) -> usize {
404    self.value.len()
405  }
406
407  fn map(&self, _: &MapOptions) -> Option<SourceMap> {
408    None
409  }
410
411  fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
412    writer.write_all(&self.value)
413  }
414}
415
416impl std::fmt::Debug for RawBufferSource {
417  fn fmt(
418    &self,
419    f: &mut std::fmt::Formatter<'_>,
420  ) -> Result<(), std::fmt::Error> {
421    let mut d = f.debug_tuple("RawBufferSource");
422    d.field(&self.value.iter().take(50).copied().collect::<Vec<u8>>());
423    d.finish()
424  }
425}
426
427impl Hash for RawBufferSource {
428  fn hash<H: Hasher>(&self, state: &mut H) {
429    "RawBufferSource".hash(state);
430    self.buffer().hash(state);
431  }
432}
433
434impl StreamChunks for RawBufferSource {
435  fn stream_chunks<'a>(
436    &'a self,
437    options: &MapOptions,
438    on_chunk: OnChunk<'_, 'a>,
439    on_source: OnSource<'_, 'a>,
440    on_name: OnName<'_, 'a>,
441  ) -> crate::helpers::GeneratedInfo {
442    if options.final_source {
443      get_generated_source_info(&*self.source())
444    } else {
445      stream_chunks_of_raw_source(
446        &**self
447          .value_as_string
448          .get_or_init(|| String::from_utf8_lossy(&self.value).to_string()),
449        options,
450        on_chunk,
451        on_source,
452        on_name,
453      )
454    }
455  }
456}
457
458#[cfg(test)]
459mod tests {
460  use crate::{ConcatSource, OriginalSource, ReplaceSource, SourceExt};
461
462  use super::*;
463
464  // Fix https://github.com/khulnasoft/rustbolt/issues/6793
465  #[test]
466  fn fix_rustbolt_issue_6793() {
467    let source1 = RawSource::from("hello\n\n".to_string());
468    let source1 = ReplaceSource::new(source1);
469    let source2 = OriginalSource::new("world".to_string(), "world.txt");
470    let concat = ConcatSource::new([source1.boxed(), source2.boxed()]);
471    let map = concat.map(&MapOptions::new(false)).unwrap();
472    assert_eq!(map.mappings(), ";;AAAA",);
473  }
474}