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::{ParsedVariableFact, ParsedVariableFactKind, StyleDialect, lex};
use omena_syntax::SyntaxKind;

use super::{
    STATIC_STYLESHEET_VALUE_RESOLUTION_FUEL_LIMIT,
    declarations::{
        collect_static_less_property_declarations, collect_static_less_variable_declarations,
        collect_static_scss_variable_declarations,
        static_less_mixin_declaration_ranges_from_declarations,
        static_scss_function_declaration_ranges_from_declarations,
        static_scss_mixin_declaration_ranges_from_declarations,
    },
    less_detached_rulesets::{
        collect_static_less_detached_ruleset_accessors, collect_static_less_detached_ruleset_calls,
        collect_static_less_detached_ruleset_declarations,
        static_less_detached_ruleset_ranges_from_accessors,
        static_less_detached_ruleset_ranges_from_calls,
        static_less_detached_ruleset_ranges_from_declarations,
    },
    less_mixin_values::static_less_value_is_detached_ruleset_reference,
    less_mixins::{
        collect_static_less_mixin_accessors, collect_static_less_mixin_calls,
        collect_static_less_mixin_declarations, static_less_mixin_accessor_ranges_from_accessors,
        static_less_mixin_ranges_from_calls,
    },
    less_variables::{
        resolve_static_less_property_abstract_value_in_scope,
        resolve_static_less_variable_abstract_value_in_scope,
    },
    model::{
        OmenaScssEvalStaticValueResolutionV0, StaticScssFunctionResolutionContext,
        StaticScssMixinIncludeCall, StaticStylesheetScope,
    },
    names::static_scss_variable_names_equal,
    scopes::{
        static_stylesheet_position_is_inside_scoped_declaration,
        static_stylesheet_position_is_inside_scss_declaration,
        static_stylesheet_scope_for_position,
    },
    scss_calls::collect_static_scss_mixin_include_calls,
    scss_declarations::{
        collect_static_scss_function_declarations, collect_static_scss_mixin_declarations,
    },
    scss_function_edits::collect_static_scss_function_value_resolution_values,
    scss_loop_control_flow::collect_static_scss_loop_candidate_ranges,
    scss_variables::resolve_static_scss_variable_abstract_value_at_position,
    tokens::{
        parser_text_size_to_usize, static_stylesheet_position_is_inside_ranges,
        static_stylesheet_token_end, static_stylesheet_token_start,
    },
    value_resolution_model::static_value_resolution_record,
    variable_references::{
        static_stylesheet_position_is_scss_module_member_reference,
        static_stylesheet_variable_reference_is_named_argument_label,
    },
};

