diff --git a/src/parse_util.cpp b/src/parse_util.cpp index 9f90aac65..dd7787cb2 100644 --- a/src/parse_util.cpp +++ b/src/parse_util.cpp @@ -809,12 +809,12 @@ std::vector parse_util_compute_indents(const wcstring &src) { } /// Append a syntax error to the given error list. -static bool append_syntax_error(parse_error_list_t *errors, size_t source_location, +static bool append_syntax_error(parse_error_list_t *errors, size_t source_location, size_t source_length, const wchar_t *fmt, ...) { if (!errors) return true; parse_error_t error; error.source_start = source_location; - error.source_length = 0; + error.source_length = source_length; error.code = parse_error_syntax; va_list va; @@ -901,11 +901,11 @@ void parse_util_expand_variable_error(const wcstring &token, size_t global_token } if (looks_like_variable) { append_syntax_error( - errors, global_after_dollar_pos, + errors, global_after_dollar_pos, 1, double_quotes ? ERROR_BRACKETED_VARIABLE_QUOTED1 : ERROR_BRACKETED_VARIABLE1, truncate(var_name, var_err_len).c_str()); } else { - append_syntax_error(errors, global_after_dollar_pos, ERROR_BAD_VAR_CHAR1, L'{'); + append_syntax_error(errors, global_after_dollar_pos, 1, ERROR_BAD_VAR_CHAR1, L'{'); } break; } @@ -913,11 +913,11 @@ void parse_util_expand_variable_error(const wcstring &token, size_t global_token // e.g.: echo foo"$"baz // These are only ever quotes, not command substitutions. Command substitutions are // handled earlier. - append_syntax_error(errors, global_dollar_pos, ERROR_NO_VAR_NAME); + append_syntax_error(errors, global_dollar_pos, 1, ERROR_NO_VAR_NAME); break; } case L'\0': { - append_syntax_error(errors, global_dollar_pos, ERROR_NO_VAR_NAME); + append_syntax_error(errors, global_dollar_pos, 1, ERROR_NO_VAR_NAME); break; } default: { @@ -932,7 +932,7 @@ void parse_util_expand_variable_error(const wcstring &token, size_t global_token // arguments we pass but that's harmless. const wchar_t *error_fmt = error_format_for_character(token_stop_char); - append_syntax_error(errors, global_after_dollar_pos, error_fmt, token_stop_char); + append_syntax_error(errors, global_after_dollar_pos, 1, error_fmt, token_stop_char); break; } } @@ -957,8 +957,8 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const ast::argumen wcstring unesc; if (!unescape_string(arg_src.c_str() + begin, end - begin, &unesc, UNESCAPE_SPECIAL)) { if (out_errors) { - append_syntax_error(out_errors, source_start + begin, L"Invalid token '%ls'", - arg_src.c_str()); + append_syntax_error(out_errors, source_start + begin, end - begin, + L"Invalid token '%ls'", arg_src.c_str()); } return 1; } @@ -1006,7 +1006,7 @@ parser_test_error_bits_t parse_util_detect_errors_in_argument(const ast::argumen case -1: { err |= PARSER_TEST_ERROR; if (out_errors) { - append_syntax_error(out_errors, source_start, L"Mismatched parenthesis"); + append_syntax_error(out_errors, source_start, 1, L"Mismatched parenthesis"); } return err; } @@ -1061,10 +1061,10 @@ static bool detect_errors_in_backgrounded_job(const ast::job_t &job, if (!job_conj) return false; if (job_conj->parent->try_as()) { - errored = append_syntax_error(parse_errors, source_range->start, + errored = append_syntax_error(parse_errors, source_range->start, source_range->length, BACKGROUND_IN_CONDITIONAL_ERROR_MSG); } else if (job_conj->parent->try_as()) { - errored = append_syntax_error(parse_errors, source_range->start, + errored = append_syntax_error(parse_errors, source_range->start, source_range->length, BACKGROUND_IN_CONDITIONAL_ERROR_MSG); } else if (const ast::job_list_t *jlist = job_conj->parent->try_as()) { // This isn't very complete, e.g. we don't catch 'foo & ; not and bar'. @@ -1082,7 +1082,7 @@ static bool detect_errors_in_backgrounded_job(const ast::job_t &job, (deco->kw == parse_keyword_t::kw_and || deco->kw == parse_keyword_t::kw_or) && "Unexpected decorator keyword"); const wchar_t *deco_name = (deco->kw == parse_keyword_t::kw_and ? L"and" : L"or"); - errored = append_syntax_error(parse_errors, deco->source_range().start, + errored = append_syntax_error(parse_errors, deco->source_range().start, deco->source_range().length, BOOL_AFTER_BACKGROUND_ERROR_MSG, deco_name); } } @@ -1099,6 +1099,7 @@ static bool detect_errors_in_decorated_statement(const wcstring &buff_src, using namespace ast; bool errored = false; auto source_start = dst.source_range().start; + auto source_length = dst.source_range().length; const statement_decoration_t decoration = dst.decoration(); // Determine if the first argument is help. @@ -1133,7 +1134,7 @@ static bool detect_errors_in_decorated_statement(const wcstring &buff_src, bool is_in_pipeline = (pipe_pos != pipeline_position_t::none); if (is_in_pipeline && decoration == statement_decoration_t::exec) { errored = - append_syntax_error(parse_errors, source_start, INVALID_PIPELINE_CMD_ERR_MSG, L"exec"); + append_syntax_error(parse_errors, source_start, source_length, INVALID_PIPELINE_CMD_ERR_MSG, L"exec"); } // This is a somewhat stale check that 'and' and 'or' are not in pipelines, except at the @@ -1144,13 +1145,13 @@ static bool detect_errors_in_decorated_statement(const wcstring &buff_src, // commands. const wcstring &command = dst.command.source(buff_src, storage); if (command == L"and" || command == L"or") { - errored = append_syntax_error(parse_errors, source_start, INVALID_PIPELINE_CMD_ERR_MSG, + errored = append_syntax_error(parse_errors, source_start, source_length, INVALID_PIPELINE_CMD_ERR_MSG, command.c_str()); } // Similarly for time (#8841). if (command == L"time") { - errored = append_syntax_error(parse_errors, source_start, TIME_IN_PIPELINE_ERR_MSG); + errored = append_syntax_error(parse_errors, source_start, source_length, TIME_IN_PIPELINE_ERR_MSG); } } @@ -1160,7 +1161,7 @@ static bool detect_errors_in_decorated_statement(const wcstring &buff_src, const wcstring &com = dst.command.source(buff_src, storage); if (com == L"$status") { errored = - append_syntax_error(parse_errors, source_start, + append_syntax_error(parse_errors, source_start, source_length, _(L"$status is not valid as a command. See `help conditions`")); } @@ -1178,7 +1179,7 @@ static bool detect_errors_in_decorated_statement(const wcstring &buff_src, // Check that pipes are sound. if (!errored && parser_is_pipe_forbidden(command) && is_in_pipeline) { - errored = append_syntax_error(parse_errors, source_start, INVALID_PIPELINE_CMD_ERR_MSG, + errored = append_syntax_error(parse_errors, source_start, source_length, INVALID_PIPELINE_CMD_ERR_MSG, command.c_str()); } @@ -1208,7 +1209,7 @@ static bool detect_errors_in_decorated_statement(const wcstring &buff_src, if (!found_loop) { errored = append_syntax_error( - parse_errors, source_start, + parse_errors, source_start, source_length, (command == L"break" ? INVALID_BREAK_ERR_MSG : INVALID_CONTINUE_ERR_MSG)); } } @@ -1219,7 +1220,7 @@ static bool detect_errors_in_decorated_statement(const wcstring &buff_src, if (expand_one(command, expand_flag::skip_cmdsubst, operation_context_t::empty(), parse_errors) && !builtin_exists(unexp_command)) { - errored = append_syntax_error(parse_errors, source_start, UNKNOWN_BUILTIN_ERR_MSG, + errored = append_syntax_error(parse_errors, source_start, source_length, UNKNOWN_BUILTIN_ERR_MSG, unexp_command.c_str()); } } @@ -1240,7 +1241,7 @@ static bool detect_errors_in_decorated_statement(const wcstring &buff_src, static bool detect_errors_in_block_redirection_list( const ast::argument_or_redirection_list_t &args_or_redirs, parse_error_list_t *out_errors) { if (const auto *first_arg = get_first_arg(args_or_redirs)) { - return append_syntax_error(out_errors, first_arg->source_range().start, END_ARG_ERR_MSG); + return append_syntax_error(out_errors, first_arg->source_range().start, first_arg->source_range().length, END_ARG_ERR_MSG); } return false; } diff --git a/tests/checks/basic.fish b/tests/checks/basic.fish index acb631c8b..617de8c58 100644 --- a/tests/checks/basic.fish +++ b/tests/checks/basic.fish @@ -562,9 +562,9 @@ end $fish -c 'echo \xtest' # CHECKERR: fish: Invalid token '\xtest' # CHECKERR: echo \xtest -# CHECKERR: ^ +# CHECKERR: ^~~~~^ $fish -c 'echo \utest' # CHECKERR: fish: Invalid token '\utest' # CHECKERR: echo \utest -# CHECKERR: ^ +# CHECKERR: ^~~~~^ diff --git a/tests/checks/invocation.fish b/tests/checks/invocation.fish index ab4db110c..1e5bd3722 100644 --- a/tests/checks/invocation.fish +++ b/tests/checks/invocation.fish @@ -82,7 +82,7 @@ and echo matched $fish --no-config -c 'echo notprinted; echo foo | exec true; echo banana' # CHECKERR: fish: The 'exec' command can not be used in a pipeline # CHECKERR: echo notprinted; echo foo | exec true; echo banana -# CHECKERR: ^ +# CHECKERR: ^~~~~~~~^ # Running multiple command lists continues even if one has a syntax error. $fish --no-config -c 'echo $$ oh no syntax error' -c 'echo this works' diff --git a/tests/checks/syntax-error-location.fish b/tests/checks/syntax-error-location.fish index 78abdcbed..6590a7f76 100644 --- a/tests/checks/syntax-error-location.fish +++ b/tests/checks/syntax-error-location.fish @@ -9,12 +9,12 @@ $status # CHECK: # CHECK: -# CHECK: < ^> +# CHECK: < ^~~~~~~~^> echo 'true | time false' | $fish 2>| string replace -r '(.*)' '<$1>' # CHECK: # CHECK: -# CHECK: < ^> +# CHECK: < ^~~~~~~~~^> echo ' @@ -42,7 +42,7 @@ $fish -c 'echo "unfinished "$(subshell' 2>| string replace -r '.*' '<$0>' $fish -c 'echo "ok $(echo still ok)syntax error: \x"' 2>| string replace -r '.*' '<$0>' # CHECK: # CHECK: -# CHECK: < ^> +# CHECK: < ^~~~~~~~~~~~~~~~^> echo "function this_should_be_an_error" >$TMPDIR/this_should_be_an_error.fish $fish -c "set -g fish_function_path $(string escape $TMPDIR); this_should_be_an_error" diff --git a/tests/checks/vars_as_commands.fish b/tests/checks/vars_as_commands.fish index b243e0cfd..0c74fa747 100644 --- a/tests/checks/vars_as_commands.fish +++ b/tests/checks/vars_as_commands.fish @@ -32,17 +32,17 @@ $CMD3 && echo $PWD echo 'if $status; echo foo; end' | $fish --no-config #CHECKERR: fish: $status is not valid as a command. See `help conditions` #CHECKERR: if $status; echo foo; end -#CHECKERR: ^ +#CHECKERR: ^~~~~~^ echo 'not $status' | $fish --no-config #CHECKERR: fish: $status is not valid as a command. See `help conditions` #CHECKERR: not $status -#CHECKERR: ^ +#CHECKERR: ^~~~~~^ # Script doesn't run at all. echo 'echo foo; and $status' | $fish --no-config #CHECKERR: fish: $status is not valid as a command. See `help conditions` #CHECKERR: echo foo; and $status -#CHECKERR: ^ +#CHECKERR: ^~~~~~^ echo 'set -l status_cmd true; if $status_cmd; echo Heck yes this is true; end' | $fish --no-config #CHECK: Heck yes this is true