swc_plugin_proxy/source_map/
plugin_source_map_proxy.rs

1#![allow(unused_imports)]
2#![allow(unused_variables)]
3#[cfg(feature = "__plugin_mode")]
4use swc_common::{
5    source_map::{
6        DistinctSources, FileLinesResult, MalformedSourceMapPositions, PartialFileLinesResult,
7        PartialLoc, SmallPos, SpanSnippetError,
8    },
9    sync::Lrc,
10    BytePos, FileName, Loc, SourceFileAndBytePos, SourceMapper, Span,
11};
12use swc_common::{sync::OnceCell, CharPos, FileLines, SourceFile};
13#[cfg(feature = "__plugin_mode")]
14use swc_ecma_ast::SourceMapperExt;
15use swc_trace_macro::swc_trace;
16
17#[cfg(all(feature = "__rkyv", feature = "__plugin_mode", target_arch = "wasm32"))]
18use crate::memory_interop::read_returned_result_from_host;
19
20#[cfg(target_arch = "wasm32")]
21extern "C" {
22    fn __lookup_char_pos_source_map_proxy(
23        byte_pos: u32,
24        should_include_source_file: i32,
25        allocated_ret_ptr: u32,
26    ) -> u32;
27    fn __doctest_offset_line_proxy(orig: u32) -> u32;
28    fn __merge_spans_proxy(
29        lhs_lo: u32,
30        lhs_hi: u32,
31        rhs_lo: u32,
32        rhs_hi: u32,
33        allocated_ptr: u32,
34    ) -> u32;
35    fn __span_to_string_proxy(span_lo: u32, span_hi: u32, allocated_ret_ptr: u32) -> u32;
36    fn __span_to_filename_proxy(span_lo: u32, span_hi: u32, allocated_ret_ptr: u32) -> u32;
37    fn __span_to_source_proxy(span_lo: u32, span_hi: u32, allocated_ret_ptr: u32) -> u32;
38    fn __span_to_lines_proxy(
39        span_lo: u32,
40        span_hi: u32,
41        should_request_source_file: i32,
42        allocated_ret_ptr: u32,
43    ) -> u32;
44    fn __lookup_byte_offset_proxy(byte_pos: u32, allocated_ret_ptr: u32) -> u32;
45}
46
47#[cfg(feature = "__plugin_mode")]
48#[derive(Debug, Clone)]
49pub struct PluginSourceMapProxy {
50    // Sharable instance to `SourceFile` per plugin transform.
51    // Since each plugin's lifecycle is tied to single `transform`,
52    // We know this'll be the same across subsequent request for lookup_char_*.
53    pub source_file: OnceCell<swc_common::sync::Lrc<SourceFile>>,
54}
55
56#[cfg(all(feature = "__rkyv", feature = "__plugin_mode", target_arch = "wasm32"))]
57#[swc_trace]
58impl PluginSourceMapProxy {
59    pub fn span_to_source<F, Ret>(
60        &self,
61        sp: Span,
62        extract_source: F,
63    ) -> Result<Ret, Box<SpanSnippetError>>
64    where
65        F: FnOnce(&str, usize, usize) -> Ret,
66    {
67        #[cfg(target_arch = "wasm32")]
68        {
69            let src: Result<String, Box<SpanSnippetError>> =
70                read_returned_result_from_host(|serialized_ptr| unsafe {
71                    __span_to_source_proxy(sp.lo.0, sp.hi.0, serialized_ptr)
72                })
73                .expect("Host should return source code");
74
75            let src = src?;
76            return Ok(extract_source(&src, 0, src.len()));
77        }
78
79        #[cfg(not(target_arch = "wasm32"))]
80        unimplemented!("Sourcemap proxy cannot be called in this context")
81    }
82}
83
84/// Subset of SourceMap interface supported in plugin.
85/// Unlike `Comments`, this does not fully implement `SourceMap`.
86#[cfg(feature = "__plugin_mode")]
87impl SourceMapper for PluginSourceMapProxy {
88    #[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
89    fn lookup_char_pos(&self, pos: BytePos) -> Loc {
90        #[cfg(target_arch = "wasm32")]
91        {
92            let should_request_source_file = if self.source_file.get().is_none() {
93                1
94            } else {
95                0
96            };
97            let partial_loc: PartialLoc = read_returned_result_from_host(|serialized_ptr| unsafe {
98                __lookup_char_pos_source_map_proxy(
99                    pos.0,
100                    should_request_source_file,
101                    serialized_ptr,
102                )
103            })
104            .expect("Host should return PartialLoc");
105
106            if self.source_file.get().is_none() {
107                if let Some(source_file) = partial_loc.source_file {
108                    self.source_file
109                        .set(source_file)
110                        .expect("Should able to set source file");
111                }
112            }
113
114            return Loc {
115                file: self
116                    .source_file
117                    .get()
118                    .expect("SourceFile should exist")
119                    .clone(),
120                line: partial_loc.line,
121                col: CharPos(partial_loc.col),
122                col_display: partial_loc.col_display,
123            };
124        }
125
126        #[cfg(not(target_arch = "wasm32"))]
127        unimplemented!("Sourcemap proxy cannot be called in this context")
128    }
129
130    fn span_to_lines(&self, sp: Span) -> FileLinesResult {
131        #[cfg(target_arch = "wasm32")]
132        {
133            let should_request_source_file = if self.source_file.get().is_none() {
134                1
135            } else {
136                0
137            };
138            let partial_files: PartialFileLinesResult =
139                read_returned_result_from_host(|serialized_ptr| unsafe {
140                    __span_to_lines_proxy(
141                        sp.lo.0,
142                        sp.hi.0,
143                        should_request_source_file,
144                        serialized_ptr,
145                    )
146                })
147                .expect("Host should return PartialFileLinesResult");
148
149            if self.source_file.get().is_none() {
150                if let Ok(p) = &partial_files {
151                    if let Some(source_file) = &p.file {
152                        self.source_file
153                            .set(source_file.clone())
154                            .expect("Should able to set source file");
155                    }
156                }
157            }
158
159            return partial_files.map(|files| FileLines {
160                file: self
161                    .source_file
162                    .get()
163                    .expect("SourceFile should exist")
164                    .clone(),
165                lines: files.lines,
166            });
167        }
168
169        #[cfg(not(target_arch = "wasm32"))]
170        unimplemented!("Sourcemap proxy cannot be called in this context")
171    }
172
173    fn span_to_string(&self, sp: Span) -> String {
174        #[cfg(target_arch = "wasm32")]
175        return read_returned_result_from_host(|serialized_ptr| unsafe {
176            __span_to_string_proxy(sp.lo.0, sp.hi.0, serialized_ptr)
177        })
178        .expect("Host should return String");
179
180        #[cfg(not(target_arch = "wasm32"))]
181        unimplemented!("Sourcemap proxy cannot be called in this context")
182    }
183
184    fn span_to_filename(&self, sp: Span) -> Lrc<FileName> {
185        #[cfg(target_arch = "wasm32")]
186        return Lrc::new(
187            read_returned_result_from_host(|serialized_ptr| unsafe {
188                __span_to_filename_proxy(sp.lo.0, sp.hi.0, serialized_ptr)
189            })
190            .expect("Host should return Filename"),
191        );
192
193        #[cfg(not(target_arch = "wasm32"))]
194        unimplemented!("Sourcemap proxy cannot be called in this context")
195    }
196
197    fn span_to_snippet(&self, sp: Span) -> Result<String, Box<SpanSnippetError>> {
198        #[cfg(target_arch = "wasm32")]
199        return self.span_to_source(sp, |src, start_index, end_index| {
200            src[start_index..end_index].to_string()
201        });
202
203        #[cfg(not(target_arch = "wasm32"))]
204        unimplemented!("Sourcemap proxy cannot be called in this context")
205    }
206
207    fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
208        #[cfg(target_arch = "wasm32")]
209        unsafe {
210            // We need to `allocate` memory, force creates empty span instead of DUMMY_SP
211            let span = Span {
212                lo: BytePos(0),
213                hi: BytePos(0),
214            };
215
216            let serialized = swc_common::plugin::serialized::PluginSerializedBytes::try_serialize(
217                &swc_common::plugin::serialized::VersionedSerializable::new(span),
218            )
219            .expect("Should be serializable");
220            let (ptr, len) = serialized.as_ptr();
221
222            let ret =
223                __merge_spans_proxy(sp_lhs.lo.0, sp_lhs.hi.0, sp_rhs.lo.0, sp_rhs.hi.0, ptr as _);
224
225            return if ret == 1 { Some(span) } else { None };
226        };
227
228        #[cfg(not(target_arch = "wasm32"))]
229        unimplemented!("Sourcemap proxy cannot be called in this context")
230    }
231
232    fn call_span_if_macro(&self, sp: Span) -> Span {
233        // This mimics host's behavior
234        // https://github.com/swc-project/swc/blob/f7dc3fff1f03c9b7cee27ef760dc11bc96083f60/crates/swc_common/src/source_map.rs#L1283-L1285=
235        sp
236    }
237
238    fn doctest_offset_line(&self, line: usize) -> usize {
239        #[cfg(target_arch = "wasm32")]
240        unsafe {
241            return __doctest_offset_line_proxy(line as u32) as usize;
242        };
243
244        #[cfg(not(target_arch = "wasm32"))]
245        unimplemented!("Sourcemap proxy cannot be called in this context")
246    }
247}
248
249#[cfg(feature = "__plugin_mode")]
250impl SourceMapperExt for PluginSourceMapProxy {
251    fn get_code_map(&self) -> &dyn SourceMapper {
252        self
253    }
254}