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}