source-map-php 0.1.3

CLI-first PHP code search indexer for Laravel and Hyperf repositories
Documentation
use regex::Regex;

use super::DeclarationCandidate;

pub fn extract_candidates(contents: &str) -> Vec<DeclarationCandidate> {
    let namespace_re = Regex::new(r"^\s*namespace\s+([^;]+);").unwrap();
    let class_re = Regex::new(
        r"^\s*(?:final\s+|abstract\s+)?(class|interface|trait|enum)\s+([A-Za-z_][A-Za-z0-9_]*)",
    )
    .unwrap();
    let function_re = Regex::new(
        r"^\s*(?:(public|protected|private)\s+)?(?:(static)\s+)?function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)",
    )
    .unwrap();

    let mut namespace = None;
    let mut current_class = None::<String>;
    let mut class_depth = 0usize;
    let mut brace_depth = 0usize;
    let mut out = Vec::new();

    for (idx, line) in contents.lines().enumerate() {
        if let Some(caps) = namespace_re.captures(line) {
            namespace = caps.get(1).map(|item| item.as_str().trim().to_string());
        }
        if let Some(caps) = class_re.captures(line) {
            let name = caps.get(2).unwrap().as_str().to_string();
            let kind = caps.get(1).unwrap().as_str().to_string();
            out.push(DeclarationCandidate {
                kind,
                name: name.clone(),
                owner_class: None,
                namespace: namespace.clone(),
                line_start: idx + 1,
                line_end: idx + 1,
                signature: Some(line.trim().to_string()),
                extraction_confidence: "fallback".to_string(),
                references_count: 0,
            });
            current_class = Some(name);
            class_depth = brace_depth + line.matches('{').count();
        } else if let Some(caps) = function_re.captures(line) {
            let name = caps.get(3).unwrap().as_str().to_string();
            let owner_class = current_class.clone();
            out.push(DeclarationCandidate {
                kind: if owner_class.is_some() {
                    "method".to_string()
                } else {
                    "function".to_string()
                },
                name,
                owner_class,
                namespace: namespace.clone(),
                line_start: idx + 1,
                line_end: idx + 1,
                signature: Some(line.trim().to_string()),
                extraction_confidence: "fallback".to_string(),
                references_count: 0,
            });
        }

        brace_depth += line.matches('{').count();
        brace_depth = brace_depth.saturating_sub(line.matches('}').count());
        if current_class.is_some() && brace_depth < class_depth {
            current_class = None;
            class_depth = 0;
        }
    }

    out
}