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