-
Notifications
You must be signed in to change notification settings - Fork 28.2k
/
Copy pathcode_builder.rs
206 lines (178 loc) · 6.47 KB
/
code_builder.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
use std::{
cmp::min,
io::{BufRead, Result as IoResult, Write},
ops,
};
use anyhow::Result;
use turbo_tasks::Vc;
use turbo_tasks_fs::rope::{Rope, RopeBuilder};
use turbo_tasks_hash::hash_xxh3_hash64;
use crate::{
source_map::{GenerateSourceMap, OptionStringifiedSourceMap, SourceMap},
source_pos::SourcePos,
};
/// A mapping of byte-offset in the code string to an associated source map.
pub type Mapping = (usize, Option<Rope>);
/// Code stores combined output code and the source map of that output code.
#[turbo_tasks::value(shared)]
#[derive(Debug, Clone)]
pub struct Code {
code: Rope,
mappings: Vec<Mapping>,
}
impl Code {
pub fn source_code(&self) -> &Rope {
&self.code
}
/// Tests if any code in this Code contains an associated source map.
pub fn has_source_map(&self) -> bool {
!self.mappings.is_empty()
}
}
/// CodeBuilder provides a mutable container to append source code.
#[derive(Default)]
pub struct CodeBuilder {
code: RopeBuilder,
mappings: Vec<Mapping>,
}
impl CodeBuilder {
/// Pushes synthetic runtime code without an associated source map. This is
/// the default concatenation operation, but it's designed to be used
/// with the `+=` operator.
fn push_static_bytes(&mut self, code: &'static [u8]) {
self.push_map(None);
self.code.push_static_bytes(code);
}
/// Pushes original user code with an optional source map if one is
/// available. If it's not, this is no different than pushing Synthetic
/// code.
pub fn push_source(&mut self, code: &Rope, map: Option<Rope>) {
self.push_map(map);
self.code += code;
}
/// Copies the Synthetic/Original code of an already constructed Code into
/// this instance.
pub fn push_code(&mut self, prebuilt: &Code) {
if let Some((index, _)) = prebuilt.mappings.first() {
if *index > 0 {
// If the index is positive, then the code starts with a synthetic section. We
// may need to push an empty map in order to end the current
// section's mappings.
self.push_map(None);
}
let len = self.code.len();
self.mappings.extend(
prebuilt
.mappings
.iter()
.map(|(index, map)| (index + len, map.clone())),
);
} else {
self.push_map(None);
}
self.code += &prebuilt.code;
}
/// Setting breakpoints on synthetic code can cause weird behaviors
/// because Chrome will treat the location as belonging to the previous
/// original code section. By inserting an empty source map when reaching a
/// synthetic section directly after an original section, we tell Chrome
/// that the previous map ended at this point.
fn push_map(&mut self, map: Option<Rope>) {
if map.is_none() && matches!(self.mappings.last(), None | Some((_, None))) {
// No reason to push an empty map directly after an empty map
return;
}
debug_assert!(
map.is_some() || !self.mappings.is_empty(),
"the first mapping is never a None"
);
self.mappings.push((self.code.len(), map));
}
/// Tests if any code in this CodeBuilder contains an associated source map.
pub fn has_source_map(&self) -> bool {
!self.mappings.is_empty()
}
pub fn build(self) -> Code {
Code {
code: self.code.build(),
mappings: self.mappings,
}
}
}
impl ops::AddAssign<&'static str> for CodeBuilder {
fn add_assign(&mut self, rhs: &'static str) {
self.push_static_bytes(rhs.as_bytes());
}
}
impl ops::AddAssign<&'static str> for &mut CodeBuilder {
fn add_assign(&mut self, rhs: &'static str) {
self.push_static_bytes(rhs.as_bytes());
}
}
impl Write for CodeBuilder {
fn write(&mut self, bytes: &[u8]) -> IoResult<usize> {
self.push_map(None);
self.code.write(bytes)
}
fn flush(&mut self) -> IoResult<()> {
self.code.flush()
}
}
#[turbo_tasks::value_impl]
impl GenerateSourceMap for Code {
/// Generates the source map out of all the pushed Original code.
/// The SourceMap v3 spec has a "sectioned" source map specifically designed
/// for concatenation in post-processing steps. This format consists of
/// a `sections` array, with section item containing a `offset` object
/// and a `map` object. The section's map applies only after the
/// starting offset, and until the start of the next section. This is by
/// far the simplest way to concatenate the source maps of the multiple
/// chunk items into a single map file.
#[turbo_tasks::function]
pub async fn generate_source_map(&self) -> Result<Vc<OptionStringifiedSourceMap>> {
Ok(Vc::cell(Some(self.generate_source_map_ref()?)))
}
}
#[turbo_tasks::value_impl]
impl Code {
/// Returns the hash of the source code of this Code.
#[turbo_tasks::function]
pub fn source_code_hash(&self) -> Vc<u64> {
let code = self;
let hash = hash_xxh3_hash64(code.source_code());
Vc::cell(hash)
}
}
impl Code {
pub fn generate_source_map_ref(&self) -> Result<Rope> {
let mut pos = SourcePos::new();
let mut last_byte_pos = 0;
let mut sections = Vec::with_capacity(self.mappings.len());
let mut read = self.code.read();
for (byte_pos, map) in &self.mappings {
let mut want = byte_pos - last_byte_pos;
while want > 0 {
let buf = read.fill_buf()?;
debug_assert!(!buf.is_empty());
let end = min(want, buf.len());
pos.update(&buf[0..end]);
read.consume(end);
want -= end;
}
last_byte_pos = *byte_pos;
if let Some(map) = map {
sections.push((pos, map.clone()))
} else {
// We don't need an empty source map when column is 0 or the next char is a newline.
if pos.column != 0 && read.fill_buf()?.first().is_some_and(|&b| b != b'\n') {
sections.push((pos, SourceMap::empty_rope()));
}
}
}
if sections.len() == 1 && sections[0].0.line == 0 && sections[0].0.column == 0 {
Ok(sections.into_iter().next().unwrap().1)
} else {
SourceMap::sections_to_rope(sections)
}
}
}