use perl_ast::ast::{Node, NodeKind, SourceLocation};
fn loc() -> SourceLocation {
SourceLocation { start: 0, end: 1 }
}
fn leaf(name: &str) -> Node {
Node::new(NodeKind::Identifier { name: name.to_string() }, loc())
}
fn num(v: &str) -> Node {
Node::new(NodeKind::Number { value: v.to_string() }, loc())
}
fn block_of(stmts: Vec<Node>) -> Node {
Node::new(NodeKind::Block { statements: stmts }, loc())
}
fn var(sigil: &str, name: &str) -> Node {
Node::new(NodeKind::Variable { sigil: sigil.to_string(), name: name.to_string() }, loc())
}
fn count_visits_mut(node: &mut Node) -> usize {
let mut count = 0_usize;
node.for_each_child_mut(|_child| count += 1);
count
}
mod for_each_child_mut {
use super::*;
#[test]
fn tie_visits_variable_package_and_args() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Tie {
variable: Box::new(leaf("var")),
package: Box::new(leaf("pkg")),
args: vec![leaf("a"), leaf("b")],
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 4);
Ok(())
}
#[test]
fn tie_without_args_visits_two_children() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Tie {
variable: Box::new(leaf("var")),
package: Box::new(leaf("pkg")),
args: vec![],
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 2);
Ok(())
}
#[test]
fn variable_declaration_with_initializer_visits_two() -> Result<(), Box<dyn std::error::Error>>
{
let mut node = Node::new(
NodeKind::VariableDeclaration {
declarator: "my".to_string(),
variable: Box::new(var("$", "x")),
attributes: vec![],
initializer: Some(Box::new(num("42"))),
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 2);
Ok(())
}
#[test]
fn variable_declaration_without_initializer_visits_one()
-> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::VariableDeclaration {
declarator: "my".to_string(),
variable: Box::new(var("$", "y")),
attributes: vec![],
initializer: None,
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn unary_visits_operand() -> Result<(), Box<dyn std::error::Error>> {
let mut node =
Node::new(NodeKind::Unary { op: "!".to_string(), operand: Box::new(num("1")) }, loc());
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn block_visits_all_statements() -> Result<(), Box<dyn std::error::Error>> {
let mut node = block_of(vec![num("1"), num("2"), num("3")]);
assert_eq!(count_visits_mut(&mut node), 3);
Ok(())
}
#[test]
fn empty_block_visits_nothing() -> Result<(), Box<dyn std::error::Error>> {
let mut node = block_of(vec![]);
assert_eq!(count_visits_mut(&mut node), 0);
Ok(())
}
#[test]
fn for_loop_visits_all_optional_and_body() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::For {
init: Some(Box::new(num("0"))),
condition: Some(Box::new(num("1"))),
update: Some(Box::new(num("2"))),
body: Box::new(block_of(vec![])),
continue_block: Some(Box::new(block_of(vec![]))),
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 5);
Ok(())
}
#[test]
fn for_loop_without_optionals_visits_body_only() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::For {
init: None,
condition: None,
update: None,
body: Box::new(block_of(vec![])),
continue_block: None,
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn foreach_with_continue_visits_four() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Foreach {
variable: Box::new(var("$", "item")),
list: Box::new(leaf("list")),
body: Box::new(block_of(vec![])),
continue_block: Some(Box::new(block_of(vec![]))),
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 4);
Ok(())
}
#[test]
fn foreach_without_continue_visits_three() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Foreach {
variable: Box::new(var("$", "item")),
list: Box::new(leaf("list")),
body: Box::new(block_of(vec![])),
continue_block: None,
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 3);
Ok(())
}
#[test]
fn defer_visits_block() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(NodeKind::Defer { block: Box::new(block_of(vec![])) }, loc());
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn try_with_catch_and_finally_visits_all() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Try {
body: Box::new(block_of(vec![])),
catch_blocks: vec![
(Some("$e".to_string()), Box::new(block_of(vec![]))),
(None, Box::new(block_of(vec![]))),
],
finally_block: Some(Box::new(block_of(vec![]))),
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 4);
Ok(())
}
#[test]
fn try_without_catch_or_finally_visits_body_only() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Try {
body: Box::new(block_of(vec![])),
catch_blocks: vec![],
finally_block: None,
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn method_call_visits_object_and_args() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::MethodCall {
object: Box::new(leaf("obj")),
method: "run".to_string(),
args: vec![num("1"), num("2")],
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 3);
Ok(())
}
#[test]
fn method_call_without_args_visits_object_only() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::MethodCall {
object: Box::new(leaf("obj")),
method: "run".to_string(),
args: vec![],
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn subroutine_with_prototype_signature_body_visits_three()
-> Result<(), Box<dyn std::error::Error>> {
let proto = Node::new(NodeKind::Prototype { content: "$$".to_string() }, loc());
let sig = Node::new(NodeKind::Signature { parameters: vec![] }, loc());
let mut node = Node::new(
NodeKind::Subroutine {
name: Some("foo".to_string()),
name_span: None,
declarator: None,
prototype: Some(Box::new(proto)),
signature: Some(Box::new(sig)),
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 3);
Ok(())
}
#[test]
fn subroutine_body_only_visits_one() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Subroutine {
name: None,
name_span: None,
declarator: None,
prototype: None,
signature: None,
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn goto_visits_target() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(NodeKind::Goto { target: Box::new(leaf("LABEL")) }, loc());
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn signature_visits_each_parameter() -> Result<(), Box<dyn std::error::Error>> {
let p1 =
Node::new(NodeKind::MandatoryParameter { variable: Box::new(var("$", "a")) }, loc());
let p2 =
Node::new(NodeKind::MandatoryParameter { variable: Box::new(var("$", "b")) }, loc());
let mut node = Node::new(NodeKind::Signature { parameters: vec![p1, p2] }, loc());
assert_eq!(count_visits_mut(&mut node), 2);
Ok(())
}
#[test]
fn hash_literal_visits_all_pairs() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::HashLiteral { pairs: vec![(leaf("k1"), num("1")), (leaf("k2"), num("2"))] },
loc(),
);
assert_eq!(count_visits_mut(&mut node), 4);
Ok(())
}
#[test]
fn error_with_partial_visits_partial() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Error {
message: "oops".to_string(),
expected: vec![],
found: None,
partial: Some(Box::new(num("0"))),
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn error_without_partial_visits_nothing() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Error {
message: "oops".to_string(),
expected: vec![],
found: None,
partial: None,
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 0);
Ok(())
}
#[test]
fn leaf_variants_visit_nothing() -> Result<(), Box<dyn std::error::Error>> {
let leaf_nodes: Vec<Node> = vec![
Node::new(NodeKind::Diamond, loc()),
Node::new(NodeKind::Ellipsis, loc()),
Node::new(NodeKind::Undef, loc()),
Node::new(NodeKind::MissingExpression, loc()),
Node::new(NodeKind::MissingStatement, loc()),
Node::new(NodeKind::MissingIdentifier, loc()),
Node::new(NodeKind::MissingBlock, loc()),
Node::new(NodeKind::UnknownRest, loc()),
Node::new(NodeKind::Readline { filehandle: None }, loc()),
Node::new(NodeKind::Glob { pattern: "*.pl".to_string() }, loc()),
Node::new(NodeKind::Typeglob { name: "main::foo".to_string() }, loc()),
];
for mut n in leaf_nodes {
let name = n.kind.kind_name();
assert_eq!(count_visits_mut(&mut n), 0, "{name} should have no children");
}
Ok(())
}
#[test]
fn for_each_child_mut_can_replace_node_values() -> Result<(), Box<dyn std::error::Error>> {
let mut prog = Node::new(NodeKind::Program { statements: vec![num("0"), num("0")] }, loc());
prog.for_each_child_mut(|child| {
if let NodeKind::Number { value } = &mut child.kind {
*value = "99".to_string();
}
});
if let NodeKind::Program { statements } = &prog.kind {
for stmt in statements {
assert_eq!(
stmt.kind,
NodeKind::Number { value: "99".to_string() },
"mutation should persist"
);
}
}
Ok(())
}
}
mod to_sexp_edges {
use super::*;
#[test]
fn defer_to_sexp() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Defer { block: Box::new(block_of(vec![])) }, loc());
let s = node.to_sexp();
assert!(s.contains("defer"), "expected 'defer' in sexp, got: {s}");
Ok(())
}
#[test]
fn named_subroutine_with_signature_but_no_prototype() -> Result<(), Box<dyn std::error::Error>>
{
let sig = Node::new(
NodeKind::Signature {
parameters: vec![Node::new(
NodeKind::MandatoryParameter { variable: Box::new(var("$", "x")) },
loc(),
)],
},
loc(),
);
let node = Node::new(
NodeKind::Subroutine {
name: Some("myfunc".to_string()),
name_span: None,
declarator: None,
prototype: None,
signature: Some(Box::new(sig)),
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("sub"), "expected 'sub' in sexp, got: {s}");
assert!(s.contains("myfunc"), "expected sub name in sexp, got: {s}");
assert!(s.contains("()"), "expected empty proto marker in sexp, got: {s}");
Ok(())
}
#[test]
fn anonymous_subroutine_with_prototype() -> Result<(), Box<dyn std::error::Error>> {
let proto = Node::new(NodeKind::Prototype { content: "$".to_string() }, loc());
let node = Node::new(
NodeKind::Subroutine {
name: None,
name_span: None,
declarator: None,
prototype: Some(Box::new(proto)),
signature: None,
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("anonymous_subroutine_expression"), "got: {s}");
Ok(())
}
#[test]
fn anonymous_subroutine_with_signature() -> Result<(), Box<dyn std::error::Error>> {
let sig = Node::new(NodeKind::Signature { parameters: vec![] }, loc());
let node = Node::new(
NodeKind::Subroutine {
name: None,
name_span: None,
declarator: None,
prototype: None,
signature: Some(Box::new(sig)),
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("anonymous_subroutine_expression"), "got: {s}");
Ok(())
}
#[test]
fn method_with_non_block_body() -> Result<(), Box<dyn std::error::Error>> {
let body = leaf("not_a_block");
let node = Node::new(
NodeKind::Method {
name: "do_thing".to_string(),
name_span: None,
signature: None,
attributes: vec![],
body: Box::new(body),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("method_declaration_statement"), "got: {s}");
Ok(())
}
#[test]
fn method_with_signature() -> Result<(), Box<dyn std::error::Error>> {
let sig = Node::new(NodeKind::Signature { parameters: vec![] }, loc());
let node = Node::new(
NodeKind::Method {
name: "foo".to_string(),
name_span: None,
signature: Some(Box::new(sig)),
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("method_declaration_statement"), "got: {s}");
assert!(s.contains("signature"), "expected signature in sexp, got: {s}");
Ok(())
}
#[test]
fn method_without_attributes_emits_no_attrlist() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Method {
name: "bare".to_string(),
name_span: None,
signature: None,
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("method_declaration_statement"), "got: {s}");
assert!(!s.contains("attrlist"), "empty attrs should produce no attrlist, got: {s}");
Ok(())
}
#[test]
fn class_with_parents() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Class {
name: "Dog".to_string(),
name_span: None,
parents: vec!["Animal".to_string(), "Speakable".to_string()],
body: Box::new(block_of(vec![])),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("class"), "got: {s}");
assert!(s.contains(":isa("), "expected :isa() in sexp, got: {s}");
assert!(s.contains("Animal"), "got: {s}");
Ok(())
}
#[test]
fn binary_operator_brace_subscript() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Binary {
op: "{}".to_string(),
left: Box::new(var("$", "h")),
right: Box::new(leaf("key")),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("binary_{}"), "expected binary_{{}} in sexp, got: {s}");
Ok(())
}
#[test]
fn binary_operator_bracket_subscript() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Binary {
op: "[]".to_string(),
left: Box::new(var("@", "arr")),
right: Box::new(num("0")),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("binary_[]"), "expected binary_[] in sexp, got: {s}");
Ok(())
}
#[test]
fn binary_operator_arrow_hash_deref() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Binary {
op: "->{}".to_string(),
left: Box::new(var("$", "ref")),
right: Box::new(leaf("key")),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("arrow_hash_deref"), "expected arrow_hash_deref in sexp, got: {s}");
Ok(())
}
#[test]
fn binary_operator_arrow_array_deref() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Binary {
op: "->[]".to_string(),
left: Box::new(var("$", "ref")),
right: Box::new(num("0")),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("arrow_array_deref"), "expected arrow_array_deref in sexp, got: {s}");
Ok(())
}
#[test]
fn binary_operator_unknown_falls_through_to_default() -> Result<(), Box<dyn std::error::Error>>
{
let node = Node::new(
NodeKind::Binary {
op: "custom_op".to_string(),
left: Box::new(num("1")),
right: Box::new(num("2")),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("binary_custom_op"), "expected binary_custom_op in sexp, got: {s}");
Ok(())
}
#[test]
fn unary_operator_unknown_falls_through_to_default() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Unary { op: "custom_unary".to_string(), operand: Box::new(num("1")) },
loc(),
);
let s = node.to_sexp();
assert!(s.contains("unary_custom_unary"), "expected unary_custom_unary in sexp, got: {s}");
Ok(())
}
#[test]
fn unary_operator_with_spaces_replaces_space_with_underscore()
-> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Unary { op: "my op".to_string(), operand: Box::new(num("1")) },
loc(),
);
let s = node.to_sexp();
assert!(s.contains("unary_my_op"), "expected space->underscore, got: {s}");
Ok(())
}
}
mod to_sexp_inner_edges {
use super::*;
#[test]
fn expression_statement_wrapping_named_subroutine_is_unwrapped()
-> Result<(), Box<dyn std::error::Error>> {
let sub_node = Node::new(
NodeKind::Subroutine {
name: Some("named_func".to_string()),
name_span: None,
declarator: None,
prototype: None,
signature: None,
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
let expr_stmt =
Node::new(NodeKind::ExpressionStatement { expression: Box::new(sub_node) }, loc());
let inner = expr_stmt.to_sexp_inner();
let outer = expr_stmt.to_sexp();
assert!(inner.contains("sub"), "inner should contain 'sub', got: {inner}");
assert!(inner.contains("named_func"), "inner should contain sub name, got: {inner}");
assert!(
outer.contains("expression_statement"),
"outer should wrap in expression_statement, got: {outer}"
);
Ok(())
}
#[test]
fn expression_statement_wrapping_anon_subroutine_stays_wrapped()
-> Result<(), Box<dyn std::error::Error>> {
let sub_node = Node::new(
NodeKind::Subroutine {
name: None,
name_span: None,
declarator: None,
prototype: None,
signature: None,
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
let expr_stmt =
Node::new(NodeKind::ExpressionStatement { expression: Box::new(sub_node) }, loc());
let inner = expr_stmt.to_sexp_inner();
assert!(
inner.contains("expression_statement"),
"anon sub should remain wrapped, got: {inner}"
);
Ok(())
}
}
mod for_each_child_immutable {
use super::*;
fn count_visits(node: &Node) -> usize {
let mut count = 0_usize;
node.for_each_child(|_child| count += 1);
count
}
#[test]
fn tie_visits_all_children() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Tie {
variable: Box::new(leaf("var")),
package: Box::new(leaf("pkg")),
args: vec![leaf("a")],
},
loc(),
);
assert_eq!(count_visits(&node), 3);
Ok(())
}
#[test]
fn unary_visits_operand() -> Result<(), Box<dyn std::error::Error>> {
let node =
Node::new(NodeKind::Unary { op: "!".to_string(), operand: Box::new(num("1")) }, loc());
assert_eq!(count_visits(&node), 1);
Ok(())
}
#[test]
fn block_visits_statements() -> Result<(), Box<dyn std::error::Error>> {
let node = block_of(vec![num("1"), num("2")]);
assert_eq!(count_visits(&node), 2);
Ok(())
}
#[test]
fn defer_visits_block() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Defer { block: Box::new(block_of(vec![])) }, loc());
assert_eq!(count_visits(&node), 1);
Ok(())
}
#[test]
fn try_visits_body_catches_and_finally() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Try {
body: Box::new(block_of(vec![])),
catch_blocks: vec![(None, Box::new(block_of(vec![])))],
finally_block: Some(Box::new(block_of(vec![]))),
},
loc(),
);
assert_eq!(count_visits(&node), 3);
Ok(())
}
#[test]
fn subroutine_with_all_parts_visits_three() -> Result<(), Box<dyn std::error::Error>> {
let proto = Node::new(NodeKind::Prototype { content: "$$".to_string() }, loc());
let sig = Node::new(NodeKind::Signature { parameters: vec![] }, loc());
let node = Node::new(
NodeKind::Subroutine {
name: Some("foo".to_string()),
name_span: None,
declarator: None,
prototype: Some(Box::new(proto)),
signature: Some(Box::new(sig)),
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
assert_eq!(count_visits(&node), 3);
Ok(())
}
#[test]
fn goto_visits_target() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::Goto { target: Box::new(leaf("LABEL")) }, loc());
assert_eq!(count_visits(&node), 1);
Ok(())
}
#[test]
fn hash_literal_visits_pairs() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(NodeKind::HashLiteral { pairs: vec![(leaf("k"), num("1"))] }, loc());
assert_eq!(count_visits(&node), 2);
Ok(())
}
#[test]
fn error_with_partial_visits_partial() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Error {
message: "bad".to_string(),
expected: vec![],
found: None,
partial: Some(Box::new(num("0"))),
},
loc(),
);
assert_eq!(count_visits(&node), 1);
Ok(())
}
#[test]
fn error_without_partial_visits_nothing() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Error {
message: "bad".to_string(),
expected: vec![],
found: None,
partial: None,
},
loc(),
);
assert_eq!(count_visits(&node), 0);
Ok(())
}
}
mod false_branch_coverage {
use super::*;
fn count_visits(node: &Node) -> usize {
let mut count = 0_usize;
node.for_each_child(|_child| count += 1);
count
}
fn count_visits_mut(node: &mut Node) -> usize {
let mut count = 0_usize;
node.for_each_child_mut(|_child| count += 1);
count
}
#[test]
fn if_without_else_branch_in_sexp() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::If {
condition: Box::new(num("1")),
then_branch: Box::new(block_of(vec![])),
elsif_branches: vec![],
else_branch: None,
keyword: None,
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("(if"), "expected if sexp, got: {s}");
assert!(!s.contains("else"), "no else should appear, got: {s}");
Ok(())
}
#[test]
fn named_subroutine_with_prototype_and_attrs_triggers_else_branch()
-> Result<(), Box<dyn std::error::Error>> {
let proto = Node::new(NodeKind::Prototype { content: "$$".to_string() }, loc());
let node = Node::new(
NodeKind::Subroutine {
name: Some("myfunc".to_string()),
name_span: None,
declarator: None,
prototype: Some(Box::new(proto)),
signature: None,
attributes: vec!["lvalue".to_string()],
body: Box::new(block_of(vec![])),
},
loc(),
);
let s = node.to_sexp();
assert!(s.contains("sub"), "got: {s}");
assert!(s.contains("myfunc"), "got: {s}");
Ok(())
}
#[test]
fn variable_list_decl_without_initializer_false_branch()
-> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::VariableListDeclaration {
declarator: "my".to_string(),
variables: vec![var("$", "a"), var("$", "b")],
attributes: vec![],
initializer: None,
},
loc(),
);
assert_eq!(count_visits(&node), 2, "only the two variables, no initializer");
Ok(())
}
#[test]
fn if_without_else_false_branch_for_each_child() -> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::If {
condition: Box::new(num("1")),
then_branch: Box::new(block_of(vec![])),
elsif_branches: vec![],
else_branch: None,
keyword: None,
},
loc(),
);
assert_eq!(count_visits(&node), 2);
Ok(())
}
#[test]
fn while_without_continue_false_branch_for_each_child() -> Result<(), Box<dyn std::error::Error>>
{
let node = Node::new(
NodeKind::While {
condition: Box::new(num("1")),
body: Box::new(block_of(vec![])),
continue_block: None,
keyword: None,
},
loc(),
);
assert_eq!(count_visits(&node), 2);
Ok(())
}
#[test]
fn method_without_signature_false_branch_for_each_child()
-> Result<(), Box<dyn std::error::Error>> {
let node = Node::new(
NodeKind::Method {
name: "run".to_string(),
name_span: None,
signature: None,
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
assert_eq!(count_visits(&node), 1);
Ok(())
}
#[test]
fn return_without_value_false_branch_for_each_child() -> Result<(), Box<dyn std::error::Error>>
{
let node = Node::new(NodeKind::Return { value: None }, loc());
assert_eq!(count_visits(&node), 0);
Ok(())
}
#[test]
fn package_without_block_false_branch_for_each_child() -> Result<(), Box<dyn std::error::Error>>
{
let node = Node::new(
NodeKind::Package { name: "Foo".to_string(), name_span: loc(), block: None },
loc(),
);
assert_eq!(count_visits(&node), 0);
Ok(())
}
#[test]
fn variable_list_decl_without_initializer_false_branch_mut()
-> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::VariableListDeclaration {
declarator: "my".to_string(),
variables: vec![var("$", "x")],
attributes: vec![],
initializer: None,
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn if_without_else_false_branch_for_each_child_mut() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::If {
condition: Box::new(num("1")),
then_branch: Box::new(block_of(vec![])),
elsif_branches: vec![],
else_branch: None,
keyword: None,
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 2);
Ok(())
}
#[test]
fn while_without_continue_false_branch_mut() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::While {
condition: Box::new(num("1")),
body: Box::new(block_of(vec![])),
continue_block: None,
keyword: None,
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 2);
Ok(())
}
#[test]
fn method_without_signature_false_branch_mut() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Method {
name: "run".to_string(),
name_span: None,
signature: None,
attributes: vec![],
body: Box::new(block_of(vec![])),
},
loc(),
);
assert_eq!(count_visits_mut(&mut node), 1);
Ok(())
}
#[test]
fn return_without_value_false_branch_mut() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(NodeKind::Return { value: None }, loc());
assert_eq!(count_visits_mut(&mut node), 0);
Ok(())
}
#[test]
fn package_without_block_false_branch_mut() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Node::new(
NodeKind::Package { name: "Bar".to_string(), name_span: loc(), block: None },
loc(),
);
assert_eq!(count_visits_mut(&mut node), 0);
Ok(())
}
}