shuck-formatter 0.0.41

Shell script formatter with configurable style options
Documentation
use super::*;

impl<'source, 'facts, S> ShellRenderer<'source, 'facts, S>
where
    S: StreamSink,
{
    pub(super) fn can_inline_body(&self, commands: &StmtSeq, enclosing_span: Span) -> bool {
        self.can_inline_body_with_upper_bound(
            commands,
            enclosing_span,
            Some(enclosing_span.end.offset),
        )
    }

    pub(super) fn can_inline_body_with_upper_bound(
        &self,
        commands: &StmtSeq,
        enclosing_span: Span,
        upper_bound: Option<usize>,
    ) -> bool {
        let [command] = commands.as_slice() else {
            return false;
        };
        if matches!(command.terminator, Some(StmtTerminator::Background(_)))
            || !self.can_inline_stmt(command)
        {
            return false;
        }

        if self.facts().sequence(commands, upper_bound).has_comments() {
            return false;
        }

        self.options().compact_layout()
            || stmt_span(command).start.line == enclosing_span.start.line
    }

    pub(super) fn can_inline_group(&self, commands: &StmtSeq, open_char: char) -> bool {
        let [command] = commands.as_slice() else {
            return false;
        };

        self.can_inline_stmt(command)
            && self.can_inline_body(commands, stmt_span(command))
            && (stmt_span(command).start.line == stmt_span(command).end.line
                || self.group_delimiters_attach_to_wrapped_body(commands, open_char))
    }

    pub(super) fn group_has_inline_source_shape(
        &self,
        commands: &StmtSeq,
        open_char: char,
    ) -> bool {
        self.facts().group_was_inline_in_source(commands)
            || self.group_delimiters_attach_to_wrapped_body(commands, open_char)
    }

    pub(super) fn group_delimiters_attach_to_wrapped_body(
        &self,
        commands: &StmtSeq,
        _open_char: char,
    ) -> bool {
        let (Some(first), Some(last)) = (commands.first(), commands.last()) else {
            return false;
        };
        let Some(group_span) = self
            .facts()
            .sequence(commands, None)
            .group_attachment_span()
        else {
            return false;
        };

        group_span.start.line == stmt_format_span(first).start.line
            && group_span.end.line == stmt_format_span(last).end.line
    }

    pub(super) fn can_inline_source_line_subshell(
        &self,
        commands: &StmtSeq,
        upper_bound: Option<usize>,
    ) -> bool {
        let [stmt] = commands.as_slice() else {
            return false;
        };
        if self.facts().sequence(commands, upper_bound).has_comments()
            || self.facts().stmt(stmt).preserve_verbatim()
            || self.facts().stmt(stmt).has_trailing_comment()
        {
            return false;
        }
        if commands.span.start.line != commands.span.end.line {
            return false;
        }

        true
    }

    pub(super) fn can_format_multiline_subshell_inline(
        &self,
        commands: &StmtSeq,
        upper_bound: Option<usize>,
    ) -> bool {
        let [stmt] = commands.as_slice() else {
            return false;
        };
        if self
            .facts()
            .sequence(commands, upper_bound)
            .group_open_suffix_span()
            .is_some()
            || self.facts().sequence(commands, upper_bound).has_comments()
        {
            return false;
        }
        let Some(group_span) = self
            .facts()
            .sequence(commands, upper_bound)
            .group_attachment_span()
        else {
            return false;
        };
        let group_source = group_span.slice(self.source());
        if !group_source.contains('\n')
            || group_source.contains("\\\n")
            || group_source.contains("\\\r\n")
        {
            return false;
        }

        let source = self.source();
        let first_start = stmt_span(stmt).start.offset.min(source.len());
        let open_end = group_span.start.offset.saturating_add('('.len_utf8());
        if source
            .get(open_end..first_start)
            .is_none_or(|between| between.contains('\n'))
        {
            return false;
        }

        let close_offset = group_close_offset(source, group_span, upper_bound, ')', ')'.len_utf8());
        let stmt_end = stmt_span(stmt)
            .end
            .offset
            .min(close_offset)
            .min(source.len());
        source
            .get(stmt_end..close_offset)
            .is_some_and(|between| !between.contains('\n'))
    }

    pub(super) fn can_inline_stmt(&self, stmt: &Stmt) -> bool {
        let stmt_facts = self.facts().stmt(stmt);
        if stmt_facts.preserve_verbatim() || stmt_facts.has_trailing_comment() {
            return false;
        }

        matches!(
            &stmt.command,
            Command::Simple(_)
                | Command::Builtin(_)
                | Command::Decl(_)
                | Command::Function(_)
                | Command::Binary(_)
                | Command::Compound(
                    CompoundCommand::Conditional(_)
                        | CompoundCommand::Arithmetic(_)
                        | CompoundCommand::Time(_)
                )
        )
    }
}