1use crate::{
2 count_characters_on_last_line, FileSystem, SourceMap, SourceMapBuilder, SpanWithSource,
3};
4
5pub trait ToString {
7 fn push(&mut self, chr: char);
9
10 fn push_new_line(&mut self);
12
13 fn push_str(&mut self, string: &str);
15
16 fn push_str_contains_new_line(&mut self, string: &str);
18
19 fn add_mapping(&mut self, source_span: &SpanWithSource);
23
24 fn should_halt(&self) -> bool {
27 false
28 }
29
30 fn characters_on_current_line(&self) -> u32;
31
32 fn is_counting(&self) -> bool {
33 false
34 }
35}
36
37impl ToString for String {
39 fn push(&mut self, chr: char) {
40 self.push(chr);
41 }
42
43 fn push_new_line(&mut self) {
44 self.push('\n');
45 }
46
47 fn push_str(&mut self, string: &str) {
48 self.push_str(string)
49 }
50
51 fn push_str_contains_new_line(&mut self, string: &str) {
52 self.push_str(string)
53 }
54
55 fn add_mapping(&mut self, _source_span: &SpanWithSource) {}
56
57 fn characters_on_current_line(&self) -> u32 {
58 count_characters_on_last_line(self)
59 }
60}
61
62pub struct Writable<T: std::io::Write> {
63 pub writable: T,
64 pub length: u32,
65 pub since_new_line: u32,
66 pub source_map: Option<SourceMapBuilder>,
67}
68
69impl<T: std::io::Write> ToString for Writable<T> {
70 fn push(&mut self, chr: char) {
71 let mut buf = [0u8; 4]; let buf = chr.encode_utf8(&mut buf).as_bytes();
73 let char_size = chr.len_utf8();
74 self.length += char_size as u32;
75 self.since_new_line += char_size as u32;
76 self.writable.write_all(buf).unwrap();
77 }
78
79 fn push_new_line(&mut self) {
80 self.length += 1;
81 self.writable.write_all(&[b'\n']).unwrap();
82 }
83
84 fn push_str(&mut self, string: &str) {
85 self.length += string.len() as u32;
86 self.since_new_line += string.len() as u32;
87 self.writable.write_all(string.as_bytes()).unwrap();
88 }
89
90 fn push_str_contains_new_line(&mut self, slice: &str) {
91 self.length += slice.len() as u32;
92 self.writable.write_all(slice.as_bytes()).unwrap();
93 if let Some(ref mut sm) = self.source_map {
94 slice
95 .chars()
96 .filter(|chr| *chr == '\n')
97 .for_each(|_| sm.add_new_line());
98 }
99 self.since_new_line = count_characters_on_last_line(slice);
100 }
101
102 fn add_mapping(&mut self, source_span: &SpanWithSource) {
103 if let Some(ref mut sm) = self.source_map {
104 sm.add_mapping(source_span, self.since_new_line);
105 }
106 }
107
108 fn characters_on_current_line(&self) -> u32 {
109 self.since_new_line
110 }
111}
112
113#[derive(Default)]
117pub struct StringWithOptionalSourceMap {
118 pub source: String,
119 pub source_map: Option<SourceMapBuilder>,
120 pub quit_after: Option<usize>,
121 pub since_new_line: u32,
122}
123
124impl StringWithOptionalSourceMap {
125 pub fn new(with_source_map: bool) -> Self {
126 Self {
127 source: String::new(),
128 source_map: with_source_map.then(SourceMapBuilder::new),
129 quit_after: None,
130 since_new_line: 0,
131 }
132 }
133
134 pub fn build(self, filesystem: &impl FileSystem) -> (String, Option<SourceMap>) {
136 (self.source, self.source_map.map(|sm| sm.build(filesystem)))
137 }
138
139 #[cfg(feature = "inline-source-map")]
140 pub fn build_with_inline_source_map(self, filesystem: &impl FileSystem) -> String {
142 use base64::Engine;
143
144 let Self {
145 mut source,
146 source_map,
147 quit_after: _,
148 since_new_line: _,
149 } = self;
150 let built_source_map = source_map.unwrap().build(filesystem);
151 source.push_str("\n//# sourceMappingURL=data:application/json;base64,");
153 source.push_str(
154 &base64::prelude::BASE64_STANDARD.encode(built_source_map.to_json(filesystem)),
155 );
156 source
157 }
158}
159
160impl ToString for StringWithOptionalSourceMap {
161 fn push(&mut self, chr: char) {
162 self.source.push(chr);
163 if let Some(ref mut sm) = self.source_map {
164 sm.add_to_column(chr.len_utf16());
165 }
166 self.since_new_line += chr.len_utf8() as u32;
167 }
168
169 fn push_new_line(&mut self) {
170 self.source.push('\n');
171 if let Some(ref mut sm) = self.source_map {
172 sm.add_new_line();
173 }
174 self.since_new_line = 0;
175 }
176
177 fn push_str(&mut self, slice: &str) {
178 self.source.push_str(slice);
179 if let Some(ref mut sm) = self.source_map {
180 sm.add_to_column(slice.chars().count());
181 }
182 self.since_new_line += slice.len() as u32;
183 }
184
185 fn push_str_contains_new_line(&mut self, slice: &str) {
186 self.source.push_str(slice);
187 if let Some(ref mut sm) = self.source_map {
188 slice
189 .chars()
190 .filter(|chr| *chr == '\n')
191 .for_each(|_| sm.add_new_line());
192 }
193 self.since_new_line = count_characters_on_last_line(slice);
194 }
195
196 fn add_mapping(&mut self, source_span: &SpanWithSource) {
197 if let Some(ref mut sm) = self.source_map {
198 sm.add_mapping(source_span, self.since_new_line);
199 }
200 }
201
202 fn should_halt(&self) -> bool {
203 self.quit_after
204 .map_or(false, |quit_after| self.source.len() > quit_after)
205 }
206
207 fn characters_on_current_line(&self) -> u32 {
208 self.since_new_line
209 }
210
211 fn is_counting(&self) -> bool {
212 self.quit_after.is_some()
213 }
214}
215
216pub struct Counter {
218 acc: usize,
219 max: usize,
220}
221
222impl Counter {
223 pub fn new(max: usize) -> Self {
224 Self { acc: 0, max }
225 }
226
227 pub fn get_count(&self) -> usize {
228 self.acc
229 }
230}
231
232impl ToString for Counter {
233 fn push(&mut self, chr: char) {
234 self.acc += chr.len_utf8();
235 }
236
237 fn push_new_line(&mut self) {
238 self.push('\n');
239 }
240
241 fn push_str(&mut self, string: &str) {
242 self.acc += string.len();
243 }
244
245 fn push_str_contains_new_line(&mut self, string: &str) {
246 self.acc += string.len();
247 }
248
249 fn add_mapping(&mut self, _source_span: &SpanWithSource) {}
250
251 fn should_halt(&self) -> bool {
252 self.acc > self.max
253 }
254
255 fn characters_on_current_line(&self) -> u32 {
256 0
258 }
259 fn is_counting(&self) -> bool {
260 true
261 }
262}
263
264#[cfg(test)]
265mod to_string_tests {
266 use super::*;
267
268 fn serializer<T: ToString>(t: &mut T) {
269 t.push_str("Hello");
270 t.push(' ');
271 t.push_str("World");
272 }
273
274 #[test]
275 fn string_concatenation() {
276 let mut s = String::new();
277 serializer(&mut s);
278 assert_eq!(&s, "Hello World");
279 }
280
281 #[test]
282 fn counting() {
283 let mut s = Counter::new(usize::MAX);
284 serializer(&mut s);
285 assert_eq!(s.get_count(), "Hello World".chars().count());
286 }
287
288 #[test]
289 fn max_counter() {
290 let mut s = Counter::new(14);
291 serializer(&mut s);
292 assert!(!s.should_halt());
293 serializer(&mut s);
294 assert!(s.should_halt());
295 }
296}