diff --git a/src/parse_constants.h b/src/parse_constants.h index 8f116b441..d9abf4863 100644 --- a/src/parse_constants.h +++ b/src/parse_constants.h @@ -174,9 +174,10 @@ enum parse_error_code_t { parse_error_tokenizer_unterminated_escape, parse_error_tokenizer_other, - parse_error_unbalancing_end, // end outside of block - parse_error_unbalancing_else, // else outside of if - parse_error_unbalancing_case // case outside of switch + parse_error_unbalancing_end, // end outside of block + parse_error_unbalancing_else, // else outside of if + parse_error_unbalancing_case, // case outside of switch + parse_error_bare_variable_assignment, // a=b without command }; enum { PARSER_TEST_ERROR = 1, PARSER_TEST_INCOMPLETE = 2 }; diff --git a/src/parse_tree.cpp b/src/parse_tree.cpp index 4454fbc74..406872379 100644 --- a/src/parse_tree.cpp +++ b/src/parse_tree.cpp @@ -53,6 +53,15 @@ wcstring parse_error_t::describe_with_prefix(const wcstring &src, const wcstring if (skip_caret && this->text.empty()) return L""; wcstring result = prefix; + if (code == parse_error_bare_variable_assignment) { + wcstring assignment_src = src.substr(this->source_start, this->source_length); + maybe_t<size_t> equals_pos = variable_assignment_equals_pos(assignment_src); + assert(equals_pos); + wcstring variable = assignment_src.substr(0, *equals_pos); + wcstring value = assignment_src.substr(*equals_pos + 1); + append_format(result, ERROR_BAD_COMMAND_ASSIGN_ERR_MSG, variable.c_str(), value.c_str()); + return result; + } result.append(this->text); if (skip_caret || source_start >= src.size() || source_start + source_length > src.size()) { return result; @@ -918,8 +927,45 @@ void parse_ll_t::accept_tokens(parse_token_t token1, parse_token_t token2) { production_for_token(stack_elem.type, token1, token2, &tag); node.tag = tag; if (production == nullptr) { - parse_error_failed_production(stack_elem, token1); - // The above sets fatal_errored, which ends the loop. + tnode_t<grammar::variable_assignments> variable_assignments; + if (const parse_node_t *parent = nodes.get_parent(node)) { + switch (parent->type) { + default: + break; + case symbol_job: + variable_assignments = + tnode_t<grammar::job>(&nodes, parent) + .try_get_child<grammar::variable_assignments, 0>(); + break; + case symbol_job_continuation: + variable_assignments = + tnode_t<grammar::job_continuation>(&nodes, parent) + .try_get_child<grammar::variable_assignments, 2>(); + break; + case symbol_not_statement: + variable_assignments = + tnode_t<grammar::not_statement>(&nodes, parent) + .try_get_child<grammar::variable_assignments, 1>(); + break; + } + } + tnode_t<grammar::variable_assignment> variable_assignment; + tnode_t<grammar::tok_string> assignment_tok; + if (variable_assignments && + (variable_assignment = + variable_assignments.try_get_child<grammar::variable_assignment, 0>()) && + (assignment_tok = variable_assignment.try_get_child<grammar::tok_string, 0>())) { + parse_token_t token(parse_token_type_string); + token.source_start = assignment_tok.source_range()->start; + token.source_length = assignment_tok.source_range()->length; + parse_error(token, parse_error_bare_variable_assignment, + L" " /* won't be printed but must be non-empty, see + describe_with_prefix */ + ); + } else { + parse_error_failed_production(stack_elem, token1); + } + // The above set fatal_errored, which ends the loop. } else { bool is_terminate = (token1.type == parse_token_type_terminate); diff --git a/tests/checks/variable-assignment.fish b/tests/checks/variable-assignment.fish index 1d3b60248..a683a6b3c 100644 --- a/tests/checks/variable-assignment.fish +++ b/tests/checks/variable-assignment.fish @@ -76,3 +76,11 @@ complete -C'a=b xalias ' alias envxalias='a=b x' complete -C'a=b envxalias ' # CHECK: arg + +# Eval invalid grammar to allow fish to parse this file +eval 'a=(echo b)' +# CHECKERR: {{.*}}: Unsupported use of '='. In fish, please use 'set a (echo b)'. +eval ': | a=b' +# CHECKERR: {{.*}}: Unsupported use of '='. In fish, please use 'set a b'. +eval 'not a=b' +# CHECKERR: {{.*}}: Unsupported use of '='. In fish, please use 'set a b'.