Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion crates/shuck-parser/src/parser/commands/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ impl<'a> Parser<'a> {
let patterns = match self.parse_case_patterns() {
Ok(patterns) => patterns,
Err(err) => {
self.recover_to_case_end();
self.pop_depth();
return Err(err);
}
Expand Down Expand Up @@ -106,6 +107,10 @@ impl<'a> Parser<'a> {
};

for span in pattern_spans {
if !span.slice(self.input).contains('(') {
continue;
}

let mut features = self.zsh_glob_parse_features_at(span.start.offset);
if self.dialect != ShellDialect::Zsh {
features.bare_groups = true;
Expand Down Expand Up @@ -133,6 +138,7 @@ impl<'a> Parser<'a> {
}

pub(super) fn parse_posix_case_patterns(&mut self) -> Result<Vec<Pattern>> {
let start = self.current_span.start;
if self.at(TokenKind::LeftParen) {
self.advance();
}
Expand All @@ -151,13 +157,38 @@ impl<'a> Parser<'a> {
}

if !self.at(TokenKind::RightParen) {
return Err(self.error("expected ')' after case pattern"));
let error = self.error("expected ')' after case pattern");
self.recover_to_case_arm_delimiter(start);
return Err(error);
}
self.advance();

Ok(patterns)
}

pub(super) fn recover_to_case_arm_delimiter(&mut self, start: Position) {
if let Some(delimiter_span) = self.scan_zsh_case_arm_delimiter(start)
&& delimiter_span.end.offset > self.current_span.start.offset
{
self.skip_raw_to_offset(delimiter_span.end.offset);
return;
}

if let Some(separator_offset) =
self.raw_recovery_separator_offset(self.current_span.start.offset)
{
self.skip_raw_to_offset(separator_offset);
} else {
self.skip_raw_to_offset(self.input.len());
}
}

pub(super) fn recover_to_case_end(&mut self) {
if let Some(end_offset) = self.raw_case_end_offset(self.current_span.start.offset) {
self.skip_raw_to_offset(end_offset);
}
}

pub(super) fn parse_zsh_case_patterns(&mut self) -> Result<Vec<Pattern>> {
let (pattern_spans, delimiter_span) = self.scan_zsh_case_pattern_spans()?;
let patterns = pattern_spans
Expand Down
30 changes: 28 additions & 2 deletions crates/shuck-parser/src/parser/commands/conditionals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ impl<'a> Parser<'a> {
self.advance(); // consume '[['
self.skip_conditional_newlines();

let expression = self.parse_conditional_or(false)?;
let expression = match self.parse_conditional_or(false) {
Ok(expression) => expression,
Err(err) => {
self.recover_to_conditional_end(left_bracket_span.start);
return Err(err);
}
};
self.skip_conditional_newlines();

let right_bracket_span = match self.current_token_kind {
Expand All @@ -21,7 +27,11 @@ impl<'a> Parser<'a> {
"unexpected end of input in [[ ]]".to_string(),
));
}
_ => return Err(self.error("expected ']]' to close conditional expression")),
_ => {
let error = self.error("expected ']]' to close conditional expression");
self.recover_to_conditional_end(left_bracket_span.start);
return Err(error);
}
};

Ok(CompoundCommand::Conditional(ConditionalCommand {
Expand All @@ -32,6 +42,22 @@ impl<'a> Parser<'a> {
}))
}

pub(super) fn recover_to_conditional_end(&mut self, start: Position) {
let search_start = self.current_span.start.offset.max(start.offset);
if let Some(end_offset) = self.raw_conditional_close_offset(search_start) {
self.skip_raw_to_offset(end_offset);
return;
}

if let Some(separator_offset) =
self.raw_recovery_separator_offset(self.current_span.start.offset)
{
self.skip_raw_to_offset(separator_offset);
} else {
self.skip_raw_to_offset(self.input.len());
}
}

pub(super) fn skip_conditional_newlines(&mut self) {
while self.at(TokenKind::Newline) {
self.advance();
Expand Down
Loading
Loading