diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-06-28 06:21:56 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-28 06:21:56 +0800 |
| commit | 0f84a88d0b2c9205ec1b3cbfa18ffe05478e5a64 (patch) | |
| tree | 0d5d3ddff999937599f82e7c0fd1b585d826c9e7 | |
| parent | da5e1a21fce9a303767af4a6d3cab8f0d66e5c87 (diff) | |
feat(mingling_pathf): add pattern analyzer module for struct detection
Add a `PatternAnalyzer` with an `AnalyzePattern` trait to detect and
extract struct declarations from Rust source files, supporting nested
inline modules.
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | mingling_pathf/src/lib.rs | 1 | ||||
| -rw-r--r-- | mingling_pathf/src/pattern_analyzer.rs | 140 | ||||
| -rw-r--r-- | mingling_pathf/src/patterns.rs | 5 | ||||
| -rw-r--r-- | mingling_pathf/src/patterns/basic_struct.rs | 51 | ||||
| -rw-r--r-- | mingling_pathf/test/src/lib.rs | 11 |
6 files changed, 206 insertions, 3 deletions
@@ -13,6 +13,7 @@ exclude = [ # Tests "mingling_core/tests/*", + "mingling_pathf/test" ] [workspace.dependencies] diff --git a/mingling_pathf/src/lib.rs b/mingling_pathf/src/lib.rs index 5e4921b..d8d81db 100644 --- a/mingling_pathf/src/lib.rs +++ b/mingling_pathf/src/lib.rs @@ -1,3 +1,4 @@ pub mod module_pathf; +pub mod pattern_analyzer; pub mod error; pub mod patterns; diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs new file mode 100644 index 0000000..cb98a5f --- /dev/null +++ b/mingling_pathf/src/pattern_analyzer.rs @@ -0,0 +1,140 @@ +use std::collections::HashSet; +use std::path::Path; + +use crate::error::MinglingPathfinderError; + +/// Top-level convenience function: creates a default PatternAnalyzer with all built-in patterns pre-registered +pub fn init() -> PatternAnalyzer { + let mut analyzer = PatternAnalyzer::new(); + macro_rules! __register { + ( $($pattern:ident),* $(,)? ) => { + $( + analyzer.add_pattern(crate::patterns::$pattern); + )* + }; + } + + __register![ + BasicStructPattern, + ]; + + analyzer +} + +/// A single analysis item representing a parseable importable/referenceable item from code +#[derive(Debug, Clone)] +pub struct AnalyzeItem { + /// The module path to which the item belongs, e.g. `"std::collections"`; empty string `""` if the item is in the root module + pub module: String, + /// The name of the item itself, e.g. `"HashMap"`, `"AnalyzeResult"`, etc. + pub item_name: String, +} + +/// Collection of analysis results +#[derive(Debug)] +pub struct AnalyzeResult { + items: Vec<AnalyzeItem>, +} + +impl AnalyzeResult { + /// Creates an empty `AnalyzeResult` instance + pub fn new() -> Self { + Self { items: Vec::new() } + } + + /// Formats all items into a set of strings in the format `"::{module_path}::{item_name}"` + pub fn into_formatted(self) -> HashSet<String> { + self.items + .into_iter() + .map(|item| { + if item.module.is_empty() { + format!("::{}", item.item_name) + } else { + format!("::{}::{}", item.module, item.item_name) + } + }) + .collect() + } +} + +impl Default for AnalyzeResult { + fn default() -> Self { + Self::new() + } +} + +/// Extension point trait — one independent implementation per syntax kind +pub trait AnalyzePattern { + /// Quickly determine whether the file content contains an analyzable item + fn contains(&self, content: &str) -> bool; + + /// Analyze the content and return all found AnalyzeItems + fn analyze(&self, content: &str) -> Vec<AnalyzeItem>; +} + +/// A pattern analyzer that registers and runs multiple `AnalyzePattern` instances to parse +/// referenceable items from code. +/// +/// Internally maintains a `Vec<Box<dyn AnalyzePattern>>` for dynamic dispatch, supporting +/// runtime registration of custom patterns. The `Default` derive provides an empty analyzer +/// instance. +/// +/// # Fields +/// - `patterns`: A list of registered analysis patterns, each responsible for detecting and +/// extracting a specific type of analyzable item (e.g., structs, functions). +#[derive(Default)] +pub struct PatternAnalyzer { + /// A list of registered analysis patterns, each responsible for detecting and extracting + /// a specific type of analyzable item. + patterns: Vec<Box<dyn AnalyzePattern>>, +} + +impl PatternAnalyzer { + /// Creates a new `PatternAnalyzer` instance. + /// + /// Internally calls `Default::default()` directly, initially containing no registered patterns. + pub fn new() -> Self { + Self::default() + } + + /// Registers an analysis pattern with the current analyzer. + /// + /// The pattern is wrapped in a `Box` and stored in the internal pattern list, + /// and will be used to scan and analyze file content in subsequent calls to `analyze_file`. + /// + /// # Parameters + /// - `pattern` —— A type instance implementing the `AnalyzePattern` trait, + /// responsible for detecting and extracting a specific syntactic structure (e.g., structs, functions). + pub fn add_pattern(&mut self, pattern: impl AnalyzePattern + 'static) { + self.patterns.push(Box::new(pattern)); + } + + /// Analyzes a single file and returns a formatted set of strings. + /// + /// This method reads the content of the file at the specified path, then uses each registered + /// pattern to analyze the content in turn. Only patterns whose `contains` method returns `true` + /// will trigger the subsequent `analyze` call. All extracted `AnalyzeItem`s are merged and + /// converted into a formatted `HashSet<String>`. + /// + /// # Parameters + /// - `path` —— The path to the file to analyze, supporting any type that implements `AsRef<Path>`. + /// + /// # Returns + /// - `Ok(HashSet<String>)` —— On success, returns a formatted set of strings, each in the form `"::module_path::item_name"`. + /// - `Err(MinglingPathfinderError)` —— If the file cannot be read, returns the corresponding I/O error wrapper. + pub fn analyze_file(&self, path: impl AsRef<Path>) -> Result<HashSet<String>, MinglingPathfinderError> { + let path = path.as_ref(); + let content = std::fs::read_to_string(path)?; + + let mut all_items = Vec::new(); + for pattern in &self.patterns { + if pattern.contains(&content) { + let items = pattern.analyze(&content); + all_items.extend(items); + } + } + + let result = AnalyzeResult { items: all_items }; + Ok(result.into_formatted()) + } +} diff --git a/mingling_pathf/src/patterns.rs b/mingling_pathf/src/patterns.rs index 9aa5502..33d3503 100644 --- a/mingling_pathf/src/patterns.rs +++ b/mingling_pathf/src/patterns.rs @@ -1,3 +1,2 @@ -pub trait AnalyzePattern { - -} +mod basic_struct; +pub use basic_struct::*; diff --git a/mingling_pathf/src/patterns/basic_struct.rs b/mingling_pathf/src/patterns/basic_struct.rs new file mode 100644 index 0000000..eeb665a --- /dev/null +++ b/mingling_pathf/src/patterns/basic_struct.rs @@ -0,0 +1,51 @@ +use syn::Item; + +use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern}; + +/// Basic struct pattern analyzer. +/// +/// Used to identify and analyze struct definitions (`struct`) in Rust source code. +/// Supports analyzing root-level structs as well as structs within inline modules. +pub struct BasicStructPattern; + +impl AnalyzePattern for BasicStructPattern { + fn contains(&self, content: &str) -> bool { + content.contains("struct") + } + + fn analyze(&self, content: &str) -> Vec<AnalyzeItem> { + let Ok(syntax) = syn::parse_file(content) else { + return Vec::new(); + }; + + let mut items = Vec::new(); + + for item in &syntax.items { + match item { + // Root-level struct + Item::Struct(s) => { + items.push(AnalyzeItem { + module: String::new(), + item_name: s.ident.to_string(), + }); + } + // Struct within inline modules + Item::Mod(item_mod) => { + if let Some((_, nested)) = &item_mod.content { + for n in nested { + if let syn::Item::Struct(s) = n { + items.push(AnalyzeItem { + module: item_mod.ident.to_string(), + item_name: s.ident.to_string(), + }); + } + } + } + } + _ => {} + } + } + + items + } +} diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs index 742e9d0..dbbd76d 100644 --- a/mingling_pathf/test/src/lib.rs +++ b/mingling_pathf/test/src/lib.rs @@ -37,3 +37,14 @@ fn test_module_pathf() { assert_eq!(mapping.get("src/use_all.rs").unwrap(), "crate"); assert_eq!(mapping.get("src/main.rs").unwrap(), "crate"); } + +#[test] +fn test_pattern_analyzer() { + let dir = current_dir().unwrap().join("test_proj"); + let mut analyzer = mingling_pathf::pattern_analyzer::init(); + analyzer.add_pattern(mingling_pathf::patterns::BasicStructPattern); + + let result = analyzer.analyze_file(dir.join("src/has_sub_mod.rs")).unwrap(); + + assert!(result.contains("::directly_sub_mod::DirectlySubModStruct")); +} |
