mago_analyzer/settings.rs
1use mago_algebra::AlgebraThresholds;
2use mago_atom::AtomSet;
3use mago_php_version::PHPVersion;
4
5/// Default maximum logical formula size during conditional analysis.
6pub const DEFAULT_FORMULA_SIZE_THRESHOLD: u16 = 512;
7
8/// Configuration settings that control the behavior of the Mago analyzer.
9///
10/// This struct allows you to enable/disable specific checks, suppress categories of issues,
11/// and tune the analyzer's performance and strictness.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Settings {
14 /// The target PHP version for the analysis.
15 pub version: PHPVersion,
16
17 /// Find and report expressions whose results are not used (e.g., `$a + $b;`). Defaults to `false`.
18 pub find_unused_expressions: bool,
19
20 /// Find and report unused definitions (e.g., private methods that are never called). Defaults to `false`.
21 pub find_unused_definitions: bool,
22
23 /// Analyze code that appears to be unreachable. Defaults to `false`.
24 pub analyze_dead_code: bool,
25
26 /// Track the literal values of class properties when they are assigned.
27 /// This improves type inference but may increase memory usage. Defaults to `true`.
28 pub memoize_properties: bool,
29
30 /// Allow accessing array keys that may not be defined without reporting an issue. Defaults to `true`.
31 pub allow_possibly_undefined_array_keys: bool,
32
33 /// Enable checking for unhandled thrown exceptions.
34 ///
35 /// When `true`, the analyzer will report any exception that is thrown but not caught
36 /// in a `try-catch` block or documented in a `@throws` tag.
37 ///
38 /// This check is disabled by default (`false`) as it can be computationally expensive.
39 pub check_throws: bool,
40
41 /// Exceptions to ignore including all subclasses (hierarchy-aware).
42 ///
43 /// When an exception class is in this set, any exception of that class or any of its
44 /// subclasses will be ignored during `check_throws` analysis.
45 ///
46 /// For example, adding `LogicException` will ignore `LogicException`, `InvalidArgumentException`,
47 /// `OutOfBoundsException`, and all other subclasses.
48 pub unchecked_exceptions: AtomSet,
49
50 /// Exceptions to ignore (exact class match only, not subclasses).
51 ///
52 /// When an exception class is in this set, only that exact class will be ignored
53 /// during `check_throws` analysis. Parent classes and subclasses are not affected.
54 pub unchecked_exception_classes: AtomSet,
55
56 /// Check for missing `#[Override]` attributes on overriding methods.
57 ///
58 /// When enabled, the analyzer reports methods that override a parent method without
59 /// the `#[Override]` attribute (PHP 8.3+).
60 ///
61 /// Defaults to `true`.
62 pub check_missing_override: bool,
63
64 /// Find and report unused function/method parameters.
65 ///
66 /// When enabled, the analyzer reports parameters that are declared but never used
67 /// within the function body.
68 ///
69 /// Defaults to `true`.
70 pub find_unused_parameters: bool,
71
72 /// Enforce strict checks when accessing list elements by index.
73 ///
74 /// When `true`, the analyzer requires that any integer used to access a `list`
75 /// element is provably non-negative (e.g., of type `int<0, max>`). This helps
76 /// prevent potential runtime errors from using a negative index.
77 ///
78 /// When `false` (the default), any `int` is permitted as an index, offering
79 /// more flexibility at the cost of type safety.
80 pub strict_list_index_checks: bool,
81
82 /// Disable comparisons to boolean literals (`true`/`false`).
83 ///
84 /// When enabled, comparisons to boolean literals will not be reported as issues.
85 ///
86 /// Defaults to `false`.
87 pub no_boolean_literal_comparison: bool,
88
89 /// Check for missing type hints on parameters, properties, and return types.
90 ///
91 /// When enabled, the analyzer will report warnings for function parameters, class properties,
92 /// and function return types that lack explicit type declarations. The analyzer uses its
93 /// type system knowledge to avoid false positives - for instance, it won't require a type hint
94 /// on a property if adding one would conflict with a parent class or trait that has no type hint.
95 ///
96 /// Defaults to `false`.
97 pub check_missing_type_hints: bool,
98
99 /// Check for missing type hints (both parameters and return types) in closures when `check_missing_type_hints` is enabled.
100 ///
101 /// When `true`, closures (anonymous functions declared with `function() {}`) will be
102 /// checked for missing type hints. When `false`, closures are ignored, which is useful
103 /// because closures often rely on type inference.
104 ///
105 /// Defaults to `false`.
106 pub check_closure_missing_type_hints: bool,
107
108 /// Check for missing type hints (both parameters and return types) in arrow functions when `check_missing_type_hints` is enabled.
109 ///
110 /// When `true`, arrow functions (declared with `fn() => ...`) will be checked for missing
111 /// type hints. When `false`, arrow functions are ignored, which is useful because arrow
112 /// functions often rely on type inference and are typically short, making types obvious.
113 ///
114 /// Defaults to `false`.
115 pub check_arrow_function_missing_type_hints: bool,
116
117 /// Register superglobals (e.g., `$_GET`, `$_POST`, `$_SERVER`) in the analysis context.
118 ///
119 /// If disabled, super globals won't be available unless explicitly imported using
120 /// the `global` keyword.
121 ///
122 /// Defaults to `true`.
123 pub register_super_globals: bool,
124
125 /// Enable colored output in terminal environments that support it. Defaults to `true`.
126 ///
127 /// This setting is primarily used for enabling/disabling colored diffs in
128 /// issue reports.
129 pub use_colors: bool,
130
131 /// **Internal use only.**
132 ///
133 /// Enables a diffing mode for incremental analysis, used by integrations like LSPs.
134 /// This avoids re-analyzing unchanged code in the same session. Defaults to `false`.
135 pub diff: bool,
136
137 /// Trust symbol existence checks to narrow types.
138 ///
139 /// When enabled, conditional checks like `method_exists()`, `property_exists()`,
140 /// `function_exists()`, and `defined()` will narrow the type within the conditional block,
141 /// suppressing errors for symbols that are verified to exist at runtime.
142 ///
143 /// When disabled, these checks are ignored and the analyzer requires explicit type hints,
144 /// which is stricter but may produce more false positives for dynamic code.
145 ///
146 /// Defaults to `true`.
147 pub trust_existence_checks: bool,
148
149 /// Method names treated as class initializers (like `__construct`).
150 ///
151 /// Properties initialized in these methods count as "definitely initialized"
152 /// just like in the constructor. This is useful for frameworks that use
153 /// lifecycle methods like `PHPUnit`'s `setUp()` or framework `boot()` methods.
154 ///
155 /// Example: `["setUp", "initialize", "boot"]`
156 ///
157 /// Defaults to empty (no additional initializers).
158 pub class_initializers: AtomSet,
159
160 /// Enable property initialization checking (`missing-constructor`, `uninitialized-property`).
161 ///
162 /// When `false`, disables both `missing-constructor` and `uninitialized-property` issues
163 /// entirely. This is useful for projects that prefer to rely on runtime errors for
164 /// property initialization.
165 ///
166 /// Defaults to `false`.
167 pub check_property_initialization: bool,
168
169 /// Check for non-existent symbols in use statements.
170 ///
171 /// When enabled, the analyzer will report use statements that import symbols
172 /// (classes, interfaces, traits, enums, functions, or constants) that do not exist
173 /// in the codebase.
174 ///
175 /// Defaults to `false`.
176 pub check_use_statements: bool,
177
178 // Performance tuning thresholds
179 // Higher values allow deeper analysis at the cost of performance.
180 // Lower values improve speed but may reduce precision on complex code.
181 /// Maximum number of clauses to process during CNF saturation.
182 ///
183 /// Controls how many clauses the simplification algorithm will work with.
184 /// If exceeded, saturation returns an empty result to avoid performance issues.
185 ///
186 /// Defaults to `8192`.
187 pub saturation_complexity_threshold: u16,
188
189 /// Maximum number of clauses per side in disjunction operations.
190 ///
191 /// Controls the complexity limit for OR operations between clause sets.
192 /// If either side exceeds this, the disjunction returns an empty result.
193 ///
194 /// Defaults to `4096`.
195 pub disjunction_complexity_threshold: u16,
196
197 /// Maximum cumulative complexity during formula negation.
198 ///
199 /// Controls how complex the negation of a formula can become.
200 /// If exceeded, negation gives up to avoid exponential blowup.
201 ///
202 /// Defaults to `4096`.
203 pub negation_complexity_threshold: u16,
204
205 /// Upper limit for consensus optimization during saturation.
206 ///
207 /// Controls when the consensus rule is applied during saturation.
208 /// Only applies when clause count is between 3 and this limit.
209 ///
210 /// Defaults to `256`.
211 pub consensus_limit_threshold: u16,
212
213 /// Maximum logical formula size during conditional analysis.
214 ///
215 /// Limits the size of generated formulas to prevent exponential blowup
216 /// in deeply nested conditionals.
217 ///
218 /// Defaults to `512`.
219 pub formula_size_threshold: u16,
220}
221
222impl Default for Settings {
223 fn default() -> Self {
224 Self::new(PHPVersion::LATEST)
225 }
226}
227
228impl Settings {
229 #[must_use]
230 pub fn new(version: PHPVersion) -> Self {
231 let default_thresholds = AlgebraThresholds::default();
232
233 Self {
234 version,
235 find_unused_expressions: true,
236 find_unused_definitions: true,
237 analyze_dead_code: false,
238 memoize_properties: true,
239 allow_possibly_undefined_array_keys: true,
240 check_throws: false,
241 unchecked_exceptions: AtomSet::default(),
242 unchecked_exception_classes: AtomSet::default(),
243 use_colors: true,
244 check_missing_override: false,
245 find_unused_parameters: false,
246 strict_list_index_checks: false,
247 no_boolean_literal_comparison: false,
248 check_missing_type_hints: false,
249 check_closure_missing_type_hints: false,
250 check_arrow_function_missing_type_hints: false,
251 register_super_globals: true,
252 diff: false,
253 trust_existence_checks: true,
254 class_initializers: AtomSet::default(),
255 check_property_initialization: false,
256 check_use_statements: false,
257 saturation_complexity_threshold: default_thresholds.saturation_complexity,
258 disjunction_complexity_threshold: default_thresholds.disjunction_complexity,
259 negation_complexity_threshold: default_thresholds.negation_complexity,
260 consensus_limit_threshold: default_thresholds.consensus_limit,
261 formula_size_threshold: DEFAULT_FORMULA_SIZE_THRESHOLD,
262 }
263 }
264
265 /// Returns the algebra thresholds derived from the settings.
266 #[must_use]
267 pub fn algebra_thresholds(&self) -> AlgebraThresholds {
268 AlgebraThresholds {
269 saturation_complexity: self.saturation_complexity_threshold,
270 disjunction_complexity: self.disjunction_complexity_threshold,
271 negation_complexity: self.negation_complexity_threshold,
272 consensus_limit: self.consensus_limit_threshold,
273 }
274 }
275}