Skip to main content

omena_bridge/
lib.rs

1use engine_input_producers::EngineInputV2;
2use omena_semantic::{
3    CssModulesSemanticSummaryV0, DesignTokenSemanticSummaryV0, LosslessCstContractV0,
4    ParserBoundarySyntaxFactsV0, SelectorIdentityEngineSummaryV0, StyleSemanticBoundarySummaryV0,
5    StyleSemanticFactsV0, Stylesheet,
6};
7pub use omena_semantic::{
8    DesignTokenExternalDeclarationCandidateScopeV0, DesignTokenWorkspaceDeclarationFactV0,
9    ParserRangeV0 as OmenaBridgeParserRangeV0,
10};
11use serde::Serialize;
12
13mod bundler_config_alias;
14mod promotion_evidence;
15mod selector_references;
16mod source_evidence;
17mod source_imports;
18mod source_language;
19mod source_syntax;
20mod style_resolution;
21
22pub use bundler_config_alias::{
23    OmenaBridgeBundlerAliasUnrecognizedEntryV0, OmenaBridgeBundlerPathAliasMappingV0,
24    OmenaBridgeBundlerPathAliasSummaryV0, summarize_omena_bridge_bundler_path_aliases_for_config,
25};
26pub use promotion_evidence::{
27    SemanticPromotionEvidenceItemV0, SemanticPromotionEvidenceSummaryV0,
28    summarize_omena_bridge_promotion_evidence_with_source_input,
29    summarize_omena_bridge_semantic_promotion_evidence,
30};
31pub use selector_references::{
32    SelectorEditableDirectReferenceSiteV0, SelectorReferenceEngineSummaryV0,
33    SelectorReferenceSiteV0, SelectorReferenceSummaryV0,
34    summarize_omena_bridge_selector_reference_engine,
35};
36pub use source_evidence::{
37    BindingOriginEvidenceV0, CertaintyReasonEvidenceV0, ReferenceSiteIdentityEvidenceV0,
38    SourceInputPromotionEvidenceSummaryV0, StyleModuleEdgeEvidenceV0,
39    ValueDomainExplanationEvidenceV0, summarize_omena_bridge_source_input_evidence,
40};
41pub use source_imports::{
42    SourceImportDeclarationSummaryV0, SourceImportDeclarationV0,
43    summarize_omena_bridge_source_import_declarations,
44    summarize_omena_bridge_source_import_declarations_for_path,
45    summarize_omena_bridge_source_import_declarations_for_source_language,
46};
47pub use source_syntax::{
48    SourceImportedStyleBindingV0, SourceSelectorReferenceFactV0,
49    SourceSelectorReferenceMatchKindV0, SourceStylePropertyAccessFactV0, SourceSyntaxIndexV0,
50    SourceTypeFactTargetV0, canonicalize_source_selector_references,
51    collect_omena_bridge_vue_style_module_bindings, summarize_omena_bridge_source_syntax_index,
52    summarize_omena_bridge_source_syntax_index_for_source_language,
53};
54pub use style_resolution::{
55    OmenaBridgeStyleResolutionInputsV0, OmenaBridgeStyleResolutionSummaryV0,
56    generate_omena_bridge_sif_for_resolved_style_path,
57    load_omena_bridge_workspace_style_resolution_inputs,
58    resolve_omena_bridge_style_uri_for_specifier,
59    resolve_omena_bridge_style_uri_for_specifier_with_package_manifests,
60    resolve_omena_bridge_style_uri_for_specifier_with_resolution_inputs,
61    summarize_omena_bridge_style_resolution_boundary,
62};
63
64pub fn collect_omena_bridge_design_token_workspace_declarations(
65    style_path: &str,
66    sheet: &Stylesheet,
67) -> Vec<DesignTokenWorkspaceDeclarationFactV0> {
68    let parser_facts = omena_semantic::summarize_parser_contract_facts(sheet);
69    omena_semantic::collect_design_token_workspace_declarations(style_path, &parser_facts)
70}
71
72pub fn collect_omena_bridge_design_token_workspace_declarations_from_source(
73    style_path: &str,
74    style_source: &str,
75) -> Vec<DesignTokenWorkspaceDeclarationFactV0> {
76    let boundary = omena_semantic::summarize_omena_parser_style_semantic_boundary_from_source(
77        style_path,
78        style_source,
79    );
80    omena_semantic::collect_design_token_workspace_declarations(style_path, &boundary.parser_facts)
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct OmenaBridgeBoundarySummaryV0 {
86    pub schema_version: &'static str,
87    pub product: &'static str,
88    pub bridge_name: &'static str,
89    pub graph_product: &'static str,
90    pub delegated_semantic_boundary_product: &'static str,
91    pub selector_reference_product: &'static str,
92    pub source_input_evidence_product: &'static str,
93    pub binder_plugin_product: &'static str,
94    pub bridge_owned_surfaces: Vec<&'static str>,
95    pub cme_coupled_surfaces: Vec<&'static str>,
96    pub next_decoupling_targets: Vec<&'static str>,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
100#[serde(rename_all = "camelCase")]
101pub struct BinderPluginBoundarySummaryV0 {
102    pub schema_version: &'static str,
103    pub product: &'static str,
104    pub owner_crate: &'static str,
105    pub contract_name: &'static str,
106    pub external_plugin_abi_stable: bool,
107    pub default_plugin: BinderPluginSummaryV0,
108    pub built_in_plugins: Vec<BinderPluginSummaryV0>,
109    pub request_path_policy: Vec<&'static str>,
110    pub next_plugin_targets: Vec<&'static str>,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
114#[serde(rename_all = "camelCase")]
115pub struct BinderPluginSummaryV0 {
116    pub id: &'static str,
117    pub version: &'static str,
118    pub stability: &'static str,
119    pub domains: Vec<&'static str>,
120    pub owns_surfaces: Vec<&'static str>,
121    pub import_targets: Vec<&'static str>,
122    pub utility_targets: Vec<&'static str>,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
126#[serde(rename_all = "camelCase")]
127pub struct StyleSemanticGraphSummaryV0 {
128    pub schema_version: &'static str,
129    pub product: &'static str,
130    pub language: &'static str,
131    pub parser_facts: ParserBoundarySyntaxFactsV0,
132    pub semantic_facts: StyleSemanticFactsV0,
133    pub css_modules_semantics: CssModulesSemanticSummaryV0,
134    pub design_token_semantics: DesignTokenSemanticSummaryV0,
135    pub selector_identity_engine: SelectorIdentityEngineSummaryV0,
136    pub selector_reference_engine: SelectorReferenceEngineSummaryV0,
137    pub source_input_evidence: SourceInputPromotionEvidenceSummaryV0,
138    pub promotion_evidence: SemanticPromotionEvidenceSummaryV0,
139    pub lossless_cst_contract: LosslessCstContractV0,
140}
141
142pub fn summarize_omena_bridge_boundary() -> OmenaBridgeBoundarySummaryV0 {
143    OmenaBridgeBoundarySummaryV0 {
144        schema_version: "0",
145        product: "omena-bridge.cme-semantic-bridge",
146        bridge_name: "cme-semantic-bridge",
147        graph_product: "omena-semantic.style-semantic-graph",
148        delegated_semantic_boundary_product: "omena-semantic.style-semantic-boundary",
149        selector_reference_product: "omena-semantic.selector-references",
150        source_input_evidence_product: "omena-semantic.source-input-evidence",
151        binder_plugin_product: "omena-bridge.binder-plugin-boundary",
152        bridge_owned_surfaces: vec![
153            "styleSemanticGraph",
154            "styleSemanticGraphFromSource",
155            "omenaParserBackedStyleSemanticBoundaryFromSource",
156            "selectorReferenceEngine",
157            "designTokenWorkspaceDeclarationsFromSource",
158            "sourceInputEvidence",
159            "sourceImportDeclarations",
160            "styleResolution",
161            "sourceSyntaxIndex",
162            "promotionEvidenceWithSourceInput",
163            "binderPluginBoundary",
164        ],
165        cme_coupled_surfaces: vec![
166            "EngineInputV2",
167            "sourceInputEvidence",
168            "selectorReferenceEngine",
169            "promotionEvidenceWithSourceInput",
170            "styleSemanticGraphFromSource",
171            "cssModulesClassnameBinding",
172            "sourceSyntaxIndex",
173            "styleResolution",
174        ],
175        next_decoupling_targets: Vec::new(),
176    }
177}
178
179pub fn summarize_omena_bridge_binder_plugin_boundary() -> BinderPluginBoundarySummaryV0 {
180    BinderPluginBoundarySummaryV0 {
181        schema_version: "0",
182        product: "omena-bridge.binder-plugin-boundary",
183        owner_crate: "omena-bridge",
184        contract_name: "BinderPluginV0",
185        external_plugin_abi_stable: false,
186        default_plugin: BinderPluginSummaryV0 {
187            id: "css-modules-classnames-bind",
188            version: "0",
189            stability: "builtIn",
190            domains: vec!["css-modules"],
191            owns_surfaces: vec![
192                "styleImportRecognition",
193                "classUtilityRecognition",
194                "classReferenceExtraction",
195                "sourceExpressionProjection",
196            ],
197            import_targets: vec!["*.module.css", "*.module.scss", "*.module.less"],
198            utility_targets: vec!["classnames/bind", "classnames", "clsx", "clsx/lite"],
199        },
200        built_in_plugins: vec![
201            BinderPluginSummaryV0 {
202                id: "css-modules-classnames-bind",
203                version: "0",
204                stability: "builtIn",
205                domains: vec!["css-modules"],
206                owns_surfaces: vec![
207                    "styleImportRecognition",
208                    "classUtilityRecognition",
209                    "classReferenceExtraction",
210                    "sourceExpressionProjection",
211                ],
212                import_targets: vec!["*.module.css", "*.module.scss", "*.module.less"],
213                utility_targets: vec!["classnames/bind", "classnames", "clsx", "clsx/lite"],
214            },
215            BinderPluginSummaryV0 {
216                id: "tailwind-uno-utility-domain",
217                version: "0",
218                stability: "builtIn",
219                domains: vec!["tailwind-utilities", "unocss-utilities"],
220                owns_surfaces: vec!["domainClassReferenceExtraction"],
221                import_targets: Vec::new(),
222                utility_targets: vec!["class", "className", "classnames", "clsx", "clsx/lite"],
223            },
224            BinderPluginSummaryV0 {
225                id: "vanilla-extract-recipe-domain",
226                version: "0",
227                stability: "builtIn",
228                domains: vec!["vanilla-extract-recipes"],
229                owns_surfaces: vec!["domainClassReferenceExtraction"],
230                import_targets: vec!["@vanilla-extract/recipes"],
231                utility_targets: vec!["recipe"],
232            },
233            BinderPluginSummaryV0 {
234                id: "vue-style-module-domain",
235                version: "0",
236                stability: "builtIn",
237                domains: vec!["vue-style-modules"],
238                owns_surfaces: vec!["domainClassReferenceExtraction"],
239                import_targets: vec!["*.vue"],
240                utility_targets: vec!["useCssModule"],
241            },
242        ],
243        request_path_policy: vec![
244            "builtInPluginsOnlyUntilAbiStabilizes",
245            "pluginOutputFeedsEngineInputV2",
246            "sourceExpressionProjectionMustPreserveBindingIdentity",
247            "styleImportResolutionMustRemainTargetAware",
248            "styleSourceExtractionIsOptionalForUtilityDomains",
249        ],
250        next_plugin_targets: Vec::new(),
251    }
252}
253
254pub fn summarize_omena_bridge_style_semantic_graph(
255    sheet: &Stylesheet,
256    input: &EngineInputV2,
257) -> StyleSemanticGraphSummaryV0 {
258    summarize_omena_bridge_style_semantic_graph_for_path(sheet, input, None)
259}
260
261pub fn summarize_omena_bridge_style_semantic_graph_for_path(
262    sheet: &Stylesheet,
263    input: &EngineInputV2,
264    style_path: Option<&str>,
265) -> StyleSemanticGraphSummaryV0 {
266    summarize_omena_bridge_style_semantic_graph_for_path_with_workspace_declarations(
267        sheet,
268        input,
269        style_path,
270        &[],
271    )
272}
273
274pub fn summarize_omena_bridge_style_semantic_graph_for_path_with_workspace_declarations(
275    sheet: &Stylesheet,
276    input: &EngineInputV2,
277    style_path: Option<&str>,
278    workspace_declarations: &[DesignTokenWorkspaceDeclarationFactV0],
279) -> StyleSemanticGraphSummaryV0 {
280    summarize_omena_bridge_style_semantic_graph_for_path_with_scoped_workspace_declarations(
281        sheet,
282        input,
283        style_path,
284        workspace_declarations,
285        DesignTokenExternalDeclarationCandidateScopeV0::Workspace,
286    )
287}
288
289pub fn summarize_omena_bridge_style_semantic_graph_for_path_with_scoped_workspace_declarations(
290    sheet: &Stylesheet,
291    input: &EngineInputV2,
292    style_path: Option<&str>,
293    workspace_declarations: &[DesignTokenWorkspaceDeclarationFactV0],
294    candidate_scope: DesignTokenExternalDeclarationCandidateScopeV0,
295) -> StyleSemanticGraphSummaryV0 {
296    let boundary = omena_semantic::summarize_style_semantic_boundary(sheet);
297    let css_modules_semantics = omena_semantic::summarize_css_modules_semantics(sheet);
298    summarize_omena_bridge_style_semantic_graph_with_boundary(
299        boundary,
300        css_modules_semantics,
301        input,
302        style_path,
303        workspace_declarations,
304        candidate_scope,
305    )
306}
307
308fn summarize_omena_bridge_style_semantic_graph_with_boundary(
309    boundary: StyleSemanticBoundarySummaryV0,
310    css_modules_semantics: CssModulesSemanticSummaryV0,
311    input: &EngineInputV2,
312    style_path: Option<&str>,
313    workspace_declarations: &[DesignTokenWorkspaceDeclarationFactV0],
314    candidate_scope: DesignTokenExternalDeclarationCandidateScopeV0,
315) -> StyleSemanticGraphSummaryV0 {
316    let parser_facts = boundary.parser_facts;
317    let semantic_facts = boundary.semantic_facts;
318    let design_token_semantics =
319        omena_semantic::summarize_design_token_semantics_with_scoped_workspace_declarations(
320            &parser_facts,
321            &semantic_facts,
322            style_path,
323            workspace_declarations,
324            candidate_scope,
325        );
326    let selector_identity_engine = boundary.selector_identity_engine;
327    let selector_reference_engine =
328        summarize_omena_bridge_selector_reference_engine(input, style_path);
329    let source_input_evidence = summarize_omena_bridge_source_input_evidence(input);
330    let promotion_evidence = summarize_omena_bridge_promotion_evidence_with_source_input(
331        &parser_facts,
332        &semantic_facts,
333        input,
334    );
335    let lossless_cst_contract = boundary.lossless_cst_contract;
336
337    StyleSemanticGraphSummaryV0 {
338        schema_version: "0",
339        product: "omena-semantic.style-semantic-graph",
340        language: boundary.language,
341        parser_facts,
342        semantic_facts,
343        css_modules_semantics,
344        design_token_semantics,
345        selector_identity_engine,
346        selector_reference_engine,
347        source_input_evidence,
348        promotion_evidence,
349        lossless_cst_contract,
350    }
351}
352
353pub fn summarize_omena_bridge_style_semantic_graph_from_source(
354    style_path: &str,
355    style_source: &str,
356    input: &EngineInputV2,
357) -> Option<StyleSemanticGraphSummaryV0> {
358    summarize_omena_bridge_style_semantic_graph_from_source_with_scoped_workspace_declarations(
359        style_path,
360        style_source,
361        input,
362        &[],
363        DesignTokenExternalDeclarationCandidateScopeV0::Workspace,
364    )
365}
366
367pub fn summarize_omena_bridge_style_semantic_graph_from_source_with_scoped_workspace_declarations(
368    style_path: &str,
369    style_source: &str,
370    input: &EngineInputV2,
371    workspace_declarations: &[DesignTokenWorkspaceDeclarationFactV0],
372    candidate_scope: DesignTokenExternalDeclarationCandidateScopeV0,
373) -> Option<StyleSemanticGraphSummaryV0> {
374    let css_modules_semantics =
375        omena_semantic::summarize_css_modules_semantics_from_source(style_path, style_source)?;
376    let boundary = omena_semantic::summarize_omena_parser_style_semantic_boundary_from_source(
377        style_path,
378        style_source,
379    );
380    Some(summarize_omena_bridge_style_semantic_graph_with_boundary(
381        boundary,
382        css_modules_semantics,
383        input,
384        Some(style_path),
385        workspace_declarations,
386        candidate_scope,
387    ))
388}
389
390#[cfg(test)]
391mod tests {
392    use super::{
393        collect_omena_bridge_design_token_workspace_declarations_from_source,
394        summarize_omena_bridge_binder_plugin_boundary, summarize_omena_bridge_boundary,
395        summarize_omena_bridge_promotion_evidence_with_source_input,
396        summarize_omena_bridge_selector_reference_engine,
397        summarize_omena_bridge_source_import_declarations,
398        summarize_omena_bridge_source_input_evidence,
399        summarize_omena_bridge_style_semantic_graph_from_source,
400    };
401    use engine_input_producers::{
402        ClassExpressionInputV2, EngineInputV2, PositionV2, RangeV2, SourceAnalysisInputV2,
403        SourceDocumentV2, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
404        StyleSelectorV2, TypeFactEntryV2,
405    };
406
407    #[test]
408    fn declares_cme_coupled_bridge_boundary() {
409        let boundary = summarize_omena_bridge_boundary();
410
411        assert_eq!(boundary.schema_version, "0");
412        assert_eq!(boundary.product, "omena-bridge.cme-semantic-bridge");
413        assert_eq!(
414            boundary.graph_product,
415            "omena-semantic.style-semantic-graph"
416        );
417        assert_eq!(
418            boundary.delegated_semantic_boundary_product,
419            "omena-semantic.style-semantic-boundary"
420        );
421        assert_eq!(
422            boundary.selector_reference_product,
423            "omena-semantic.selector-references"
424        );
425        assert_eq!(
426            boundary.source_input_evidence_product,
427            "omena-semantic.source-input-evidence"
428        );
429        assert_eq!(
430            boundary.binder_plugin_product,
431            "omena-bridge.binder-plugin-boundary"
432        );
433        assert!(
434            boundary
435                .bridge_owned_surfaces
436                .contains(&"styleSemanticGraphFromSource")
437        );
438        assert!(
439            boundary
440                .bridge_owned_surfaces
441                .contains(&"omenaParserBackedStyleSemanticBoundaryFromSource")
442        );
443        assert!(
444            boundary
445                .bridge_owned_surfaces
446                .contains(&"selectorReferenceEngine")
447        );
448        assert!(
449            boundary
450                .bridge_owned_surfaces
451                .contains(&"designTokenWorkspaceDeclarationsFromSource")
452        );
453        assert!(
454            boundary
455                .bridge_owned_surfaces
456                .contains(&"sourceInputEvidence")
457        );
458        assert!(
459            boundary
460                .bridge_owned_surfaces
461                .contains(&"promotionEvidenceWithSourceInput")
462        );
463        assert!(
464            boundary
465                .bridge_owned_surfaces
466                .contains(&"sourceImportDeclarations")
467        );
468        assert!(boundary.bridge_owned_surfaces.contains(&"styleResolution"));
469        assert!(
470            boundary
471                .bridge_owned_surfaces
472                .contains(&"sourceSyntaxIndex")
473        );
474        assert!(
475            boundary
476                .bridge_owned_surfaces
477                .contains(&"binderPluginBoundary")
478        );
479        assert!(
480            boundary
481                .cme_coupled_surfaces
482                .contains(&"promotionEvidenceWithSourceInput")
483        );
484        assert!(
485            boundary.next_decoupling_targets.is_empty(),
486            "all current omena-bridge decoupling targets should be bridge-owned"
487        );
488    }
489
490    #[test]
491    fn declares_built_in_binder_plugin_boundary() {
492        let boundary = summarize_omena_bridge_binder_plugin_boundary();
493
494        assert_eq!(boundary.schema_version, "0");
495        assert_eq!(boundary.product, "omena-bridge.binder-plugin-boundary");
496        assert_eq!(boundary.contract_name, "BinderPluginV0");
497        assert!(
498            !boundary.external_plugin_abi_stable,
499            "the first boundary cut should not promise a stable external plugin ABI"
500        );
501        assert_eq!(boundary.default_plugin.id, "css-modules-classnames-bind");
502        assert_eq!(boundary.default_plugin.stability, "builtIn");
503        assert!(boundary.default_plugin.domains.contains(&"css-modules"));
504        assert!(
505            boundary
506                .default_plugin
507                .owns_surfaces
508                .contains(&"classReferenceExtraction")
509        );
510        assert!(
511            boundary
512                .default_plugin
513                .utility_targets
514                .contains(&"classnames/bind")
515        );
516        assert!(
517            boundary
518                .request_path_policy
519                .contains(&"pluginOutputFeedsEngineInputV2")
520        );
521        assert!(
522            boundary
523                .request_path_policy
524                .contains(&"styleSourceExtractionIsOptionalForUtilityDomains")
525        );
526        assert!(boundary.built_in_plugins.iter().any(|plugin| {
527            plugin.id == "tailwind-uno-utility-domain"
528                && plugin.domains.contains(&"tailwind-utilities")
529                && plugin
530                    .owns_surfaces
531                    .contains(&"domainClassReferenceExtraction")
532        }));
533        assert!(
534            !boundary
535                .next_plugin_targets
536                .contains(&"tailwind-utility-domain")
537        );
538        assert!(boundary.built_in_plugins.iter().any(|plugin| {
539            plugin.id == "vanilla-extract-recipe-domain"
540                && plugin.domains.contains(&"vanilla-extract-recipes")
541                && plugin
542                    .owns_surfaces
543                    .contains(&"domainClassReferenceExtraction")
544        }));
545        assert!(
546            !boundary
547                .next_plugin_targets
548                .contains(&"vanilla-extract-recipe-domain")
549        );
550        assert!(boundary.built_in_plugins.iter().any(|plugin| {
551            plugin.id == "vue-style-module-domain"
552                && plugin.domains.contains(&"vue-style-modules")
553                && plugin
554                    .owns_surfaces
555                    .contains(&"domainClassReferenceExtraction")
556        }));
557        assert!(
558            boundary.next_plugin_targets.is_empty(),
559            "all planned BinderPluginV0 proof-point domains should now be built in"
560        );
561    }
562
563    #[test]
564    fn summarizes_source_import_declarations_for_css_modules_binding_inputs() {
565        let summary = summarize_omena_bridge_source_import_declarations(
566            r#"
567import bind from "classnames/bind";
568import styles from "./Button.module.scss";
569import * as tokens from "./tokens.module.css";
570import { type BadgeProps } from "./types";
571const lazy = import("./ignored.module.scss");
572"#,
573        );
574
575        assert_eq!(summary.product, "omena-bridge.source-import-declarations");
576        assert_eq!(summary.import_count, 3);
577        assert_eq!(
578            summary
579                .imports
580                .iter()
581                .map(|import| (import.binding.as_str(), import.specifier.as_str()))
582                .collect::<Vec<_>>(),
583            vec![
584                ("bind", "classnames/bind"),
585                ("styles", "./Button.module.scss"),
586                ("tokens", "./tokens.module.css"),
587            ]
588        );
589    }
590
591    #[test]
592    fn exposes_source_input_evidence_through_bridge() {
593        let evidence = summarize_omena_bridge_source_input_evidence(&sample_engine_input());
594
595        assert_eq!(evidence.product, "omena-semantic.source-input-evidence");
596        assert_eq!(evidence.reference_site_identity.status, "ready");
597        assert_eq!(evidence.reference_site_identity.reference_site_count, 1);
598        assert_eq!(evidence.certainty_reason.status, "ready");
599        assert_eq!(evidence.binding_origin.status, "ready");
600        assert!(evidence.blocking_gaps.is_empty());
601    }
602
603    #[test]
604    fn exposes_style_semantic_graph_through_bridge() -> Result<(), String> {
605        let graph = summarize_omena_bridge_style_semantic_graph_from_source(
606            "/tmp/Component.module.scss",
607            ".button { color: red; }",
608            &sample_engine_input(),
609        )
610        .ok_or_else(|| "SCSS module source should parse".to_string())?;
611
612        assert_eq!(graph.product, "omena-semantic.style-semantic-graph");
613        assert_eq!(graph.selector_reference_engine.selector_count, 1);
614        assert_eq!(graph.selector_reference_engine.referenced_selector_count, 1);
615        assert_eq!(
616            graph.source_input_evidence.product,
617            "omena-semantic.source-input-evidence"
618        );
619        assert!(graph.promotion_evidence.blocking_gaps.is_empty());
620        Ok(())
621    }
622
623    #[test]
624    fn exposes_style_semantic_graph_from_source_through_bridge() -> Result<(), String> {
625        let graph = summarize_omena_bridge_style_semantic_graph_from_source(
626            "/tmp/Component.module.scss",
627            r#"@use "./tokens" as tokens; .button { --brand: red; color: var(--brand); color: tokens.$brand; }"#,
628            &sample_engine_input(),
629        )
630        .ok_or_else(|| "bridge should parse SCSS module source".to_string())?;
631
632        assert_eq!(graph.product, "omena-semantic.style-semantic-graph");
633        assert_eq!(
634            graph.parser_facts.custom_properties.decl_names,
635            vec!["--brand".to_string()]
636        );
637        assert_eq!(
638            graph.parser_facts.custom_properties.ref_names,
639            vec!["--brand".to_string()]
640        );
641        assert_eq!(
642            graph.parser_facts.sass.module_use_edges[0].namespace_kind,
643            "alias"
644        );
645        assert_eq!(
646            graph.parser_facts.sass.variable_ref_names,
647            vec!["brand".to_string()]
648        );
649        assert_eq!(
650            graph.selector_reference_engine.style_path,
651            Some("/tmp/Component.module.scss".to_string())
652        );
653        assert_eq!(
654            graph.source_input_evidence.reference_site_identity.status,
655            "ready"
656        );
657        Ok(())
658    }
659
660    #[test]
661    fn collects_design_token_workspace_declarations_from_source_through_bridge() {
662        let declarations = collect_omena_bridge_design_token_workspace_declarations_from_source(
663            "/tmp/tokens.module.scss",
664            r#":root { --brand: red; } .button { --local: blue; color: var(--brand); }"#,
665        );
666
667        assert_eq!(
668            declarations
669                .iter()
670                .map(|declaration| declaration.name.as_str())
671                .collect::<Vec<_>>(),
672            vec!["--brand", "--local"]
673        );
674        assert!(
675            declarations
676                .iter()
677                .all(|declaration| declaration.file_path == "/tmp/tokens.module.scss")
678        );
679        assert_eq!(declarations[0].source_order, 0);
680        assert_eq!(declarations[1].source_order, 1);
681    }
682
683    #[test]
684    fn owns_selector_reference_engine_without_changing_host_product() {
685        let bridge_references = summarize_omena_bridge_selector_reference_engine(
686            &sample_engine_input(),
687            Some("/tmp/Component.module.scss"),
688        );
689        let semantic_references = omena_semantic::summarize_selector_reference_engine(
690            &sample_engine_input(),
691            Some("/tmp/Component.module.scss"),
692        );
693
694        assert_eq!(
695            bridge_references.product,
696            "omena-semantic.selector-references"
697        );
698        assert_eq!(bridge_references.product, semantic_references.product);
699        assert_eq!(bridge_references.style_path, semantic_references.style_path);
700        assert_eq!(
701            bridge_references.selector_count,
702            semantic_references.selector_count
703        );
704        assert_eq!(
705            bridge_references.referenced_selector_count,
706            semantic_references.referenced_selector_count
707        );
708        assert_eq!(
709            bridge_references.total_reference_sites,
710            semantic_references.total_reference_sites
711        );
712        assert_eq!(
713            bridge_references.selectors[0].canonical_id,
714            semantic_references.selectors[0].canonical_id
715        );
716        assert_eq!(
717            bridge_references.selectors[0].editable_direct_reference_count,
718            semantic_references.selectors[0].editable_direct_reference_count
719        );
720    }
721
722    #[test]
723    fn owns_source_input_evidence_without_changing_host_product() {
724        let bridge_evidence = summarize_omena_bridge_source_input_evidence(&sample_engine_input());
725        let semantic_evidence =
726            omena_semantic::summarize_source_input_evidence(&sample_engine_input());
727
728        assert_bridge_source_input_evidence_matches_semantic(&bridge_evidence, &semantic_evidence);
729    }
730
731    #[test]
732    fn owns_source_backed_promotion_evidence_without_changing_host_product() -> Result<(), String> {
733        let boundary = omena_semantic::summarize_omena_parser_style_semantic_boundary_from_source(
734            "/tmp/Component.module.scss",
735            ".button { color: red; }",
736        );
737        let input = sample_engine_input();
738        let bridge_evidence = summarize_omena_bridge_promotion_evidence_with_source_input(
739            &boundary.parser_facts,
740            &boundary.semantic_facts,
741            &input,
742        );
743        let semantic_evidence =
744            omena_semantic::summarize_semantic_promotion_evidence_with_source_input(
745                &boundary.parser_facts,
746                &boundary.semantic_facts,
747                &input,
748            );
749
750        assert_bridge_promotion_evidence_matches_semantic(&bridge_evidence, &semantic_evidence);
751        Ok(())
752    }
753
754    #[test]
755    fn owns_graph_assembly_without_changing_host_product() -> Result<(), String> {
756        let bridge_graph = summarize_omena_bridge_style_semantic_graph_from_source(
757            "/tmp/Component.module.scss",
758            ".button { color: red; }",
759            &sample_engine_input(),
760        )
761        .ok_or_else(|| "bridge should parse SCSS module source".to_string())?;
762        let semantic_graph = omena_semantic::summarize_style_semantic_graph_from_source(
763            "/tmp/Component.module.scss",
764            ".button { color: red; }",
765            &sample_engine_input(),
766        )
767        .ok_or_else(|| "semantic should parse SCSS module source".to_string())?;
768
769        assert_eq!(bridge_graph.product, "omena-semantic.style-semantic-graph");
770        assert_eq!(bridge_graph.product, semantic_graph.product);
771        assert_eq!(bridge_graph.language, semantic_graph.language);
772        assert_eq!(
773            bridge_graph.selector_reference_engine.product,
774            semantic_graph.selector_reference_engine.product
775        );
776        assert_eq!(
777            bridge_graph.selector_reference_engine.selector_count,
778            semantic_graph.selector_reference_engine.selector_count
779        );
780        assert_eq!(
781            bridge_graph.selector_reference_engine.total_reference_sites,
782            semantic_graph
783                .selector_reference_engine
784                .total_reference_sites
785        );
786        assert_bridge_source_input_evidence_matches_semantic(
787            &bridge_graph.source_input_evidence,
788            &semantic_graph.source_input_evidence,
789        );
790        assert_eq!(
791            bridge_graph.design_token_semantics,
792            semantic_graph.design_token_semantics
793        );
794        assert_bridge_promotion_evidence_matches_semantic(
795            &bridge_graph.promotion_evidence,
796            &semantic_graph.promotion_evidence,
797        );
798        Ok(())
799    }
800
801    fn assert_bridge_promotion_evidence_matches_semantic(
802        bridge: &super::SemanticPromotionEvidenceSummaryV0,
803        semantic: &omena_semantic::SemanticPromotionEvidenceSummaryV0,
804    ) {
805        assert_eq!(bridge.schema_version, semantic.schema_version);
806        assert_eq!(bridge.product, semantic.product);
807        assert_eq!(bridge.blocking_gaps, semantic.blocking_gaps);
808        assert_eq!(bridge.next_priorities, semantic.next_priorities);
809        assert_eq!(bridge.items.len(), semantic.items.len());
810
811        for (bridge_item, semantic_item) in bridge.items.iter().zip(&semantic.items) {
812            assert_eq!(bridge_item.evidence, semantic_item.evidence);
813            assert_eq!(bridge_item.status, semantic_item.status);
814            assert_eq!(bridge_item.provider, semantic_item.provider);
815            assert_eq!(bridge_item.observed_count, semantic_item.observed_count);
816            assert_eq!(bridge_item.reason, semantic_item.reason);
817        }
818    }
819
820    fn assert_bridge_source_input_evidence_matches_semantic(
821        bridge: &super::SourceInputPromotionEvidenceSummaryV0,
822        semantic: &omena_semantic::SourceInputPromotionEvidenceSummaryV0,
823    ) {
824        assert_eq!(bridge.schema_version, semantic.schema_version);
825        assert_eq!(bridge.product, semantic.product);
826        assert_eq!(bridge.input_version, semantic.input_version);
827        assert_eq!(
828            bridge.reference_site_identity.status,
829            semantic.reference_site_identity.status
830        );
831        assert_eq!(
832            bridge.reference_site_identity.selector_count,
833            semantic.reference_site_identity.selector_count
834        );
835        assert_eq!(
836            bridge.reference_site_identity.reference_site_count,
837            semantic.reference_site_identity.reference_site_count
838        );
839        assert_eq!(
840            bridge.reference_site_identity.direct_reference_site_count,
841            semantic.reference_site_identity.direct_reference_site_count
842        );
843        assert_eq!(
844            bridge.reference_site_identity.expanded_reference_site_count,
845            semantic
846                .reference_site_identity
847                .expanded_reference_site_count
848        );
849        assert_eq!(
850            bridge
851                .reference_site_identity
852                .style_dependency_reference_site_count,
853            semantic
854                .reference_site_identity
855                .style_dependency_reference_site_count
856        );
857        assert_eq!(
858            bridge.reference_site_identity.editable_direct_site_count,
859            semantic.reference_site_identity.editable_direct_site_count
860        );
861        assert_eq!(
862            bridge.reference_site_identity.reference_kind_counts,
863            semantic.reference_site_identity.reference_kind_counts
864        );
865        assert_eq!(
866            bridge.certainty_reason.status,
867            semantic.certainty_reason.status
868        );
869        assert_eq!(
870            bridge.certainty_reason.expression_count,
871            semantic.certainty_reason.expression_count
872        );
873        assert_eq!(
874            bridge.certainty_reason.exact_count,
875            semantic.certainty_reason.exact_count
876        );
877        assert_eq!(
878            bridge.certainty_reason.inferred_count,
879            semantic.certainty_reason.inferred_count
880        );
881        assert_eq!(
882            bridge.certainty_reason.possible_count,
883            semantic.certainty_reason.possible_count
884        );
885        assert_eq!(
886            bridge.certainty_reason.missing_reason_count,
887            semantic.certainty_reason.missing_reason_count
888        );
889        assert_eq!(
890            bridge.certainty_reason.reason_counts,
891            semantic.certainty_reason.reason_counts
892        );
893        assert_eq!(
894            bridge.certainty_reason.shape_kind_counts,
895            semantic.certainty_reason.shape_kind_counts
896        );
897        assert_eq!(
898            bridge.certainty_reason.shape_label_counts,
899            semantic.certainty_reason.shape_label_counts
900        );
901        assert_eq!(bridge.binding_origin.status, semantic.binding_origin.status);
902        assert_eq!(
903            bridge.binding_origin.expression_count,
904            semantic.binding_origin.expression_count
905        );
906        assert_eq!(
907            bridge.binding_origin.direct_class_name_count,
908            semantic.binding_origin.direct_class_name_count
909        );
910        assert_eq!(
911            bridge.binding_origin.root_binding_count,
912            semantic.binding_origin.root_binding_count
913        );
914        assert_eq!(
915            bridge.binding_origin.access_path_count,
916            semantic.binding_origin.access_path_count
917        );
918        assert_eq!(
919            bridge.binding_origin.access_path_segment_count,
920            semantic.binding_origin.access_path_segment_count
921        );
922        assert_eq!(
923            bridge.binding_origin.expression_kind_counts,
924            semantic.binding_origin.expression_kind_counts
925        );
926        assert_eq!(
927            bridge.style_module_edge.status,
928            semantic.style_module_edge.status
929        );
930        assert_eq!(
931            bridge.style_module_edge.source_style_edge_count,
932            semantic.style_module_edge.source_style_edge_count
933        );
934        assert_eq!(
935            bridge.style_module_edge.distinct_style_module_count,
936            semantic.style_module_edge.distinct_style_module_count
937        );
938        assert_eq!(
939            bridge.style_module_edge.missing_style_document_edge_count,
940            semantic.style_module_edge.missing_style_document_edge_count
941        );
942        assert_eq!(
943            bridge.style_module_edge.composed_edge_count,
944            semantic.style_module_edge.composed_edge_count
945        );
946        assert_eq!(
947            bridge.style_module_edge.imported_composed_edge_count,
948            semantic.style_module_edge.imported_composed_edge_count
949        );
950        assert_eq!(
951            bridge.style_module_edge.global_composed_edge_count,
952            semantic.style_module_edge.global_composed_edge_count
953        );
954        assert_eq!(
955            bridge.value_domain_explanation.status,
956            semantic.value_domain_explanation.status
957        );
958        assert_eq!(
959            bridge.value_domain_explanation.expression_count,
960            semantic.value_domain_explanation.expression_count
961        );
962        assert_eq!(
963            bridge.value_domain_explanation.exact_expression_count,
964            semantic.value_domain_explanation.exact_expression_count
965        );
966        assert_eq!(
967            bridge
968                .value_domain_explanation
969                .finite_value_expression_count,
970            semantic
971                .value_domain_explanation
972                .finite_value_expression_count
973        );
974        assert_eq!(
975            bridge.value_domain_explanation.constrained_expression_count,
976            semantic
977                .value_domain_explanation
978                .constrained_expression_count
979        );
980        assert_eq!(
981            bridge.value_domain_explanation.unknown_expression_count,
982            semantic.value_domain_explanation.unknown_expression_count
983        );
984        assert_eq!(
985            bridge.value_domain_explanation.finite_value_count,
986            semantic.value_domain_explanation.finite_value_count
987        );
988        assert_eq!(
989            bridge.value_domain_explanation.derivation_count,
990            semantic.value_domain_explanation.derivation_count
991        );
992        assert_eq!(
993            bridge.value_domain_explanation.derivation_step_count,
994            semantic.value_domain_explanation.derivation_step_count
995        );
996        assert_eq!(
997            bridge.value_domain_explanation.value_domain_kind_counts,
998            semantic.value_domain_explanation.value_domain_kind_counts
999        );
1000        assert_eq!(
1001            bridge.value_domain_explanation.constraint_kind_counts,
1002            semantic.value_domain_explanation.constraint_kind_counts
1003        );
1004        assert_eq!(
1005            bridge.value_domain_explanation.derivation_product_counts,
1006            semantic.value_domain_explanation.derivation_product_counts
1007        );
1008        assert_eq!(
1009            bridge
1010                .value_domain_explanation
1011                .derivation_reduced_kind_counts,
1012            semantic
1013                .value_domain_explanation
1014                .derivation_reduced_kind_counts
1015        );
1016        assert_eq!(
1017            bridge.value_domain_explanation.derivation_operation_counts,
1018            semantic
1019                .value_domain_explanation
1020                .derivation_operation_counts
1021        );
1022        assert_eq!(bridge.blocking_gaps, semantic.blocking_gaps);
1023        assert_eq!(bridge.next_priorities, semantic.next_priorities);
1024    }
1025
1026    fn sample_engine_input() -> EngineInputV2 {
1027        EngineInputV2 {
1028            version: "2".to_string(),
1029            sources: vec![SourceAnalysisInputV2 {
1030                document: SourceDocumentV2 {
1031                    class_expressions: vec![ClassExpressionInputV2 {
1032                        id: "expr-literal".to_string(),
1033                        kind: "literal".to_string(),
1034                        scss_module_path: "/tmp/Component.module.scss".to_string(),
1035                        range: range(4, 12, 4, 18),
1036                        class_name: Some("button".to_string()),
1037                        root_binding_decl_id: None,
1038                        access_path: None,
1039                    }],
1040                },
1041            }],
1042            styles: vec![StyleAnalysisInputV2 {
1043                file_path: "/tmp/Component.module.scss".to_string(),
1044                source: None,
1045                document: StyleDocumentV2 {
1046                    selectors: vec![StyleSelectorV2 {
1047                        name: "button".to_string(),
1048                        view_kind: "canonical".to_string(),
1049                        canonical_name: Some("button".to_string()),
1050                        range: range(0, 1, 0, 7),
1051                        nested_safety: Some("flat".to_string()),
1052                        composes: None,
1053                        bem_suffix: None,
1054                    }],
1055                },
1056            }],
1057            type_facts: vec![TypeFactEntryV2 {
1058                file_path: "/tmp/Component.tsx".to_string(),
1059                expression_id: "expr-literal".to_string(),
1060                facts: StringTypeFactsV2 {
1061                    kind: "exact".to_string(),
1062                    constraint_kind: None,
1063                    values: Some(vec!["button".to_string()]),
1064                    prefix: None,
1065                    suffix: None,
1066                    min_len: None,
1067                    max_len: None,
1068                    char_must: None,
1069                    char_may: None,
1070                    may_include_other_chars: None,
1071                },
1072            }],
1073        }
1074    }
1075
1076    fn range(
1077        start_line: usize,
1078        start_character: usize,
1079        end_line: usize,
1080        end_character: usize,
1081    ) -> RangeV2 {
1082        RangeV2 {
1083            start: PositionV2 {
1084                line: start_line,
1085                character: start_character,
1086            },
1087            end: PositionV2 {
1088                line: end_line,
1089                character: end_character,
1090            },
1091        }
1092    }
1093}