omena-query 0.3.0

Omena query boundary over CME producer query fragments
Documentation
//! Config-state worklist golden (the worklist replaces the RawAllPaths super-poly closure
//! enumeration). The oracle corpus had NO config-bearing diamonds. Invariants vs re-baseline:
//! - the DIAGNOSTIC bytes (sassModuleInstanceIdentity incl "in N hop(s)", sassModuleConfigurationConflict)
//!   are byte-identical to RawAllPaths;
//! - the only structural change is that the worklist keeps the MIN-depth representative per
//!   config-state, so non-min-depth duplicate paths collapse. Same-depth convergence via distinct
//!   parents is preserved (so the count matches RawAllPaths for a min-depth-symmetric diamond).

use super::support::sample_input;
use crate::{
    OmenaQueryStyleSourceInputV0, summarize_omena_query_style_diagnostics_for_workspace_file,
    summarize_omena_query_style_semantic_graph_batch_from_sources,
};

fn sources(entries: &[(&str, &str)]) -> Vec<OmenaQueryStyleSourceInputV0> {
    entries
        .iter()
        .map(|(path, source)| OmenaQueryStyleSourceInputV0 {
            style_path: path.to_string(),
            style_source: source.to_string(),
        })
        .collect()
}

// EQUAL-config convergence (a reaches d via b AND via c, both `with red`, BOTH at depth 2) +
// DIFFERENT-config (via e `with green`). The two red paths are distinct min-depth edges reached via
// distinct parents, so BOTH survive (the worklist only drops non-min-depth duplicates) — count is
// identical to RawAllPaths. The green path drives a ConfigurationConflict. The two distinct
// identity_keys each emit ONE identity diagnostic.
const DIAMOND: [(&str, &str); 5] = [
    (
        "/tmp/d.scss",
        "$brand: blue !default; .base { color: $brand; }",
    ),
    ("/tmp/b.scss", "@forward \"./d\" with ($brand: red);"),
    ("/tmp/c.scss", "@forward \"./d\" with ($brand: red);"),
    ("/tmp/e.scss", "@forward \"./d\" with ($brand: green);"),
    (
        "/tmp/a.scss",
        "@use \"./b\" as b; @use \"./c\" as c; @use \"./e\" as e;",
    ),
];

#[test]
fn golden_equal_and_different_config_convergence_diamond() -> Result<(), &'static str> {
    let input = sample_input();
    let batch = summarize_omena_query_style_semantic_graph_batch_from_sources(DIAMOND, &input);
    let resolution = &batch.sass_module_resolution;

    // INVARIANT: both `with red` a->d edges are at min depth 2 via distinct parents (b, c), so both
    // survive — the worklist keeps every min-depth representative, identical to RawAllPaths here.
    let a_to_d_red = resolution
        .graph_closure_edges
        .iter()
        .filter(|edge| {
            edge.from_style_path == "/tmp/a.scss"
                && edge.target_style_path == "/tmp/d.scss"
                && edge.configuration_signature.contains("brand=3:red")
        })
        .count();
    assert_eq!(
        a_to_d_red, 2,
        "both min-depth red convergence edges survive"
    );
    assert_eq!(
        resolution.graph_closure_edge_count, 9,
        "min-depth-symmetric diamond: count matches RawAllPaths"
    );

    // INVARIANT (must stay byte-identical after the worklist): the diagnostic bytes.
    let diagnostics = summarize_omena_query_style_diagnostics_for_workspace_file(
        "/tmp/a.scss",
        sources(&DIAMOND).as_slice(),
        &[],
        &[],
        None,
    )
    .ok_or("workspace diagnostics")?;
    let identity = diagnostics
        .diagnostics
        .iter()
        .filter(|d| d.code == "sassModuleInstanceIdentity")
        .collect::<Vec<_>>();
    assert_eq!(
        identity.len(),
        2,
        "one identity per distinct config: {identity:?}"
    );
    assert!(identity.iter().all(|d| d.message.contains("in 2 hop(s)")));
    assert!(
        identity.iter().any(|d| d.message.contains("brand=3:red"))
            && identity.iter().any(|d| d.message.contains("brand=5:green"))
    );
    let conflict = diagnostics
        .diagnostics
        .iter()
        .filter(|d| d.code == "sassModuleConfigurationConflict")
        .collect::<Vec<_>>();
    assert_eq!(conflict.len(), 1, "{conflict:?}");
    assert!(
        conflict[0].message.contains("2 different configurations")
            && conflict[0].message.contains("brand=3:red")
            && conflict[0].message.contains("brand=5:green")
    );
    Ok(())
}

