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 apply_f64(config: &Config, plugin: &str, key: &str, target: &mut f64) {
91 if let Some(v) = config.get_f64(plugin, key) {
92 *target = v;
93 }
94}
95
96fn apply_bool(config: &Config, plugin: &str, key: &str, target: &mut bool) {
98 if let Some(v) = config.get_bool(plugin, key) {
99 *target = v;
100 }
101}
102
103fn register_if_enabled(
105 plugins: &mut Vec<Box<dyn Plugin>>,
106 config: &Config,
107 name: &str,
108 build: impl FnOnce() -> Box<dyn Plugin>,
109) {
110 if config.is_enabled(name) {
111 plugins.push(build());
112 }
113}
114
115fn register_simple_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
116 register_classic_plugins(plugins, config);
117 register_smell_plugins(plugins, config);
118}
119
120fn register_classic_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
121 register_if_enabled(plugins, config, "coupling", || {
122 let mut p = CouplingAnalyzer::default();
123 apply_usize(config, "coupling", "max_imports", &mut p.max_imports);
124 Box::new(p)
125 });
126 register_if_enabled(plugins, config, "naming", || {
127 let mut p = NamingAnalyzer::default();
128 apply_usize(config, "naming", "min_name_length", &mut p.min_name_length);
129 apply_usize(config, "naming", "max_name_length", &mut p.max_name_length);
130 Box::new(p)
131 });
132 register_if_enabled(plugins, config, "duplicate_code", || {
133 Box::new(DuplicateCodeAnalyzer)
134 });
135 register_if_enabled(plugins, config, "dead_code", || Box::new(DeadCodeAnalyzer));
136 register_if_enabled(plugins, config, "api_surface", || {
137 let mut p = ApiSurfaceAnalyzer::default();
138 apply_usize(
139 config,
140 "api_surface",
141 "max_exported_count",
142 &mut p.max_exported_count,
143 );
144 apply_f64(
145 config,
146 "api_surface",
147 "max_exported_ratio",
148 &mut p.max_exported_ratio,
149 );
150 apply_usize(
151 config,
152 "api_surface",
153 "c_max_exported_count",
154 &mut p.c_max_exported_count,
155 );
156 apply_f64(
157 config,
158 "api_surface",
159 "c_max_exported_ratio",
160 &mut p.c_max_exported_ratio,
161 );
162 apply_bool(
163 config,
164 "api_surface",
165 "skip_c_headers",
166 &mut p.skip_c_headers,
167 );
168 Box::new(p)
169 });
170}
171
172fn register_smell_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
173 register_if_enabled(plugins, config, "long_parameter_list", || {
174 let mut p = LongParameterListAnalyzer::default();
175 apply_usize(
176 config,
177 "long_parameter_list",
178 "max_params",
179 &mut p.max_params,
180 );
181 Box::new(p)
182 });
183 register_if_enabled(plugins, config, "switch_statement", || {
184 let mut p = SwitchStatementAnalyzer::default();
185 apply_usize(config, "switch_statement", "max_arms", &mut p.max_arms);
186 Box::new(p)
187 });
188 register_if_enabled(plugins, config, "message_chain", || {
189 let mut p = MessageChainAnalyzer::default();
190 apply_usize(config, "message_chain", "max_depth", &mut p.max_depth);
191 Box::new(p)
192 });
193 register_if_enabled(plugins, config, "primitive_obsession", || {
194 Box::new(PrimitiveObsessionAnalyzer::default())
195 });
196 register_if_enabled(plugins, config, "data_clumps", || {
197 Box::new(DataClumpsAnalyzer::default())
198 });
199 register_if_enabled(plugins, config, "feature_envy", || {
200 Box::new(FeatureEnvyAnalyzer::default())
201 });
202 register_if_enabled(plugins, config, "middle_man", || {
203 Box::new(MiddleManAnalyzer::default())
204 });
205 register_extended_smell_plugins(plugins, config);
206}
207
208fn register_extended_smell_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
209 register_if_enabled(plugins, config, "comments", || {
210 Box::new(CommentsAnalyzer::default())
211 });
212 register_if_enabled(plugins, config, "lazy_class", || {
213 Box::new(LazyClassAnalyzer::default())
214 });
215 register_if_enabled(plugins, config, "data_class", || {
216 Box::new(DataClassAnalyzer::default())
217 });
218 register_if_enabled(plugins, config, "async_callback_leak", || {
219 Box::new(AsyncCallbackLeakAnalyzer)
220 });
221 register_if_enabled(plugins, config, "design_pattern", || {
222 Box::new(DesignPatternAdvisor)
223 });
224 register_if_enabled(plugins, config, "temporary_field", || {
225 Box::new(TemporaryFieldAnalyzer::default())
226 });
227 register_if_enabled(plugins, config, "speculative_generality", || {
228 Box::new(SpeculativeGeneralityAnalyzer)
229 });
230 register_change_preventer_plugins(plugins, config);
231 register_advanced_plugins(plugins, config);
232}
233
234fn register_change_preventer_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
235 register_if_enabled(plugins, config, "refused_bequest", || {
236 Box::new(RefusedBequestAnalyzer::default())
237 });
238 register_if_enabled(plugins, config, "shotgun_surgery", || {
239 Box::new(ShotgunSurgeryAnalyzer::default())
240 });
241 register_if_enabled(plugins, config, "divergent_change", || {
242 Box::new(DivergentChangeAnalyzer::default())
243 });
244 register_if_enabled(plugins, config, "inappropriate_intimacy", || {
245 Box::new(InappropriateIntimacyAnalyzer)
246 });
247 register_if_enabled(plugins, config, "hardcoded_secret", || {
248 Box::new(HardcodedSecretAnalyzer)
249 });
250}
251
252fn register_advanced_plugins(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
254 register_if_enabled(plugins, config, "cognitive_complexity", || {
255 let mut p = CognitiveComplexityAnalyzer::default();
256 apply_usize(
257 config,
258 "cognitive_complexity",
259 "threshold",
260 &mut p.threshold,
261 );
262 Box::new(p)
263 });
264 register_if_enabled(plugins, config, "god_class", || {
265 let mut p = GodClassAnalyzer::default();
266 apply_usize(
267 config,
268 "god_class",
269 "max_external_refs",
270 &mut p.max_external_refs,
271 );
272 apply_usize(config, "god_class", "min_wmc", &mut p.min_wmc);
273 Box::new(p)
274 });
275 register_if_enabled(plugins, config, "brain_method", || {
276 let mut p = BrainMethodAnalyzer::default();
277 apply_usize(config, "brain_method", "min_lines", &mut p.min_lines);
278 apply_usize(
279 config,
280 "brain_method",
281 "min_complexity",
282 &mut p.min_complexity,
283 );
284 Box::new(p)
285 });
286 register_if_enabled(plugins, config, "hub_like_dependency", || {
287 let mut p = HubLikeDependencyAnalyzer::default();
288 apply_usize(
289 config,
290 "hub_like_dependency",
291 "max_imports",
292 &mut p.max_imports,
293 );
294 Box::new(p)
295 });
296 register_if_enabled(plugins, config, "error_handling", || {
297 let mut p = ErrorHandlingAnalyzer::default();
298 apply_usize(
299 config,
300 "error_handling",
301 "max_unwraps_per_function",
302 &mut p.max_unwraps_per_function,
303 );
304 Box::new(p)
305 });
306 register_if_enabled(plugins, config, "todo_tracker", || {
307 Box::new(TodoTrackerAnalyzer)
308 });
309 register_if_enabled(plugins, config, "unsafe_api", || {
310 Box::new(UnsafeApiAnalyzer)
311 });
312}
313
314fn register_length(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
315 if !config.is_enabled("length") {
316 return;
317 }
318 let mut p = LengthAnalyzer::default();
319 if let Some(v) = config.get_usize("length", "max_function_lines") {
320 p.max_function_lines = v;
321 }
322 if let Some(v) = config.get_usize("length", "max_class_methods") {
323 p.max_class_methods = v;
324 }
325 if let Some(v) = config.get_usize("length", "max_class_lines") {
326 p.max_class_lines = v;
327 }
328 if let Some(v) = config.get_usize("length", "max_file_lines") {
329 p.max_file_lines = v;
330 }
331 plugins.push(Box::new(p));
332}
333
334fn register_complexity(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
335 if !config.is_enabled("complexity") {
336 return;
337 }
338 let mut p = ComplexityAnalyzer::default();
339 if let Some(v) = config.get_usize("complexity", "warn_threshold") {
340 p.warn_threshold = v;
341 }
342 if let Some(v) = config.get_usize("complexity", "error_threshold") {
343 p.error_threshold = v;
344 }
345 plugins.push(Box::new(p));
346}
347
348fn register_layer_violation(plugins: &mut Vec<Box<dyn Plugin>>, config: &Config) {
349 if !config.is_enabled("layer_violation") {
350 return;
351 }
352 let p = config
353 .get_str("layer_violation", "layers")
354 .map(|s| LayerViolationAnalyzer::from_config_str(&s))
355 .unwrap_or_default();
356 plugins.push(Box::new(p));
357}