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