swc_node_comments/
lib.rs

1#![cfg_attr(test, deny(warnings))]
2
3use std::sync::Arc;
4
5use dashmap::DashMap;
6use rustc_hash::FxBuildHasher;
7use swc_atoms::atom;
8use swc_common::{
9    comments::{Comment, CommentKind, Comments},
10    BytePos, DUMMY_SP,
11};
12
13type CommentMap = Arc<DashMap<BytePos, Vec<Comment>, FxBuildHasher>>;
14
15/// Multi-threaded implementation of [Comments]
16#[derive(Clone, Default)]
17pub struct SwcComments {
18    pub leading: CommentMap,
19    pub trailing: CommentMap,
20}
21
22impl Comments for SwcComments {
23    fn add_leading(&self, pos: BytePos, cmt: Comment) {
24        self.leading.entry(pos).or_default().push(cmt);
25    }
26
27    fn add_leading_comments(&self, pos: BytePos, comments: Vec<Comment>) {
28        self.leading.entry(pos).or_default().extend(comments);
29    }
30
31    fn has_leading(&self, pos: BytePos) -> bool {
32        if let Some(v) = self.leading.get(&pos) {
33            !v.is_empty()
34        } else {
35            false
36        }
37    }
38
39    fn move_leading(&self, from: BytePos, to: BytePos) {
40        let cmt = self.take_leading(from);
41
42        if let Some(mut cmt) = cmt {
43            if from < to && self.has_leading(to) {
44                cmt.extend(self.take_leading(to).unwrap());
45            }
46
47            self.add_leading_comments(to, cmt);
48        }
49    }
50
51    fn take_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
52        self.leading.remove(&pos).map(|v| v.1)
53    }
54
55    fn get_leading(&self, pos: BytePos) -> Option<Vec<Comment>> {
56        self.leading.get(&pos).map(|v| v.to_owned())
57    }
58
59    fn add_trailing(&self, pos: BytePos, cmt: Comment) {
60        self.trailing.entry(pos).or_default().push(cmt)
61    }
62
63    fn add_trailing_comments(&self, pos: BytePos, comments: Vec<Comment>) {
64        self.trailing.entry(pos).or_default().extend(comments)
65    }
66
67    fn has_trailing(&self, pos: BytePos) -> bool {
68        if let Some(v) = self.trailing.get(&pos) {
69            !v.is_empty()
70        } else {
71            false
72        }
73    }
74
75    fn move_trailing(&self, from: BytePos, to: BytePos) {
76        let cmt = self.take_trailing(from);
77
78        if let Some(mut cmt) = cmt {
79            if from < to && self.has_trailing(to) {
80                cmt.extend(self.take_trailing(to).unwrap());
81            }
82
83            self.add_trailing_comments(to, cmt);
84        }
85    }
86
87    fn take_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
88        self.trailing.remove(&pos).map(|v| v.1)
89    }
90
91    fn get_trailing(&self, pos: BytePos) -> Option<Vec<Comment>> {
92        self.trailing.get(&pos).map(|v| v.to_owned())
93    }
94
95    fn add_pure_comment(&self, pos: BytePos) {
96        let mut leading = self.leading.entry(pos).or_default();
97        let pure_comment = Comment {
98            kind: CommentKind::Block,
99            span: DUMMY_SP,
100            text: atom!("#__PURE__"),
101        };
102
103        if !leading.iter().any(|c| c.text == pure_comment.text) {
104            leading.push(pure_comment);
105        }
106    }
107
108    fn with_leading<F, Ret>(&self, pos: BytePos, f: F) -> Ret
109    where
110        Self: Sized,
111        F: FnOnce(&[Comment]) -> Ret,
112    {
113        let ret = if let Some(cmts) = self.leading.get(&pos) {
114            f(&cmts)
115        } else {
116            f(&[])
117        };
118
119        ret
120    }
121
122    fn with_trailing<F, Ret>(&self, pos: BytePos, f: F) -> Ret
123    where
124        Self: Sized,
125        F: FnOnce(&[Comment]) -> Ret,
126    {
127        let ret = if let Some(cmts) = &self.trailing.get(&pos) {
128            f(cmts)
129        } else {
130            f(&[])
131        };
132
133        ret
134    }
135
136    fn has_flag(&self, lo: BytePos, flag: &str) -> bool {
137        self.with_leading(lo, |comments| {
138            for c in comments {
139                if c.kind == CommentKind::Block {
140                    for line in c.text.lines() {
141                        // jsdoc
142                        let line = line.trim_start_matches(['*', ' ']);
143                        let line = line.trim();
144
145                        //
146                        if line.len() == (flag.len() + 5)
147                            && (line.starts_with("#__") || line.starts_with("@__"))
148                            && line.ends_with("__")
149                            && flag == &line[3..line.len() - 2]
150                        {
151                            return true;
152                        }
153                    }
154                }
155            }
156
157            false
158        })
159    }
160}