// MIN-DEPTH-wins: a reaches the SAME configured d-red at two GRAPH depths (via x = depth 2, via
// y->x = depth 3) -> ONE config-state at two depths. The dedup key omits depth, so the diagnostic
// embeds the MIN depth ("in 2 hop(s)"). The worklist records the min depth and emits ONE edge -> the
// depth-3 edge collapses (count drops), the diagnostic stays "in 2 hop(s)".
const MIN_DEPTH: [(&str, &str); 4] = [
    (
        "/tmp/md_d.scss",
        "$brand: blue !default; .x { color: $brand; }",
    ),
    ("/tmp/md_x.scss", "@forward \"./md_d\" with ($brand: red);"),
    ("/tmp/md_y.scss", "@forward \"./md_x\";"),
    (
        "/tmp/md_a.scss",
        "@use \"./md_x\" as x; @use \"./md_y\" as y;",
    ),
];

#[test]
fn golden_min_depth_wins_over_deeper_same_config_path() -> Result<(), &'static str> {
    let input = sample_input();
    let batch = summarize_omena_query_style_semantic_graph_batch_from_sources(MIN_DEPTH, &input);
    let resolution = &batch.sass_module_resolution;

    // a reaches the SAME configured d-red at two graph depths (via x = depth 2, via y->x = depth 3);
    // the worklist keeps the min-depth representative only (RawAllPaths kept both {2,3}).
    let a_to_d_depths = resolution
        .graph_closure_edges
        .iter()
        .filter(|edge| {
            edge.from_style_path == "/tmp/md_a.scss"
                && edge.target_style_path == "/tmp/md_d.scss"
                && edge.configuration_signature.contains("brand=3:red")
        })
        .map(|edge| edge.depth)
        .collect::<std::collections::BTreeSet<_>>();
    assert_eq!(a_to_d_depths, [2].into_iter().collect());

    // INVARIANT: the identity diagnostic embeds the MIN depth ("in 2 hop(s)").
    let diagnostics = summarize_omena_query_style_diagnostics_for_workspace_file(
        "/tmp/md_a.scss",
        sources(&MIN_DEPTH).as_slice(),
        &[],
        &[],
        None,
    )
    .ok_or("workspace diagnostics")?;
    let identity = diagnostics
        .diagnostics
        .iter()
        .find(|d| {
            d.code == "sassModuleInstanceIdentity"
                && d.message.contains("/tmp/md_d.scss")
                && d.message.contains("hop(s)")
        })
        .ok_or("graph-reach identity diagnostic")?;
    assert!(
        identity.message.contains("in 2 hop(s)"),
        "min depth wins: {}",
        identity.message
    );
    Ok(())
}

// rule_ordinal / field FAN-OUT: two `@forward` of the same target that DIFFER (plain vs prefixed)
// are two distinct closure edges that must NOT collapse — the sentinel for a worklist that wrongly
// dedups on config-state alone (they share an empty config but differ on forward_prefix).
const FAN_OUT: [(&str, &str); 2] = [
    (
        "/tmp/fo_d.scss",
        "$brand: blue !default; .x { color: $brand; }",
    ),
    (
        "/tmp/fo_a.scss",
        "@forward \"./fo_d\"; @forward \"./fo_d\" as foo-*;",
    ),
];

#[test]
fn golden_distinct_forward_prefix_fan_out_does_not_collapse() {
    let input = sample_input();
    let batch = summarize_omena_query_style_semantic_graph_batch_from_sources(FAN_OUT, &input);
    let resolution = &batch.sass_module_resolution;

    let fo_a_to_d = resolution
        .graph_closure_edges
        .iter()
        .filter(|edge| {
            edge.from_style_path == "/tmp/fo_a.scss" && edge.target_style_path == "/tmp/fo_d.scss"
        })
        .map(|edge| edge.forward_prefix.clone())
        .collect::<std::collections::BTreeSet<_>>();
    // INVARIANT: both the plain (None) and prefixed (foo-) forward edges survive — the worklist
    // must NOT collapse same-config different-field edges.
    assert_eq!(
        fo_a_to_d,
        [None, Some("foo-".to_string())].into_iter().collect()
    );
    assert_eq!(resolution.graph_closure_edge_count, 2);
}