1use rustc_hash::FxHashMap;
2
3pub(crate) mod analyzer_db;
4pub(crate) mod attributes;
5pub mod batch;
6pub(crate) mod body_analysis;
7#[doc(hidden)]
8pub mod cache;
9pub(crate) mod call;
10pub(crate) mod class;
11pub(crate) mod collector;
12pub(crate) mod contradiction;
13#[doc(hidden)]
14pub mod db;
15pub(crate) mod dead_code;
16pub(crate) mod diagnostics;
17pub(crate) mod expr;
18pub mod file_analyzer;
19pub(crate) mod flow_state;
20pub(crate) mod generic;
21pub mod indexing;
22#[doc(hidden)]
23pub mod metrics;
24pub(crate) mod narrowing;
25#[doc(hidden)]
26pub mod parse_cache;
27#[doc(hidden)]
28pub mod parser;
29pub mod php_version;
30pub mod prelude;
31pub mod session;
32pub mod source_provider;
33pub(crate) mod stmt;
34#[doc(hidden)]
35pub mod stub_cache;
36#[doc(hidden)]
37pub mod stubs;
38pub(crate) mod subtype;
39pub mod suppression;
40pub(crate) mod taint;
41pub(crate) mod type_env;
42pub(crate) mod util;
43
44pub use batch::{
45 analyze_source, dead_code_issue_kinds, discover_files, AnalysisResult, BatchOptions,
46};
47pub use file_analyzer::{BatchFileAnalyzer, FileAnalysis, FileAnalyzer, ParsedFile};
48pub use indexing::{IndexBatchOutcome, IndexCancel, IndexParallelism};
49pub use parser::type_from_hint::type_from_hint;
50pub use parser::{DocblockParser, ParsedDocblock};
51pub use php_version::{ParsePhpVersionError, PhpVersion};
52pub use session::AnalysisSession;
53pub use source_provider::{FsSourceProvider, SourceProvider};
54
55pub(crate) fn fqcn_case_mismatch(written: &str, canonical: &str) -> Option<(String, String)> {
59 let w = written.trim_start_matches('\\');
60 let c = canonical.trim_start_matches('\\');
61 if w == c || !w.eq_ignore_ascii_case(c) {
62 return None;
63 }
64 let w_last = w.rsplit('\\').next().unwrap_or(w);
65 let c_last = c.rsplit('\\').next().unwrap_or(c);
66 if w_last != c_last {
67 let w_prefix = w.rsplit_once('\\').map_or("", |(p, _)| p);
68 let c_prefix = c.rsplit_once('\\').map_or("", |(p, _)| p);
69 if w_prefix == c_prefix {
70 return Some((w_last.to_string(), c_last.to_string()));
71 }
72 }
73 Some((w.to_string(), c.to_string()))
74}
75pub use stubs::{
76 is_builtin_function, stub_files, stub_path_for_class, ChainedClassResolver, StubClassResolver,
77 StubVfs,
78};
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
101pub struct Position {
102 pub line: u32,
103 pub column: u32,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
108pub struct Range {
109 pub start: Position,
110 pub end: Position,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Hash)]
120pub enum Name {
121 Class(std::sync::Arc<str>),
123 Function(std::sync::Arc<str>),
125 Method {
127 class: std::sync::Arc<str>,
128 name: std::sync::Arc<str>,
129 },
130 Property {
132 class: std::sync::Arc<str>,
133 name: std::sync::Arc<str>,
134 },
135 ClassConstant {
137 class: std::sync::Arc<str>,
138 name: std::sync::Arc<str>,
139 },
140 GlobalConstant(std::sync::Arc<str>),
142}
143
144impl Name {
145 pub fn method(class: impl Into<std::sync::Arc<str>>, name: &str) -> Self {
148 Name::Method {
149 class: class.into(),
150 name: std::sync::Arc::from(name.to_ascii_lowercase()),
151 }
152 }
153
154 pub fn class(fqcn: impl Into<std::sync::Arc<str>>) -> Self {
156 Name::Class(fqcn.into())
157 }
158
159 pub fn function(fqn: impl Into<std::sync::Arc<str>>) -> Self {
161 Name::Function(fqn.into())
162 }
163
164 pub fn property(
166 class: impl Into<std::sync::Arc<str>>,
167 name: impl Into<std::sync::Arc<str>>,
168 ) -> Self {
169 Name::Property {
170 class: class.into(),
171 name: name.into(),
172 }
173 }
174
175 pub fn class_constant(
177 class: impl Into<std::sync::Arc<str>>,
178 name: impl Into<std::sync::Arc<str>>,
179 ) -> Self {
180 Name::ClassConstant {
181 class: class.into(),
182 name: name.into(),
183 }
184 }
185
186 pub fn global_constant(fqn: impl Into<std::sync::Arc<str>>) -> Self {
188 Name::GlobalConstant(fqn.into())
189 }
190
191 pub fn codebase_key(&self) -> String {
194 match self {
195 Name::Class(fqcn) => fqcn.to_string(),
196 Name::Function(fqn) => fqn.to_string(),
197 Name::Method { class, name } => format!("{class}::{name}"),
198 Name::Property { class, name } => format!("{class}::{name}"),
199 Name::ClassConstant { class, name } => format!("{class}::{name}"),
200 Name::GlobalConstant(fqn) => fqn.to_string(),
201 }
202 }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq)]
207pub enum SymbolLookupError {
208 NotFound,
210 NoSourceLocation,
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum LoadOutcome {
218 AlreadyLoaded,
220 Loaded,
223 NotResolvable,
227}
228
229impl LoadOutcome {
230 pub fn is_loaded(self) -> bool {
233 !matches!(self, LoadOutcome::NotResolvable)
234 }
235}
236
237pub trait ClassResolver: Send + Sync {
245 fn resolve(&self, fqcn: &str) -> Option<std::path::PathBuf>;
248}
249
250impl ClassResolver for composer::Psr4Map {
251 fn resolve(&self, fqcn: &str) -> Option<std::path::PathBuf> {
252 composer::Psr4Map::resolve(self, fqcn)
253 }
254}
255
256impl std::fmt::Display for SymbolLookupError {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 match self {
259 SymbolLookupError::NotFound => write!(f, "symbol not found"),
260 SymbolLookupError::NoSourceLocation => write!(f, "symbol has no source location"),
261 }
262 }
263}
264
265impl std::error::Error for SymbolLookupError {}
266
267#[derive(Debug, Clone)]
270pub struct HoverInfo {
271 pub ty: Type,
273 pub docstring: Option<String>,
275 pub definition: Option<mir_types::Location>,
277}
278
279#[derive(Debug, Clone)]
282pub struct DependencyGraph {
283 dependencies: FxHashMap<String, Vec<String>>,
285 dependents: FxHashMap<String, Vec<String>>,
287}
288
289impl DependencyGraph {
290 pub fn dependencies_of(&self, file: &str) -> &[String] {
292 self.dependencies
293 .get(file)
294 .map(|v| v.as_slice())
295 .unwrap_or(&[])
296 }
297
298 pub fn dependents_of(&self, file: &str) -> &[String] {
300 self.dependents
301 .get(file)
302 .map(|v| v.as_slice())
303 .unwrap_or(&[])
304 }
305
306 pub fn transitive_dependencies(&self, file: &str) -> Vec<String> {
308 let mut visited = rustc_hash::FxHashSet::default();
309 let mut queue = vec![file.to_string()];
310 let mut result = Vec::new();
311
312 while let Some(current) = queue.pop() {
313 if !visited.insert(current.clone()) {
314 continue;
315 }
316 for dep in self.dependencies_of(¤t) {
317 if !visited.contains(dep) {
318 queue.push(dep.clone());
319 result.push(dep.clone());
320 }
321 }
322 }
323 result
324 }
325
326 pub fn transitive_dependents(&self, file: &str) -> Vec<String> {
328 let mut visited = rustc_hash::FxHashSet::default();
329 let mut queue = vec![file.to_string()];
330 let mut result = Vec::new();
331
332 while let Some(current) = queue.pop() {
333 if !visited.insert(current.clone()) {
334 continue;
335 }
336 for dep in self.dependents_of(¤t) {
337 if !visited.contains(dep) {
338 queue.push(dep.clone());
339 result.push(dep.clone());
340 }
341 }
342 }
343 result
344 }
345}
346
347pub mod symbol;
348pub use mir_codebase::storage::{FnParam, TemplateParam, Visibility};
349pub use mir_issues::{Issue, IssueKind, Severity};
350pub use mir_types::Type;
351
352pub fn location_from_span(
362 span: php_ast::Span,
363 file: std::sync::Arc<str>,
364 source: &str,
365 source_map: &php_rs_parser::source_map::SourceMap,
366) -> mir_types::Location {
367 let (line, col_start) = diagnostics::offset_to_line_col(source, span.start, source_map);
368 let (line_end, col_end) = if span.start < span.end {
369 diagnostics::offset_to_line_col(source, span.end, source_map)
370 } else {
371 (line, col_start)
372 };
373 mir_types::Location {
374 file,
375 line,
376 line_end,
377 col_start,
378 col_end: col_end.max(col_start.saturating_add(1)),
379 }
380}
381pub use symbol::{DeclarationKind, DocumentSymbol, ReferenceKind, ResolvedSymbol};
382
383pub mod composer;
384pub use composer::{ComposerError, Psr4Map};
385pub use type_env::ScopeId;
386
387#[doc(hidden)]
388pub mod test_utils;