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
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),
        _ => panic!("Please confirm if plugin macro is specified for the function"),
    }
}

fn handle_func(func: ItemFn) -> TokenStream {
    let ident = func.sig.ident.clone();
    let process_impl_ident = Ident::new("__plugin_process_impl", 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: i32, bytes_ptr_len: i32);
            fn __free(bytes_ptr: i32, size: i32) -> i32;
        }


        /// Call hosts's imported fn to set transform results, then free allocated memory in guest side.
        /// When guest calls __set_transform_result host should've completed read guest's memory and allocates its byte
        /// into host's enviroment so guest can free its memory later.
        fn set_transform_result_volatile(bytes_ptr: i32, bytes_ptr_len: i32) {
            #[cfg(target_arch = "wasm32")] // Allow testing
            unsafe {
                __set_transform_result(bytes_ptr, bytes_ptr_len);
                __free(bytes_ptr, bytes_ptr_len);
            }
        }

        /// Internal function plugin_macro uses to create ptr to PluginError.
        fn construct_error_ptr(plugin_error: swc_plugin::PluginError) -> i32 {
            let ret = swc_plugin::Serialized::serialize(&plugin_error).expect("Should able to serialize PluginError");
            let ret_ref = ret.as_ref();

            set_transform_result_volatile(
                ret_ref.as_ptr() as _,
                std::convert::TryInto::try_into(ret_ref.len()).expect("Should able to convert size of PluginError")
            );
            1
        }

        // 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]
        pub fn #process_impl_ident(ast_ptr: *const u8, ast_ptr_len: i32, config_str_ptr: *const u8, config_str_ptr_len: i32) -> i32 {
            let ast_ptr_len_usize: Result<usize, std::num::TryFromIntError> = std::convert::TryInto::try_into(ast_ptr_len);
            let config_str_ptr_len_usize: Result<usize, std::num::TryFromIntError> = std::convert::TryInto::try_into(config_str_ptr_len);

            if ast_ptr_len_usize.is_err() {
                let err = swc_plugin::PluginError::SizeInteropFailure("Failed to convert size of AST pointer".to_string());
                return construct_error_ptr(err);
            }

            if config_str_ptr_len_usize.is_err() {
                let err = swc_plugin::PluginError::SizeInteropFailure("Failed to convert size of plugin config string pointer".to_string());
                return construct_error_ptr(err);
            }

            // Read raw serialized bytes from wasm's memory space. Host (SWC) should
            // allocate memory, copy bytes and pass ptr to plugin.
            let raw_ast_serialized_bytes =
                unsafe { std::slice::from_raw_parts(ast_ptr, ast_ptr_len_usize.unwrap()) };
            let raw_config_serialized_bytes =
                unsafe { std::slice::from_raw_parts(config_str_ptr, config_str_ptr_len_usize.unwrap()) };


            // Reconstruct SerializedProgram from raw bytes
            let serialized_program = swc_plugin::Serialized::new_for_plugin(raw_ast_serialized_bytes, ast_ptr_len);
            let serialized_config = swc_plugin::Serialized::new_for_plugin(raw_config_serialized_bytes, config_str_ptr_len);

            // Reconstruct `Program` & config string from serialized program
            let program = swc_plugin::Serialized::deserialize(&serialized_program);
            let config = swc_plugin::Serialized::deserialize(&serialized_config);

            if program.is_err() {
                let err = swc_plugin::PluginError::Deserialize(
                        ("Failed to deserialize program received from host".to_string(),
                            raw_ast_serialized_bytes.to_vec())
                    );
                return construct_error_ptr(err);
            }
            let program: Program = program.expect("Should be a program");

            if config.is_err() {
                let err = swc_plugin::PluginError::Deserialize(
                        ("Failed to deserialize config string received from host".to_string(),
                            raw_config_serialized_bytes.to_vec())
                    );
                return construct_error_ptr(err);
            }
            let config: String = config.expect("Should be a string");


            // Create a handler wired with plugin's diagnostic emitter, set it for global context.
            let handler = swc_plugin::errors::Handler::with_emitter(
                true,
                false,
                Box::new(swc_plugin::environment::PluginDiagnosticsEmitter {})
            );
            let handler_set_result = swc_plugin::errors::HANDLER.inner.set(handler);

            if handler_set_result.is_err() {
                let err = swc_plugin::PluginError::Serialize(
                        "Failed to set handler for plugin".to_string()
                    );
                return construct_error_ptr(err);
            }

            // Take original plugin fn ident, then call it with interop'ed args
            let transformed_program = #ident(program, config);

            // Serialize transformed result, return back to the host.
            let serialized_result = swc_plugin::Serialized::serialize(&transformed_program);

            if serialized_result.is_err() {
                let err = swc_plugin::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 = serialized_result.as_ref();

            let serialized_result_len: Result<i32, std::num::TryFromIntError> = std::convert::TryInto::try_into(serialized_result.len());

            if serialized_result_len.is_err() {
                let err = swc_plugin::PluginError::SizeInteropFailure("Failed to convert size of transformed AST pointer".to_string());
                return construct_error_ptr(err);
            }

            set_transform_result_volatile(serialized_result.as_ptr() as _, serialized_result_len.expect("Should be an i32"));
            0
        }
    };

    ret.into()
}