1use std::path::Path;
2
3use crate::{
4 Plugin,
5 config::Config,
6 plugins::{
7 ApiSurfaceAnalyzer, AsyncCallbackLeakAnalyzer, BrainMethodAnalyzer,
8 CognitiveComplexityAnalyzer, CommentsAnalyzer, ComplexityAnalyzer, CouplingAnalyzer,
9 DataClassAnalyzer, DataClumpsAnalyzer, DeadCodeAnalyzer, DesignPatternAdvisor,
10 DivergentChangeAnalyzer, DuplicateCodeAnalyzer, ErrorHandlingAnalyzer, FeatureEnvyAnalyzer,
11 GodClassAnalyzer, HardcodedSecretAnalyzer, HubLikeDependencyAnalyzer,
12 InappropriateIntimacyAnalyzer, LayerViolationAnalyzer, LazyClassAnalyzer, LengthAnalyzer,
13 LongParameterListAnalyzer, MessageChainAnalyzer, MiddleManAnalyzer, NamingAnalyzer,
14 PrimitiveObsessionAnalyzer, RefusedBequestAnalyzer, ShotgunSurgeryAnalyzer,
15 SpeculativeGeneralityAnalyzer, SwitchStatementAnalyzer, TemporaryFieldAnalyzer,
16 TodoTrackerAnalyzer, UnsafeApiAnalyzer,
17 },
18 wasm,
19};
20
21pub struct PluginRegistry {
23 plugins: Vec<Box<dyn Plugin>>,
24}
25
26impl PluginRegistry {
27 pub fn from_config(config: &Config, project_dir: &Path) -> Self {
31 Self::from_config_for_language(config, project_dir, "")
32 }
33
34 pub fn from_config_for_language(config: &Config, project_dir: &Path, language: &str) -> Self {
38 let mut plugins: Vec<Box<dyn Plugin>> = Vec::new();
39
40 register_length(&mut plugins, config);
41 register_complexity(&mut plugins, config);
42 register_simple_plugins(&mut plugins, config);
43 register_layer_violation(&mut plugins, config);
44
45 let disabled_smells = config.disabled_smells_for_language(language);
46
47 for mut wp in wasm::load_wasm_plugins(project_dir) {
48 if config.is_enabled(wp.name()) {
49 let mut opts: Vec<(String, wasm::wit::OptionValue)> = Vec::new();
50 if let Some(pc) = config.plugins.get(wp.name()) {
51 opts.extend(pc.options.iter().filter_map(|(k, v)| {
52 wasm::toml_to_option_value(v).map(|ov| (k.clone(), ov))
53 }));
54 }
55 if !disabled_smells.is_empty() {
56 opts.push((
57 "__disabled_smells__".into(),
58 wasm::wit::OptionValue::ListStr(disabled_smells.clone()),
59 ));
60 }
61 wp.set_options(opts);
62 plugins.push(Box::new(wp));
63 }
64 }
65
66 Self { plugins }
67 }
68
69 pub fn plugins(&self) -> &[Box<dyn Plugin>] {
70 &self.plugins
71 }
72
73 pub fn plugin_info(&self) -> Vec<(String, String)> {
75 self.plugins
76 .iter()
77 .map(|p| (p.name().to_string(), p.description().to_string()))
78 .collect()
79 }
80}
81
82fn apply_usize(config: &Config, plugin: &str, key: &str, target: &mut usize) {
84 if let Some(v) = config.get_usize(plugin, key) {
85 *target = v;
86 }
87}
88
89fn register_if_enabled(
91 plugins: &mut Vec<Box<dyn Plugin>>,
92 config: &Config,
93 name: &str,
94 build: impl FnOnce() -> Box<dyn Plugin>,
95) {
96 if config.is_enabled(name) {
97 plugins.push(build());
98 }
99}
100
101fn register_simple_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
102 register_classic_plugins(plugins, config);
103 register_smell_plugins(plugins, config);
104}
105
106fn register_classic_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
107 register_if_enabled(plugins, config, "coupling", || {
108 let mut p = CouplingAnalyzer::default();
109 apply_usize(config, "coupling", "max_imports", &mut p.max_imports);
110 Box::new(p)
111 });
112 register_if_enabled(plugins, config, "naming", || {
113 let mut p = NamingAnalyzer::default();
114 apply_usize(config, "naming", "min_name_length", &mut p.min_name_length);
115 apply_usize(config, "naming", "max_name_length", &mut p.max_name_length);
116 Box::new(p)
117 });
118 register_if_enabled(plugins, config, "duplicate_code", || {
119 Box::new(DuplicateCodeAnalyzer)
120 });
121 register_if_enabled(plugins, config, "dead_code", || Box::new(DeadCodeAnalyzer));
122 register_if_enabled(plugins, config, "api_surface", || {
123 let mut p = ApiSurfaceAnalyzer::default();
124 apply_usize(
125 config,
126 "api_surface",
127 "max_exported_count",
128 &mut p.max_exported_count,
129 );
130 Box::new(p)
131 });
132}
133
134fn register_smell_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
135 register_if_enabled(plugins, config, "long_parameter_list", || {
136 let mut p = LongParameterListAnalyzer::default();
137 apply_usize(
138 config,
139 "long_parameter_list",
140 "max_params",
141 &mut p.max_params,
142 );
143 Box::new(p)
144 });
145 register_if_enabled(plugins, config, "switch_statement", || {
146 let mut p = SwitchStatementAnalyzer::default();
147 apply_usize(config, "switch_statement", "max_arms", &mut p.max_arms);
148 Box::new(p)
149 });
150 register_if_enabled(plugins, config, "message_chain", || {
151 let mut p = MessageChainAnalyzer::default();
152 apply_usize(config, "message_chain", "max_depth", &mut p.max_depth);
153 Box::new(p)
154 });
155 register_if_enabled(plugins, config, "primitive_obsession", || {
156 Box::new(PrimitiveObsessionAnalyzer::default())
157 });
158 register_if_enabled(plugins, config, "data_clumps", || {
159 Box::new(DataClumpsAnalyzer::default())
160 });
161 register_if_enabled(plugins, config, "feature_envy", || {
162 Box::new(FeatureEnvyAnalyzer::default())
163 });
164 register_if_enabled(plugins, config, "middle_man", || {
165 Box::new(MiddleManAnalyzer::default())
166 });
167 register_extended_smell_plugins(plugins, config);
168}
169
170fn register_extended_smell_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
171 register_if_enabled(plugins, config, "comments", || {
172 Box::new(CommentsAnalyzer::default())
173 });
174 register_if_enabled(plugins, config, "lazy_class", || {
175 Box::new(LazyClassAnalyzer::default())
176 });
177 register_if_enabled(plugins, config, "data_class", || {
178 Box::new(DataClassAnalyzer::default())
179 });
180 register_if_enabled(plugins, config, "async_callback_leak", || {
181 Box::new(AsyncCallbackLeakAnalyzer)
182 });
183 register_if_enabled(plugins, config, "design_pattern", || {
184 Box::new(DesignPatternAdvisor)
185 });
186 register_if_enabled(plugins, config, "temporary_field", || {
187 Box::new(TemporaryFieldAnalyzer::default())
188 });
189 register_if_enabled(plugins, config, "speculative_generality", || {
190 Box::new(SpeculativeGeneralityAnalyzer)
191 });
192 register_change_preventer_plugins(plugins, config);
193 register_advanced_plugins(plugins, config);
194}
195
196fn register_change_preventer_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
197 register_if_enabled(plugins, config, "refused_bequest", || {
198 Box::new(RefusedBequestAnalyzer::default())
199 });
200 register_if_enabled(plugins, config, "shotgun_surgery", || {
201 Box::new(ShotgunSurgeryAnalyzer::default())
202 });
203 register_if_enabled(plugins, config, "divergent_change", || {
204 Box::new(DivergentChangeAnalyzer::default())
205 });
206 register_if_enabled(plugins, config, "inappropriate_intimacy", || {
207 Box::new(InappropriateIntimacyAnalyzer)
208 });
209 register_if_enabled(plugins, config, "hardcoded_secret", || {
210 Box::new(HardcodedSecretAnalyzer)
211 });
212}
213
214fn register_advanced_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
216 register_if_enabled(plugins, config, "cognitive_complexity", || {
217 let mut p = CognitiveComplexityAnalyzer::default();
218 apply_usize(
219 config,
220 "cognitive_complexity",
221 "threshold",
222 &mut p.threshold,
223 );
224 Box::new(p)
225 });
226 register_if_enabled(plugins, config, "god_class", || {
227 let mut p = GodClassAnalyzer::default();
228 apply_usize(
229 config,
230 "god_class",
231 "max_external_refs",
232 &mut p.max_external_refs,
233 );
234 apply_usize(config, "god_class", "min_wmc", &mut p.min_wmc);
235 Box::new(p)
236 });
237 register_if_enabled(plugins, config, "brain_method", || {
238 let mut p = BrainMethodAnalyzer::default();
239 apply_usize(config, "brain_method", "min_lines", &mut p.min_lines);
240 apply_usize(
241 config,
242 "brain_method",
243 "min_complexity",
244 &mut p.min_complexity,
245 );
246 Box::new(p)
247 });
248 register_if_enabled(plugins, config, "hub_like_dependency", || {
249 let mut p = HubLikeDependencyAnalyzer::default();
250 apply_usize(
251 config,
252 "hub_like_dependency",
253 "max_imports",
254 &mut p.max_imports,
255 );
256 Box::new(p)
257 });
258 register_if_enabled(plugins, config, "error_handling", || {
259 let mut p = ErrorHandlingAnalyzer::default();
260 apply_usize(
261 config,
262 "error_handling",
263 "max_unwraps_per_function",
264 &mut p.max_unwraps_per_function,
265 );
266 Box::new(p)
267 });
268 register_if_enabled(plugins, config, "todo_tracker", || {
269 Box::new(TodoTrackerAnalyzer)
270 });
271 register_if_enabled(plugins, config, "unsafe_api", || {
272 Box::new(UnsafeApiAnalyzer)
273 });
274}
275
276fn register_length(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
277 if !config.is_enabled("length") {
278 return;
279 }
280 let mut p = LengthAnalyzer::default();
281 if let Some(v) = config.get_usize("length", "max_function_lines") {
282 p.max_function_lines = v;
283 }
284 if let Some(v) = config.get_usize("length", "max_class_methods") {
285 p.max_class_methods = v;
286 }
287 if let Some(v) = config.get_usize("length", "max_class_lines") {
288 p.max_class_lines = v;
289 }
290 if let Some(v) = config.get_usize("length", "max_file_lines") {
291 p.max_file_lines = v;
292 }
293 plugins.push(Box::new(p));
294}
295
296fn register_complexity(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
297 if !config.is_enabled("complexity") {
298 return;
299 }
300 let mut p = ComplexityAnalyzer::default();
301 if let Some(v) = config.get_usize("complexity", "warn_threshold") {
302 p.warn_threshold = v;
303 }
304 if let Some(v) = config.get_usize("complexity", "error_threshold") {
305 p.error_threshold = v;
306 }
307 plugins.push(Box::new(p));
308}
309
310fn register_layer_violation(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
311 if !config.is_enabled("layer_violation") {
312 return;
313 }
314 let p = config
315 .get_str("layer_violation", "layers")
316 .map(|s| LayerViolationAnalyzer::from_config_str(&s))
317 .unwrap_or_default();
318 plugins.push(Box::new(p));
319}