#![allow(unused)]
#![cfg_attr(coverage_nightly, coverage(off))]
use crate::services::context::AstItem;
use std::path::{Path, PathBuf};
use syn::spanned::Spanned;
use syn::visit::Visit;
use syn::{ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, ItemTrait, ItemUse, Visibility};
pub struct EnhancedAstVisitor {
items: Vec<AstItem>,
file_path: PathBuf,
module_path: Vec<String>,
}
impl EnhancedAstVisitor {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn new(file_path: &Path) -> Self {
Self {
items: Vec::new(),
file_path: file_path.to_path_buf(),
module_path: vec![],
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn extract_items(mut self, syntax_tree: &syn::File) -> Vec<AstItem> {
self.visit_file(syntax_tree);
self.items
}
fn get_visibility(&self, vis: &Visibility) -> String {
match vis {
Visibility::Public(_) => "pub".to_string(),
Visibility::Restricted(r) => {
if r.path.is_ident("crate") {
"pub(crate)".to_string()
} else if r.path.is_ident("super") {
"pub(super)".to_string()
} else if r.path.is_ident("self") {
"pub(self)".to_string()
} else {
#[cfg(feature = "rust-ast")]
{
format!("pub(in {})", quote::quote!(#r.path))
}
#[cfg(not(feature = "rust-ast"))]
{
format!("pub(in {:?})", r.path)
}
}
}
Visibility::Inherited => "private".to_string(),
}
}
fn get_line_from_span_debug(&self, span_debug: &str) -> usize {
if let Some(line_start) = span_debug.find("line: ") {
let line_str = &span_debug[line_start + 6..];
if let Some(comma_pos) = line_str.find(',') {
if let Ok(line) = line_str[..comma_pos].parse::<usize>() {
return line;
}
}
}
self.items.len() + 1
}
fn get_line<S: Spanned>(&self, item: &S) -> usize {
let span = item.span();
let debug_str = format!("{span:?}");
self.get_line_from_span_debug(&debug_str)
}
fn get_qualified_name(&self, name: &str) -> String {
if self.module_path.is_empty() {
name.to_string()
} else {
format!("{}::{}", self.module_path.join("::"), name)
}
}
}
impl<'ast> Visit<'ast> for EnhancedAstVisitor {
fn visit_item_fn(&mut self, node: &'ast ItemFn) {
let name = self.get_qualified_name(&node.sig.ident.to_string());
let visibility = self.get_visibility(&node.vis);
let is_async = node.sig.asyncness.is_some();
let line = self.get_line(node);
self.items.push(AstItem::Function {
name,
visibility,
is_async,
line,
});
syn::visit::visit_item_fn(self, node);
}
fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
let name = self.get_qualified_name(&node.ident.to_string());
let visibility = self.get_visibility(&node.vis);
let fields_count = node.fields.len();
let line = self.get_line(node);
let mut derives = Vec::new();
for attr in &node.attrs {
if attr.path().is_ident("derive") {
if let Ok(syn::Meta::List(meta_list)) = attr.parse_args::<syn::Meta>() {
#[cfg(feature = "rust-ast")]
{
let tokens = quote::quote!(#meta_list);
let derive_str = tokens.to_string();
derives.push(derive_str);
}
#[cfg(not(feature = "rust-ast"))]
{
let derive_str = format!("{:?}", meta_list);
derives.push(derive_str);
}
}
}
}
self.items.push(AstItem::Struct {
name,
visibility,
fields_count,
derives,
line,
});
syn::visit::visit_item_struct(self, node);
}
fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
let name = self.get_qualified_name(&node.ident.to_string());
let visibility = self.get_visibility(&node.vis);
let variants_count = node.variants.len();
let line = self.get_line(node);
self.items.push(AstItem::Enum {
name,
visibility,
variants_count,
line,
});
syn::visit::visit_item_enum(self, node);
}
fn visit_item_trait(&mut self, node: &'ast ItemTrait) {
let name = self.get_qualified_name(&node.ident.to_string());
let visibility = self.get_visibility(&node.vis);
let line = self.get_line(node);
self.items.push(AstItem::Trait {
name,
visibility,
line,
});
syn::visit::visit_item_trait(self, node);
}
fn visit_item_impl(&mut self, node: &'ast ItemImpl) {
#[cfg(feature = "rust-ast")]
let type_name = quote::quote!(#node.self_ty).to_string();
#[cfg(not(feature = "rust-ast"))]
let type_name = format!("{:?}", node.self_ty);
#[cfg(feature = "rust-ast")]
let trait_name = node
.trait_
.as_ref()
.map(|(_, path, _)| quote::quote!(#path).to_string());
#[cfg(not(feature = "rust-ast"))]
let trait_name = node
.trait_
.as_ref()
.map(|(_, path, _)| format!("{:?}", path));
let line = self.get_line(node);
self.items.push(AstItem::Impl {
type_name,
trait_name,
line,
});
syn::visit::visit_item_impl(self, node);
}
fn visit_item_mod(&mut self, node: &'ast ItemMod) {
let name = node.ident.to_string();
let visibility = self.get_visibility(&node.vis);
let line = self.get_line(node);
self.items.push(AstItem::Module {
name: self.get_qualified_name(&name),
visibility,
line,
});
self.module_path.push(name);
syn::visit::visit_item_mod(self, node);
self.module_path.pop();
}
fn visit_item_use(&mut self, node: &'ast ItemUse) {
#[cfg(feature = "rust-ast")]
let path = quote::quote!(#node.tree).to_string();
#[cfg(not(feature = "rust-ast"))]
let path = format!("{:?}", node.tree);
let line = self.get_line(node);
self.items.push(AstItem::Use { path, line });
syn::visit::visit_item_use(self, node);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_real_function_names() {
let code = r#"
/// Calculate complexity.
pub fn calculate_complexity() -> u32 { 42 }
async fn process_data(input: &str) -> Result<String, Error> { Ok(input.to_string()) }
pub(crate) fn helper_function() {}
"#;
let syntax = syn::parse_file(code).unwrap();
let visitor = EnhancedAstVisitor::new(Path::new("test.rs"));
let items = visitor.extract_items(&syntax);
assert_eq!(items.len(), 3);
match &items[0] {
AstItem::Function {
name,
visibility,
is_async,
..
} => {
assert_eq!(name, "calculate_complexity");
assert_eq!(visibility, "pub");
assert!(!is_async);
}
_ => panic!("Expected Function"),
}
match &items[1] {
AstItem::Function {
name,
visibility,
is_async,
..
} => {
assert_eq!(name, "process_data");
assert_eq!(visibility, "private");
assert!(is_async);
}
_ => panic!("Expected Function"),
}
}
#[test]
fn test_preserve_module_hierarchy() {
let code = r#"
mod services {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
/// Service function.
pub fn service_function() {}
mod internal {
fn internal_helper() {}
}
}
"#;
let syntax = syn::parse_file(code).unwrap();
let visitor = EnhancedAstVisitor::new(Path::new("test.rs"));
let items = visitor.extract_items(&syntax);
let service_fn = items.iter().find(|item| {
matches!(item, AstItem::Function { name, .. } if name.contains("service_function"))
});
assert!(service_fn.is_some());
if let Some(AstItem::Function { name, .. }) = service_fn {
assert_eq!(name, "services::service_function");
}
}
#[test]
fn test_extract_struct_details() {
let code = r#"
#[derive(Debug, Clone)]
/// Configuration.
pub struct Configuration {
pub name: String,
value: u32,
internal: bool,
}
"#;
let syntax = syn::parse_file(code).unwrap();
let visitor = EnhancedAstVisitor::new(Path::new("test.rs"));
let items = visitor.extract_items(&syntax);
assert_eq!(items.len(), 1);
match &items[0] {
AstItem::Struct {
name,
visibility,
fields_count,
..
} => {
assert_eq!(name, "Configuration");
assert_eq!(visibility, "pub");
assert_eq!(*fields_count, 3);
}
_ => panic!("Expected Struct"),
}
}
#[test]
fn test_visibility_modifiers() {
let code = r#"
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
/// Public fn.
pub fn public_fn() {}
pub(crate) fn crate_fn() {}
pub(super) fn super_fn() {}
fn private_fn() {}
"#;
let syntax = syn::parse_file(code).unwrap();
let visitor = EnhancedAstVisitor::new(Path::new("test.rs"));
let items = visitor.extract_items(&syntax);
let visibilities: Vec<String> = items
.iter()
.filter_map(|item| match item {
AstItem::Function { visibility, .. } => Some(visibility.clone()),
_ => None,
})
.collect();
assert_eq!(
visibilities,
vec!["pub", "pub(crate)", "pub(super)", "private"]
);
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn visitor_produces_valid_items(seed in 0u64..1000) {
let code = generate_test_code(seed);
if let Ok(syntax) = syn::parse_file(&code) {
let visitor = EnhancedAstVisitor::new(Path::new("test.rs"));
let items = visitor.extract_items(&syntax);
for item in &items {
match item {
AstItem::Function { name, .. } |
AstItem::Struct { name, .. } |
AstItem::Enum { name, .. } |
AstItem::Trait { name, .. } |
AstItem::Module { name, .. } => {
prop_assert!(!name.is_empty());
}
AstItem::Impl { type_name, .. } => {
prop_assert!(!type_name.is_empty());
}
AstItem::Use { path, .. } => {
prop_assert!(!path.is_empty());
}
_ => {}
}
}
}
}
#[test]
fn qualified_names_preserve_hierarchy(module_depth in 0usize..5) {
let code = generate_nested_modules(module_depth);
if let Ok(syntax) = syn::parse_file(&code) {
let visitor = EnhancedAstVisitor::new(Path::new("test.rs"));
let items = visitor.extract_items(&syntax);
for item in &items {
if let AstItem::Function { name, .. } = item {
let separators = name.matches("::").count();
prop_assert!(separators <= module_depth);
}
}
}
}
}
fn generate_test_code(seed: u64) -> String {
let fn_count = (seed % 5) + 1;
let mut code = String::new();
for i in 0..fn_count {
code.push_str(&format!("fn function_{}() {{}}\n", i));
}
if seed % 3 == 0 {
code.push_str("pub struct TestStruct { field: u32 }\n");
}
if seed % 5 == 0 {
code.push_str("enum TestEnum { Variant1, Variant2 }\n");
}
code
}
fn generate_nested_modules(depth: usize) -> String {
let mut code = String::new();
let mut indent = String::new();
for i in 0..depth {
code.push_str(&format!("{}mod level_{} {{\n", indent, i));
indent.push_str(" ");
}
code.push_str(&format!("{}fn nested_function() {{}}\n", indent));
for _ in 0..depth {
indent.truncate(indent.len().saturating_sub(4));
code.push_str(&format!("{}}}\n", indent));
}
code
}
}