use vala::{
walk, Class, CodeContext, Constant, Enum, EnumValue, ErrorCode, ErrorDomain, Method, Namespace,
Parser, SourceFile, SourceFileType, Visitor, Walker,
};
#[derive(Default)]
struct Collector {
classes: Vec<String>,
methods: Vec<(String, i32)>, }
impl Visitor for Collector {
fn visit_class(&mut self, w: &Walker, node: &Class) {
if let Some(name) = node.name() {
self.classes.push(name);
}
w.walk_children(node);
}
fn visit_method(&mut self, w: &Walker, node: &Method) {
let full = node.full_name().unwrap_or_default();
let line = node.source_reference().map(|r| r.begin().line).unwrap_or(0);
self.methods.push((full, line));
w.walk_children(node);
}
}
fn with_parsed(src: &str, f: impl FnOnce(&CodeContext)) {
let ctx = CodeContext::new();
ctx.with_current(|ctx| {
let file = SourceFile::new(ctx, SourceFileType::Source, "t.vala", Some(src));
ctx.add_source_file(&file);
Parser::new().parse(ctx);
assert_eq!(ctx.report().errors(), 0, "test source should parse cleanly");
f(ctx);
});
}
#[test]
fn visitor_collects_classes_and_methods() {
let src = "\
namespace Demo {
public class Widget {
public void draw () {}
public int width () { return 1; }
}
}
";
with_parsed(src, |ctx| {
let mut collector = Collector::default();
walk(&mut collector, &ctx.root());
assert_eq!(collector.classes, vec!["Widget".to_string()]);
let names: Vec<&str> = collector.methods.iter().map(|(n, _)| n.as_str()).collect();
assert_eq!(names, vec!["Demo.Widget.draw", "Demo.Widget.width"]);
let draw_line = collector
.methods
.iter()
.find(|(n, _)| n == "Demo.Widget.draw")
.map(|(_, l)| *l);
assert_eq!(draw_line, Some(3));
});
}
#[test]
fn container_getters_walk_the_tree() {
let src = "\
namespace Outer {
public class A {
public void m () {}
}
public class B {}
}
";
with_parsed(src, |ctx| {
let root: Namespace = ctx.root();
let outer = root
.namespaces()
.iter()
.find(|ns| ns.name().as_deref() == Some("Outer"))
.expect("Outer namespace");
let class_names: Vec<String> = outer.classes().iter().filter_map(|c| c.name()).collect();
assert_eq!(class_names, vec!["A".to_string(), "B".to_string()]);
let a = outer
.classes()
.iter()
.find(|c| c.name().as_deref() == Some("A"))
.expect("class A");
let method_names: Vec<String> = a.methods().iter().filter_map(|m| m.name()).collect();
assert!(
method_names.contains(&"m".to_string()),
"expected user method m, got {method_names:?}"
);
});
}
#[derive(Default)]
struct MemberCollector {
enums: Vec<String>,
enum_values: Vec<(String, i32)>, error_domains: Vec<String>,
error_codes: Vec<String>,
constants: Vec<String>,
}
impl Visitor for MemberCollector {
fn visit_enum(&mut self, w: &Walker, node: &Enum) {
self.enums.extend(node.name());
w.walk_children(node);
}
fn visit_enum_value(&mut self, w: &Walker, node: &EnumValue) {
if let Some(name) = node.name() {
let line = node.source_reference().map(|r| r.begin().line).unwrap_or(0);
self.enum_values.push((name, line));
}
w.walk_children(node);
}
fn visit_error_domain(&mut self, w: &Walker, node: &ErrorDomain) {
self.error_domains.extend(node.name());
w.walk_children(node);
}
fn visit_error_code(&mut self, w: &Walker, node: &ErrorCode) {
self.error_codes.extend(node.name());
w.walk_children(node);
}
fn visit_constant(&mut self, w: &Walker, node: &Constant) {
self.constants.extend(node.name());
w.walk_children(node);
}
}
#[test]
fn visitor_reaches_enum_values_and_error_codes() {
let src = "\
public enum Direction {
NORTH,
SOUTH
}
public errordomain MyError {
BAD,
WORSE
}
public class Holder {
public const int LIMIT = 10;
}
";
with_parsed(src, |ctx| {
let mut c = MemberCollector::default();
walk(&mut c, &ctx.root());
assert_eq!(c.enums, vec!["Direction".to_string()]);
assert_eq!(c.error_domains, vec!["MyError".to_string()]);
let value_names: Vec<&str> = c.enum_values.iter().map(|(n, _)| n.as_str()).collect();
assert_eq!(value_names, vec!["NORTH", "SOUTH"]);
assert_eq!(c.error_codes, vec!["BAD".to_string(), "WORSE".to_string()]);
assert_eq!(c.enum_values[0].1, 2);
assert_eq!(c.constants, vec!["LIMIT".to_string()]);
});
}
#[test]
fn pruning_subtree_skips_children() {
let src = "\
namespace N {
public class C {
public void hidden () {}
}
}
";
struct Pruner {
saw_method: bool,
}
impl Visitor for Pruner {
fn visit_class(&mut self, _w: &Walker, _node: &Class) {
}
fn visit_method(&mut self, _w: &Walker, _node: &Method) {
self.saw_method = true;
}
}
with_parsed(src, |ctx| {
let mut pruner = Pruner { saw_method: false };
walk(&mut pruner, &ctx.root());
assert!(!pruner.saw_method, "pruned class body should not be walked");
});
}