pub(super) fn summarize_static_scss_value_resolution_values(
    style_source: &str,
    dialect: StyleDialect,
    variable_facts: &[ParsedVariableFact],
    scopes: &[StaticStylesheetScope],
    truthiness_evaluator: fn(&str) -> Option<bool>,
) -> Option<Vec<OmenaScssEvalStaticValueResolutionV0>> {
    let declarations =
        collect_static_scss_variable_declarations(style_source, dialect, variable_facts, scopes)?;
    let lexed = lex(style_source, dialect);
    let tokens = lexed.tokens();
    let function_declarations =
        collect_static_scss_function_declarations(style_source, dialect, tokens)?;
    let mixin_declarations = collect_static_scss_mixin_declarations(style_source, dialect, tokens)?;
    let function_declaration_ranges =
        static_scss_function_declaration_ranges_from_declarations(function_declarations.as_slice());
    let mixin_declaration_ranges =
        static_scss_mixin_declaration_ranges_from_declarations(mixin_declarations.as_slice());
    let mut loop_excluded_ranges = function_declaration_ranges.clone();
    loop_excluded_ranges.extend(mixin_declaration_ranges.iter().copied());
    let loop_candidate_ranges = collect_static_scss_loop_candidate_ranges(
        style_source,
        dialect,
        tokens,
        &loop_excluded_ranges,
    );
    let mixin_include_calls = collect_static_scss_mixin_include_calls(
        style_source,
        dialect,
        tokens,
        mixin_declarations.as_slice(),
    )?;
    let mut values = Vec::new();
    for fact in variable_facts {
        if fact.kind != ParsedVariableFactKind::ScssReference {
            continue;
        }
        let reference_start = parser_text_size_to_usize(fact.range.start().into());
        if static_stylesheet_position_is_scss_module_member_reference(style_source, reference_start)
        {
            continue;
        }
        if static_scss_reference_is_content_parameter_binding(
            fact.name.as_str(),
            reference_start,
            mixin_include_calls.as_slice(),
        ) {
            continue;
        }
        if static_stylesheet_position_is_inside_scss_declaration(&declarations, reference_start)
            || static_stylesheet_position_is_inside_ranges(
                reference_start,
                &function_declaration_ranges,
            )
            || static_stylesheet_position_is_inside_ranges(
                reference_start,
                &mixin_declaration_ranges,
            )
            || static_stylesheet_position_is_inside_ranges(reference_start, &loop_candidate_ranges)
        {
            continue;
        }
        let reference_end = parser_text_size_to_usize(fact.range.end().into());
        let mut stack = BTreeSet::new();
        let resolution = resolve_static_scss_variable_abstract_value_at_position(
            fact.name.as_str(),
            reference_start,
            scopes,
            &declarations,
            &mut stack,
            STATIC_STYLESHEET_VALUE_RESOLUTION_FUEL_LIMIT,
        );
        values.push(static_value_resolution_record(
            fact.name.as_str(),
            reference_start,
            reference_end,
            style_source
                .get(reference_start..reference_end)
                .unwrap_or(""),
            resolution,
        ));
    }
    let active_functions = BTreeSet::new();
    let function_resolution_context = StaticScssFunctionResolutionContext {
        dialect,
        declarations: &function_declarations,
        mixin_declarations: &mixin_declarations,
        scopes,
        variable_declarations: &declarations,
        active_functions: &active_functions,
        truthiness_evaluator,
    };
    values.extend(collect_static_scss_function_value_resolution_values(
        style_source,
        tokens,
        function_resolution_context,
    )?);
    Some(values)
}

fn static_scss_reference_is_content_parameter_binding(
    name: &str,
    reference_start: usize,
    mixin_include_calls: &[StaticScssMixinIncludeCall],
) -> bool {
    mixin_include_calls.iter().any(|call| {
        call.content_parameters
            .iter()
            .any(|parameter| static_scss_variable_names_equal(parameter, name))
            && (call
                .content_parameter_range
                .is_some_and(|range| position_is_inside_range(reference_start, range))
                || call
                    .content_body_range
                    .is_some_and(|range| position_is_inside_range(reference_start, range)))
    })
}

fn position_is_inside_range(position: usize, range: (usize, usize)) -> bool {
    position >= range.0 && position < range.1
}

