swc_plugin_macro/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use proc_macro2::{Ident, Span};
4use quote::quote;
5use syn::{Item as SynItem, ItemFn};
6
7#[proc_macro_attribute]
8pub fn plugin_transform(
9    _args: proc_macro::TokenStream,
10    input: proc_macro::TokenStream,
11) -> proc_macro::TokenStream {
12    let token = proc_macro2::TokenStream::from(input);
13    let parsed_results = syn::parse2::<SynItem>(token).expect("Failed to parse tokens");
14    match parsed_results {
15        SynItem::Fn(func) => handle_func(func, Ident::new("Program", Span::call_site())),
16        _ => panic!("Please confirm if plugin macro is specified for the function"),
17    }
18}
19
20#[proc_macro_attribute]
21pub fn css_plugin_transform(
22    _args: proc_macro::TokenStream,
23    input: proc_macro::TokenStream,
24) -> proc_macro::TokenStream {
25    let token = proc_macro2::TokenStream::from(input);
26    let parsed_results = syn::parse2::<SynItem>(token).expect("Failed to parse tokens");
27    match parsed_results {
28        SynItem::Fn(func) => handle_func(func, Ident::new("Stylesheet", Span::call_site())),
29        _ => panic!("Please confirm if plugin macro is specified for the function"),
30    }
31}
32
33#[allow(clippy::redundant_clone)]
34fn handle_func(func: ItemFn, ast_type: Ident) -> TokenStream {
35    let ident = func.sig.ident.clone();
36    let transform_process_impl_ident =
37        Ident::new("__transform_plugin_process_impl", Span::call_site());
38    let transform_core_pkg_diag_ident =
39        Ident::new("__get_transform_plugin_core_pkg_diag", Span::call_site());
40
41    let ret = quote! {
42        #func
43
44        // Declaration for imported function from swc host.
45        // Refer swc_plugin_runner for the actual implementation.
46        #[cfg(target_arch = "wasm32")] // Allow testing
47        extern "C" {
48            fn __set_transform_result(bytes_ptr: u32, bytes_ptr_len: u32);
49            fn __set_transform_plugin_core_pkg_diagnostics(bytes_ptr: u32, bytes_ptr_len: u32);
50            fn __emit_diagnostics(bytes_ptr: u32, bytes_ptr_len: u32);
51        }
52
53        /// An emitter for the Diagnostic in plugin's context by borrowing host's
54        /// environment context.
55        ///
56        /// It is not expected to call this directly inside of plugin transform.
57        /// Instead, it is encouraged to use global HANDLER.
58        pub struct PluginDiagnosticsEmitter;
59
60        impl swc_core::common::errors::Emitter for PluginDiagnosticsEmitter {
61            #[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
62            fn emit(&mut self, db: &mut swc_core::common::errors::DiagnosticBuilder<'_>) {
63                let diag = swc_core::common::plugin::serialized::PluginSerializedBytes::try_serialize(&swc_core::common::plugin::serialized::VersionedSerializable::new(*db.diagnostic.clone()))
64                    .expect("Should able to serialize Diagnostic");
65                let (ptr, len) = diag.as_ptr();
66
67                #[cfg(target_arch = "wasm32")] // Allow testing
68                unsafe {
69                    __emit_diagnostics(ptr as u32, len as u32);
70                }
71            }
72        }
73
74
75        /// Call hosts's imported fn to set transform results.
76        /// __set_transform_result is host side imported fn, which read and copies guest's byte into host.
77        fn send_transform_result_to_host(bytes_ptr: u32, bytes_ptr_len: u32) {
78            #[cfg(target_arch = "wasm32")] // Allow testing
79            unsafe {
80                __set_transform_result(bytes_ptr, bytes_ptr_len);
81            }
82        }
83
84        /// Internal function plugin_macro uses to create ptr to PluginError.
85        fn construct_error_ptr(plugin_error: swc_core::common::plugin::serialized::PluginError) -> u32 {
86            let ret = swc_core::common::plugin::serialized::PluginSerializedBytes::try_serialize(&swc_core::common::plugin::serialized::VersionedSerializable::new(plugin_error)).expect("Should able to serialize PluginError");
87            let (ptr, len) = ret.as_ptr();
88
89            send_transform_result_to_host(
90                ptr as _,
91                len as u32
92            );
93            1
94        }
95
96        #[no_mangle]
97        #[allow(clippy::not_unsafe_ptr_arg_deref)]
98        pub fn #transform_core_pkg_diag_ident() -> u32 {
99            let schema_version = swc_core::common::plugin::PLUGIN_TRANSFORM_AST_SCHEMA_VERSION;
100            let core_pkg_diag = swc_core::diagnostics::get_core_engine_diagnostics();
101
102            let result = swc_core::common::plugin::diagnostics::PluginCorePkgDiagnostics {
103                ast_schema_version: schema_version,
104                pkg_version: core_pkg_diag.package_semver,
105                git_sha: core_pkg_diag.git_sha,
106                cargo_features: core_pkg_diag.cargo_features,
107            };
108
109            let serialized_result = swc_core::common::plugin::serialized::PluginSerializedBytes::try_serialize(
110                &swc_core::common::plugin::serialized::VersionedSerializable::new(result)
111            ).expect("Diagnostics should be always serializable");
112
113            let (serialized_result_ptr, serialized_result_ptr_len) = serialized_result.as_ptr();
114
115            #[cfg(target_arch = "wasm32")] // Allow testing
116            unsafe {
117                __set_transform_plugin_core_pkg_diagnostics(serialized_result_ptr as _, serialized_result_ptr_len as u32);
118            }
119            0
120        }
121
122        // Macro to allow compose plugin's transform function without manual pointer operation.
123        // Internally it wraps pointer operation also bubbles up error in forms of PluginError.
124        // There are some cases error won't be wrapped up however - for example, we expect
125        // serialization of PluginError itself should succeed.
126        #[no_mangle]
127        #[allow(clippy::not_unsafe_ptr_arg_deref)]
128        pub fn #transform_process_impl_ident(
129            ast_ptr: *const u8, ast_ptr_len: u32,
130            unresolved_mark: u32, should_enable_comments_proxy: i32) -> u32 {
131            // Reconstruct `Program` & config string from serialized program
132            // Host (SWC) should allocate memory, copy bytes and pass ptr to plugin.
133            let program = swc_core::common::plugin::serialized::PluginSerializedBytes::from_raw_ptr(ast_ptr, ast_ptr_len.try_into().expect("Should able to convert ptr length")).deserialize();
134            if program.is_err() {
135                let err = swc_core::common::plugin::serialized::PluginError::Deserialize("Failed to deserialize program received from host".to_string());
136                return construct_error_ptr(err);
137            }
138            let program: #ast_type = program.expect("Should be a program").into_inner();
139
140            // Create a handler wired with plugin's diagnostic emitter, set it for global context.
141            let handler = swc_core::common::errors::Handler::with_emitter(
142                true,
143                false,
144                Box::new(PluginDiagnosticsEmitter)
145            );
146
147            // Construct metadata to the `Program` for the transform plugin.
148            let plugin_comments_proxy = if should_enable_comments_proxy == 1 { Some(swc_core::plugin::proxies::PluginCommentsProxy) } else { None };
149            let mut metadata = swc_core::plugin::metadata::TransformPluginProgramMetadata {
150                comments: plugin_comments_proxy,
151                source_map: swc_core::plugin::proxies::PluginSourceMapProxy { source_file: swc_core::common::sync::OnceCell::new() },
152                unresolved_mark: swc_core::common::Mark::from_u32(unresolved_mark as u32),
153            };
154
155            // Take original plugin fn ident, then call it with interop'ed args
156            let transformed_program = swc_core::common::plugin::serialized::VersionedSerializable::new(swc_core::common::errors::HANDLER.set(&handler, || {
157                #ident(program, metadata)
158            }));
159
160            // Serialize transformed result, return back to the host.
161            let serialized_result = swc_core::common::plugin::serialized::PluginSerializedBytes::try_serialize(
162                &transformed_program
163            );
164
165            if serialized_result.is_err() {
166                let err = swc_core::common::plugin::serialized::PluginError::Serialize("Failed to serialize transformed program".to_string());
167                return construct_error_ptr(err);
168            }
169
170            let serialized_result = serialized_result.expect("Should be a realized transformed program");
171            let (serialized_result_ptr, serialized_result_ptr_len) = serialized_result.as_ptr();
172
173            send_transform_result_to_host(serialized_result_ptr as _, serialized_result_ptr_len as u32);
174            0
175        }
176    };
177
178    ret.into()
179}