omena-scss-eval 0.3.0

Oracle-first SCSS and Less value evaluator rail for Omena CSS
Documentation
use std::collections::BTreeSet;

use omena_parser::{StyleDialect, lex};
use omena_syntax::SyntaxKind;

use super::{
    StaticScssFunctionDeclaration, canonical_static_scss_function_name,
    static_stylesheet_skip_trivia_tokens,
};

pub(super) fn extend_static_scss_used_function_dependencies(
    used_declaration_names: &mut BTreeSet<String>,
    declarations: &[StaticScssFunctionDeclaration],
) {
    let declaration_names = declarations
        .iter()
        .map(|declaration| canonical_static_scss_function_name(declaration.name.as_str()))
        .collect::<BTreeSet<_>>();
    let mut changed = true;
    while changed {
        changed = false;
        for declaration in declarations {
            let declaration_name = canonical_static_scss_function_name(declaration.name.as_str());
            if !used_declaration_names.contains(&declaration_name) {
                continue;
            }
            for dependency_name in
                static_scss_function_dependency_names(declaration, &declaration_names)
            {
                if used_declaration_names.insert(dependency_name) {
                    changed = true;
                }
            }
        }
    }
}

fn static_scss_function_dependency_names(
    declaration: &StaticScssFunctionDeclaration,
    declaration_names: &BTreeSet<String>,
) -> Vec<String> {
    declaration
        .return_clauses
        .iter()
        .flat_map(|clause| {
            std::iter::once(clause.value.as_str()).chain(clause.condition.as_deref())
        })
        .chain(
            declaration
                .local_variables
                .iter()
                .map(|local_variable| local_variable.value.as_str()),
        )
        .flat_map(|value| static_scss_callable_names_in_value(value, declaration_names))
        .collect()
}

fn static_scss_callable_names_in_value(
    value: &str,
    declaration_names: &BTreeSet<String>,
) -> Vec<String> {
    let lexed = lex(value, StyleDialect::Scss);
    let tokens = lexed.tokens();
    tokens
        .iter()
        .enumerate()
        .filter_map(|(index, token)| {
            if token.kind != SyntaxKind::Ident || token.text.eq_ignore_ascii_case("if") {
                return None;
            }
            let canonical_name = canonical_static_scss_function_name(token.text.as_str());
            (declaration_names.contains(&canonical_name)
                && tokens
                    .get(static_stylesheet_skip_trivia_tokens(tokens, index + 1))
                    .is_some_and(|candidate| candidate.kind == SyntaxKind::LeftParen))
            .then_some(canonical_name)
        })
        .collect()
}

pub(super) fn static_scss_function_value_contains_any_callable(value: &str) -> bool {
    let lexed = lex(value, StyleDialect::Scss);
    let tokens = lexed.tokens();
    tokens.iter().enumerate().any(|(index, token)| {
        token.kind == SyntaxKind::Ident
            && !token.text.eq_ignore_ascii_case("if")
            && tokens
                .get(static_stylesheet_skip_trivia_tokens(tokens, index + 1))
                .is_some_and(|candidate| candidate.kind == SyntaxKind::LeftParen)
    })
}

pub(super) fn static_scss_function_value_contains_callable_to(value: &str, name: &str) -> bool {
    let canonical_name = canonical_static_scss_function_name(name);
    let lexed = lex(value, StyleDialect::Scss);
    let tokens = lexed.tokens();
    tokens.iter().enumerate().any(|(index, token)| {
        token.kind == SyntaxKind::Ident
            && canonical_static_scss_function_name(token.text.as_str()) == canonical_name
            && tokens
                .get(static_stylesheet_skip_trivia_tokens(tokens, index + 1))
                .is_some_and(|candidate| candidate.kind == SyntaxKind::LeftParen)
    })
}