Skip to main content

cha_plugin_sdk_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Ident, parse_macro_input};
4
5/// The WIT content embedded at proc-macro compile time.
6const WIT: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/wit/plugin.wit"));
7
8/// Set up bindings and export a plugin implementation.
9///
10/// Expands to `wit_bindgen::generate!` with the embedded WIT and `export!`.
11/// No local WIT file needed in the plugin project.
12///
13/// # Example
14/// ```rust,ignore
15/// cha_plugin_sdk::plugin!(MyPlugin);
16///
17/// struct MyPlugin;
18/// impl Guest for MyPlugin {
19///     fn name() -> String { "my-plugin".into() }
20///     fn analyze(input: AnalysisInput) -> Vec<Finding> { vec![] }
21/// }
22/// ```
23#[proc_macro]
24pub fn plugin(input: TokenStream) -> TokenStream {
25    let ty = parse_macro_input!(input as Ident);
26    let wit = WIT;
27
28    quote! {
29        wit_bindgen::generate!({
30            inline: #wit,
31            world: "analyzer",
32        });
33        #[allow(unused_imports)]
34        use cha::plugin::types::{
35            ClassInfo, FunctionInfo, ImportInfo, Location, OptionValue, Severity, SmellCategory,
36        };
37
38        /// Implement this trait in your plugin struct.
39        /// `version`, `description`, and `authors` are filled automatically from Cargo.toml.
40        pub trait PluginImpl {
41            fn name() -> String;
42            fn analyze(input: AnalysisInput) -> Vec<Finding>;
43        }
44
45        struct __ChaPluginWrapper(std::marker::PhantomData<#ty>);
46
47        impl Guest for __ChaPluginWrapper {
48            fn name() -> String { <#ty as PluginImpl>::name() }
49            fn version() -> String { env!("CARGO_PKG_VERSION").to_string() }
50            fn description() -> String {
51                let d = env!("CARGO_PKG_DESCRIPTION");
52                if d.is_empty() { <#ty as PluginImpl>::name() } else { d.to_string() }
53            }
54            fn authors() -> Vec<String> {
55                let a = env!("CARGO_PKG_AUTHORS");
56                if a.is_empty() { vec![] } else { a.split(':').map(str::to_string).collect() }
57            }
58            fn analyze(input: AnalysisInput) -> Vec<Finding> { <#ty as PluginImpl>::analyze(input) }
59        }
60
61        export!(__ChaPluginWrapper);
62    }
63    .into()
64}