1pub(crate) mod arena;
2#[doc(hidden)]
3pub mod cache;
4pub(crate) mod call;
5pub(crate) mod class;
6pub(crate) mod collector;
7pub(crate) mod context;
8#[doc(hidden)]
9pub mod db;
10pub(crate) mod dead_code;
11pub(crate) mod diagnostics;
12pub(crate) mod expr;
13pub mod file_analyzer;
14pub(crate) mod generic;
15pub(crate) mod narrowing;
16#[doc(hidden)]
17pub mod parser;
18pub(crate) mod pass2;
19pub mod php_version;
20pub mod project;
21pub mod session;
22pub(crate) mod shared_db;
23pub(crate) mod stmt;
24#[doc(hidden)]
25pub mod stubs;
26pub(crate) mod taint;
27pub(crate) mod type_env;
28
29pub use file_analyzer::{BatchFileAnalyzer, FileAnalysis, FileAnalyzer, ParsedFile};
30pub use parser::type_from_hint::type_from_hint;
31pub use parser::{DocblockParser, ParsedDocblock};
32pub use php_version::{ParsePhpVersionError, PhpVersion};
33pub use project::{AnalysisResult, ProjectAnalyzer};
34pub use session::AnalysisSession;
35pub use stubs::{is_builtin_function, stub_files, StubVfs};
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
59pub struct Position {
60 pub line: u32,
61 pub column: u32,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub struct Range {
67 pub start: Position,
68 pub end: Position,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Hash)]
78pub enum Symbol {
79 Class(std::sync::Arc<str>),
81 Function(std::sync::Arc<str>),
83 Method {
85 class: std::sync::Arc<str>,
86 name: std::sync::Arc<str>,
87 },
88 Property {
90 class: std::sync::Arc<str>,
91 name: std::sync::Arc<str>,
92 },
93 ClassConstant {
95 class: std::sync::Arc<str>,
96 name: std::sync::Arc<str>,
97 },
98 GlobalConstant(std::sync::Arc<str>),
100}
101
102impl Symbol {
103 pub fn method(class: impl Into<std::sync::Arc<str>>, name: &str) -> Self {
106 Symbol::Method {
107 class: class.into(),
108 name: std::sync::Arc::from(name.to_ascii_lowercase()),
109 }
110 }
111
112 pub fn class(fqcn: impl Into<std::sync::Arc<str>>) -> Self {
114 Symbol::Class(fqcn.into())
115 }
116
117 pub fn function(fqn: impl Into<std::sync::Arc<str>>) -> Self {
119 Symbol::Function(fqn.into())
120 }
121
122 pub fn property(
124 class: impl Into<std::sync::Arc<str>>,
125 name: impl Into<std::sync::Arc<str>>,
126 ) -> Self {
127 Symbol::Property {
128 class: class.into(),
129 name: name.into(),
130 }
131 }
132
133 pub fn class_constant(
135 class: impl Into<std::sync::Arc<str>>,
136 name: impl Into<std::sync::Arc<str>>,
137 ) -> Self {
138 Symbol::ClassConstant {
139 class: class.into(),
140 name: name.into(),
141 }
142 }
143
144 pub fn global_constant(fqn: impl Into<std::sync::Arc<str>>) -> Self {
146 Symbol::GlobalConstant(fqn.into())
147 }
148
149 pub fn codebase_key(&self) -> String {
152 match self {
153 Symbol::Class(fqcn) => fqcn.to_string(),
154 Symbol::Function(fqn) => fqn.to_string(),
155 Symbol::Method { class, name } => format!("{class}::{name}"),
156 Symbol::Property { class, name } => format!("{class}::{name}"),
157 Symbol::ClassConstant { class, name } => format!("{class}::{name}"),
158 Symbol::GlobalConstant(fqn) => fqn.to_string(),
159 }
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
165pub enum SymbolLookupError {
166 NotFound,
168 NoSourceLocation,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum LazyLoadOutcome {
176 AlreadyLoaded,
178 Loaded,
181 NotResolvable,
185}
186
187pub trait ClassResolver: Send + Sync {
195 fn resolve(&self, fqcn: &str) -> Option<std::path::PathBuf>;
198}
199
200impl ClassResolver for composer::Psr4Map {
201 fn resolve(&self, fqcn: &str) -> Option<std::path::PathBuf> {
202 composer::Psr4Map::resolve(self, fqcn)
203 }
204}
205
206impl std::fmt::Display for SymbolLookupError {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 match self {
209 SymbolLookupError::NotFound => write!(f, "symbol not found"),
210 SymbolLookupError::NoSourceLocation => write!(f, "symbol has no source location"),
211 }
212 }
213}
214
215impl std::error::Error for SymbolLookupError {}
216
217#[derive(Debug, Clone)]
220pub struct HoverInfo {
221 pub ty: Type,
223 pub docstring: Option<String>,
225 pub definition: Option<mir_codebase::storage::Location>,
227}
228
229#[derive(Debug, Clone)]
232pub struct DependencyGraph {
233 dependencies: std::collections::HashMap<String, Vec<String>>,
235 dependents: std::collections::HashMap<String, Vec<String>>,
237}
238
239impl DependencyGraph {
240 pub fn dependencies_of(&self, file: &str) -> &[String] {
242 self.dependencies
243 .get(file)
244 .map(|v| v.as_slice())
245 .unwrap_or(&[])
246 }
247
248 pub fn dependents_of(&self, file: &str) -> &[String] {
250 self.dependents
251 .get(file)
252 .map(|v| v.as_slice())
253 .unwrap_or(&[])
254 }
255
256 pub fn transitive_dependencies(&self, file: &str) -> Vec<String> {
258 let mut visited = std::collections::HashSet::new();
259 let mut queue = vec![file.to_string()];
260 let mut result = Vec::new();
261
262 while let Some(current) = queue.pop() {
263 if !visited.insert(current.clone()) {
264 continue;
265 }
266 for dep in self.dependencies_of(¤t) {
267 if !visited.contains(dep) {
268 queue.push(dep.clone());
269 result.push(dep.clone());
270 }
271 }
272 }
273 result
274 }
275
276 pub fn transitive_dependents(&self, file: &str) -> Vec<String> {
278 let mut visited = std::collections::HashSet::new();
279 let mut queue = vec![file.to_string()];
280 let mut result = Vec::new();
281
282 while let Some(current) = queue.pop() {
283 if !visited.insert(current.clone()) {
284 continue;
285 }
286 for dep in self.dependents_of(¤t) {
287 if !visited.contains(dep) {
288 queue.push(dep.clone());
289 result.push(dep.clone());
290 }
291 }
292 }
293 result
294 }
295}
296
297pub mod symbol;
298pub use mir_codebase::storage::{FnParam, TemplateParam, Visibility};
299pub use mir_issues::{Issue, IssueKind, Location, Severity};
300pub use mir_types::Union as Type;
301
302pub fn location_from_span(
312 span: php_ast::Span,
313 file: std::sync::Arc<str>,
314 source: &str,
315 source_map: &php_rs_parser::source_map::SourceMap,
316) -> mir_codebase::storage::Location {
317 let (line, col_start) = diagnostics::offset_to_line_col(source, span.start, source_map);
318 let (line_end, col_end) = if span.start < span.end {
319 diagnostics::offset_to_line_col(source, span.end, source_map)
320 } else {
321 (line, col_start)
322 };
323 mir_codebase::storage::Location {
324 file,
325 line,
326 line_end,
327 col_start,
328 col_end: col_end.max(col_start.saturating_add(1)),
329 }
330}
331pub use symbol::{DocumentSymbol, DocumentSymbolKind, ResolvedSymbol, SymbolKind};
332
333pub mod composer;
334pub use composer::{ComposerError, Psr4Map};
335pub use type_env::ScopeId;
336
337#[doc(hidden)]
338pub mod test_utils;