swc_common/
comments.rs

1use std::{
2    cell::{Ref, RefCell, RefMut},
3    rc::Rc,
4    sync::Arc,
5};
6
7use rustc_hash::FxHashMap;
8use swc_atoms::{atom, Atom};
9
10use crate::{
11    pos::Spanned,
12    syntax_pos::{BytePos, Span, DUMMY_SP},
13};
14
15/// Stores comment.
16///
17/// ## Implementation notes
18///
19/// Methods uses `(&self)` instead of `(&mut self)` for some reasons. Firstly,
20/// this is similar to the previous api. Secondly, typescript parser requires
21/// backtracking, which requires [Clone]. To avoid cloning large vectors, we
22/// must use [Rc<RefCell<Comments>>]. We have two option. We may implement it in
23/// the parser or in the implementation. If we decide to go with first option,
24/// we should pass [Comments] to parser, and as a result we need another method
25/// to take comments back. If we decide to go with second way, we can just pass
26/// [&Comments] to the parser. Thirdly, `(&self)` allows multi-threaded
27/// use-cases such as swc itself.
28///
29/// We use [Option] instead of no-op Comments implementation to avoid allocation
30/// unless required.
31pub trait Comments {
32    fn add_leading(&self, pos: BytePos, cmt: Comment);
33    fn add_leading_comments(&self, pos: BytePos, comments: Vec<Comment>);
34    fn has_leading(&self, pos: BytePos) -> bool;
35    fn move_leading(&self, from: BytePos, to: BytePos);
36    fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>>;
37    fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>>;
38
39    fn add_trailing(&self, pos: BytePos, cmt: Comment);
40    fn add_trailing_comments(&self, pos: BytePos, comments: Vec<Comment>);
41    fn has_trailing(&self, pos: BytePos) -> bool;
42    fn move_trailing(&self, from: BytePos, to: BytePos);
43    fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>>;
44    fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>>;
45
46    fn add_pure_comment(&self, pos: BytePos);
47
48    fn with_leading<F, Ret>(&self, pos: BytePos, f: F) -> Ret
49    where
50        Self: Sized,
51        F: FnOnce(&[Comment]) -> Ret,
52    {
53        let cmts = self.take_leading(pos);
54
55        let ret = if let Some(cmts) = &cmts {
56            f(cmts)
57        } else {
58            f(&[])
59        };
60
61        if let Some(cmts) = cmts {
62            self.add_leading_comments(pos, cmts);
63        }
64
65        ret
66    }
67
68    fn with_trailing<F, Ret>(&self, pos: BytePos, f: F) -> Ret
69    where
70        Self: Sized,
71        F: FnOnce(&[Comment]) -> Ret,
72    {
73        let cmts = self.take_trailing(pos);
74
75        let ret = if let Some(cmts) = &cmts {
76            f(cmts)
77        } else {
78            f(&[])
79        };
80
81        if let Some(cmts) = cmts {
82            self.add_trailing_comments(pos, cmts);
83        }
84
85        ret
86    }
87
88    /// This method is used to check if a comment with the given flag exist.
89    ///
90    /// If `flag` is `PURE`, this method will look for `@__PURE__` and
91    /// `#__PURE__`.
92    fn has_flag(&self, lo: BytePos, flag: &str) -> bool {
93        let cmts = self.take_leading(lo);
94
95        let ret = if let Some(comments) = &cmts {
96            (|| {
97                for c in comments {
98                    if c.kind == CommentKind::Block {
99                        for line in c.text.lines() {
100                            // jsdoc
101                            let line = line.trim_start_matches(['*', ' ']);
102                            let line = line.trim();
103
104                            //
105                            if line.len() == (flag.len() + 5)
106                                && (line.starts_with("#__") || line.starts_with("@__"))
107                                && line.ends_with("__")
108                                && flag == &line[3..line.len() - 2]
109                            {
110                                return true;
111                            }
112                        }
113                    }
114                }
115
116                false
117            })()
118        } else {
119            false
120        };
121
122        if let Some(cmts) = cmts {
123            self.add_trailing_comments(lo, cmts);
124        }
125
126        ret
127    }
128}
129
130macro_rules! delegate {
131    () => {
132        fn add_leading(&self, pos: BytePos, cmt: Comment) {
133            (**self).add_leading(pos, cmt)
134        }
135
136        fn add_leading_comments(&self, pos: BytePos, comments: Vec<Comment>) {
137            (**self).add_leading_comments(pos, comments)
138        }
139
140        fn has_leading(&self, pos: BytePos) -> bool {
141            (**self).has_leading(pos)
142        }
143
144        fn move_leading(&self, from: BytePos, to: BytePos) {
145            (**self).move_leading(from, to)
146        }
147
148        fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
149            (**self).take_leading(pos)
150        }
151
152        fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
153            (**self).get_leading(pos)
154        }
155
156        fn add_trailing(&self, pos: BytePos, cmt: Comment) {
157            (**self).add_trailing(pos, cmt)
158        }
159
160        fn add_trailing_comments(&self, pos: BytePos, comments: Vec<Comment>) {
161            (**self).add_trailing_comments(pos, comments)
162        }
163
164        fn has_trailing(&self, pos: BytePos) -> bool {
165            (**self).has_trailing(pos)
166        }
167
168        fn move_trailing(&self, from: BytePos, to: BytePos) {
169            (**self).move_trailing(from, to)
170        }
171
172        fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
173            (**self).take_trailing(pos)
174        }
175
176        fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
177            (**self).get_trailing(pos)
178        }
179
180        fn add_pure_comment(&self, pos: BytePos) {
181            (**self).add_pure_comment(pos)
182        }
183
184        fn has_flag(&self, lo: BytePos, flag: &str) -> bool {
185            (**self).has_flag(lo, flag)
186        }
187    };
188}
189
190impl<T> Comments for &'_ T
191where
192    T: ?Sized + Comments,
193{
194    delegate!();
195}
196
197impl<T> Comments for Arc<T>
198where
199    T: ?Sized + Comments,
200{
201    delegate!();
202}
203
204impl<T> Comments for Rc<T>
205where
206    T: ?Sized + Comments,
207{
208    delegate!();
209}
210
211impl<T> Comments for Box<T>
212where
213    T: ?Sized + Comments,
214{
215    delegate!();
216}
217
218/// Implementation of [Comments] which does not store any comments.
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
220pub struct NoopComments;
221
222impl Comments for NoopComments {
223    #[cfg_attr(not(debug_assertions), inline(always))]
224    fn add_leading(&self, _: BytePos, _: Comment) {}
225
226    #[cfg_attr(not(debug_assertions), inline(always))]
227    fn add_leading_comments(&self, _: BytePos, _: Vec<Comment>) {}
228
229    #[cfg_attr(not(debug_assertions), inline(always))]
230    fn has_leading(&self, _: BytePos) -> bool {
231        false
232    }
233
234    #[cfg_attr(not(debug_assertions), inline(always))]
235    fn move_leading(&self, _: BytePos, _: BytePos) {}
236
237    #[cfg_attr(not(debug_assertions), inline(always))]
238    fn take_leading(&self, _: BytePos) -> Option<Vec<Comment>> {
239        None
240    }
241
242    #[cfg_attr(not(debug_assertions), inline(always))]
243    fn get_leading(&self, _: BytePos) -> Option<Vec<Comment>> {
244        None
245    }
246
247    #[cfg_attr(not(debug_assertions), inline(always))]
248    fn add_trailing(&self, _: BytePos, _: Comment) {}
249
250    #[cfg_attr(not(debug_assertions), inline(always))]
251    fn add_trailing_comments(&self, _: BytePos, _: Vec<Comment>) {}
252
253    #[cfg_attr(not(debug_assertions), inline(always))]
254    fn has_trailing(&self, _: BytePos) -> bool {
255        false
256    }
257
258    #[cfg_attr(not(debug_assertions), inline(always))]
259    fn move_trailing(&self, _: BytePos, _: BytePos) {}
260
261    #[cfg_attr(not(debug_assertions), inline(always))]
262    fn take_trailing(&self, _: BytePos) -> Option<Vec<Comment>> {
263        None
264    }
265
266    #[cfg_attr(not(debug_assertions), inline(always))]
267    fn get_trailing(&self, _: BytePos) -> Option<Vec<Comment>> {
268        None
269    }
270
271    #[cfg_attr(not(debug_assertions), inline(always))]
272    fn add_pure_comment(&self, _: BytePos) {}
273
274    #[inline]
275    fn has_flag(&self, _: BytePos, _: &str) -> bool {
276        false
277    }
278}
279
280/// This implementation behaves like [NoopComments] if it's [None].
281impl<C> Comments for Option<C>
282where
283    C: Comments,
284{
285    fn add_leading(&self, pos: BytePos, cmt: Comment) {
286        if let Some(c) = self {
287            c.add_leading(pos, cmt)
288        }
289    }
290
291    fn add_leading_comments(&self, pos: BytePos, comments: Vec<Comment>) {
292        if let Some(c) = self {
293            c.add_leading_comments(pos, comments)
294        }
295    }
296
297    fn has_leading(&self, pos: BytePos) -> bool {
298        if let Some(c) = self {
299            c.has_leading(pos)
300        } else {
301            false
302        }
303    }
304
305    fn move_leading(&self, from: BytePos, to: BytePos) {
306        if let Some(c) = self {
307            c.move_leading(from, to)
308        }
309    }
310
311    fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
312        if let Some(c) = self {
313            c.take_leading(pos)
314        } else {
315            None
316        }
317    }
318
319    fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
320        if let Some(c) = self {
321            c.get_leading(pos)
322        } else {
323            None
324        }
325    }
326
327    fn add_trailing(&self, pos: BytePos, cmt: Comment) {
328        if let Some(c) = self {
329            c.add_trailing(pos, cmt)
330        }
331    }
332
333    fn add_trailing_comments(&self, pos: BytePos, comments: Vec<Comment>) {
334        if let Some(c) = self {
335            c.add_trailing_comments(pos, comments)
336        }
337    }
338
339    fn has_trailing(&self, pos: BytePos) -> bool {
340        if let Some(c) = self {
341            c.has_trailing(pos)
342        } else {
343            false
344        }
345    }
346
347    fn move_trailing(&self, from: BytePos, to: BytePos) {
348        if let Some(c) = self {
349            c.move_trailing(from, to)
350        }
351    }
352
353    fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
354        if let Some(c) = self {
355            c.take_trailing(pos)
356        } else {
357            None
358        }
359    }
360
361    fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
362        if let Some(c) = self {
363            c.get_trailing(pos)
364        } else {
365            None
366        }
367    }
368
369    fn add_pure_comment(&self, pos: BytePos) {
370        assert_ne!(pos, BytePos(0), "cannot add pure comment to zero position");
371
372        if let Some(c) = self {
373            c.add_pure_comment(pos)
374        }
375    }
376
377    fn with_leading<F, Ret>(&self, pos: BytePos, f: F) -> Ret
378    where
379        Self: Sized,
380        F: FnOnce(&[Comment]) -> Ret,
381    {
382        if let Some(c) = self {
383            c.with_leading(pos, f)
384        } else {
385            f(&[])
386        }
387    }
388
389    fn with_trailing<F, Ret>(&self, pos: BytePos, f: F) -> Ret
390    where
391        Self: Sized,
392        F: FnOnce(&[Comment]) -> Ret,
393    {
394        if let Some(c) = self {
395            c.with_trailing(pos, f)
396        } else {
397            f(&[])
398        }
399    }
400
401    #[inline]
402    fn has_flag(&self, lo: BytePos, flag: &str) -> bool {
403        if let Some(c) = self {
404            c.has_flag(lo, flag)
405        } else {
406            false
407        }
408    }
409}
410
411pub type SingleThreadedCommentsMapInner = FxHashMap<BytePos, Vec<Comment>>;
412pub type SingleThreadedCommentsMap = Rc<RefCell<SingleThreadedCommentsMapInner>>;
413
414/// Single-threaded storage for comments.
415#[derive(Debug, Clone, Default)]
416pub struct SingleThreadedComments {
417    leading: SingleThreadedCommentsMap,
418    trailing: SingleThreadedCommentsMap,
419}
420
421impl Comments for SingleThreadedComments {
422    fn add_leading(&self, pos: BytePos, cmt: Comment) {
423        self.leading.borrow_mut().entry(pos).or_default().push(cmt);
424    }
425
426    fn add_leading_comments(&self, pos: BytePos, comments: Vec<Comment>) {
427        self.leading
428            .borrow_mut()
429            .entry(pos)
430            .or_default()
431            .extend(comments);
432    }
433
434    fn has_leading(&self, pos: BytePos) -> bool {
435        if let Some(v) = self.leading.borrow().get(&pos) {
436            !v.is_empty()
437        } else {
438            false
439        }
440    }
441
442    fn move_leading(&self, from: BytePos, to: BytePos) {
443        let cmt = self.take_leading(from);
444
445        if let Some(mut cmt) = cmt {
446            if from < to && self.has_leading(to) {
447                cmt.extend(self.take_leading(to).unwrap());
448            }
449
450            self.add_leading_comments(to, cmt);
451        }
452    }
453
454    fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
455        self.leading.borrow_mut().remove(&pos)
456    }
457
458    fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
459        self.leading.borrow().get(&pos).map(|c| c.to_owned())
460    }
461
462    fn add_trailing(&self, pos: BytePos, cmt: Comment) {
463        self.trailing.borrow_mut().entry(pos).or_default().push(cmt);
464    }
465
466    fn add_trailing_comments(&self, pos: BytePos, comments: Vec<Comment>) {
467        self.trailing
468            .borrow_mut()
469            .entry(pos)
470            .or_default()
471            .extend(comments);
472    }
473
474    fn has_trailing(&self, pos: BytePos) -> bool {
475        if let Some(v) = self.trailing.borrow().get(&pos) {
476            !v.is_empty()
477        } else {
478            false
479        }
480    }
481
482    fn move_trailing(&self, from: BytePos, to: BytePos) {
483        let cmt = self.take_trailing(from);
484
485        if let Some(mut cmt) = cmt {
486            if from < to && self.has_trailing(to) {
487                cmt.extend(self.take_trailing(to).unwrap());
488            }
489
490            self.add_trailing_comments(to, cmt);
491        }
492    }
493
494    fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
495        self.trailing.borrow_mut().remove(&pos)
496    }
497
498    fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
499        self.trailing.borrow().get(&pos).map(|c| c.to_owned())
500    }
501
502    fn add_pure_comment(&self, pos: BytePos) {
503        assert_ne!(pos, BytePos(0), "cannot add pure comment to zero position");
504
505        let mut leading_map = self.leading.borrow_mut();
506        let leading = leading_map.entry(pos).or_default();
507        let pure_comment = Comment {
508            kind: CommentKind::Block,
509            span: DUMMY_SP,
510            text: atom!("#__PURE__"),
511        };
512
513        if !leading.iter().any(|c| c.text == pure_comment.text) {
514            leading.push(pure_comment);
515        }
516    }
517
518    fn with_leading<F, Ret>(&self, pos: BytePos, f: F) -> Ret
519    where
520        Self: Sized,
521        F: FnOnce(&[Comment]) -> Ret,
522    {
523        let b = self.leading.borrow();
524        let cmts = b.get(&pos);
525
526        if let Some(cmts) = &cmts {
527            f(cmts)
528        } else {
529            f(&[])
530        }
531    }
532
533    fn with_trailing<F, Ret>(&self, pos: BytePos, f: F) -> Ret
534    where
535        Self: Sized,
536        F: FnOnce(&[Comment]) -> Ret,
537    {
538        let b = self.trailing.borrow();
539        let cmts = b.get(&pos);
540
541        if let Some(cmts) = &cmts {
542            f(cmts)
543        } else {
544            f(&[])
545        }
546    }
547
548    fn has_flag(&self, lo: BytePos, flag: &str) -> bool {
549        self.with_leading(lo, |comments| {
550            for c in comments {
551                if c.kind == CommentKind::Block {
552                    for line in c.text.lines() {
553                        // jsdoc
554                        let line = line.trim_start_matches(['*', ' ']);
555                        let line = line.trim();
556
557                        //
558                        if line.len() == (flag.len() + 5)
559                            && (line.starts_with("#__") || line.starts_with("@__"))
560                            && line.ends_with("__")
561                            && flag == &line[3..line.len() - 2]
562                        {
563                            return true;
564                        }
565                    }
566                }
567            }
568
569            false
570        })
571    }
572}
573
574impl SingleThreadedComments {
575    /// Creates a new `SingleThreadedComments` from the provided leading and
576    /// trailing.
577    pub fn from_leading_and_trailing(
578        leading: SingleThreadedCommentsMap,
579        trailing: SingleThreadedCommentsMap,
580    ) -> Self {
581        SingleThreadedComments { leading, trailing }
582    }
583
584    /// Takes all the comments as (leading, trailing).
585    pub fn take_all(self) -> (SingleThreadedCommentsMap, SingleThreadedCommentsMap) {
586        (self.leading, self.trailing)
587    }
588
589    /// Borrows all the comments as (leading, trailing).
590    pub fn borrow_all(
591        &self,
592    ) -> (
593        Ref<SingleThreadedCommentsMapInner>,
594        Ref<SingleThreadedCommentsMapInner>,
595    ) {
596        (self.leading.borrow(), self.trailing.borrow())
597    }
598
599    /// Borrows all the comments as (leading, trailing).
600    pub fn borrow_all_mut(
601        &self,
602    ) -> (
603        RefMut<SingleThreadedCommentsMapInner>,
604        RefMut<SingleThreadedCommentsMapInner>,
605    ) {
606        (self.leading.borrow_mut(), self.trailing.borrow_mut())
607    }
608
609    pub fn with_leading<F, Ret>(&self, pos: BytePos, op: F) -> Ret
610    where
611        F: FnOnce(&[Comment]) -> Ret,
612    {
613        if let Some(comments) = self.leading.borrow().get(&pos) {
614            op(comments)
615        } else {
616            op(&[])
617        }
618    }
619
620    pub fn with_trailing<F, Ret>(&self, pos: BytePos, op: F) -> Ret
621    where
622        F: FnOnce(&[Comment]) -> Ret,
623    {
624        if let Some(comments) = self.trailing.borrow().get(&pos) {
625            op(comments)
626        } else {
627            op(&[])
628        }
629    }
630}
631
632#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
633#[cfg_attr(
634    any(feature = "rkyv-impl"),
635    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
636)]
637#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
638#[cfg_attr(feature = "rkyv-impl", repr(C))]
639pub struct Comment {
640    pub kind: CommentKind,
641    pub span: Span,
642    /// [`Atom::new_bad`][] is perfectly fine for this value.
643    pub text: Atom,
644}
645
646impl Spanned for Comment {
647    fn span(&self) -> Span {
648        self.span
649    }
650}
651
652#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
653#[cfg_attr(
654    any(feature = "rkyv-impl"),
655    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
656)]
657#[cfg_attr(feature = "rkyv-impl", derive(bytecheck::CheckBytes))]
658#[cfg_attr(feature = "rkyv-impl", repr(u32))]
659pub enum CommentKind {
660    Line = 0,
661    Block = 1,
662}
663
664#[deprecated(
665    since = "0.13.5",
666    note = "helper methods are merged into Comments itself"
667)]
668pub trait CommentsExt: Comments {
669    fn with_leading<F, Ret>(&self, pos: BytePos, op: F) -> Ret
670    where
671        F: FnOnce(&[Comment]) -> Ret,
672    {
673        if let Some(comments) = self.get_leading(pos) {
674            op(&comments)
675        } else {
676            op(&[])
677        }
678    }
679
680    fn with_trailing<F, Ret>(&self, pos: BytePos, op: F) -> Ret
681    where
682        F: FnOnce(&[Comment]) -> Ret,
683    {
684        if let Some(comments) = self.get_trailing(pos) {
685            op(&comments)
686        } else {
687            op(&[])
688        }
689    }
690}
691
692#[allow(deprecated)]
693impl<C> CommentsExt for C where C: Comments {}
694
695better_scoped_tls::scoped_tls!(
696    /// **This is not a public API**. Used to handle comments while **testing**.
697    #[doc(hidden)]
698    pub static COMMENTS: Box<dyn Comments>
699);