mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-28 13:53:10 +00:00
Allow newlines after && and ||
We do the same for pipes (#1285). This matches POSIX sh behavior.
This commit is contained in:
parent
e6616d7017
commit
b947e360db
6 changed files with 62 additions and 5 deletions
|
@ -28,7 +28,8 @@ Notable improvements and fixes
|
||||||
Syntax changes and new commands
|
Syntax changes and new commands
|
||||||
-------------------------------
|
-------------------------------
|
||||||
- A new ``fish_is_root_user`` simplifies checking for superuser privilege, largely for use in prompts (#7031).
|
- A new ``fish_is_root_user`` simplifies checking for superuser privilege, largely for use in prompts (#7031).
|
||||||
- Range limits in index range expansions like ``$x[$start..$end]`` may be omitted: ``$start`` and ``$end`` default to 1 and -1 (the last item) respectively.
|
- Range limits in index range expansions like ``$x[$start..$end]`` may be omitted: ``$start`` and ``$end`` default to 1 and -1 (the last item) respectively.
|
||||||
|
- Logical operators ``&&`` and ``||`` can be followed by newlines before their right operand, matching POSIX shells.
|
||||||
|
|
||||||
Scripting improvements
|
Scripting improvements
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
@ -730,11 +730,12 @@ struct job_conjunction_continuation_t final
|
||||||
: public branch_t<type_t::job_conjunction_continuation> {
|
: public branch_t<type_t::job_conjunction_continuation> {
|
||||||
// The && or || token.
|
// The && or || token.
|
||||||
token_t<parse_token_type_t::andand, parse_token_type_t::oror> conjunction;
|
token_t<parse_token_type_t::andand, parse_token_type_t::oror> conjunction;
|
||||||
|
maybe_newlines_t newlines;
|
||||||
|
|
||||||
// The job itself.
|
// The job itself.
|
||||||
job_t job;
|
job_t job;
|
||||||
|
|
||||||
FIELDS(conjunction, job)
|
FIELDS(conjunction, newlines, job)
|
||||||
};
|
};
|
||||||
|
|
||||||
// An andor_job just wraps a job, but requires that the job have an 'and' or 'or' job_decorator.
|
// An andor_job just wraps a job, but requires that the job have an 'and' or 'or' job_decorator.
|
||||||
|
|
|
@ -1190,6 +1190,18 @@ static void test_parser() {
|
||||||
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
err(L"bogus boolean statement error not detected on line %d", __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (detect_errors(L"true && ") != PARSER_TEST_INCOMPLETE) {
|
||||||
|
err(L"unterminated conjunction not reported properly");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detect_errors(L"true && \n true")) {
|
||||||
|
err(L"newline after && reported as error");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detect_errors(L"true || \n") != PARSER_TEST_INCOMPLETE) {
|
||||||
|
err(L"unterminated conjunction not reported properly");
|
||||||
|
}
|
||||||
|
|
||||||
say(L"Testing basic evaluation");
|
say(L"Testing basic evaluation");
|
||||||
|
|
||||||
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
|
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
|
||||||
|
@ -4344,7 +4356,7 @@ static void test_new_parser_correctness() {
|
||||||
{L"true || false; and true", true},
|
{L"true || false; and true", true},
|
||||||
{L"true || ||", false},
|
{L"true || ||", false},
|
||||||
{L"|| true", false},
|
{L"|| true", false},
|
||||||
{L"true || \n\n false", false},
|
{L"true || \n\n false", true},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto &test : parser_tests) {
|
for (const auto &test : parser_tests) {
|
||||||
|
|
|
@ -1215,6 +1215,10 @@ parser_test_error_bits_t parse_util_detect_errors(const ast::ast_t &ast, const w
|
||||||
// detecting job_continuations that have source for pipes but not the statement.
|
// detecting job_continuations that have source for pipes but not the statement.
|
||||||
bool has_unclosed_pipe = false;
|
bool has_unclosed_pipe = false;
|
||||||
|
|
||||||
|
// Whether we encounter a missing job, i.e. a newline after && or ||. This is found by
|
||||||
|
// detecting job_conjunction_continuations that have source for && or || but not the job.
|
||||||
|
bool has_unclosed_conjunction = false;
|
||||||
|
|
||||||
// Expand all commands.
|
// Expand all commands.
|
||||||
// Verify 'or' and 'and' not used inside pipelines.
|
// Verify 'or' and 'and' not used inside pipelines.
|
||||||
// Verify pipes via parser_is_pipe_forbidden.
|
// Verify pipes via parser_is_pipe_forbidden.
|
||||||
|
@ -1229,6 +1233,12 @@ parser_test_error_bits_t parse_util_detect_errors(const ast::ast_t &ast, const w
|
||||||
if (!jc->pipe.unsourced && !jc->statement.try_source_range().has_value()) {
|
if (!jc->pipe.unsourced && !jc->statement.try_source_range().has_value()) {
|
||||||
has_unclosed_pipe = true;
|
has_unclosed_pipe = true;
|
||||||
}
|
}
|
||||||
|
} else if (const auto *jcc = node.try_as<job_conjunction_continuation_t>()) {
|
||||||
|
// Somewhat clumsy way of checking for a job without source in a conjunction.
|
||||||
|
// See if our conjunction operator (&& or ||) has source but our job does not.
|
||||||
|
if (!jcc->conjunction.unsourced && !jcc->job.try_source_range().has_value()) {
|
||||||
|
has_unclosed_conjunction = true;
|
||||||
|
}
|
||||||
} else if (const argument_t *arg = node.try_as<argument_t>()) {
|
} else if (const argument_t *arg = node.try_as<argument_t>()) {
|
||||||
const wcstring &arg_src = arg->source(buff_src, &storage);
|
const wcstring &arg_src = arg->source(buff_src, &storage);
|
||||||
res |= parse_util_detect_errors_in_argument(*arg, arg_src, out_errors);
|
res |= parse_util_detect_errors_in_argument(*arg, arg_src, out_errors);
|
||||||
|
@ -1262,7 +1272,8 @@ parser_test_error_bits_t parse_util_detect_errors(const ast::ast_t &ast, const w
|
||||||
|
|
||||||
if (errored) res |= PARSER_TEST_ERROR;
|
if (errored) res |= PARSER_TEST_ERROR;
|
||||||
|
|
||||||
if (has_unclosed_block || has_unclosed_pipe) res |= PARSER_TEST_INCOMPLETE;
|
if (has_unclosed_block || has_unclosed_pipe || has_unclosed_conjunction)
|
||||||
|
res |= PARSER_TEST_INCOMPLETE;
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,24 @@ end
|
||||||
# CHECK: 3
|
# CHECK: 3
|
||||||
# CHECK: 4
|
# CHECK: 4
|
||||||
|
|
||||||
|
# Newlines
|
||||||
|
true &&
|
||||||
|
echo newline after conjunction
|
||||||
|
# CHECK: newline after conjunction
|
||||||
|
false ||
|
||||||
|
echo newline after disjunction
|
||||||
|
# CHECK: newline after disjunction
|
||||||
|
|
||||||
|
true &&
|
||||||
|
|
||||||
|
echo empty lines after conjunction
|
||||||
|
# CHECK: empty lines after conjunction
|
||||||
|
|
||||||
|
true &&
|
||||||
|
# can have comments here!
|
||||||
|
echo comment after conjunction
|
||||||
|
# CHECK: comment after conjunction
|
||||||
|
|
||||||
# --help works
|
# --help works
|
||||||
builtin and --help >/dev/null
|
builtin and --help >/dev/null
|
||||||
echo $status
|
echo $status
|
||||||
|
|
|
@ -298,9 +298,23 @@ printf '%s\n' "a; and b" | $fish_indent
|
||||||
printf '%s\n' "a;" "and b" | $fish_indent
|
printf '%s\n' "a;" "and b" | $fish_indent
|
||||||
#CHECK: a
|
#CHECK: a
|
||||||
#CHECK: and b
|
#CHECK: and b
|
||||||
printf '%s\n' "a" "; and b" | $fish_indent
|
printf '%s\n' a "; and b" | $fish_indent
|
||||||
#CHECK: a
|
#CHECK: a
|
||||||
#CHECK: and b
|
#CHECK: and b
|
||||||
printf '%s\n' "a; b" | $fish_indent
|
printf '%s\n' "a; b" | $fish_indent
|
||||||
#CHECK: a
|
#CHECK: a
|
||||||
#CHECK: b
|
#CHECK: b
|
||||||
|
|
||||||
|
echo 'foo &&
|
||||||
|
#
|
||||||
|
bar' | $fish_indent
|
||||||
|
#CHECK: {{^}}foo &&
|
||||||
|
#CHECK: {{^}}#
|
||||||
|
#CHECK: {{^}}bar
|
||||||
|
|
||||||
|
echo 'command 1 |
|
||||||
|
command 1 cont ||
|
||||||
|
command 2' | $fish_indent
|
||||||
|
#CHECK: {{^}}command 1 |
|
||||||
|
#CHECK: {{^ }}command 1 cont ||
|
||||||
|
#CHECK: {{^}}command 2
|
||||||
|
|
Loading…
Reference in a new issue