omena-transform-passes 0.1.14

Transform pass registry and DAG planner for Omena CSS
Documentation
use omena_parser::LexedToken;
use omena_syntax::SyntaxKind;

use super::tokens::{
    is_comment_token, matching_right_brace_index, token_end, token_start, tokens_between_byte_range,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SimpleRuleSlice {
    pub(crate) selector: String,
    pub(crate) block: String,
    pub(crate) start: usize,
    pub(crate) end: usize,
    pub(crate) block_start: usize,
    pub(crate) block_end: usize,
    pub(crate) context_start: usize,
    pub(crate) context_end: usize,
}

pub(crate) fn collect_top_level_ordinary_rule_slices(
    source: &str,
    tokens: &[LexedToken],
) -> Vec<SimpleRuleSlice> {
    let mut rules = Vec::new();
    let mut depth = 0usize;
    let mut top_level_prelude_start = 0usize;
    let mut index = 0;

    while index < tokens.len() {
        match tokens[index].kind {
            SyntaxKind::LeftBrace => {
                if depth == 0
                    && let Some(close_index) = matching_right_brace_index(tokens, index)
                    && is_ordinary_top_level_rule_prelude(tokens, top_level_prelude_start, index)
                    && !tokens[index + 1..close_index].iter().any(|token| {
                        matches!(token.kind, SyntaxKind::LeftBrace | SyntaxKind::RightBrace)
                            || is_comment_token(token.kind)
                    })
                    && let Some(start) =
                        first_non_trivia_token_start(tokens, top_level_prelude_start, index)
                {
                    let selector = source[start..token_start(&tokens[index])]
                        .trim()
                        .to_string();
                    let block = source
                        [token_end(&tokens[index])..token_start(&tokens[close_index])]
                        .trim()
                        .to_string();
                    if !selector.is_empty() && !block.is_empty() {
                        rules.push(SimpleRuleSlice {
                            selector,
                            block,
                            start,
                            end: token_end(&tokens[close_index]),
                            block_start: token_start(&tokens[index]),
                            block_end: token_start(&tokens[close_index]),
                            context_start: 0,
                            context_end: source.len(),
                        });
                    }
                    index = close_index + 1;
                    top_level_prelude_start = index;
                    continue;
                }
                depth += 1;
            }
            SyntaxKind::RightBrace => {
                depth = depth.saturating_sub(1);
                if depth == 0 {
                    top_level_prelude_start = index + 1;
                }
            }
            SyntaxKind::Semicolon if depth == 0 => {
                top_level_prelude_start = index + 1;
            }
            _ => {}
        }
        index += 1;
    }

    rules
}

pub(crate) fn collect_declaration_ordinary_rule_slices(
    source: &str,
    tokens: &[LexedToken],
) -> Vec<SimpleRuleSlice> {
    let mut rules = Vec::new();
    let mut depth = 0usize;
    let mut prelude_starts = vec![0usize];
    let mut rule_contexts = vec![(0usize, source.len())];
    let mut index = 0;

    while index < tokens.len() {
        match tokens[index].kind {
            SyntaxKind::LeftBrace => {
                let prelude_start = prelude_starts.get(depth).copied().unwrap_or(0);
                let parent_context = rule_contexts
                    .get(depth)
                    .copied()
                    .unwrap_or((0, source.len()));
                if let Some(close_index) = matching_right_brace_index(tokens, index)
                    && is_ordinary_rule_prelude(tokens, prelude_start, index)
                    && !tokens[index + 1..close_index].iter().any(|token| {
                        matches!(token.kind, SyntaxKind::LeftBrace | SyntaxKind::RightBrace)
                            || is_comment_token(token.kind)
                    })
                    && let Some(start) = first_non_trivia_token_start(tokens, prelude_start, index)
                {
                    let selector = source[start..token_start(&tokens[index])]
                        .trim()
                        .to_string();
                    let block = source
                        [token_end(&tokens[index])..token_start(&tokens[close_index])]
                        .trim()
                        .to_string();
                    if !selector.is_empty() && !block.is_empty() {
                        rules.push(SimpleRuleSlice {
                            selector,
                            block,
                            start,
                            end: token_end(&tokens[close_index]),
                            block_start: token_start(&tokens[index]),
                            block_end: token_start(&tokens[close_index]),
                            context_start: parent_context.0,
                            context_end: parent_context.1,
                        });
                    }
                }
                let child_context = matching_right_brace_index(tokens, index)
                    .map(|close_index| {
                        (token_start(&tokens[index]), token_end(&tokens[close_index]))
                    })
                    .unwrap_or((token_start(&tokens[index]), token_end(&tokens[index])));
                depth += 1;
                set_prelude_start(&mut prelude_starts, depth, index + 1);
                set_rule_context(&mut rule_contexts, depth, child_context);
            }
            SyntaxKind::RightBrace => {
                depth = depth.saturating_sub(1);
                set_prelude_start(&mut prelude_starts, depth, index + 1);
            }
            SyntaxKind::Semicolon => {
                set_prelude_start(&mut prelude_starts, depth, index + 1);
            }
            _ => {}
        }
        index += 1;
    }

    rules
}

pub(crate) fn collect_ordinary_rule_selector_slices(
    source: &str,
    tokens: &[LexedToken],
) -> Vec<SimpleRuleSlice> {
    let mut rules = Vec::new();
    let mut depth = 0usize;
    let mut prelude_starts = vec![0usize];
    let mut index = 0;

    while index < tokens.len() {
        match tokens[index].kind {
            SyntaxKind::LeftBrace => {
                let prelude_start = prelude_starts.get(depth).copied().unwrap_or(0);
                if let Some(close_index) = matching_right_brace_index(tokens, index)
                    && is_ordinary_rule_prelude(tokens, prelude_start, index)
                    && let Some(start) = first_non_trivia_token_start(tokens, prelude_start, index)
                {
                    let selector = source[start..token_start(&tokens[index])]
                        .trim()
                        .to_string();
                    if !selector.is_empty() {
                        rules.push(SimpleRuleSlice {
                            selector,
                            block: source
                                [token_end(&tokens[index])..token_start(&tokens[close_index])]
                                .trim()
                                .to_string(),
                            start,
                            end: token_end(&tokens[close_index]),
                            block_start: token_start(&tokens[index]),
                            block_end: token_start(&tokens[close_index]),
                            context_start: 0,
                            context_end: source.len(),
                        });
                    }
                }
                depth += 1;
                set_prelude_start(&mut prelude_starts, depth, index + 1);
            }
            SyntaxKind::RightBrace => {
                depth = depth.saturating_sub(1);
                set_prelude_start(&mut prelude_starts, depth, index + 1);
            }
            SyntaxKind::Semicolon => {
                set_prelude_start(&mut prelude_starts, depth, index + 1);
            }
            _ => {}
        }
        index += 1;
    }

    rules
}

pub(crate) fn rule_gap_is_whitespace_only(tokens: &[LexedToken], start: usize, end: usize) -> bool {
    tokens_between_byte_range(tokens, start, end)
        .iter()
        .all(|token| token.kind == SyntaxKind::Whitespace)
}

pub(crate) fn set_prelude_start(prelude_starts: &mut Vec<usize>, depth: usize, start: usize) {
    if prelude_starts.len() <= depth {
        prelude_starts.resize(depth + 1, start);
    }
    prelude_starts[depth] = start;
}

pub(crate) fn is_ordinary_rule_prelude(
    tokens: &[LexedToken],
    start: usize,
    end_exclusive: usize,
) -> bool {
    let prelude = &tokens[start..end_exclusive];
    prelude
        .iter()
        .any(|token| !is_comment_token(token.kind) && token.kind != SyntaxKind::Whitespace)
        && prelude
            .iter()
            .all(|token| token.kind != SyntaxKind::AtKeyword && !is_comment_token(token.kind))
}

pub(crate) fn is_ordinary_top_level_rule_prelude(
    tokens: &[LexedToken],
    start: usize,
    end_exclusive: usize,
) -> bool {
    is_ordinary_rule_prelude(tokens, start, end_exclusive)
}

pub(crate) fn first_non_trivia_token_start(
    tokens: &[LexedToken],
    start: usize,
    end_exclusive: usize,
) -> Option<usize> {
    tokens[start..end_exclusive]
        .iter()
        .find(|token| !is_comment_token(token.kind) && token.kind != SyntaxKind::Whitespace)
        .map(token_start)
}

fn set_rule_context(
    rule_contexts: &mut Vec<(usize, usize)>,
    depth: usize,
    context: (usize, usize),
) {
    if rule_contexts.len() <= depth {
        rule_contexts.resize(depth + 1, context);
    }
    rule_contexts[depth] = context;
}