diff --git a/src/composer.rs b/src/composer.rs index 2155fa1c..25e41984 100644 --- a/src/composer.rs +++ b/src/composer.rs @@ -21,7 +21,7 @@ //! Composer JSON parsing is delegated to the [`mago_composer`] crate, //! which provides typed Rust structs for the full `composer.json` schema. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fs; use std::path::{Path, PathBuf}; @@ -342,6 +342,54 @@ pub fn parse_autoload_files(workspace_root: &Path, vendor_dir: &str) -> Vec Vec { + let mut out = Vec::new(); + let mut scanned_dirs = HashSet::new(); + let mut seen_files = HashSet::new(); + + for file in autoload_files { + let Some(dir) = file.parent() else { + continue; + }; + if !scanned_dirs.insert(dir.to_path_buf()) { + continue; + } + let Ok(entries) = fs::read_dir(dir) else { + continue; + }; + for entry in entries.flatten() { + let path = entry.path(); + let Some(name) = path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + if name.ends_with("_global.php") && path.is_file() && seen_files.insert(path.clone()) { + out.push(path); + } + } + } + + out +} + // ── PSR-4 path abstraction ───────────────────────────────────────── // // `mago-composer` emits two structurally identical but nominally @@ -972,6 +1020,59 @@ mod tests { // ── detect_drupal_web_root ────────────────────────────────────── + #[test] + fn discovers_global_sibling_beside_autoload_file() { + // Mirrors CakePHP: an autoloaded `functions.php` sits next to a + // `functions_global.php` sibling that Composer never lists. + let dir = tempfile::tempdir().unwrap(); + let core = dir.path().join("Core"); + std::fs::create_dir_all(&core).unwrap(); + let autoloaded = core.join("functions.php"); + let sibling = core.join("functions_global.php"); + std::fs::write( + &autoloaded, + " = autoload_files; + file_queue.extend(sibling_globals); let mut visited: HashSet = HashSet::new(); while let Some(file_path) = file_queue.pop() {