#[cfg(test)] #[allow(clippy::unwrap_used, reason = "these are tests. they may unwrap.")] mod test; use crate::{ error::{ErrorKind, Errors, SyntaxErrorKind}, syntax::CommandPart, }; use super::{CommandPartKind, PipelineElement, PipelineElementKind}; pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec> { let mut errs = Vec::new(); if let Err(e_span) = check_missing_streamer(syntax) { errs.push(Errors::new_single( ErrorKind::SyntaxError(SyntaxErrorKind::MissingStreamer), e_span, )); } if let Err(err_locs) = check_missing_filters(syntax) { errs.push(Errors::new( ErrorKind::SyntaxError(SyntaxErrorKind::MissingFilter), err_locs, )); } if let Err(e_span) = check_missing_sink(syntax) { errs.push(Errors::new_single( ErrorKind::SyntaxError(SyntaxErrorKind::MissingSink), e_span, )); } if let Err(e_span) = check_literal_as_sink(syntax) { errs.push(Errors::new_single( ErrorKind::SyntaxError(SyntaxErrorKind::LiteralAsSink), e_span, )); } if let Err(err_locs) = check_literal_as_filter(syntax) { errs.push(Errors::new( ErrorKind::SyntaxError(SyntaxErrorKind::LiteralAsFilter), err_locs, )); } if let Err(e_span) = check_literal_with_args(syntax) { errs.push(Errors::new_single( ErrorKind::SyntaxError(SyntaxErrorKind::LiteralWithArgs), e_span, )); } if errs.is_empty() { Ok(()) } else { Err(errs) } } fn check_missing_streamer(syntax: &[PipelineElement]) -> Result<(), logos::Span> { if let Some(&PipelineElement { kind: PipelineElementKind::Pipe, ref span, }) = syntax.first() { Err(span.clone()) } else { Ok(()) } } fn check_missing_filters(syntax: &[PipelineElement]) -> Result<(), Vec> { let mut missing_filter_locs = Vec::new(); for i in 0..syntax.len() { if let ( Some(&PipelineElement { kind: PipelineElementKind::Pipe, ref span, }), Some(&PipelineElement { kind: PipelineElementKind::Pipe, span: ref span1, }), ) = (syntax.get(i), syntax.get(i + 1)) { missing_filter_locs.push(span.start..span1.end); } } if missing_filter_locs.is_empty() { Ok(()) } else { Err(missing_filter_locs) } } fn check_missing_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> { if let Some(&PipelineElement { kind: PipelineElementKind::Pipe, ref span, }) = syntax.last() { Err(span.clone()) } else { Ok(()) } } fn check_literal_as_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> { let last_block: Option<&[PipelineElement]> = syntax .split(|PipelineElement { kind, .. }| kind == &PipelineElementKind::Pipe) .last(); // there HAS to be a better way to do this... this is HORRIBLE if let Some(last_block) = last_block { if let Some(PipelineElement { kind: PipelineElementKind::Command(parts), span, }) = last_block.first() { if let Some(first_part) = parts.first() { if matches!( first_part, CommandPart { kind: CommandPartKind::Word(_), .. } ) { Ok(()) } else { Err(span.clone()) } } else { Ok(()) } } else { Ok(()) } } else { Ok(()) } } fn check_literal_as_filter(syntax: &[PipelineElement]) -> Result<(), Vec> { let errs = syntax .iter() .take(syntax.len() - 1) .skip(1) .filter(|element| { !matches!( element, PipelineElement { kind: PipelineElementKind::Pipe, .. } ) }) .map(|item| { let PipelineElement { kind: PipelineElementKind::Command(c), span, } = item else { return None; }; let Some(CommandPart { kind, .. }) = c.first() else { return None; }; if matches!(kind, CommandPartKind::Word(_)) { None } else { Some(span) } }) .filter_map(|err| err.map(Clone::clone)) .collect::>(); if errs.is_empty() { Ok(()) } else { Err(errs) } } fn check_literal_with_args(syntax: &[PipelineElement]) -> Result<(), logos::Span> { if let Some(PipelineElement { kind: PipelineElementKind::Command(c), span, }) = syntax.first() { if c.len() > 1 && !matches!( c.first(), Some(CommandPart { kind: CommandPartKind::Word(_), .. }) ) { Err(span.clone()) } else { Ok(()) } } else { Ok(()) } }