diff --git a/fish-rust/src/tests/parser.rs b/fish-rust/src/tests/parser.rs index b662a1261..4d09940bf 100644 --- a/fish-rust/src/tests/parser.rs +++ b/fish-rust/src/tests/parser.rs @@ -2,8 +2,9 @@ use crate::builtins::shared::{STATUS_CMD_OK, STATUS_UNMATCHED_WILDCARD}; use crate::expand::ExpandFlags; use crate::io::{IoBufferfill, IoChain}; -use crate::parse_constants::StatementDecoration; -use crate::parse_constants::{ParseTreeFlags, ParserTestErrorBits}; +use crate::parse_constants::{ + ParseErrorCode, ParseTreeFlags, ParserTestErrorBits, StatementDecoration, +}; use crate::parse_util::{parse_util_detect_errors, parse_util_detect_errors_in_argument}; use crate::parser::Parser; use crate::reader::reader_reset_interrupted; @@ -499,6 +500,64 @@ macro_rules! check_function_help { check_function_help!("function foo; end", ast::Type::function_header); }); +add_test!("test_new_parser_ad_hoc", || { + // Very ad-hoc tests for issues encountered. + + // Ensure that 'case' terminates a job list. + let src = L!("switch foo ; case bar; case baz; end"); + let ast = Ast::parse(src, ParseTreeFlags::default(), None); + assert!(!ast.errored()); + // Expect two case_item_lists. The bug was that we'd + // try to run a command 'case'. + assert_eq!( + Traversal::new(ast.top()) + .filter(|n| n.typ() == ast::Type::case_item) + .count(), + 2 + ); + + // Ensure that naked variable assignments don't hang. + // The bug was that "a=" would produce an error but not be consumed, + // leading to an infinite loop. + + // By itself it should produce an error. + let ast = Ast::parse(L!("a="), ParseTreeFlags::default(), None); + assert!(ast.errored()); + + // If we are leaving things unterminated, this should not produce an error. + // i.e. when typing "a=" at the command line, it should be treated as valid + // because we don't want to color it as an error. + let ast = Ast::parse(L!("a="), ParseTreeFlags::LEAVE_UNTERMINATED, None); + assert!(!ast.errored()); + + let mut errors = vec![]; + Ast::parse( + L!("begin; echo ("), + ParseTreeFlags::LEAVE_UNTERMINATED, + Some(&mut errors), + ); + assert!(errors.len() == 1); + assert!(errors[0].code == ParseErrorCode::tokenizer_unterminated_subshell); + + errors.clear(); + Ast::parse( + L!("for x in ("), + ParseTreeFlags::LEAVE_UNTERMINATED, + Some(&mut errors), + ); + assert!(errors.len() == 1); + assert!(errors[0].code == ParseErrorCode::tokenizer_unterminated_subshell); + + errors.clear(); + Ast::parse( + L!("begin; echo '"), + ParseTreeFlags::LEAVE_UNTERMINATED, + Some(&mut errors), + ); + assert!(errors.len() == 1); + assert!(errors[0].code == ParseErrorCode::tokenizer_unterminated_quote); +}); + add_test!("test_eval_recursion_detection", || { // Ensure that we don't crash on infinite self recursion and mutual recursion. These must use // the principal parser because we cannot yet execute jobs on other parsers. diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 46c911819..1198a06b9 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1077,61 +1077,6 @@ static void test_input() { } } -// todo!("port this") -static void test_new_parser_ad_hoc() { - using namespace ast; - // Very ad-hoc tests for issues encountered. - say(L"Testing new parser ad hoc tests"); - - // Ensure that 'case' terminates a job list. - const wcstring src = L"switch foo ; case bar; case baz; end"; - auto ast = ast_parse(src); - if (ast->errored()) { - err(L"Parsing failed"); - } - - // Expect two case_item_lists. The bug was that we'd - // try to run a command 'case'. - int count = 0; - for (auto ast_traversal = new_ast_traversal(*ast->top());;) { - auto n = ast_traversal->next(); - if (!n->has_value()) break; - count += (n->typ() == type_t::case_item); - } - if (count != 2) { - err(L"Expected 2 case item nodes, found %d", count); - } - - // Ensure that naked variable assignments don't hang. - // The bug was that "a=" would produce an error but not be consumed, - // leading to an infinite loop. - - // By itself it should produce an error. - ast = ast_parse(L"a="); - do_test(ast->errored()); - - // If we are leaving things unterminated, this should not produce an error. - // i.e. when typing "a=" at the command line, it should be treated as valid - // because we don't want to color it as an error. - ast = ast_parse(L"a=", parse_flag_leave_unterminated); - do_test(!ast->errored()); - - auto errors = new_parse_error_list(); - ast = ast_parse(L"begin; echo (", parse_flag_leave_unterminated, &*errors); - do_test(errors->size() == 1 && - errors->at(0)->code() == parse_error_code_t::tokenizer_unterminated_subshell); - - errors->clear(); - ast = ast_parse(L"for x in (", parse_flag_leave_unterminated, &*errors); - do_test(errors->size() == 1 && - errors->at(0)->code() == parse_error_code_t::tokenizer_unterminated_subshell); - - errors->clear(); - ast = ast_parse(L"begin; echo '", parse_flag_leave_unterminated, &*errors); - do_test(errors->size() == 1 && - errors->at(0)->code() == parse_error_code_t::tokenizer_unterminated_quote); -} - // todo!("port this") static void test_new_parser_errors() { say(L"Testing new parser error reporting"); @@ -1594,7 +1539,6 @@ static const test_t s_tests[]{ {TEST_GROUP("enum"), test_enum_array}, {TEST_GROUP("autosuggestion"), test_autosuggestion_combining}, {TEST_GROUP("test_abbreviations"), test_abbreviations}, - {TEST_GROUP("new_parser_ad_hoc"), test_new_parser_ad_hoc}, {TEST_GROUP("new_parser_errors"), test_new_parser_errors}, {TEST_GROUP("error_messages"), test_error_messages}, {TEST_GROUP("convert"), test_convert},