1use std::{borrow::Cow, cell::RefCell, mem::take, rc::Rc};
2
3use rustc_hash::FxHashMap;
4use swc_core::{
5 base::SwcComments,
6 common::{
7 BytePos,
8 comments::{Comment, CommentKind, Comments, SingleThreadedComments},
9 },
10};
11
12use crate::source_map::extract_source_mapping_url;
13
14pub fn swc_comments_to_single_threaded(comments: &SwcComments) -> SingleThreadedComments {
18 let mut leading = FxHashMap::default();
19 for entry in comments.leading.as_ref() {
20 leading.insert(*entry.key(), entry.value().clone());
21 }
22
23 let mut trailing = FxHashMap::default();
24 for entry in comments.trailing.as_ref() {
25 trailing.insert(*entry.key(), entry.value().clone());
26 }
27
28 SingleThreadedComments::from_leading_and_trailing(
29 Rc::new(RefCell::new(leading)),
30 Rc::new(RefCell::new(trailing)),
31 )
32}
33
34#[derive(Default)]
38pub struct ImmutableComments {
39 pub leading: FxHashMap<BytePos, Vec<Comment>>,
40 pub trailing: FxHashMap<BytePos, Vec<Comment>>,
41}
42
43impl ImmutableComments {
44 pub fn new(comments: SwcComments) -> Self {
45 Self {
46 leading: comments
47 .leading
48 .iter_mut()
49 .filter_map(|mut r| {
50 let c = take(r.value_mut());
51 (!c.is_empty()).then_some((*r.key(), c))
52 })
53 .collect(),
54 trailing: comments
55 .trailing
56 .iter_mut()
57 .filter_map(|mut r| {
58 let c = take(r.value_mut());
59 (!c.is_empty()).then_some((*r.key(), c))
60 })
61 .collect(),
62 }
63 }
64
65 pub fn new_with_source_mapping_url(comments: SwcComments) -> (Self, Option<String>) {
70 let mut source_mapping_url_by_pos: Vec<(BytePos, String)> = Vec::new();
71
72 let leading: FxHashMap<BytePos, Vec<Comment>> = comments
73 .leading
74 .iter_mut()
75 .filter_map(|mut r| {
76 let pos = *r.key();
77 let mut c = take(r.value_mut());
78 c.retain(|comment| {
80 if let Some(url) = extract_source_mapping_url(comment) {
81 source_mapping_url_by_pos.push((pos, url.to_string()));
82 false
83 } else {
84 true
85 }
86 });
87 (!c.is_empty()).then_some((pos, c))
88 })
89 .collect();
90
91 let trailing: FxHashMap<BytePos, Vec<Comment>> = comments
92 .trailing
93 .iter_mut()
94 .filter_map(|mut r| {
95 let pos = *r.key();
96 let mut c = take(r.value_mut());
97 c.retain(|comment| {
99 if let Some(url) = extract_source_mapping_url(comment) {
100 source_mapping_url_by_pos.push((pos, url.to_string()));
101 false
102 } else {
103 true
104 }
105 });
106 (!c.is_empty()).then_some((pos, c))
107 })
108 .collect();
109
110 let source_mapping_url = source_mapping_url_by_pos
112 .into_iter()
113 .max_by_key(|&(pos, _)| pos)
114 .map(|(_, url)| url);
115
116 (Self { leading, trailing }, source_mapping_url)
117 }
118
119 pub fn into_consumable(self) -> CowComments<'static> {
120 CowComments::owned(self)
121 }
122
123 pub fn consumable(&self) -> CowComments<'_> {
124 CowComments::borrowed(self)
125 }
126}
127
128impl Comments for ImmutableComments {
129 fn add_leading(
130 &self,
131 _pos: swc_core::common::BytePos,
132 _cmt: swc_core::common::comments::Comment,
133 ) {
134 panic!("Comments are immutable after parsing")
135 }
136
137 fn add_leading_comments(
138 &self,
139 _pos: swc_core::common::BytePos,
140 _comments: Vec<swc_core::common::comments::Comment>,
141 ) {
142 panic!("Comments are immutable after parsing")
143 }
144
145 fn has_leading(&self, pos: swc_core::common::BytePos) -> bool {
146 self.leading.contains_key(&pos)
147 }
148
149 fn move_leading(&self, _from: swc_core::common::BytePos, _to: swc_core::common::BytePos) {
150 panic!("Comments are immutable after parsing")
151 }
152
153 fn take_leading(
154 &self,
155 _pos: swc_core::common::BytePos,
156 ) -> Option<Vec<swc_core::common::comments::Comment>> {
157 panic!(
158 "Comments are immutable after parsing (Use ImmutableComments::consumable() to allow \
159 taking out values)"
160 )
161 }
162
163 fn get_leading(
164 &self,
165 pos: swc_core::common::BytePos,
166 ) -> Option<Vec<swc_core::common::comments::Comment>> {
167 self.leading.get(&pos).map(|v| v.to_owned())
168 }
169
170 fn add_trailing(
171 &self,
172 _pos: swc_core::common::BytePos,
173 _cmt: swc_core::common::comments::Comment,
174 ) {
175 panic!("Comments are immutable after parsing")
176 }
177
178 fn add_trailing_comments(
179 &self,
180 _pos: swc_core::common::BytePos,
181 _comments: Vec<swc_core::common::comments::Comment>,
182 ) {
183 panic!("Comments are immutable after parsing")
184 }
185
186 fn has_trailing(&self, pos: swc_core::common::BytePos) -> bool {
187 self.trailing.contains_key(&pos)
188 }
189
190 fn move_trailing(&self, _from: swc_core::common::BytePos, _to: swc_core::common::BytePos) {
191 panic!("Comments are immutable after parsing")
192 }
193
194 fn take_trailing(
195 &self,
196 _pos: swc_core::common::BytePos,
197 ) -> Option<Vec<swc_core::common::comments::Comment>> {
198 panic!(
199 "Comments are immutable after parsing (Use ImmutableComments::consumable() to allow \
200 taking out values)"
201 )
202 }
203
204 fn get_trailing(
205 &self,
206 pos: swc_core::common::BytePos,
207 ) -> Option<Vec<swc_core::common::comments::Comment>> {
208 self.trailing.get(&pos).map(|v| v.to_owned())
209 }
210
211 fn add_pure_comment(&self, _pos: swc_core::common::BytePos) {
212 panic!("Comments are immutable after parsing")
213 }
214
215 fn has_flag(&self, pos: BytePos, flag: &str) -> bool {
216 self.with_leading(pos, |cmts| {
217 for c in cmts {
218 if c.kind == CommentKind::Block {
219 for line in c.text.lines() {
220 let line = line.trim_start_matches(['*', ' ']);
222 let line = line.trim();
223
224 if line.len() == (flag.len() + 5)
226 && (line.starts_with("#__") || line.starts_with("@__"))
227 && line.ends_with("__")
228 && flag == &line[3..line.len() - 2]
229 {
230 return true;
231 }
232 }
233 }
234 }
235
236 false
237 })
238 }
239
240 fn with_leading<F, Ret>(&self, pos: BytePos, f: F) -> Ret
241 where
242 Self: Sized,
243 F: FnOnce(&[Comment]) -> Ret,
244 {
245 let cmts = self.get_leading(pos);
246
247 if let Some(cmts) = &cmts {
248 f(cmts)
249 } else {
250 f(&[])
251 }
252 }
253
254 fn with_trailing<F, Ret>(&self, pos: BytePos, f: F) -> Ret
255 where
256 Self: Sized,
257 F: FnOnce(&[Comment]) -> Ret,
258 {
259 let cmts = self.get_trailing(pos);
260
261 if let Some(cmts) = &cmts {
262 f(cmts)
263 } else {
264 f(&[])
265 }
266 }
267}
268
269pub struct CowComments<'a> {
270 leading: RefCell<FxHashMap<BytePos, Cow<'a, [Comment]>>>,
271 trailing: RefCell<FxHashMap<BytePos, Cow<'a, [Comment]>>>,
272}
273
274impl<'a> CowComments<'a> {
275 fn borrowed(comments: &'a ImmutableComments) -> Self {
276 Self {
277 leading: RefCell::new(
278 comments
279 .leading
280 .iter()
281 .map(|(&key, value)| (key, Cow::Borrowed(&value[..])))
282 .collect(),
283 ),
284 trailing: RefCell::new(
285 comments
286 .trailing
287 .iter()
288 .map(|(&key, value)| (key, Cow::Borrowed(&value[..])))
289 .collect(),
290 ),
291 }
292 }
293
294 fn owned(comments: ImmutableComments) -> Self {
295 Self {
296 leading: RefCell::new(
297 comments
298 .leading
299 .into_iter()
300 .map(|(key, value)| (key, Cow::Owned(value)))
301 .collect(),
302 ),
303 trailing: RefCell::new(
304 comments
305 .trailing
306 .into_iter()
307 .map(|(key, value)| (key, Cow::Owned(value)))
308 .collect(),
309 ),
310 }
311 }
312}
313
314impl Comments for CowComments<'_> {
315 fn add_leading(
316 &self,
317 _pos: swc_core::common::BytePos,
318 _cmt: swc_core::common::comments::Comment,
319 ) {
320 panic!("Comments are immutable after parsing")
321 }
322
323 fn add_leading_comments(
324 &self,
325 _pos: swc_core::common::BytePos,
326 _comments: Vec<swc_core::common::comments::Comment>,
327 ) {
328 panic!("Comments are immutable after parsing")
329 }
330
331 fn has_leading(&self, pos: swc_core::common::BytePos) -> bool {
332 self.leading.borrow().contains_key(&pos)
333 }
334
335 fn move_leading(&self, _from: swc_core::common::BytePos, _to: swc_core::common::BytePos) {
336 panic!("Comments are immutable after parsing")
337 }
338
339 fn take_leading(
340 &self,
341 pos: swc_core::common::BytePos,
342 ) -> Option<Vec<swc_core::common::comments::Comment>> {
343 self.leading
344 .borrow_mut()
345 .remove(&pos)
346 .map(|v| v.into_owned())
347 }
348
349 fn get_leading(
350 &self,
351 pos: swc_core::common::BytePos,
352 ) -> Option<Vec<swc_core::common::comments::Comment>> {
353 self.leading.borrow().get(&pos).map(|v| (**v).to_vec())
354 }
355
356 fn add_trailing(
357 &self,
358 _pos: swc_core::common::BytePos,
359 _cmt: swc_core::common::comments::Comment,
360 ) {
361 panic!("Comments are immutable after parsing")
362 }
363
364 fn add_trailing_comments(
365 &self,
366 _pos: swc_core::common::BytePos,
367 _comments: Vec<swc_core::common::comments::Comment>,
368 ) {
369 panic!("Comments are immutable after parsing")
370 }
371
372 fn has_trailing(&self, pos: swc_core::common::BytePos) -> bool {
373 self.trailing.borrow().contains_key(&pos)
374 }
375
376 fn move_trailing(&self, _from: swc_core::common::BytePos, _to: swc_core::common::BytePos) {
377 panic!("Comments are immutable after parsing")
378 }
379
380 fn take_trailing(
381 &self,
382 pos: swc_core::common::BytePos,
383 ) -> Option<Vec<swc_core::common::comments::Comment>> {
384 self.trailing
385 .borrow_mut()
386 .remove(&pos)
387 .map(|v| v.into_owned())
388 }
389
390 fn get_trailing(
391 &self,
392 pos: swc_core::common::BytePos,
393 ) -> Option<Vec<swc_core::common::comments::Comment>> {
394 self.trailing.borrow().get(&pos).map(|v| (**v).to_vec())
395 }
396
397 fn add_pure_comment(&self, _pos: swc_core::common::BytePos) {
398 panic!("Comments are immutable after parsing")
399 }
400
401 fn with_leading<F, Ret>(&self, pos: BytePos, f: F) -> Ret
402 where
403 Self: Sized,
404 F: FnOnce(&[Comment]) -> Ret,
405 {
406 let cmts = self.get_leading(pos);
407
408 if let Some(cmts) = &cmts {
409 f(cmts)
410 } else {
411 f(&[])
412 }
413 }
414
415 fn with_trailing<F, Ret>(&self, pos: BytePos, f: F) -> Ret
416 where
417 Self: Sized,
418 F: FnOnce(&[Comment]) -> Ret,
419 {
420 let cmts = self.get_trailing(pos);
421
422 if let Some(cmts) = &cmts {
423 f(cmts)
424 } else {
425 f(&[])
426 }
427 }
428}