use vb6parse::parsers::cst::ConcreteSyntaxTree;
fn assert_failure_count_matches_snapshot(snapshot_dir: &str, snapshot_name: &str, actual: usize) {
let primary_path = format!("snapshots/tests/invalid_syntax/{snapshot_dir}/{snapshot_name}.snap");
let prefixed_path = format!(
"snapshots/tests/invalid_syntax/{snapshot_dir}/invalid_syntax__{snapshot_dir}__{snapshot_name}.snap"
);
let path = if std::path::Path::new(&primary_path).exists() {
primary_path
} else {
prefixed_path
};
let content = std::fs::read_to_string(&path)
.unwrap_or_else(|err| panic!("Failed to read snapshot file {path}: {err}"));
let expected = content
.lines()
.filter(|line| line.trim_start().starts_with("- "))
.count();
assert_eq!(
actual,
expected,
"Unexpected failure count for snapshot {snapshot_name}"
);
}
#[test]
fn exit_sub_outside_sub() {
let source = r"
Exit Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for exit_sub_outside_sub ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("exit_sub_outside_sub_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "exit_sub_outside_sub_failures", failures.len());
insta::assert_yaml_snapshot!("exit_sub_outside_sub_failures", failure_messages);
}
#[test]
fn exit_function_outside_function() {
let source = r"
Sub Test()
Exit Function
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for exit_function_outside_function ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("exit_function_outside_function_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "exit_function_outside_function_failures", failures.len());
insta::assert_yaml_snapshot!("exit_function_outside_function_failures", failure_messages);
}
#[test]
fn exit_property_outside_property() {
let source = r"
Function Calculate() As Integer
Exit Property
Calculate = 0
End Function
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for exit_property_outside_property ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("exit_property_outside_property_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "exit_property_outside_property_failures", failures.len());
insta::assert_yaml_snapshot!("exit_property_outside_property_failures", failure_messages);
}
#[test]
fn exit_for_outside_loop() {
let source = r"
Sub Test()
Exit For
Dim i As Integer
i = 5
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for exit_for_outside_loop ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("exit_for_outside_loop_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "exit_for_outside_loop_failures", failures.len());
insta::assert_yaml_snapshot!("exit_for_outside_loop_failures", failure_messages);
}
#[test]
fn exit_do_outside_loop() {
let source = r"
Sub Test()
While True
Exit Do
Wend
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for exit_do_outside_loop ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("exit_do_outside_loop_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "exit_do_outside_loop_failures", failures.len());
insta::assert_yaml_snapshot!("exit_do_outside_loop_failures", failure_messages);
}
#[test]
fn goto_missing_label() {
let source = r#"
Sub Test()
GoTo
Debug.Print "Test"
End Sub
"#;
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for goto_missing_label ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("goto_missing_label_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "goto_missing_label_failures", failures.len());
insta::assert_yaml_snapshot!("goto_missing_label_failures", failure_messages);
}
#[test]
fn gosub_missing_label() {
let source = r"
Sub Test()
GoSub
Exit Sub
ErrorHandler:
Resume Next
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for gosub_missing_label ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("gosub_missing_label_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "gosub_missing_label_failures", failures.len());
insta::assert_yaml_snapshot!("gosub_missing_label_failures", failure_messages);
}
#[test]
fn on_error_missing_destination() {
let source = r"
Sub Test()
On Error
Dim x As Integer
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for on_error_missing_destination ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("on_error_missing_destination_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "on_error_missing_destination_failures", failures.len());
insta::assert_yaml_snapshot!("on_error_missing_destination_failures", failure_messages);
}
#[test]
fn resume_without_on_error() {
let source = r"
Sub Test()
Resume Next
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for resume_without_on_error ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("resume_without_on_error_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "resume_without_on_error_failures", failures.len());
insta::assert_yaml_snapshot!("resume_without_on_error_failures", failure_messages);
}
#[test]
fn nested_exit_wrong_context() {
let source = r"
Sub Test()
For i = 1 To 10
Do While i < 5
Exit Sub
Loop
Next i
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for nested_exit_wrong_context ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("nested_exit_wrong_context_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "nested_exit_wrong_context_failures", failures.len());
insta::assert_yaml_snapshot!("nested_exit_wrong_context_failures", failure_messages);
}
#[test]
fn return_in_module() {
let source = r"
Sub Test()
Return
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for return_in_module ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("return_in_module_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "return_in_module_failures", failures.len());
insta::assert_yaml_snapshot!("return_in_module_failures", failure_messages);
}
#[test]
fn on_error_goto_missing_target() {
let source = r"
Sub Test()
On Error GoTo
Dim x As Integer
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for on_error_goto_missing_target ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("on_error_goto_missing_target_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "on_error_goto_missing_target_failures", failures.len());
insta::assert_yaml_snapshot!("on_error_goto_missing_target_failures", failure_messages);
}
#[test]
fn stop_with_arguments() {
let source = r"
Sub Test()
Stop 123
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for stop_with_arguments ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("stop_with_arguments_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "stop_with_arguments_failures", failures.len());
insta::assert_yaml_snapshot!("stop_with_arguments_failures", failure_messages);
}
#[test]
fn resume_invalid_combination() {
let source = r"
Sub Test()
On Error Resume 0
End Sub
";
let (cst_opt, failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
eprintln!("=== Failures for resume_invalid_combination ===");
eprintln!("Number of failures: {}", failures.len());
for failure in &failures {
failure.eprint();
}
eprintln!("=== End Failures ===");
let cst = cst_opt.expect("CST should be present even with syntax errors");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../snapshots/tests/invalid_syntax/invalid_control_flow");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!("resume_invalid_combination_cst", tree);
let failure_messages: Vec<String> = failures.iter().map(|f| format!("{f:?}")).collect();
assert_failure_count_matches_snapshot("invalid_control_flow", "resume_invalid_combination_failures", failures.len());
insta::assert_yaml_snapshot!("resume_invalid_combination_failures", failure_messages);
}