use std::collections::HashMap;
include!(concat!(env!("OUT_DIR"), "/stub_map_generated.rs"));
pub const STUBS_VERSION: &str = env!("PHPANTOM_STUBS_VERSION");
pub fn build_stub_class_index() -> HashMap<&'static str, &'static str> {
STUB_CLASS_MAP
.iter()
.map(|&(name, idx)| (name, STUB_FILES[idx]))
.collect()
}
pub fn build_stub_function_index() -> HashMap<&'static str, &'static str> {
STUB_FUNCTION_MAP
.iter()
.map(|&(name, idx)| (name, STUB_FILES[idx]))
.collect()
}
pub fn is_stub_function_removed(
source: &str,
func_name: &str,
php_version: crate::types::PhpVersion,
) -> bool {
if !source.contains("@removed") {
return false;
}
let short = func_name.rsplit('\\').next().unwrap_or(func_name);
let needle = format!("function {short}(");
let Some(func_pos) = source.find(&needle).or_else(|| {
let needle2 = format!("function {short} ");
source.find(&needle2)
}) else {
return false;
};
is_preceding_docblock_removed(source, func_pos, php_version)
}
pub fn is_stub_class_removed(
source: &str,
class_name: &str,
php_version: crate::types::PhpVersion,
) -> bool {
if !source.contains("@removed") {
return false;
}
let short = class_name.rsplit('\\').next().unwrap_or(class_name);
let candidates = [
format!("class {short}"),
format!("interface {short}"),
format!("trait {short}"),
];
let decl_pos = candidates
.iter()
.filter_map(|needle| {
source.find(needle.as_str()).and_then(|pos| {
let after = pos + needle.len();
if after >= source.len() {
return Some(pos);
}
let ch = source.as_bytes()[after];
if ch == b' ' || ch == b'\n' || ch == b'\r' || ch == b'{' || ch == b'\t' {
Some(pos)
} else {
None
}
})
})
.next();
let Some(pos) = decl_pos else {
return false;
};
is_preceding_docblock_removed(source, pos, php_version)
}
fn is_preceding_docblock_removed(
source: &str,
decl_pos: usize,
php_version: crate::types::PhpVersion,
) -> bool {
let before = &source[..decl_pos];
let Some(doc_end) = before.rfind("*/") else {
return false;
};
let between = &source[doc_end + 2..decl_pos];
if between.contains("function ") || between.contains("class ") || between.contains("interface ")
{
return false;
}
let Some(doc_start) = source[..doc_end].rfind("/**") else {
return false;
};
let docblock = &source[doc_start..doc_end + 2];
for line in docblock.lines() {
let trimmed = line.trim().trim_start_matches('*').trim();
let rest = if let Some(r) = trimmed.strip_prefix("@removed") {
r
} else {
continue;
};
let rest = rest.trim_start();
if rest.is_empty() {
continue;
}
if let Some(ver) = crate::types::PhpVersion::from_composer_constraint(rest)
&& php_version >= ver
{
return true;
}
}
false
}
pub fn build_stub_constant_index() -> HashMap<&'static str, &'static str> {
STUB_CONSTANT_MAP
.iter()
.map(|&(name, idx)| (name, STUB_FILES[idx]))
.collect()
}