1use std::{
2 cmp::min,
3 io::{BufRead, Result as IoResult, Write},
4 ops,
5 sync::Arc,
6};
7
8use anyhow::Result;
9use bincode::{Decode, Encode};
10use tracing::instrument;
11use turbo_rcstr::RcStr;
12use turbo_tasks::{NonLocalValue, ResolvedVc, Vc, trace::TraceRawVcs};
13use turbo_tasks_fs::{
14 File, FileContent,
15 rope::{Rope, RopeBuilder},
16};
17use turbo_tasks_hash::{DeterministicHash, DeterministicHasher, hash_xxh3_hash128};
18
19use crate::{
20 debug_id::generate_debug_id,
21 output::OutputAsset,
22 source_map::{GenerateSourceMap, SourceMap, SourceMapAsset, structured::StructuredSourceMap},
23 source_pos::SourcePos,
24};
25
26#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, TraceRawVcs, NonLocalValue)]
29pub enum SectionMap {
30 Raw(Rope),
31 Structured(Box<StructuredSourceMap>),
32}
33
34impl From<Rope> for SectionMap {
35 fn from(map: Rope) -> Self {
36 SectionMap::Raw(map)
37 }
38}
39
40impl From<StructuredSourceMap> for SectionMap {
41 fn from(map: StructuredSourceMap) -> Self {
42 SectionMap::Structured(Box::new(map))
43 }
44}
45
46impl DeterministicHash for SectionMap {
47 fn deterministic_hash<H: DeterministicHasher>(&self, state: &mut H) {
48 match self {
49 SectionMap::Raw(map) => {
50 state.write_u8(0);
51 map.deterministic_hash(state);
52 }
53 SectionMap::Structured(map) => {
54 state.write_u8(1);
55 map.deterministic_hash(state);
56 }
57 }
58 }
59}
60
61impl SectionMap {
62 pub fn to_rope(&self) -> Rope {
63 match self {
64 SectionMap::Raw(map) => map.clone(),
65 SectionMap::Structured(map) => map.to_rope(),
66 }
67 }
68}
69
70pub type Mapping = (usize, Option<SectionMap>);
72
73#[turbo_tasks::value(shared, serialization = "hash")]
75#[derive(Debug, Clone, Encode, Decode)]
76pub struct Code {
77 code: Rope,
78 mappings: Arc<Vec<Mapping>>,
79 should_generate_debug_id: bool,
80}
81
82#[turbo_tasks::value(transparent)]
83#[derive(Debug, Clone)]
84pub struct PersistedCode(Code);
85
86#[turbo_tasks::value_impl]
87impl PersistedCode {
88 #[turbo_tasks::function]
89 pub async fn to_code(self: Vc<Self>) -> Result<Vc<Code>> {
90 Ok(self.owned().await?.cell())
92 }
93}
94
95impl Code {
96 pub fn source_code(&self) -> &Rope {
97 &self.code
98 }
99
100 pub fn has_source_map(&self) -> bool {
102 !self.mappings.is_empty()
103 }
104 pub fn should_generate_debug_id(&self) -> bool {
106 self.should_generate_debug_id
107 }
108
109 pub fn into_source_code(self) -> Rope {
111 self.code
112 }
113
114 pub fn cell_persisted(self) -> ResolvedVc<PersistedCode> {
117 PersistedCode(self).resolved_cell()
118 }
119
120 pub async fn to_rope_with_magic_comments(
122 self: Vc<Self>,
123 source_map_path_fn: impl FnOnce() -> Vc<SourceMapAsset>,
124 ) -> Result<Rope> {
125 let code = self.await?;
126 Ok(
127 if code.has_source_map() || code.should_generate_debug_id() {
128 let mut rope_builder = RopeBuilder::default();
129 let debug_id = self.debug_id().await?;
130 if let Some(debug_id) = &*debug_id {
143 const GLOBALTHIS_EXPR: &str = r#""undefined"!=typeof globalThis?globalThis:"undefined"!=typeof global?global:"undefined"!=typeof window?window:"undefined"!=typeof self?self:{}"#;
146 const GLOBAL_VAR_NAME: &str = "_debugIds";
147 writeln!(
148 rope_builder,
149 r#";!function(){{try {{ var e={GLOBALTHIS_EXPR},n=(new e.Error).stack;n&&((e.{GLOBAL_VAR_NAME}|| (e.{GLOBAL_VAR_NAME}={{}}))[n]="{debug_id}")}}catch(e){{}}}}();"#,
150 )?;
151 }
152
153 rope_builder.concat(&code.code);
154 rope_builder.push_static_bytes(b"\n");
155 if let Some(debug_id) = &*debug_id {
157 write!(rope_builder, "\n//# debugId={}", debug_id)?;
158 }
159
160 if code.has_source_map() {
161 let source_map_path = source_map_path_fn().path().await?;
162 write!(
163 rope_builder,
164 "\n//# sourceMappingURL={}",
165 urlencoding::encode(source_map_path.file_name())
166 )?;
167 }
168 rope_builder.build()
169 } else {
170 code.code.clone()
171 },
172 )
173 }
174}
175
176pub struct CodeBuilder {
178 code: RopeBuilder,
179 mappings: Option<Vec<Mapping>>,
180 should_generate_debug_id: bool,
181}
182
183impl Default for CodeBuilder {
184 fn default() -> Self {
185 Self {
186 code: RopeBuilder::default(),
187 mappings: Some(Vec::new()),
188 should_generate_debug_id: false,
189 }
190 }
191}
192
193impl CodeBuilder {
194 pub fn new(collect_mappings: bool, should_generate_debug_id: bool) -> Self {
195 Self {
196 code: RopeBuilder::default(),
197 mappings: collect_mappings.then(Vec::new),
198 should_generate_debug_id,
199 }
200 }
201
202 fn push_static_bytes(&mut self, code: &'static [u8]) {
206 self.push_map(None);
207 self.code.push_static_bytes(code);
208 }
209
210 pub fn push_source<M: Into<SectionMap>>(&mut self, code: &Rope, map: Option<M>) {
214 self.push_map(map.map(Into::into));
215 self.code += code;
216 }
217
218 pub fn push_code(&mut self, prebuilt: &Code) {
223 if let Some((index, _)) = prebuilt.mappings.first() {
224 if *index > 0 {
225 self.push_map(None);
229 }
230
231 let len = self.code.len();
232 if let Some(mappings) = self.mappings.as_mut() {
233 mappings.extend(
234 prebuilt
235 .mappings
236 .iter()
237 .map(|(index, map)| (index + len, map.clone())),
238 );
239 }
240 } else {
241 self.push_map(None);
242 }
243
244 self.code += &prebuilt.code;
245 }
246
247 fn push_map(&mut self, map: Option<SectionMap>) {
253 let Some(mappings) = self.mappings.as_mut() else {
254 return;
255 };
256 if map.is_none() && matches!(mappings.last(), None | Some((_, None))) {
257 return;
259 }
260
261 debug_assert!(
262 map.is_some() || !mappings.is_empty(),
263 "the first mapping is never a None"
264 );
265 mappings.push((self.code.len(), map));
266 }
267
268 pub fn has_source_map(&self) -> bool {
270 self.mappings
271 .as_ref()
272 .is_some_and(|mappings| !mappings.is_empty())
273 }
274
275 pub fn build(self) -> Code {
276 Code {
277 code: self.code.build(),
278 mappings: Arc::new(self.mappings.unwrap_or_default()),
279 should_generate_debug_id: self.should_generate_debug_id,
280 }
281 }
282}
283
284impl ops::AddAssign<&'static str> for CodeBuilder {
285 fn add_assign(&mut self, rhs: &'static str) {
286 self.push_static_bytes(rhs.as_bytes());
287 }
288}
289
290impl ops::AddAssign<&'static str> for &mut CodeBuilder {
291 fn add_assign(&mut self, rhs: &'static str) {
292 self.push_static_bytes(rhs.as_bytes());
293 }
294}
295
296impl Write for CodeBuilder {
297 fn write(&mut self, bytes: &[u8]) -> IoResult<usize> {
298 self.push_map(None);
299 self.code.write(bytes)
300 }
301
302 fn flush(&mut self) -> IoResult<()> {
303 self.code.flush()
304 }
305}
306
307impl From<Code> for CodeBuilder {
308 fn from(code: Code) -> Self {
309 let mut builder = CodeBuilder::default();
310 builder.push_code(&code);
311 builder
312 }
313}
314
315#[turbo_tasks::value_impl]
316impl GenerateSourceMap for Code {
317 #[turbo_tasks::function]
326 pub async fn generate_source_map(self: ResolvedVc<Self>) -> Result<Vc<FileContent>> {
327 let debug_id = self.debug_id().owned().await?;
328 Ok(FileContent::Content(File::from(self.await?.generate_source_map_ref(debug_id))).cell())
329 }
330}
331
332#[turbo_tasks::value(transparent)]
333pub struct OptionDebugId(Option<RcStr>);
334
335#[turbo_tasks::value_impl]
336impl Code {
337 #[turbo_tasks::function]
339 pub fn source_code_hash(&self) -> Vc<u128> {
340 let code = self;
341 let hash = hash_xxh3_hash128(code.source_code());
342 Vc::cell(hash)
343 }
344
345 #[turbo_tasks::function]
346 pub fn debug_id(&self) -> Vc<OptionDebugId> {
347 Vc::cell(if self.should_generate_debug_id {
348 Some(generate_debug_id(self.source_code()))
349 } else {
350 None
351 })
352 }
353}
354
355impl Code {
356 #[instrument(level = "trace", name = "Code::generate_source_map", skip_all)]
358 pub fn generate_source_map_ref(&self, debug_id: Option<RcStr>) -> Rope {
359 debug_assert!(debug_id.is_none() || self.should_generate_debug_id);
362 let mut pos = SourcePos::new(if debug_id.is_some() { 1 } else { 0 });
365
366 let mut last_byte_pos = 0;
367
368 let mut sections = Vec::with_capacity(self.mappings.len());
369 let mut read = self.code.read();
370 for (byte_pos, map) in self.mappings.iter() {
371 let mut want = byte_pos - last_byte_pos;
372 while want > 0 {
373 let buf = read.fill_buf().unwrap();
375 debug_assert!(!buf.is_empty());
376
377 let end = min(want, buf.len());
378 pos.update(&buf[0..end]);
379
380 read.consume(end);
381 want -= end;
382 }
383 last_byte_pos = *byte_pos;
384
385 if let Some(map) = map {
386 sections.push((pos, map.to_rope()))
387 } else {
388 if pos.column != 0
390 && read
391 .fill_buf()
392 .unwrap()
393 .first()
394 .is_some_and(|&b| b != b'\n')
395 {
396 sections.push((pos, SourceMap::empty_rope()));
397 }
398 }
399 }
400
401 if sections.len() == 1
402 && sections[0].0.line == 0
403 && sections[0].0.column == 0
404 && debug_id.is_none()
405 {
406 sections.into_iter().next().unwrap().1
407 } else {
408 SourceMap::sections_to_rope(sections, debug_id)
409 }
410 }
411}