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 stub_cache;
26#[doc(hidden)]
27pub mod stubs;
28pub(crate) mod taint;
29pub(crate) mod type_env;
30
31pub use file_analyzer::{BatchFileAnalyzer, FileAnalysis, FileAnalyzer, ParsedFile};
32pub use parser::type_from_hint::type_from_hint;
33pub use parser::{DocblockParser, ParsedDocblock};
34pub use php_version::{ParsePhpVersionError, PhpVersion};
35pub use project::{AnalysisResult, ProjectAnalyzer};
36pub use session::AnalysisSession;
37pub use stubs::{is_builtin_function, stub_files, StubVfs};
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
61pub struct Position {
62 pub line: u32,
63 pub column: u32,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68pub struct Range {
69 pub start: Position,
70 pub end: Position,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Hash)]
80pub enum Symbol {
81 Class(std::sync::Arc<str>),
83 Function(std::sync::Arc<str>),
85 Method {
87 class: std::sync::Arc<str>,
88 name: std::sync::Arc<str>,
89 },
90 Property {
92 class: std::sync::Arc<str>,
93 name: std::sync::Arc<str>,
94 },
95 ClassConstant {
97 class: std::sync::Arc<str>,
98 name: std::sync::Arc<str>,
99 },
100 GlobalConstant(std::sync::Arc<str>),
102}
103
104impl Symbol {
105 pub fn method(class: impl Into<std::sync::Arc<str>>, name: &str) -> Self {
108 Symbol::Method {
109 class: class.into(),
110 name: std::sync::Arc::from(name.to_ascii_lowercase()),
111 }
112 }
113
114 pub fn class(fqcn: impl Into<std::sync::Arc<str>>) -> Self {
116 Symbol::Class(fqcn.into())
117 }
118
119 pub fn function(fqn: impl Into<std::sync::Arc<str>>) -> Self {
121 Symbol::Function(fqn.into())
122 }
123
124 pub fn property(
126 class: impl Into<std::sync::Arc<str>>,
127 name: impl Into<std::sync::Arc<str>>,
128 ) -> Self {
129 Symbol::Property {
130 class: class.into(),
131 name: name.into(),
132 }
133 }
134
135 pub fn class_constant(
137 class: impl Into<std::sync::Arc<str>>,
138 name: impl Into<std::sync::Arc<str>>,
139 ) -> Self {
140 Symbol::ClassConstant {
141 class: class.into(),
142 name: name.into(),
143 }
144 }
145
146 pub fn global_constant(fqn: impl Into<std::sync::Arc<str>>) -> Self {
148 Symbol::GlobalConstant(fqn.into())
149 }
150
151 pub fn codebase_key(&self) -> String {
154 match self {
155 Symbol::Class(fqcn) => fqcn.to_string(),
156 Symbol::Function(fqn) => fqn.to_string(),
157 Symbol::Method { class, name } => format!("{class}::{name}"),
158 Symbol::Property { class, name } => format!("{class}::{name}"),
159 Symbol::ClassConstant { class, name } => format!("{class}::{name}"),
160 Symbol::GlobalConstant(fqn) => fqn.to_string(),
161 }
162 }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
167pub enum SymbolLookupError {
168 NotFound,
170 NoSourceLocation,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum LazyLoadOutcome {
178 AlreadyLoaded,
180 Loaded,
183 NotResolvable,
187}
188
189pub trait ClassResolver: Send + Sync {
197 fn resolve(&self, fqcn: &str) -> Option<std::path::PathBuf>;
200}
201
202impl ClassResolver for composer::Psr4Map {
203 fn resolve(&self, fqcn: &str) -> Option<std::path::PathBuf> {
204 composer::Psr4Map::resolve(self, fqcn)
205 }
206}
207
208impl std::fmt::Display for SymbolLookupError {
209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 match self {
211 SymbolLookupError::NotFound => write!(f, "symbol not found"),
212 SymbolLookupError::NoSourceLocation => write!(f, "symbol has no source location"),
213 }
214 }
215}
216
217impl std::error::Error for SymbolLookupError {}
218
219#[derive(Debug, Clone)]
222pub struct HoverInfo {
223 pub ty: Type,
225 pub docstring: Option<String>,
227 pub definition: Option<mir_codebase::storage::Location>,
229}
230
231#[derive(Debug, Clone)]
234pub struct DependencyGraph {
235 dependencies: std::collections::HashMap<String, Vec<String>>,
237 dependents: std::collections::HashMap<String, Vec<String>>,
239}
240
241impl DependencyGraph {
242 pub fn dependencies_of(&self, file: &str) -> &[String] {
244 self.dependencies
245 .get(file)
246 .map(|v| v.as_slice())
247 .unwrap_or(&[])
248 }
249
250 pub fn dependents_of(&self, file: &str) -> &[String] {
252 self.dependents
253 .get(file)
254 .map(|v| v.as_slice())
255 .unwrap_or(&[])
256 }
257
258 pub fn transitive_dependencies(&self, file: &str) -> Vec<String> {
260 let mut visited = std::collections::HashSet::new();
261 let mut queue = vec![file.to_string()];
262 let mut result = Vec::new();
263
264 while let Some(current) = queue.pop() {
265 if !visited.insert(current.clone()) {
266 continue;
267 }
268 for dep in self.dependencies_of(¤t) {
269 if !visited.contains(dep) {
270 queue.push(dep.clone());
271 result.push(dep.clone());
272 }
273 }
274 }
275 result
276 }
277
278 pub fn transitive_dependents(&self, file: &str) -> Vec<String> {
280 let mut visited = std::collections::HashSet::new();
281 let mut queue = vec![file.to_string()];
282 let mut result = Vec::new();
283
284 while let Some(current) = queue.pop() {
285 if !visited.insert(current.clone()) {
286 continue;
287 }
288 for dep in self.dependents_of(¤t) {
289 if !visited.contains(dep) {
290 queue.push(dep.clone());
291 result.push(dep.clone());
292 }
293 }
294 }
295 result
296 }
297}
298
299pub mod symbol;
300pub use mir_codebase::storage::{FnParam, TemplateParam, Visibility};
301pub use mir_issues::{Issue, IssueKind, Location, Severity};
302pub use mir_types::Union as Type;
303
304pub fn location_from_span(
314 span: php_ast::Span,
315 file: std::sync::Arc<str>,
316 source: &str,
317 source_map: &php_rs_parser::source_map::SourceMap,
318) -> mir_codebase::storage::Location {
319 let (line, col_start) = diagnostics::offset_to_line_col(source, span.start, source_map);
320 let (line_end, col_end) = if span.start < span.end {
321 diagnostics::offset_to_line_col(source, span.end, source_map)
322 } else {
323 (line, col_start)
324 };
325 mir_codebase::storage::Location {
326 file,
327 line,
328 line_end,
329 col_start,
330 col_end: col_end.max(col_start.saturating_add(1)),
331 }
332}
333pub use symbol::{DocumentSymbol, DocumentSymbolKind, ResolvedSymbol, SymbolKind};
334
335pub mod composer;
336pub use composer::{ComposerError, Psr4Map};
337pub use type_env::ScopeId;
338
339#[doc(hidden)]
340pub mod test_utils;