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#[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 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
141pub 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 #[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}