pub(super) fn summarize_static_less_value_resolution_values(
    style_source: &str,
    variable_facts: &[ParsedVariableFact],
    scopes: &[StaticStylesheetScope],
) -> Option<Vec<OmenaScssEvalStaticValueResolutionV0>> {
    let lexed = lex(style_source, StyleDialect::Less);
    let tokens = lexed.tokens();
    let mixin_declarations = collect_static_less_mixin_declarations(style_source, tokens)?;
    let mixin_declaration_ranges =
        static_less_mixin_declaration_ranges_from_declarations(mixin_declarations.as_slice());
    let detached_rulesets =
        collect_static_less_detached_ruleset_declarations(style_source, tokens, scopes)?;
    let detached_ruleset_ranges =
        static_less_detached_ruleset_ranges_from_declarations(detached_rulesets.as_slice());
    let detached_ruleset_calls = collect_static_less_detached_ruleset_calls(style_source, tokens)?;
    let detached_ruleset_call_ranges =
        static_less_detached_ruleset_ranges_from_calls(detached_ruleset_calls.as_slice());
    let detached_ruleset_accessors =
        collect_static_less_detached_ruleset_accessors(style_source, tokens)?;
    let detached_ruleset_accessor_ranges =
        static_less_detached_ruleset_ranges_from_accessors(detached_ruleset_accessors.as_slice());
    let mixin_calls = collect_static_less_mixin_calls(style_source, tokens).unwrap_or_default();
    let mixin_call_ranges = static_less_mixin_ranges_from_calls(mixin_calls.as_slice());
    let mixin_accessors = collect_static_less_mixin_accessors(style_source, tokens)?;
    let mixin_accessor_ranges = static_less_mixin_accessor_ranges_from_accessors(&mixin_accessors);
    let mut variable_excluded_ranges = mixin_declaration_ranges.clone();
    variable_excluded_ranges.extend(detached_ruleset_ranges.iter().copied());
    variable_excluded_ranges.extend(detached_ruleset_accessor_ranges.iter().copied());
    variable_excluded_ranges.extend(mixin_accessor_ranges.iter().copied());
    let declarations = collect_static_less_variable_declarations(
        style_source,
        variable_facts,
        scopes,
        &variable_excluded_ranges,
    )?;
    let property_declarations =
        collect_static_less_property_declarations(style_source, tokens, scopes)?;
    let mut values = Vec::new();
    for fact in variable_facts {
        if fact.kind != ParsedVariableFactKind::LessReference {
            continue;
        }
        let reference_start = parser_text_size_to_usize(fact.range.start().into());
        let reference_end = parser_text_size_to_usize(fact.range.end().into());
        if static_stylesheet_variable_reference_is_named_argument_label(
            style_source,
            reference_start,
            reference_end,
        ) {
            continue;
        }
        if static_stylesheet_position_is_inside_scoped_declaration(&declarations, reference_start) {
            continue;
        }
        if static_stylesheet_position_is_inside_ranges(reference_start, &mixin_declaration_ranges) {
            continue;
        }
        if static_stylesheet_position_is_inside_ranges(reference_start, &detached_ruleset_ranges) {
            continue;
        }
        if static_stylesheet_position_is_inside_ranges(
            reference_start,
            &detached_ruleset_call_ranges,
        ) {
            continue;
        }
        if static_stylesheet_position_is_inside_ranges(
            reference_start,
            &detached_ruleset_accessor_ranges,
        ) {
            continue;
        }
        if static_stylesheet_position_is_inside_ranges(reference_start, &mixin_accessor_ranges) {
            continue;
        }
        let reference_scope_id = static_stylesheet_scope_for_position(scopes, reference_start)?;
        if static_stylesheet_position_is_inside_ranges(reference_start, &mixin_call_ranges)
            && static_less_value_is_detached_ruleset_reference(
                fact.name.as_str(),
                reference_scope_id,
                scopes,
                detached_rulesets.as_slice(),
            )
        {
            continue;
        }
        let mut stack = BTreeSet::new();
        let resolution = resolve_static_less_variable_abstract_value_in_scope(
            fact.name.as_str(),
            reference_scope_id,
            scopes,
            &declarations,
            &property_declarations,
            detached_rulesets.as_slice(),
            &mut stack,
            STATIC_STYLESHEET_VALUE_RESOLUTION_FUEL_LIMIT,
        );
        values.push(static_value_resolution_record(
            fact.name.as_str(),
            reference_start,
            reference_end,
            style_source
                .get(reference_start..reference_end)
                .unwrap_or(""),
            resolution,
        ));
    }
    for token in tokens {
        if token.kind != SyntaxKind::LessPropertyVariableToken {
            continue;
        }
        let reference_start = static_stylesheet_token_start(token);
        if static_stylesheet_position_is_inside_scoped_declaration(&declarations, reference_start) {
            continue;
        }
        if static_stylesheet_position_is_inside_ranges(reference_start, &mixin_declaration_ranges) {
            continue;
        }
        if static_stylesheet_position_is_inside_ranges(reference_start, &detached_ruleset_ranges) {
            continue;
        }
        if static_stylesheet_position_is_inside_ranges(
            reference_start,
            &detached_ruleset_accessor_ranges,
        ) {
            continue;
        }
        if static_stylesheet_position_is_inside_ranges(reference_start, &mixin_accessor_ranges) {
            continue;
        }
        let reference_scope_id = static_stylesheet_scope_for_position(scopes, reference_start)?;
        let mut stack = BTreeSet::new();
        let resolution = resolve_static_less_property_abstract_value_in_scope(
            token.text.as_str(),
            reference_scope_id,
            scopes,
            &property_declarations,
            &mut stack,
            STATIC_STYLESHEET_VALUE_RESOLUTION_FUEL_LIMIT,
        );
        values.push(static_value_resolution_record(
            token.text.as_str(),
            reference_start,
            static_stylesheet_token_end(token),
            token.text.as_str(),
            resolution,
        ));
    }
    Some(values)
}