omena-scss-eval 0.3.0

Oracle-first SCSS and Less value evaluator rail for Omena CSS
Documentation
use omena_parser::LexedToken;
use omena_syntax::SyntaxKind;

pub(super) fn tokens_between_are_trivia(tokens: &[LexedToken], start: usize, end: usize) -> bool {
    start <= end
        && end <= tokens.len()
        && tokens[start..end]
            .iter()
            .all(|token| is_trivia_token(token.kind))
}

pub(super) fn token_range_start(token: &LexedToken) -> usize {
    token.range.start().into()
}

pub(super) fn token_range_end(token: &LexedToken) -> usize {
    token.range.end().into()
}

pub(super) fn matching_right_paren_token_index(
    tokens: &[LexedToken],
    left_paren_index: usize,
) -> Option<usize> {
    let mut depth = 0usize;
    for (index, token) in tokens.iter().enumerate().skip(left_paren_index) {
        match token.kind {
            SyntaxKind::LeftParen => depth += 1,
            SyntaxKind::RightParen => {
                depth = depth.checked_sub(1)?;
                if depth == 0 {
                    return Some(index);
                }
            }
            _ => {}
        }
    }
    None
}

pub(super) fn matching_right_brace_token_index(
    tokens: &[LexedToken],
    left_brace_index: usize,
) -> Option<usize> {
    let mut depth = 0usize;
    for (index, token) in tokens.iter().enumerate().skip(left_brace_index) {
        match token.kind {
            SyntaxKind::LeftBrace => depth += 1,
            SyntaxKind::RightBrace => {
                depth = depth.checked_sub(1)?;
                if depth == 0 {
                    return Some(index);
                }
            }
            _ => {}
        }
    }
    None
}

pub(super) fn matching_block_end_token_index(
    tokens: &[LexedToken],
    block_start_index: usize,
) -> Option<usize> {
    match tokens.get(block_start_index)?.kind {
        SyntaxKind::LeftBrace => matching_right_brace_token_index(tokens, block_start_index),
        SyntaxKind::SassIndent => matching_sass_dedent_token_index(tokens, block_start_index),
        _ => None,
    }
}

pub(super) fn next_block_start_token_index(
    tokens: &[LexedToken],
    mut index: usize,
) -> Option<usize> {
    while index < tokens.len() {
        match tokens[index].kind {
            SyntaxKind::LeftBrace | SyntaxKind::SassIndent => return Some(index),
            SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon => return None,
            _ => index += 1,
        }
    }
    None
}

fn matching_sass_dedent_token_index(
    tokens: &[LexedToken],
    sass_indent_index: usize,
) -> Option<usize> {
    let mut depth = 0usize;
    for (index, token) in tokens.iter().enumerate().skip(sass_indent_index) {
        match token.kind {
            SyntaxKind::SassIndent => depth += 1,
            SyntaxKind::SassDedent => {
                depth = depth.checked_sub(1)?;
                if depth == 0 {
                    return Some(index);
                }
            }
            _ => {}
        }
    }
    None
}

pub(super) fn next_non_trivia_token_index(
    tokens: &[LexedToken],
    mut index: usize,
) -> Option<usize> {
    while tokens
        .get(index)
        .is_some_and(|token| is_trivia_token(token.kind))
    {
        index += 1;
    }
    (index < tokens.len()).then_some(index)
}

pub(super) fn declaration_end_token_index(
    tokens: &[LexedToken],
    mut index: usize,
) -> Option<usize> {
    let mut paren_depth = 0usize;
    while index < tokens.len() {
        match tokens[index].kind {
            SyntaxKind::LeftParen => paren_depth += 1,
            SyntaxKind::RightParen => paren_depth = paren_depth.checked_sub(1)?,
            SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon | SyntaxKind::SassIndent
                if paren_depth == 0 =>
            {
                return Some(index);
            }
            _ => {}
        }
        index += 1;
    }
    None
}

const fn is_trivia_token(kind: SyntaxKind) -> bool {
    matches!(
        kind,
        SyntaxKind::Whitespace
            | SyntaxKind::LineComment
            | SyntaxKind::BlockComment
            | SyntaxKind::ScssSilentComment
            | SyntaxKind::SassIndentedNewline
    )
}