use super::*;
use perl_tdd_support::must;
#[test]
fn test_unclosed_sub_block_at_eof() {
let code = "sub foo { my $x = 1; ";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed sub block");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(!statements.is_empty(), "Program should have at least one statement");
let has_sub = statements.iter().any(|s| {
matches!(&s.kind, NodeKind::Subroutine { name, .. } if name.as_deref() == Some("foo"))
|| matches!(&s.kind, NodeKind::Error { partial: Some(p), .. } if matches!(&p.kind, NodeKind::Subroutine { name, .. } if name.as_deref() == Some("foo")))
});
assert!(
has_sub,
"Should have a (possibly partial) subroutine node for 'foo'. Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed block");
}
#[test]
fn test_unclosed_else_block_parses_if_branch() {
let code = "if ($cond) { $x = 1; } else { $y = 2;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed else block");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(!statements.is_empty(), "Program should have at least one statement");
let has_if = statements.iter().any(|s| {
matches!(&s.kind, NodeKind::If { .. })
|| matches!(&s.kind, NodeKind::Error { partial: Some(p), .. } if matches!(&p.kind, NodeKind::If { .. }))
});
assert!(
has_if,
"Should have an if statement node. Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed else block");
}
#[test]
fn test_nested_unclosed_blocks() {
let code = "{ my $x = 1; { my $y = 2; }";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from nested unclosed blocks");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(!statements.is_empty(), "Program should have at least one statement");
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed block");
}
#[test]
fn test_first_sub_clean_when_second_unclosed() {
let code = "sub foo { } sub bar { my $x = 1;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed second sub");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
statements.len() >= 2,
"Should have at least 2 statements (two subs). Got {} statements: {:?}",
statements.len(),
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
let first_is_foo = matches!(
&statements[0].kind,
NodeKind::Subroutine { name, .. } if name.as_deref() == Some("foo")
);
assert!(
first_is_foo,
"First statement should be sub foo. Got: {}",
statements[0].kind.kind_name()
);
let second_is_bar = statements[1..].iter().any(|s| {
matches!(&s.kind, NodeKind::Subroutine { name, .. } if name.as_deref() == Some("bar"))
|| matches!(&s.kind, NodeKind::Error { partial: Some(p), .. } if matches!(&p.kind, NodeKind::Subroutine { name, .. } if name.as_deref() == Some("bar")))
});
assert!(
second_is_bar,
"Should have a (possibly partial) subroutine node for 'bar'. Got: {:?}",
statements[1..].iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed block");
}
#[test]
fn test_clean_file_unaffected() {
let code = "sub foo { my $x = 1; } sub bar { my $y = 2; }";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Clean file should parse successfully");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 2, "Should have exactly 2 subroutines");
assert!(matches!(
&statements[0].kind,
NodeKind::Subroutine { name, .. } if name.as_deref() == Some("foo")
));
assert!(matches!(
&statements[1].kind,
NodeKind::Subroutine { name, .. } if name.as_deref() == Some("bar")
));
} else {
panic!("Expected Program node");
}
assert!(parser.errors().is_empty(), "Clean file should have no errors");
}
#[test]
fn test_unclosed_while_block_at_eof() {
let code = "while (1) { print 'hello';";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed while block");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(!statements.is_empty(), "Should have at least one statement");
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed block");
}
#[test]
fn test_recovery_on_sub_keyword_in_unclosed_block() {
let code = "sub foo { my $x = 1;\nsub bar { my $y = 2; }";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover on encountering new sub");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least 1 statement. Got {} statements: {:?}",
statements.len(),
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
let has_foo = statements.iter().any(|s| {
matches!(&s.kind, NodeKind::Subroutine { name, .. } if name.as_deref() == Some("foo"))
});
assert!(
has_foo,
"Should have subroutine 'foo'. Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
if let NodeKind::Subroutine { body, .. } = &statements[0].kind {
if let NodeKind::Block { statements: body_stmts } = &body.kind {
let has_bar = body_stmts.iter().any(|s| {
matches!(&s.kind, NodeKind::Subroutine { name, .. } if name.as_deref() == Some("bar"))
});
assert!(
has_bar,
"foo's body should contain nested sub bar. Got: {:?}",
body_stmts.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
}
}
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed block in foo");
}
#[test]
fn test_unclosed_c_for_loop_body() {
let code = "for (my $i = 0; $i < 10; $i++) { print $i;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed C-style for loop body");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least one statement (the for loop). Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed for loop body");
}
#[test]
fn test_unclosed_foreach_body() {
let code = "foreach my $x (@list) { print $x;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed foreach body");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least one statement (the foreach). Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed foreach body");
}
#[test]
fn test_unclosed_unless_block() {
let code = "unless ($x) { do_thing();";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed unless block");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least one statement (the unless). Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed unless block");
}
#[test]
fn test_unclosed_begin_phase_block() {
let code = "BEGIN { use strict; use warnings;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed BEGIN block");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(!statements.is_empty(), "Should have at least one statement");
let has_phase = statements.iter().any(|s| {
matches!(&s.kind, NodeKind::PhaseBlock { phase, .. } if phase == "BEGIN")
|| matches!(&s.kind, NodeKind::Error { .. })
});
assert!(
has_phase,
"Should have a PhaseBlock or Error for BEGIN. Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed BEGIN block");
}
#[test]
fn test_doubly_nested_unclosed_blocks() {
let code = "{ my $x = 1; { my $y = 2; my $z = 3;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from doubly-nested unclosed blocks");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least one statement. Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about doubly-nested unclosed blocks");
}
#[test]
fn test_unclosed_nested_block_inside_sub() {
let code = "sub foo { if ($x) { my $y = 1;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed nested block inside sub");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least one statement. Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
let has_sub = statements.iter().any(|s| {
matches!(&s.kind, NodeKind::Subroutine { name, .. } if name.as_deref() == Some("foo"))
|| matches!(&s.kind, NodeKind::Error { partial: Some(p), .. } if matches!(&p.kind, NodeKind::Subroutine { name, .. } if name.as_deref() == Some("foo")))
});
assert!(
has_sub,
"Should have a (possibly partial) subroutine node for 'foo'. Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(
!parser.errors().is_empty(),
"Should have errors about unclosed nested block inside sub"
);
}
#[test]
fn test_unclosed_until_loop_body() {
let code = "until ($done) { process_item();";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed until loop body");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least one statement (the until loop). Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed until loop body");
}
#[test]
fn test_unclosed_eval_block_at_eof() {
let code = "eval { my $result = dangerous_call();";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed eval block");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least one statement (the eval). Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed eval block");
}
#[test]
fn test_unclosed_do_block_at_eof() {
let code = "my $result = do { my $tmp = compute();";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed do block");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least one statement. Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed do block");
}
#[test]
fn test_bare_open_brace_at_eof() {
let code = "{";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from a bare open brace at EOF");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(
!statements.is_empty(),
"Should have at least one statement. Got: {:?}",
statements.iter().map(|s| s.kind.kind_name()).collect::<Vec<_>>()
);
} else {
panic!("Expected Program node");
}
assert!(!parser.errors().is_empty(), "Should have errors about unclosed bare block");
}