dgmod/
parser.rs

1//! Rust source file parsing using syn
2
3use std::fs;
4use std::path::Path;
5
6use syn::{
7    Attribute, Expr, ExprLit, Item, ItemMod, ItemUse, Lit, Meta, UseGroup, UseName, UsePath,
8    UseRename, UseTree,
9};
10
11use crate::ParseError;
12
13/// Parse a Rust source file
14///
15/// # Errors
16/// Returns `ParseError::Io` if the file cannot be read, or `ParseError::Syntax`
17/// if the file contains invalid Rust syntax.
18pub fn parse_file(path: &Path) -> Result<syn::File, ParseError> {
19    let content = fs::read_to_string(path).map_err(|error| ParseError::Io {
20        path: path.to_path_buf(),
21        error,
22    })?;
23    syn::parse_file(&content).map_err(|error| ParseError::Syntax {
24        path: path.to_path_buf(),
25        error,
26    })
27}
28
29/// Extract all mod declarations from a parsed file
30#[must_use]
31pub fn extract_mod_declarations(file: &syn::File) -> Vec<&ItemMod> {
32    file.items
33        .iter()
34        .filter_map(|item| {
35            if let Item::Mod(item_mod) = item {
36                Some(item_mod)
37            } else {
38                None
39            }
40        })
41        .collect()
42}
43
44/// Extract all use statements from a parsed file
45#[must_use]
46pub fn extract_use_statements(file: &syn::File) -> Vec<&ItemUse> {
47    file.items
48        .iter()
49        .filter_map(|item| {
50            if let Item::Use(item_use) = item {
51                Some(item_use)
52            } else {
53                None
54            }
55        })
56        .collect()
57}
58
59/// Get the `#[path = "..."]` attribute value if present
60#[must_use]
61pub fn get_path_attribute(item_mod: &ItemMod) -> Option<String> {
62    find_path_in_attrs(&item_mod.attrs)
63}
64
65/// Helper to find path attribute in a list of attributes
66fn find_path_in_attrs(attrs: &[Attribute]) -> Option<String> {
67    for attr in attrs {
68        if attr.path().is_ident("path") {
69            if let Meta::NameValue(nv) = &attr.meta {
70                if let Expr::Lit(ExprLit {
71                    lit: Lit::Str(s), ..
72                }) = &nv.value
73                {
74                    return Some(s.value());
75                }
76            }
77        }
78    }
79    None
80}
81
82/// Walk a `UseTree` and extract all import paths as vectors of segments
83pub fn walk_use_tree(
84    tree: &UseTree,
85    current_path: &mut Vec<String>,
86    results: &mut Vec<Vec<String>>,
87) {
88    match tree {
89        UseTree::Path(UsePath { ident, tree, .. }) => {
90            current_path.push(ident.to_string());
91            walk_use_tree(tree, current_path, results);
92            current_path.pop();
93        }
94        UseTree::Name(UseName { ident, .. }) | UseTree::Rename(UseRename { ident, .. }) => {
95            current_path.push(ident.to_string());
96            results.push(current_path.clone());
97            current_path.pop();
98        }
99        UseTree::Glob(_) => {
100            // For glob imports, we add edge to the module itself
101            results.push(current_path.clone());
102        }
103        UseTree::Group(UseGroup { items, .. }) => {
104            for item in items {
105                walk_use_tree(item, current_path, results);
106            }
107        }
108    }
109}
110
111/// Extract all import paths from a use statement
112#[must_use]
113pub fn extract_use_paths(item_use: &ItemUse) -> Vec<Vec<String>> {
114    let mut results = Vec::new();
115    let mut path = Vec::new();
116    walk_use_tree(&item_use.tree, &mut path, &mut results);
117    results
118}