omena-transform-passes 0.2.0

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

use crate::helpers::tokens::{is_comment_token, skip_whitespace_tokens, token_end, token_start};

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct StaticCssModulesValueDefinition {
    pub(crate) name: String,
    pub(crate) value: String,
    pub(crate) start: usize,
    pub(crate) end: usize,
}

pub(crate) fn collect_static_local_css_modules_value_definitions(
    tokens: &[LexedToken],
) -> Vec<StaticCssModulesValueDefinition> {
    let mut definitions = Vec::new();
    let mut depth = 0usize;
    let mut index = 0;

    while index < tokens.len() {
        match tokens[index].kind {
            SyntaxKind::AtKeyword
                if depth == 0 && tokens[index].text.eq_ignore_ascii_case("@value") =>
            {
                let Some((definition, next_index)) =
                    parse_static_local_css_modules_value_definition(tokens, index)
                else {
                    index += 1;
                    continue;
                };
                definitions.push(definition);
                index = next_index;
                continue;
            }
            SyntaxKind::LeftBrace => depth += 1,
            SyntaxKind::RightBrace => depth = depth.saturating_sub(1),
            _ => {}
        }
        index += 1;
    }

    definitions
}

fn parse_static_local_css_modules_value_definition(
    tokens: &[LexedToken],
    at_value_index: usize,
) -> Option<(StaticCssModulesValueDefinition, usize)> {
    let mut index = skip_whitespace_tokens(tokens, at_value_index + 1, tokens.len());
    let name_token = tokens.get(index)?;
    if name_token.kind != SyntaxKind::Ident {
        return None;
    }
    let name = name_token.text.clone();

    index = skip_whitespace_tokens(tokens, index + 1, tokens.len());
    if tokens.get(index)?.kind != SyntaxKind::Colon {
        return None;
    }

    let value_start = index + 1;
    index += 1;
    while index < tokens.len() {
        match tokens[index].kind {
            SyntaxKind::Semicolon => {
                let value_tokens = tokens[value_start..index].iter().collect::<Vec<_>>();
                if value_tokens.is_empty()
                    || value_tokens.iter().any(|token| {
                        is_comment_token(token.kind) || token.kind == SyntaxKind::AtKeyword
                    })
                {
                    return None;
                }
                let value = value_tokens
                    .iter()
                    .map(|token| token.text.as_str())
                    .collect::<String>()
                    .trim()
                    .to_string();
                return Some((
                    StaticCssModulesValueDefinition {
                        name,
                        value,
                        start: token_start(&tokens[at_value_index]),
                        end: token_end(&tokens[index]),
                    },
                    index + 1,
                ));
            }
            SyntaxKind::LeftBrace | SyntaxKind::RightBrace => return None,
            _ => index += 1,
        }
    }

    None
}