pedant_core/check_config/
runtime.rs1use std::borrow::Cow;
2use std::sync::Arc;
3
4use super::file::{
5 ConfigFile, FeatureBoundaryRule, FlatModuleFamily, ItemVisibilityRule, PathOverride,
6 default_else_chain_threshold, default_max_depth, default_max_function_body_lines,
7 default_max_methods, default_max_params, default_module_root_files,
8 default_source_file_deny_lines, default_source_file_warn_lines, default_test_api_patterns,
9 default_test_support_feature,
10};
11use super::naming::NamingCheck;
12use super::pattern::PatternCheck;
13use crate::pattern::matches_glob;
14
15pub fn check_path_override<'a>(
22 file_path: &str,
23 config: &'a ConfigFile,
24) -> Option<&'a PathOverride> {
25 config
26 .overrides
27 .iter()
28 .filter(|(pattern, _)| matches_glob(pattern, file_path))
29 .max_by_key(|(pattern, _)| pattern.len())
30 .map(|(_, override_config)| override_config)
31}
32
33macro_rules! for_each_bool_check {
47 ($callback:ident!) => {
48 $callback! {
49 "Flag `if` inside `if`.", check_nested_if, true;
50 "Flag `if` inside `match` arm.", check_if_in_match, true;
51 "Flag `match` inside `match`.", check_nested_match, true;
52 "Flag `match` inside `if` branch.", check_match_in_if, true;
53 "Flag long `if/else if` chains.", check_else_chain, true;
54 "Flag any use of the `else` keyword.", forbid_else, false;
55 "Flag any `unsafe` block.", forbid_unsafe, true;
56 "Flag dynamic dispatch in return types.", check_dyn_return, false;
57 "Flag dynamic dispatch in function parameters.", check_dyn_param, false;
58 "Flag `Vec<Box<dyn T>>`.", check_vec_box_dyn, false;
59 "Flag dynamic dispatch in struct fields.", check_dyn_field, false;
60 "Flag `.clone()` inside loop bodies.", check_clone_in_loop, false;
61 "Flag `HashMap`/`HashSet` with default hasher.", check_default_hasher, false;
62 "Flag disconnected type groups in a single file.", check_mixed_concerns, false;
63 "Flag `#[cfg(test)] mod` blocks in source files.", check_inline_tests, false;
64 "Flag `let _ = expr` that discards a Result.", check_let_underscore_result, false;
65 "Flag functions with too many parameters.", check_high_param_count, false;
66 "Flag function bodies that exceed the line ceiling.", check_long_function_body, false;
67 "Flag item definitions in module-root files.", check_module_root_definitions, false;
68 "Flag source files that exceed the line ceiling.", check_large_source_file, false;
69 "Flag god-object types by inherent-method count.", check_high_method_count, false;
70 "Count pure forwarders toward `high-method-count`.", count_forwarders, false;
71 "Enforce configured item-visibility policies.", check_item_visibility_policy, true;
72 "Flag ungated test-only APIs under `src/`.", check_ungated_test_api, false;
73 "Flag sibling `<stem>.rs` and `<stem>/` module roots.", check_conflicting_module_root, false;
74 "Enforce configured flat-module-family layout rules.", check_flat_module_family, true;
75 "Enforce configured Cargo feature-boundary invariants.", check_feature_boundary, true;
76 "Flag types whose inherent impls span more than one file.", check_scattered_inherent_impl, false;
77 }
78 };
79}
80
81macro_rules! impl_check_config {
84 ($($doc:literal, $field:ident, $default:expr;)*) => {
85 #[derive(Debug, Clone)]
87 pub struct CheckConfig {
88 pub max_depth: usize,
90 pub else_chain_threshold: usize,
92 pub max_params: usize,
94 pub max_function_body_lines: usize,
96 pub module_root_files: Arc<[Arc<str>]>,
98 pub source_file_warn_lines: usize,
100 pub source_file_deny_lines: usize,
102 pub max_methods: usize,
104 pub item_visibility_policy: Arc<[ItemVisibilityRule]>,
106 pub flat_module_families: Arc<[FlatModuleFamily]>,
108 pub feature_boundaries: Arc<[FeatureBoundaryRule]>,
110 pub test_api_patterns: Arc<[Arc<str>]>,
112 pub test_support_feature: Arc<str>,
114 pub forbid_attributes: PatternCheck,
116 pub forbid_types: PatternCheck,
118 pub forbid_calls: PatternCheck,
120 pub forbid_macros: PatternCheck,
122 pub check_naming: NamingCheck,
124 $(
125 #[doc = $doc]
126 pub $field: bool,
127 )*
128 }
129
130 impl Default for CheckConfig {
131 fn default() -> Self {
132 Self {
133 max_depth: default_max_depth(),
134 else_chain_threshold: default_else_chain_threshold(),
135 max_params: default_max_params(),
136 max_function_body_lines: default_max_function_body_lines(),
137 module_root_files: default_module_root_files(),
138 source_file_warn_lines: default_source_file_warn_lines(),
139 source_file_deny_lines: default_source_file_deny_lines(),
140 max_methods: default_max_methods(),
141 item_visibility_policy: Arc::from([]),
142 flat_module_families: Arc::from([]),
143 feature_boundaries: Arc::from([]),
144 test_api_patterns: default_test_api_patterns(),
145 test_support_feature: Arc::from(default_test_support_feature()),
146 forbid_attributes: PatternCheck::default(),
147 forbid_types: PatternCheck::default(),
148 forbid_calls: PatternCheck::default(),
149 forbid_macros: PatternCheck::default(),
150 check_naming: NamingCheck::default(),
151 $( $field: $default, )*
152 }
153 }
154 }
155
156 impl CheckConfig {
157 pub fn from_config_file(fc: &ConfigFile) -> Self {
159 Self {
160 max_depth: fc.max_depth,
161 else_chain_threshold: fc.else_chain_threshold,
162 max_params: fc.max_params,
163 max_function_body_lines: fc.max_function_body_lines,
164 module_root_files: fc.module_root_files.clone(),
165 source_file_warn_lines: fc.source_file_warn_lines,
166 source_file_deny_lines: fc.source_file_deny_lines,
167 max_methods: fc.max_methods,
168 item_visibility_policy: fc.item_visibility_policy.iter().cloned().collect(),
169 flat_module_families: fc.flat_module_families.iter().cloned().collect(),
170 feature_boundaries: fc.feature_boundaries.iter().cloned().collect(),
171 test_api_patterns: fc.test_api_patterns.clone(),
172 test_support_feature: Arc::from(&*fc.test_support_feature),
173 forbid_attributes: fc.forbid_attributes.clone(),
174 forbid_types: fc.forbid_types.clone(),
175 forbid_calls: fc.forbid_calls.clone(),
176 forbid_macros: fc.forbid_macros.clone(),
177 check_naming: fc.check_naming.clone(),
178 $( $field: fc.$field, )*
179 }
180 }
181
182 pub fn merge_bool_overrides(&mut self, ovr: &PathOverride) {
184 $(
185 if let Some(v) = ovr.$field {
186 self.$field = v;
187 }
188 )*
189 }
190 }
191 };
192}
193
194for_each_bool_check!(impl_check_config!);
195
196macro_rules! assert_bool_fields_in_sync {
200 ($($doc:literal, $field:ident, $default:expr;)*) => {
201 const _: () = {
202 const fn _check(cf: &ConfigFile, po: &PathOverride) {
205 $( let _ = (cf.$field, po.$field); )*
206 }
207 };
208 };
209}
210
211for_each_bool_check!(assert_bool_fields_in_sync!);
212
213impl CheckConfig {
214 pub fn resolve_for_path<'a>(
220 &'a self,
221 file_path: &str,
222 file_config: Option<&ConfigFile>,
223 ) -> Option<Cow<'a, Self>> {
224 let Some(fc) = file_config else {
225 return Some(Cow::Borrowed(self));
226 };
227
228 let Some(override_cfg) = check_path_override(file_path, fc) else {
229 return Some(Cow::Borrowed(self));
230 };
231
232 if override_cfg.enabled == Some(false) {
233 return None;
234 }
235
236 let mut config = self.clone();
237 if let Some(max_depth) = override_cfg.max_depth {
238 config.max_depth = max_depth;
239 }
240 if let Some(max_params) = override_cfg.max_params {
241 config.max_params = max_params;
242 }
243 if let Some(max_body) = override_cfg.max_function_body_lines {
244 config.max_function_body_lines = max_body;
245 }
246 if let Some(warn) = override_cfg.source_file_warn_lines {
247 config.source_file_warn_lines = warn;
248 }
249 if let Some(deny) = override_cfg.source_file_deny_lines {
250 config.source_file_deny_lines = deny;
251 }
252 if let Some(max_methods) = override_cfg.max_methods {
253 config.max_methods = max_methods;
254 }
255
256 config.merge_bool_overrides(override_cfg);
257
258 macro_rules! apply {
259 ($field:ident) => {
260 if let Some(ref ovr) = override_cfg.$field {
261 config.$field.apply_override(ovr);
262 }
263 };
264 }
265 apply!(forbid_attributes);
266 apply!(forbid_types);
267 apply!(forbid_calls);
268 apply!(forbid_macros);
269 apply!(check_naming);
270
271 Some(Cow::Owned(config))
272 }
273}