From e012ab29e69350ff1c4f5e81d3578be951c1a4eb Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 17 May 2026 01:50:32 +0900 Subject: feat(port): port ClassMap.php --- .../shirabe-class-map-generator/src/class_map.rs | 134 +++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 crates/shirabe-class-map-generator/src/class_map.rs (limited to 'crates/shirabe-class-map-generator') diff --git a/crates/shirabe-class-map-generator/src/class_map.rs b/crates/shirabe-class-map-generator/src/class_map.rs new file mode 100644 index 0000000..51d2bef --- /dev/null +++ b/crates/shirabe-class-map-generator/src/class_map.rs @@ -0,0 +1,134 @@ +//! ref: composer/vendor/composer/class-map-generator/src/ClassMap.php + +use indexmap::IndexMap; +use shirabe_php_shim::{Countable, InvalidArgumentException, OutOfBoundsException, rtrim, strpos, strtr}; +use shirabe_external_packages::composer::pcre::preg::Preg; + +#[derive(Debug, Clone)] +pub struct PsrViolationEntry { + pub warning: String, + pub class_name: String, +} + +#[derive(Debug)] +pub struct ClassMap { + pub map: IndexMap, + ambiguous_classes: IndexMap>, + psr_violations: IndexMap>, +} + +impl ClassMap { + pub fn new() -> Self { + ClassMap { + map: IndexMap::new(), + ambiguous_classes: IndexMap::new(), + psr_violations: IndexMap::new(), + } + } + + /// Returns the class map, which is a list of paths indexed by class name + pub fn get_map(&self) -> &IndexMap { + &self.map + } + + /// Returns warning strings containing details about PSR-0/4 violations that were detected + pub fn get_psr_violations(&self) -> Vec { + if self.psr_violations.is_empty() { + return vec![]; + } + + self.psr_violations + .values() + .flatten() + .map(|violation| violation.warning.clone()) + .collect() + } + + /// A map of class names to their list of ambiguous paths + /// + /// Pass `None` for `duplicates_filter` to disable filtering (equivalent to PHP's `false`). + /// Pass `Some(pattern)` for a regex pattern to filter out matching paths. + pub fn get_ambiguous_classes( + &self, + duplicates_filter: Option<&str>, + ) -> anyhow::Result>> { + let duplicates_filter = match duplicates_filter { + None => return Ok(self.ambiguous_classes.clone()), + Some(pattern) => pattern, + }; + + let mut ambiguous_classes: IndexMap> = IndexMap::new(); + for (class, paths) in &self.ambiguous_classes { + let paths: Vec = paths + .iter() + .filter(|path| { + !Preg::is_match(duplicates_filter, &strtr(path, "\\", "/")).unwrap_or(false) + }) + .cloned() + .collect(); + if !paths.is_empty() { + ambiguous_classes.insert(class.clone(), paths); + } + } + + Ok(ambiguous_classes) + } + + /// Sorts the class map alphabetically by class names + pub fn sort(&mut self) { + self.map.sort_keys(); + } + + pub fn add_class(&mut self, class_name: String, path: String) { + self.psr_violations.remove(&strtr(&path, "\\", "/")); + + self.map.insert(class_name, path); + } + + pub fn get_class_path(&self, class_name: &str) -> anyhow::Result<&str> { + match self.map.get(class_name) { + Some(path) => Ok(path.as_str()), + None => Err(anyhow::anyhow!(OutOfBoundsException { + message: format!("Class {} is not present in the map", class_name), + code: 0, + })), + } + } + + pub fn has_class(&self, class_name: &str) -> bool { + self.map.contains_key(class_name) + } + + pub fn add_psr_violation(&mut self, warning: String, class_name: String, path: String) { + let path = rtrim(&strtr(&path, "\\", "/"), Some("/")); + + self.psr_violations + .entry(path) + .or_default() + .push(PsrViolationEntry { warning, class_name }); + } + + pub fn clear_psr_violations_by_path(&mut self, path_prefix: &str) { + let path_prefix = rtrim(&strtr(path_prefix, "\\", "/"), Some("/")); + + self.psr_violations.retain(|path, _| { + path != &path_prefix + && strpos(path, &format!("{}/", path_prefix)) != Some(0) + }); + } + + pub fn add_ambiguous_class(&mut self, class_name: String, path: String) { + self.ambiguous_classes.entry(class_name).or_default().push(path); + } + + /// Get the raw psr violations + pub fn get_raw_psr_violations(&self) -> &IndexMap> { + &self.psr_violations + } +} + +impl Countable for ClassMap { + fn count(&self) -> i64 { + self.map.len() as i64 + } +} -- cgit v1.3.1