mir_analyzer/parser/
mod.rs1pub mod docblock;
2pub mod type_from_hint;
3
4use std::sync::Arc;
5
6use php_ast::Span;
7use php_rs_parser::parse;
8use thiserror::Error;
9
10pub use docblock::{DocblockParser, ParsedDocblock};
11pub use type_from_hint::type_from_hint;
12
13#[derive(Debug, Error)]
18pub enum ParseError {
19 #[error("PHP parse error in {file}: {message}")]
20 SyntaxError { file: Arc<str>, message: String },
21}
22
23pub struct ParsedFile<'arena, 'src> {
28 pub program: php_ast::ast::Program<'arena, 'src>,
29 pub errors: Vec<ParseError>,
30 pub file: Arc<str>,
31}
32
33pub struct FileParser {
38 pub arena: bumpalo::Bump,
39}
40
41impl FileParser {
42 pub fn new() -> Self {
43 Self {
44 arena: bumpalo::Bump::new(),
45 }
46 }
47
48 pub fn parse<'arena, 'src>(
52 &'arena self,
53 src: &'src str,
54 file: Arc<str>,
55 ) -> ParsedFile<'arena, 'src> {
56 let result = parse(&self.arena, src);
57 let errors = result
58 .errors
59 .iter()
60 .map(|e| ParseError::SyntaxError {
61 file: file.clone(),
62 message: e.to_string(),
63 })
64 .collect();
65
66 ParsedFile {
67 program: result.program,
68 errors,
69 file,
70 }
71 }
72}
73
74impl Default for FileParser {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80pub fn span_text(src: &str, span: Span) -> Option<String> {
86 if span.start >= span.end {
87 return None;
88 }
89 let s = span.start as usize;
90 let e = (span.end as usize).min(src.len());
91 src.get(s..e)
92 .map(|t| t.trim().to_string())
93 .filter(|t| !t.is_empty())
94}
95
96pub fn span_snippet(src: &str, span: Span) -> String {
98 let offset = span.start as usize;
99 let line_start = src[..offset].rfind('\n').map(|p| p + 1).unwrap_or(0);
100 let line_end = src[offset..]
101 .find('\n')
102 .map(|p| offset + p)
103 .unwrap_or(src.len());
104 src[line_start..line_end].to_string()
105}
106
107pub fn find_preceding_docblock(source: &str, offset: u32) -> Option<String> {
115 let offset = (offset as usize).min(source.len());
116 if offset == 0 {
117 return None;
118 }
119 let before = &source[..offset];
120 let trimmed = before.trim_end();
121 if !trimmed.ends_with("*/") {
122 return None;
123 }
124 let end = trimmed.rfind("*/")?;
125 let start = trimmed[..end].rfind("/**")?;
126 Some(trimmed[start..end + 2].to_string())
127}
128
129pub fn name_to_string(name: &php_ast::ast::Name<'_, '_>) -> String {
134 name.to_string_repr().into_owned()
135}