malva 0.16.0

Configurable, smart and fast CSS, SCSS, Sass and Less formatter.
Documentation
use crate::{LineBounds, config::LanguageOptions, doc_gen::format_comment};
use raffia::{
    Syntax,
    token::{Comment, CommentKind},
};
use std::{array, iter::Peekable, mem};
use tiny_pretty::Doc;

pub(crate) struct Ctx<'a, 's: 'a> {
    pub source: Option<&'s str>,
    pub syntax: Syntax,
    pub options: &'a LanguageOptions,
    pub comments: &'a [Comment<'s>],
    pub indent_width: usize,
    pub line_bounds: LineBounds,
}

impl<'a, 's> Ctx<'a, 's> {
    pub(crate) fn get_comments_between(
        &self,
        start: usize,
        end: usize,
    ) -> impl Iterator<Item = &Comment<'s>> + Clone {
        debug_assert!(start <= end);

        self.comments
            .iter()
            .filter(move |comment| comment.span.start >= start && comment.span.end <= end)
    }

    pub(crate) fn start_spaced_comments(
        &'a self,
        comments: impl Iterator<Item = &'a Comment<'s>> + 'a,
    ) -> impl Iterator<Item = Doc<'s>> + 'a {
        comments
            .scan(CommentKind::Block, |prev_kind, comment| {
                Some(
                    [
                        match mem::replace(prev_kind, comment.kind.clone()) {
                            CommentKind::Block => Doc::soft_line(),
                            CommentKind::Line => Doc::nil(),
                        },
                        format_comment(comment, self),
                        match comment.kind {
                            CommentKind::Block => Doc::nil(),
                            CommentKind::Line => Doc::hard_line(),
                        },
                    ]
                    .into_iter(),
                )
            })
            .flatten()
    }

    pub(crate) fn start_spaced_comments_without_last_hard_line(
        &'a self,
        comments: impl Iterator<Item = &'a Comment<'s>> + 'a,
        has_last_line_comment: &'a mut bool,
    ) -> impl Iterator<Item = Doc<'s>> + 'a {
        StartSpacedCommentsWithoutLastHardLine {
            ctx: self,
            iter: comments.peekable(),
            prev_kind: CommentKind::Block,
            has_last_line_comment,
        }
        .flatten()
    }

    pub(crate) fn end_spaced_comments(
        &'a self,
        comments: impl Iterator<Item = &'a Comment<'s>> + 'a,
    ) -> impl Iterator<Item = Doc<'s>> + 'a {
        comments.flat_map(|comment| {
            [
                format_comment(comment, self),
                match comment.kind {
                    CommentKind::Block => Doc::soft_line(),
                    CommentKind::Line => Doc::hard_line(),
                },
            ]
            .into_iter()
        })
    }

    pub(crate) fn end_spaced_comments_without_last_space(
        &'a self,
        comments: impl Iterator<Item = &'a Comment<'s>> + 'a,
        comment_end: &'a mut Option<usize>,
    ) -> impl Iterator<Item = Doc<'s>> + 'a {
        EndSpacedCommentsWithoutLastSpace {
            ctx: self,
            iter: comments.peekable(),
            comment_end,
        }
        .flatten()
    }

    pub(crate) fn unspaced_comments(
        &'a self,
        comments: impl Iterator<Item = &'a Comment<'s>> + 'a,
    ) -> impl Iterator<Item = Doc<'s>> + 'a {
        comments.filter_map(|comment| match comment.kind {
            CommentKind::Block => Some(format_comment(comment, self)),
            CommentKind::Line => None,
        })
    }
}

struct StartSpacedCommentsWithoutLastHardLine<'a, 's, I>
where
    's: 'a,
    I: Iterator<Item = &'a Comment<'s>>,
{
    ctx: &'a Ctx<'a, 's>,
    iter: Peekable<I>,
    prev_kind: CommentKind,
    has_last_line_comment: &'a mut bool,
}

impl<'a, 's, I> Iterator for StartSpacedCommentsWithoutLastHardLine<'a, 's, I>
where
    's: 'a,
    I: Iterator<Item = &'a Comment<'s>>,
{
    type Item = array::IntoIter<Doc<'s>, 3>;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|comment| {
            let has_next = self.iter.peek().is_some();
            if !has_next {
                *self.has_last_line_comment = comment.kind == CommentKind::Line;
            }

            [
                match mem::replace(&mut self.prev_kind, comment.kind.clone()) {
                    CommentKind::Block => Doc::space(),
                    CommentKind::Line => Doc::nil(),
                },
                format_comment(comment, self.ctx),
                match comment.kind {
                    CommentKind::Line if has_next => Doc::hard_line(),
                    _ => Doc::nil(),
                },
            ]
            .into_iter()
        })
    }
}

struct EndSpacedCommentsWithoutLastSpace<'a, 's, I>
where
    's: 'a,
    I: Iterator<Item = &'a Comment<'s>>,
{
    ctx: &'a Ctx<'a, 's>,
    iter: Peekable<I>,
    comment_end: &'a mut Option<usize>,
}

impl<'a, 's, I> Iterator for EndSpacedCommentsWithoutLastSpace<'a, 's, I>
where
    's: 'a,
    I: Iterator<Item = &'a Comment<'s>>,
{
    type Item = array::IntoIter<Doc<'s>, 2>;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|comment| {
            let is_last = self.iter.peek().is_none();
            if is_last && comment.kind == CommentKind::Block {
                *self.comment_end = Some(comment.span.end);
            }

            [
                format_comment(comment, self.ctx),
                match comment.kind {
                    CommentKind::Block => {
                        if is_last {
                            Doc::nil()
                        } else {
                            Doc::soft_line()
                        }
                    }
                    CommentKind::Line => Doc::hard_line(),
                },
            ]
            .into_iter()
        })
    }
}