source-map-php 0.1.3

CLI-first PHP code search indexer for Laravel and Hyperf repositories
Documentation
mod hyperf;
mod laravel;

use std::fs;
use std::path::Path;

use anyhow::Result;
use regex::Regex;

use crate::Framework;
use crate::models::{RouteDoc, SchemaDoc, make_stable_id};
use crate::sanitizer::Sanitizer;

pub fn detect_framework(repo: &Path, requested: Framework, package_names: &[String]) -> Framework {
    if requested != Framework::Auto {
        return requested;
    }
    if package_names.iter().any(|name| name == "hyperf/hyperf")
        || repo.join("bin/hyperf.php").exists()
    {
        Framework::Hyperf
    } else if package_names.iter().any(|name| name == "laravel/framework")
        || repo.join("artisan").exists()
    {
        Framework::Laravel
    } else {
        Framework::Auto
    }
}

pub fn extract_routes(
    repo: &Path,
    repo_name: &str,
    framework: Framework,
    sanitizer: &Sanitizer,
) -> Result<Vec<RouteDoc>> {
    match framework {
        Framework::Laravel => laravel::extract_routes(repo, repo_name, sanitizer),
        Framework::Hyperf => hyperf::extract_routes(repo, repo_name, sanitizer),
        Framework::Auto => Ok(Vec::new()),
    }
}

pub fn extract_schema(repo: &Path, repo_name: &str) -> Result<Vec<SchemaDoc>> {
    let migration_roots = [repo.join("database/migrations"), repo.join("migrations")];
    let create_re = Regex::new(r#"Schema::create\(\s*['"]([^'"]+)['"]"#).unwrap();
    let table_re = Regex::new(r#"Schema::table\(\s*['"]([^'"]+)['"]"#).unwrap();
    let mut docs = Vec::new();
    for migrations in migration_roots {
        if !migrations.exists() {
            continue;
        }
        for entry in walkdir::WalkDir::new(&migrations)
            .into_iter()
            .filter_map(Result::ok)
            .filter(|entry| entry.file_type().is_file())
            .filter(|entry| entry.path().extension().and_then(|ext| ext.to_str()) == Some("php"))
        {
            let contents = fs::read_to_string(entry.path())?;
            for (idx, line) in contents.lines().enumerate() {
                let capture = create_re.captures(line).or_else(|| table_re.captures(line));
                if let Some(capture) = capture {
                    let table = capture.get(1).unwrap().as_str().to_string();
                    let operation = if line.contains("Schema::create") {
                        "create"
                    } else {
                        "table"
                    };
                    let path = entry
                        .path()
                        .strip_prefix(repo)
                        .unwrap()
                        .to_string_lossy()
                        .into_owned();
                    docs.push(SchemaDoc {
                        id: make_stable_id(&[repo_name, &path, &table, operation]),
                        repo: repo_name.to_string(),
                        migration: entry.file_name().to_string_lossy().into_owned(),
                        table: Some(table),
                        operation: operation.to_string(),
                        path,
                        line_start: idx + 1,
                    });
                }
            }
        }
    }
    Ok(docs)
}