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            ArmValue, ClassInfo, CommentInfo, FileRole, FunctionInfo, ImportInfo,
36            Location, OptionValue, Severity, SmellCategory,
37        };
38        #[allow(unused_imports)]
39        pub use cha::plugin::tree_query;
40
41        /// Implement this trait in your plugin struct.
42        /// `version`, `description`, `authors`, `smells` all have default impls.
43        pub trait PluginImpl {
44            fn name() -> String;
45            fn analyze(input: AnalysisInput) -> Vec<Finding>;
46            /// Smell names this plugin can produce. Default: empty.
47            /// Declaring them lets the host filter by smell_name and show
48            /// accurate docs in `cha plugin list`.
49            fn smells() -> Vec<String> { vec![] }
50        }
51
52        struct __ChaPluginWrapper(std::marker::PhantomData<#ty>);
53
54        impl Guest for __ChaPluginWrapper {
55            fn name() -> String { <#ty as PluginImpl>::name() }
56            fn version() -> String { env!("CARGO_PKG_VERSION").to_string() }
57            fn description() -> String {
58                let d = env!("CARGO_PKG_DESCRIPTION");
59                if d.is_empty() { <#ty as PluginImpl>::name() } else { d.to_string() }
60            }
61            fn authors() -> Vec<String> {
62                let a = env!("CARGO_PKG_AUTHORS");
63                if a.is_empty() { vec![] } else { a.split(':').map(str::to_string).collect() }
64            }
65            fn smells() -> Vec<String> { <#ty as PluginImpl>::smells() }
66            fn analyze(input: AnalysisInput) -> Vec<Finding> { <#ty as PluginImpl>::analyze(input) }
67        }
68
69        export!(__ChaPluginWrapper);
70    }
71    .into()
72}