turbopack_core/
code_builder.rs1use std::{
2 cmp::min,
3 io::{BufRead, Result as IoResult, Write},
4 ops,
5};
6
7use anyhow::Result;
8use tracing::{Level, instrument};
9use turbo_tasks::Vc;
10use turbo_tasks_fs::rope::{Rope, RopeBuilder};
11use turbo_tasks_hash::hash_xxh3_hash64;
12
13use crate::{
14 source_map::{GenerateSourceMap, OptionStringifiedSourceMap, SourceMap},
15 source_pos::SourcePos,
16};
17
18pub type Mapping = (usize, Option<Rope>);
20
21#[turbo_tasks::value(shared)]
23#[derive(Debug, Clone)]
24pub struct Code {
25 code: Rope,
26 mappings: Vec<Mapping>,
27}
28
29impl Code {
30 pub fn source_code(&self) -> &Rope {
31 &self.code
32 }
33
34 pub fn has_source_map(&self) -> bool {
36 !self.mappings.is_empty()
37 }
38
39 pub fn into_source_code(self) -> Rope {
41 self.code
42 }
43}
44
45pub struct CodeBuilder {
47 code: RopeBuilder,
48 mappings: Option<Vec<Mapping>>,
49}
50
51impl Default for CodeBuilder {
52 fn default() -> Self {
53 Self {
54 code: RopeBuilder::default(),
55 mappings: Some(Vec::new()),
56 }
57 }
58}
59
60impl CodeBuilder {
61 pub fn new(collect_mappings: bool) -> Self {
62 Self {
63 code: RopeBuilder::default(),
64 mappings: collect_mappings.then(Vec::new),
65 }
66 }
67
68 fn push_static_bytes(&mut self, code: &'static [u8]) {
72 self.push_map(None);
73 self.code.push_static_bytes(code);
74 }
75
76 pub fn push_source(&mut self, code: &Rope, map: Option<Rope>) {
80 self.push_map(map);
81 self.code += code;
82 }
83
84 pub fn push_code(&mut self, prebuilt: &Code) {
87 if let Some((index, _)) = prebuilt.mappings.first() {
88 if *index > 0 {
89 self.push_map(None);
93 }
94
95 let len = self.code.len();
96 if let Some(mappings) = self.mappings.as_mut() {
97 mappings.extend(
98 prebuilt
99 .mappings
100 .iter()
101 .map(|(index, map)| (index + len, map.clone())),
102 );
103 }
104 } else {
105 self.push_map(None);
106 }
107
108 self.code += &prebuilt.code;
109 }
110
111 fn push_map(&mut self, map: Option<Rope>) {
117 let Some(mappings) = self.mappings.as_mut() else {
118 return;
119 };
120 if map.is_none() && matches!(mappings.last(), None | Some((_, None))) {
121 return;
123 }
124
125 debug_assert!(
126 map.is_some() || !mappings.is_empty(),
127 "the first mapping is never a None"
128 );
129 mappings.push((self.code.len(), map));
130 }
131
132 pub fn has_source_map(&self) -> bool {
134 self.mappings
135 .as_ref()
136 .is_some_and(|mappings| !mappings.is_empty())
137 }
138
139 pub fn build(self) -> Code {
140 Code {
141 code: self.code.build(),
142 mappings: self.mappings.unwrap_or_default(),
143 }
144 }
145}
146
147impl ops::AddAssign<&'static str> for CodeBuilder {
148 fn add_assign(&mut self, rhs: &'static str) {
149 self.push_static_bytes(rhs.as_bytes());
150 }
151}
152
153impl ops::AddAssign<&'static str> for &mut CodeBuilder {
154 fn add_assign(&mut self, rhs: &'static str) {
155 self.push_static_bytes(rhs.as_bytes());
156 }
157}
158
159impl Write for CodeBuilder {
160 fn write(&mut self, bytes: &[u8]) -> IoResult<usize> {
161 self.push_map(None);
162 self.code.write(bytes)
163 }
164
165 fn flush(&mut self) -> IoResult<()> {
166 self.code.flush()
167 }
168}
169
170impl From<Code> for CodeBuilder {
171 fn from(code: Code) -> Self {
172 let mut builder = CodeBuilder::default();
173 builder.push_code(&code);
174 builder
175 }
176}
177
178#[turbo_tasks::value_impl]
179impl GenerateSourceMap for Code {
180 #[turbo_tasks::function]
189 pub fn generate_source_map(&self) -> Result<Vc<OptionStringifiedSourceMap>> {
190 Ok(Vc::cell(Some(self.generate_source_map_ref()?)))
191 }
192}
193
194#[turbo_tasks::value_impl]
195impl Code {
196 #[turbo_tasks::function]
198 pub fn source_code_hash(&self) -> Vc<u64> {
199 let code = self;
200 let hash = hash_xxh3_hash64(code.source_code());
201 Vc::cell(hash)
202 }
203}
204
205impl Code {
206 #[instrument(name = "Code::generate_source_map", level = Level::INFO, skip_all)]
207 pub fn generate_source_map_ref(&self) -> Result<Rope> {
208 let mut pos = SourcePos::new();
209 let mut last_byte_pos = 0;
210
211 let mut sections = Vec::with_capacity(self.mappings.len());
212 let mut read = self.code.read();
213 for (byte_pos, map) in &self.mappings {
214 let mut want = byte_pos - last_byte_pos;
215 while want > 0 {
216 let buf = read.fill_buf()?;
217 debug_assert!(!buf.is_empty());
218
219 let end = min(want, buf.len());
220 pos.update(&buf[0..end]);
221
222 read.consume(end);
223 want -= end;
224 }
225 last_byte_pos = *byte_pos;
226
227 if let Some(map) = map {
228 sections.push((pos, map.clone()))
229 } else {
230 if pos.column != 0 && read.fill_buf()?.first().is_some_and(|&b| b != b'\n') {
232 sections.push((pos, SourceMap::empty_rope()));
233 }
234 }
235 }
236
237 if sections.len() == 1 && sections[0].0.line == 0 && sections[0].0.column == 0 {
238 Ok(sections.into_iter().next().unwrap().1)
239 } else {
240 SourceMap::sections_to_rope(sections)
241 }
242 }
243}