mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-03-27 14:45:13 +08:00
Factor our plain statement case of parse_util_detect_errors
Factors out a lot of code into a separate function.
This commit is contained in:
parent
81b3baaa9c
commit
cefb9e6d03
@ -1085,6 +1085,102 @@ static bool detect_errors_in_backgrounded_job(tnode_t<grammar::job> job,
|
|||||||
return errored;
|
return errored;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool detect_errors_in_plain_statement(const wcstring &buff_src,
|
||||||
|
const parse_node_tree_t &node_tree,
|
||||||
|
tnode_t<grammar::plain_statement> pst,
|
||||||
|
parse_error_list_t *parse_errors) {
|
||||||
|
using namespace grammar;
|
||||||
|
bool errored = false;
|
||||||
|
auto source_start = pst.source_range()->start;
|
||||||
|
|
||||||
|
// In a few places below, we want to know if we are in a pipeline.
|
||||||
|
tnode_t<statement> st = pst.try_get_parent<decorated_statement>().try_get_parent<statement>();
|
||||||
|
const bool is_in_pipeline = statement_is_in_pipeline(st, true /* count first */);
|
||||||
|
|
||||||
|
// We need to know the decoration.
|
||||||
|
const enum parse_statement_decoration_t decoration = get_decoration(pst);
|
||||||
|
|
||||||
|
// Check that we don't try to pipe through exec.
|
||||||
|
if (is_in_pipeline && decoration == parse_statement_decoration_exec) {
|
||||||
|
errored = append_syntax_error(parse_errors, source_start, EXEC_ERR_MSG, L"exec");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maybe_t<wcstring> mcommand = command_for_plain_statement(pst, buff_src)) {
|
||||||
|
wcstring command = std::move(*mcommand);
|
||||||
|
// Check that we can expand the command.
|
||||||
|
if (!expand_one(command, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS,
|
||||||
|
NULL)) {
|
||||||
|
// TODO: leverage the resulting errors.
|
||||||
|
errored = append_syntax_error(parse_errors, source_start, ILLEGAL_CMD_ERR_MSG,
|
||||||
|
command.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that pipes are sound.
|
||||||
|
if (!errored && parser_is_pipe_forbidden(command) && is_in_pipeline) {
|
||||||
|
errored =
|
||||||
|
append_syntax_error(parse_errors, source_start, EXEC_ERR_MSG, command.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we don't return from outside a function. But we allow it if it's
|
||||||
|
// 'return --help'.
|
||||||
|
if (!errored && command == L"return") {
|
||||||
|
bool found_function = false;
|
||||||
|
for (const parse_node_t *ancestor = pst.node(); ancestor != nullptr;
|
||||||
|
ancestor = node_tree.get_parent(*ancestor)) {
|
||||||
|
auto fh = tnode_t<block_statement>::try_create(&node_tree, ancestor)
|
||||||
|
.child<0>()
|
||||||
|
.try_get_child<function_header, 0>();
|
||||||
|
if (fh) {
|
||||||
|
found_function = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found_function && !first_argument_is_help(pst, buff_src)) {
|
||||||
|
errored = append_syntax_error(parse_errors, source_start, INVALID_RETURN_ERR_MSG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we don't break or continue from outside a loop.
|
||||||
|
if (!errored && (command == L"break" || command == L"continue")) {
|
||||||
|
// Walk up until we hit a 'for' or 'while' loop. If we hit a function first,
|
||||||
|
// stop the search; we can't break an outer loop from inside a function.
|
||||||
|
// This is a little funny because we can't tell if it's a 'for' or 'while'
|
||||||
|
// loop from the ancestor alone; we need the header. That is, we hit a
|
||||||
|
// block_statement, and have to check its header.
|
||||||
|
bool found_loop = false;
|
||||||
|
for (const parse_node_t *ancestor = pst.node(); ancestor != nullptr;
|
||||||
|
ancestor = node_tree.get_parent(*ancestor)) {
|
||||||
|
tnode_t<block_header> bh =
|
||||||
|
tnode_t<block_statement>::try_create(&node_tree, ancestor).child<0>();
|
||||||
|
if (bh.try_get_child<while_header, 0>() || bh.try_get_child<for_header, 0>()) {
|
||||||
|
// This is a loop header, so we can break or continue.
|
||||||
|
found_loop = true;
|
||||||
|
break;
|
||||||
|
} else if (bh.try_get_child<function_header, 0>()) {
|
||||||
|
// This is a function header, so we cannot break or
|
||||||
|
// continue. We stop our search here.
|
||||||
|
found_loop = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found_loop && !first_argument_is_help(pst, buff_src)) {
|
||||||
|
errored = append_syntax_error(
|
||||||
|
parse_errors, source_start,
|
||||||
|
(command == L"break" ? INVALID_BREAK_ERR_MSG : INVALID_CONTINUE_ERR_MSG));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we don't do an invalid builtin (issue #1252).
|
||||||
|
if (!errored && decoration == parse_statement_decoration_builtin &&
|
||||||
|
!builtin_exists(command)) {
|
||||||
|
errored = append_syntax_error(parse_errors, source_start, UNKNOWN_BUILTIN_ERR_MSG,
|
||||||
|
command.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errored;
|
||||||
|
}
|
||||||
|
|
||||||
parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
||||||
parse_error_list_t *out_errors,
|
parse_error_list_t *out_errors,
|
||||||
bool allow_incomplete,
|
bool allow_incomplete,
|
||||||
@ -1112,12 +1208,12 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
|||||||
&parse_errors);
|
&parse_errors);
|
||||||
|
|
||||||
if (allow_incomplete) {
|
if (allow_incomplete) {
|
||||||
for (size_t i = 0; i < parse_errors.size(); i++) {
|
size_t idx = parse_errors.size();
|
||||||
if (parse_errors.at(i).code == parse_error_tokenizer_unterminated_quote) {
|
while (idx--) {
|
||||||
|
if (parse_errors.at(idx).code == parse_error_tokenizer_unterminated_quote) {
|
||||||
// Remove this error, since we don't consider it a real error.
|
// Remove this error, since we don't consider it a real error.
|
||||||
has_unclosed_quote = true;
|
has_unclosed_quote = true;
|
||||||
parse_errors.erase(parse_errors.begin() + i);
|
parse_errors.erase(parse_errors.begin() + idx);
|
||||||
i--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1185,100 +1281,9 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (node.type == symbol_plain_statement) {
|
} else if (node.type == symbol_plain_statement) {
|
||||||
using namespace grammar;
|
tnode_t<grammar::plain_statement> pst{&node_tree, &node};
|
||||||
tnode_t<plain_statement> pst{&node_tree, &node};
|
errored |=
|
||||||
// In a few places below, we want to know if we are in a pipeline.
|
detect_errors_in_plain_statement(buff_src, node_tree, pst, &parse_errors);
|
||||||
tnode_t<statement> st =
|
|
||||||
pst.try_get_parent<decorated_statement>().try_get_parent<statement>();
|
|
||||||
const bool is_in_pipeline = statement_is_in_pipeline(st, true /* count first */);
|
|
||||||
|
|
||||||
// We need to know the decoration.
|
|
||||||
const enum parse_statement_decoration_t decoration = get_decoration(pst);
|
|
||||||
|
|
||||||
// Check that we don't try to pipe through exec.
|
|
||||||
if (is_in_pipeline && decoration == parse_statement_decoration_exec) {
|
|
||||||
errored = append_syntax_error(&parse_errors, node.source_start, EXEC_ERR_MSG,
|
|
||||||
L"exec");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maybe_t<wcstring> mcommand = command_for_plain_statement(pst, buff_src)) {
|
|
||||||
wcstring command = std::move(*mcommand);
|
|
||||||
// Check that we can expand the command.
|
|
||||||
if (!expand_one(command,
|
|
||||||
EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS,
|
|
||||||
NULL)) {
|
|
||||||
// TODO: leverage the resulting errors.
|
|
||||||
errored = append_syntax_error(&parse_errors, node.source_start,
|
|
||||||
ILLEGAL_CMD_ERR_MSG, command.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that pipes are sound.
|
|
||||||
if (!errored && parser_is_pipe_forbidden(command) && is_in_pipeline) {
|
|
||||||
errored = append_syntax_error(&parse_errors, node.source_start,
|
|
||||||
EXEC_ERR_MSG, command.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that we don't return from outside a function. But we allow it if it's
|
|
||||||
// 'return --help'.
|
|
||||||
if (!errored && command == L"return") {
|
|
||||||
bool found_function = false;
|
|
||||||
for (const parse_node_t *ancestor = &node; ancestor != nullptr;
|
|
||||||
ancestor = node_tree.get_parent(*ancestor)) {
|
|
||||||
auto fh = tnode_t<block_statement>::try_create(&node_tree, ancestor)
|
|
||||||
.child<0>()
|
|
||||||
.try_get_child<function_header, 0>();
|
|
||||||
if (fh) {
|
|
||||||
found_function = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found_function && !first_argument_is_help(pst, buff_src)) {
|
|
||||||
errored = append_syntax_error(&parse_errors, node.source_start,
|
|
||||||
INVALID_RETURN_ERR_MSG);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that we don't break or continue from outside a loop.
|
|
||||||
if (!errored && (command == L"break" || command == L"continue")) {
|
|
||||||
// Walk up until we hit a 'for' or 'while' loop. If we hit a function first,
|
|
||||||
// stop the search; we can't break an outer loop from inside a function.
|
|
||||||
// This is a little funny because we can't tell if it's a 'for' or 'while'
|
|
||||||
// loop from the ancestor alone; we need the header. That is, we hit a
|
|
||||||
// block_statement, and have to check its header.
|
|
||||||
bool found_loop = false;
|
|
||||||
for (const parse_node_t *ancestor = &node; ancestor != nullptr;
|
|
||||||
ancestor = node_tree.get_parent(*ancestor)) {
|
|
||||||
tnode_t<block_header> bh =
|
|
||||||
tnode_t<block_statement>::try_create(&node_tree, ancestor)
|
|
||||||
.child<0>();
|
|
||||||
if (bh.try_get_child<while_header, 0>() ||
|
|
||||||
bh.try_get_child<for_header, 0>()) {
|
|
||||||
// This is a loop header, so we can break or continue.
|
|
||||||
found_loop = true;
|
|
||||||
break;
|
|
||||||
} else if (bh.try_get_child<function_header, 0>()) {
|
|
||||||
// This is a function header, so we cannot break or
|
|
||||||
// continue. We stop our search here.
|
|
||||||
found_loop = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found_loop && !first_argument_is_help(pst, buff_src)) {
|
|
||||||
errored = append_syntax_error(
|
|
||||||
&parse_errors, node.source_start,
|
|
||||||
(command == L"break" ? INVALID_BREAK_ERR_MSG
|
|
||||||
: INVALID_CONTINUE_ERR_MSG));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that we don't do an invalid builtin (issue #1252).
|
|
||||||
if (!errored && decoration == parse_statement_decoration_builtin &&
|
|
||||||
!builtin_exists(command)) {
|
|
||||||
errored = append_syntax_error(&parse_errors, node.source_start,
|
|
||||||
UNKNOWN_BUILTIN_ERR_MSG, command.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user