use super::*;
use perl_tdd_support::must;
#[test]
fn test_recovery_missing_expression() {
let code = "my $x = ; print 1;";
let mut parser = Parser::new(code);
let result = parser.parse();
match result {
Ok(ast) => {
println!("AST: {}", ast.to_sexp());
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(
statements.len(),
2,
"Should have 2 statements (1 recovered decl, 1 valid)"
);
assert!(
matches!(
statements[0].kind,
NodeKind::VariableDeclaration { .. } | NodeKind::Error { .. }
),
"Expected VariableDeclaration or Error for first statement, got: {:?}",
statements[0].kind
);
match &statements[1].kind {
NodeKind::ExpressionStatement { .. } => {
println!("Found valid second statement");
}
_ => unreachable!(
"Expected ExpressionStatement for second statement, got: {:?}",
statements[1].kind
),
}
} else {
unreachable!("Expected Program node");
}
let errors = parser.errors();
assert!(!errors.is_empty(), "Should have recorded errors");
println!("Errors: {:?}", errors);
}
Err(e) => {
unreachable!("Parser failed to recover: {}", e);
}
}
}
#[test]
fn test_recovery_missing_rhs_before_sub_declaration_keyword() {
let code = "my $x = sub foo { print 1; }";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover missing assignment RHS");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 2, "Should recover and keep the following sub declaration");
assert!(
matches!(statements[0].kind, NodeKind::VariableDeclaration { .. }),
"First statement should stay a recovered variable declaration"
);
assert!(
matches!(statements[1].kind, NodeKind::Subroutine { .. }),
"Second statement should parse as subroutine declaration"
);
} else {
unreachable!("Expected program root");
}
assert!(!parser.errors().is_empty(), "Recovery should record a missing operand diagnostic");
}
#[test]
fn test_no_recovery_for_anonymous_sub_assignment_rhs() {
let code = "local $SIG{__WARN__} = sub { };";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should accept anonymous sub assignment RHS");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 1, "Anonymous sub RHS should stay in a single statement");
} else {
unreachable!("Expected program root");
}
assert!(
!ast.to_sexp().contains("missing_expression"),
"Anonymous sub assignment should not create MissingExpression recovery nodes"
);
}
#[test]
fn test_recovery_multiple_errors() {
let code = "
my $a = ; # Recovered 1
print 1; # Valid
my $b = ; # Recovered 2
print 2; # Valid
";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok());
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 4, "Should have 4 statements");
assert!(
matches!(
statements[0].kind,
NodeKind::VariableDeclaration { .. } | NodeKind::Error { .. }
),
"Expected VariableDeclaration or Error, got: {:?}",
statements[0].kind
);
assert!(matches!(statements[1].kind, NodeKind::ExpressionStatement { .. }));
assert!(
matches!(
statements[2].kind,
NodeKind::VariableDeclaration { .. } | NodeKind::Error { .. }
),
"Expected VariableDeclaration or Error, got: {:?}",
statements[2].kind
);
assert!(matches!(statements[3].kind, NodeKind::ExpressionStatement { .. }));
}
assert!(!parser.errors().is_empty(), "Should have errors");
assert!(parser.errors().len() >= 2, "Expected at least 2 errors, got: {:?}", parser.errors());
}
#[test]
fn test_recovery_inside_block() {
let code = "sub foo { my $x = ; print 1; }";
let mut parser = Parser::new(code);
let result = parser.parse();
match result {
Ok(ast) => {
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 1);
if let NodeKind::Subroutine { body, .. } = &statements[0].kind {
if let NodeKind::Block { statements } = &body.kind {
assert_eq!(
statements.len(),
2,
"Block should have 2 statements (1 recovered decl, 1 valid)"
);
assert!(
matches!(
statements[0].kind,
NodeKind::VariableDeclaration { .. } | NodeKind::Error { .. }
),
"Expected VariableDeclaration or Error in block, got: {:?}",
statements[0].kind
);
match &statements[1].kind {
NodeKind::ExpressionStatement { .. } => {
println!("Found valid statement in block")
}
_ => unreachable!("Expected ExpressionStatement in block"),
}
} else {
unreachable!("Expected Block in subroutine body");
}
} else {
unreachable!("Expected Subroutine node, got: {:?}", statements[0].kind);
}
}
assert!(!parser.errors().is_empty());
}
Err(e) => unreachable!("Failed to recover from block error: {}", e),
}
}
#[test]
fn test_451_ac1_maintains_error_collection() {
let code = "my $x = ; my $y = 10;";
let mut parser = Parser::new(code);
let _result = parser.parse();
let errors = parser.errors();
assert!(!errors.is_empty(), "AC1: Parser should maintain errors collection");
}
#[test]
fn test_451_ac2_parse_with_recovery_method() {
let code = "my $x = ; print 1;";
let mut parser = Parser::new(code);
let output = parser.parse_with_recovery();
assert!(matches!(output.ast.kind, NodeKind::Program { .. }), "AC2: Should return AST");
assert!(!output.diagnostics.is_empty(), "AC2: Should return collected errors");
}
#[test]
fn test_451_ac3_parse_output_structure() {
let code = "my $x = ;";
let mut parser = Parser::new(code);
let output = parser.parse_with_recovery();
assert!(matches!(output.ast.kind, NodeKind::Program { .. }), "AC3: ast field present");
assert!(!output.diagnostics.is_empty(), "AC3: diagnostics field present");
}
#[test]
fn test_451_ac4_continues_after_error() {
let code = "my $a = ; print 'hello'; my $b = ; print 'world';";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "AC4: Parser should continue after errors");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 4, "AC4: Should continue parsing after each error");
}
}
#[test]
fn test_451_ac5_error_limit_enforcement() {
let mut code = String::new();
for i in 0..150 {
code.push_str(&format!("my $x{} = ;\n", i));
}
let mut parser = Parser::new(&code);
let _result = parser.parse();
let errors = parser.errors();
assert!(errors.len() < 500, "AC5: Should limit error collection (found {})", errors.len());
}
#[test]
fn test_451_ac6_recovery_prevents_infinite_loops() {
let code = ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "AC6: Recovery should complete on pathological input");
let code2 = "{ { { { { { { { { {";
let mut parser2 = Parser::new(code2);
let result2 = parser2.parse();
assert!(result2.is_ok(), "AC6: Should handle nested unclosed blocks");
}
#[test]
fn test_451_ac7_statement_level_recovery() {
let code = "
print 1;
my $bad = ;
print 2;
my $good = 42;
";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "AC7: Statement-level parsing should recover");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 4, "AC7: Should parse all statements");
let has_valid = statements.iter().any(|s| !matches!(s.kind, NodeKind::Error { .. }));
assert!(has_valid, "AC7: Should have valid statements after error");
}
assert!(!parser.errors().is_empty(), "AC7: Should have recorded errors");
}
#[test]
fn test_451_ac8_expression_level_recovery() {
let code = "my $x = ;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "AC8: Should recover from expression errors");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(!statements.is_empty(), "AC8: Should have statement");
let has_recovered = statements.iter().any(|s| {
matches!(
s.kind,
NodeKind::VariableDeclaration { .. }
| NodeKind::Error { .. }
| NodeKind::MissingExpression
)
});
assert!(has_recovered, "AC8: Should produce a recovered or error node");
}
assert!(!parser.errors().is_empty(), "AC8: Should record expression-level error");
}
#[test]
fn test_451_ac9_block_level_recovery() {
let code = "
sub test {
my $a = ;
print 1;
my $b = ;
print 2;
}
";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "AC9: Block-level parsing should recover");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
if let Some(sub_node) = statements.first() {
if let NodeKind::Subroutine { body, .. } = &sub_node.kind {
if let NodeKind::Block { statements: block_stmts } = &body.kind {
assert_eq!(block_stmts.len(), 4, "AC9: Block should have all statements");
let print_count = block_stmts
.iter()
.filter(|s| matches!(s.kind, NodeKind::ExpressionStatement { .. }))
.count();
assert_eq!(
print_count, 2,
"AC9: Should have 2 valid ExpressionStatement in block"
);
}
}
}
}
let errors = parser.errors();
assert!(errors.len() >= 2, "AC9: Should collect multiple errors from block");
}
#[test]
fn test_451_ac10_comprehensive_scenarios() {
let code1 = "
my $a = ;
print 'valid';
my $b = ;
my $c = 10;
my $d = ;
";
let mut parser1 = Parser::new(code1);
let result1 = parser1.parse();
assert!(result1.is_ok(), "AC10: Should handle interleaved errors");
assert!(parser1.errors().len() >= 3, "AC10: Should collect all 3 errors");
let code2 = "
if (1) {
my $x = ;
print 1;
}
while (1) {
my $y = ;
print 2;
}
";
let mut parser2 = Parser::new(code2);
let result2 = parser2.parse();
assert!(result2.is_ok(), "AC10: Should handle nested block errors");
assert!(parser2.errors().len() >= 2, "AC10: Should collect errors from nested blocks");
let code3 = "my $x = ; my $y = ";
let mut parser3 = Parser::new(code3);
let _result3 = parser3.parse();
assert!(!parser3.errors().is_empty(), "AC10: Should handle different error types");
}
#[test]
fn test_no_recovery_for_my_code_eq_anon_sub() {
let code = "my $code = sub { my ($x) = @_; return $x * 2; };";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should accept `my $var = sub {{...}};`");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(
statements.len(),
1,
"my $code = sub {{...}} must be a single statement, not split in two"
);
let sexp = ast.to_sexp();
assert!(
!sexp.contains("missing_expression"),
"RHS anonymous sub must not produce MissingExpression: {sexp}"
);
assert!(!sexp.contains("error"), "RHS anonymous sub must not produce Error nodes: {sexp}");
} else {
unreachable!("Expected program root");
}
assert!(
parser.errors().is_empty(),
"No recovery errors expected for valid anonymous sub assignment: {:?}",
parser.errors()
);
}
#[test]
fn test_local_as_assignment_rhs() {
let code = "local(*RS) = local(*/);";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should accept `local(x) = local(y);`");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert_eq!(statements.len(), 1, "`local(x) = local(y)` must parse as a single statement");
let sexp = ast.to_sexp();
assert!(
!sexp.contains("missing_expression"),
"local-as-RHS must not produce MissingExpression: {sexp}"
);
} else {
unreachable!("Expected program root");
}
assert!(
parser.errors().is_empty(),
"No recovery errors expected for `local(x) = local(y)`: {:?}",
parser.errors()
);
}
#[test]
fn test_recovery_unclosed_qw() {
let code = "my @items = qw(one two three print 1;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed qw()");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(statements.len() >= 1, "Should have recovered statements after unclosed qw");
}
assert!(!parser.errors().is_empty(), "Should record unclosed delimiter error");
}
#[test]
fn test_recovery_unclosed_q_brace() {
let code = "my $str = q{ hello world print 1;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed q braces");
let ast = must(result);
if let NodeKind::Program { statements } = &ast.kind {
assert!(statements.len() >= 1, "Should have recovered statements");
}
assert!(!parser.errors().is_empty(), "Should record unclosed brace error");
}
#[test]
fn test_recovery_unclosed_qq() {
let code = "my $name = \"unknown; print 1;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed qq string");
assert!(!parser.errors().is_empty(), "Should record unclosed quote error");
}
#[test]
fn test_recovery_nested_qw_paren_mismatch() {
let code = "my @list = qw(one (two three) print 1;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from nested paren in qw");
assert!(!parser.errors().is_empty(), "Should record delimiter mismatch error");
}
#[test]
fn test_recovery_unclosed_s_slash() {
let code = "my $x = s/pattern; print 1;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from unclosed s///");
assert!(!parser.errors().is_empty(), "Should record unclosed s delimiter error");
}
#[test]
fn test_recovery_unclosed_s_replacement() {
let code = "s/find/; print 1;";
let mut parser = Parser::new(code);
let result = parser.parse();
assert!(result.is_ok(), "Parser should recover from s/ with unclosed replacement");
assert!(!parser.errors().is_empty(), "Should record unclosed s delimiter error");
}