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