swc_plugin_macro/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{Item as SynItem, ItemFn};

#[proc_macro_attribute]
pub fn plugin_transform(
    _args: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let token = proc_macro2::TokenStream::from(input);
    let parsed_results = syn::parse2::<SynItem>(token).expect("Failed to parse tokens");
    match parsed_results {
        SynItem::Fn(func) => handle_func(func, Ident::new("Program", Span::call_site())),
        _ => panic!("Please confirm if plugin macro is specified for the function"),
    }
}

#[proc_macro_attribute]
pub fn css_plugin_transform(
    _args: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let token = proc_macro2::TokenStream::from(input);
    let parsed_results = syn::parse2::<SynItem>(token).expect("Failed to parse tokens");
    match parsed_results {
        SynItem::Fn(func) => handle_func(func, Ident::new("Stylesheet", Span::call_site())),
        _ => panic!("Please confirm if plugin macro is specified for the function"),
    }
}

#[allow(clippy::redundant_clone)]
fn handle_func(func: ItemFn, ast_type: Ident) -> TokenStream {
    let ident = func.sig.ident.clone();
    let transform_process_impl_ident =
        Ident::new("__transform_plugin_process_impl", Span::call_site());
    let transform_core_pkg_diag_ident =
        Ident::new("__get_transform_plugin_core_pkg_diag", Span::call_site());

    let ret = quote! {
        #func

        // Declaration for imported function from swc host.
        // Refer swc_plugin_runner for the actual implementation.
        #[cfg(target_arch = "wasm32")] // Allow testing
        extern "C" {
            fn __set_transform_result(bytes_ptr: u32, bytes_ptr_len: u32);
            fn __set_transform_plugin_core_pkg_diagnostics(bytes_ptr: u32, bytes_ptr_len: u32);
            fn __emit_diagnostics(bytes_ptr: u32, bytes_ptr_len: u32);
        }

        /// An emitter for the Diagnostic in plugin's context by borrowing host's
        /// environment context.
        ///
        /// It is not expected to call this directly inside of plugin transform.
        /// Instead, it is encouraged to use global HANDLER.
        pub struct PluginDiagnosticsEmitter;

        impl swc_core::common::errors::Emitter for PluginDiagnosticsEmitter {
            #[cfg_attr(not(target_arch = "wasm32"), allow(unused))]
            fn emit(&mut self, db: &swc_core::common::errors::DiagnosticBuilder<'_>) {
                let diag = swc_core::common::plugin::serialized::PluginSerializedBytes::try_serialize(&swc_core::common::plugin::serialized::VersionedSerializable::new(*db.diagnostic.clone()))
                    .expect("Should able to serialize Diagnostic");
                let (ptr, len) = diag.as_ptr();

                #[cfg(target_arch = "wasm32")] // Allow testing
                unsafe {
                    __emit_diagnostics(ptr as u32, len as u32);
                }
            }
        }


        /// Call hosts's imported fn to set transform results.
        /// __set_transform_result is host side imported fn, which read and copies guest's byte into host.
        fn send_transform_result_to_host(bytes_ptr: u32, bytes_ptr_len: u32) {
            #[cfg(target_arch = "wasm32")] // Allow testing
            unsafe {
                __set_transform_result(bytes_ptr, bytes_ptr_len);
            }
        }

        /// Internal function plugin_macro uses to create ptr to PluginError.
        fn construct_error_ptr(plugin_error: swc_core::common::plugin::serialized::PluginError) -> u32 {
            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");
            let (ptr, len) = ret.as_ptr();

            send_transform_result_to_host(
                ptr as _,
                len as u32
            );
            1
        }

        #[no_mangle]
        #[allow(clippy::not_unsafe_ptr_arg_deref)]
        pub fn #transform_core_pkg_diag_ident() -> u32 {
            let schema_version = swc_core::common::plugin::PLUGIN_TRANSFORM_AST_SCHEMA_VERSION;
            let core_pkg_diag = swc_core::diagnostics::get_core_engine_diagnostics();

            let result = swc_core::common::plugin::diagnostics::PluginCorePkgDiagnostics {
                ast_schema_version: schema_version,
                pkg_version: core_pkg_diag.package_semver,
                git_sha: core_pkg_diag.git_sha,
                cargo_features: core_pkg_diag.cargo_features,
            };

            let serialized_result = swc_core::common::plugin::serialized::PluginSerializedBytes::try_serialize(
                &swc_core::common::plugin::serialized::VersionedSerializable::new(result)
            ).expect("Diagnostics should be always serializable");

            let (serialized_result_ptr, serialized_result_ptr_len) = serialized_result.as_ptr();

            #[cfg(target_arch = "wasm32")] // Allow testing
            unsafe {
                __set_transform_plugin_core_pkg_diagnostics(serialized_result_ptr as _, serialized_result_ptr_len as u32);
            }
            0
        }

        // Macro to allow compose plugin's transform function without manual pointer operation.
        // Internally it wraps pointer operation also bubbles up error in forms of PluginError.
        // There are some cases error won't be wrapped up however - for example, we expect
        // serialization of PluginError itself should succeed.
        #[no_mangle]
        #[allow(clippy::not_unsafe_ptr_arg_deref)]
        pub fn #transform_process_impl_ident(
            ast_ptr: *const u8, ast_ptr_len: u32,
            unresolved_mark: u32, should_enable_comments_proxy: i32) -> u32 {
            // Reconstruct `Program` & config string from serialized program
            // Host (SWC) should allocate memory, copy bytes and pass ptr to plugin.
            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();
            if program.is_err() {
                let err = swc_core::common::plugin::serialized::PluginError::Deserialize("Failed to deserialize program received from host".to_string());
                return construct_error_ptr(err);
            }
            let program: #ast_type = program.expect("Should be a program").into_inner();

            // Create a handler wired with plugin's diagnostic emitter, set it for global context.
            let handler = swc_core::common::errors::Handler::with_emitter(
                true,
                false,
                Box::new(PluginDiagnosticsEmitter)
            );

            // Construct metadata to the `Program` for the transform plugin.
            let plugin_comments_proxy = if should_enable_comments_proxy == 1 { Some(swc_core::plugin::proxies::PluginCommentsProxy) } else { None };
            let mut metadata = swc_core::plugin::metadata::TransformPluginProgramMetadata {
                comments: plugin_comments_proxy,
                source_map: swc_core::plugin::proxies::PluginSourceMapProxy { source_file: swc_core::common::sync::OnceCell::new() },
                unresolved_mark: swc_core::common::Mark::from_u32(unresolved_mark as u32),
            };

            // Take original plugin fn ident, then call it with interop'ed args
            let transformed_program = swc_core::common::plugin::serialized::VersionedSerializable::new(swc_core::common::errors::HANDLER.set(&handler, || {
                #ident(program, metadata)
            }));

            // Serialize transformed result, return back to the host.
            let serialized_result = swc_core::common::plugin::serialized::PluginSerializedBytes::try_serialize(
                &transformed_program
            );

            if serialized_result.is_err() {
                let err = swc_core::common::plugin::serialized::PluginError::Serialize("Failed to serialize transformed program".to_string());
                return construct_error_ptr(err);
            }

            let serialized_result = serialized_result.expect("Should be a realized transformed program");
            let (serialized_result_ptr, serialized_result_ptr_len) = serialized_result.as_ptr();

            send_transform_result_to_host(serialized_result_ptr as _, serialized_result_ptr_len as u32);
            0
        }
    };

    ret.into()
}