Skip to main content

omena_bridge/
lib.rs

1use engine_input_producers::EngineInputV2;
2use engine_style_parser::{
3    ParserBoundarySyntaxFactsV0, StyleSemanticFactsV0, Stylesheet, parse_style_module,
4};
5use omena_semantic::{LosslessCstContractV0, SelectorIdentityEngineSummaryV0};
6use serde::Serialize;
7
8mod promotion_evidence;
9mod selector_references;
10mod source_evidence;
11
12pub use promotion_evidence::{
13    SemanticPromotionEvidenceItemV0, SemanticPromotionEvidenceSummaryV0,
14    summarize_omena_bridge_promotion_evidence_with_source_input,
15    summarize_omena_bridge_semantic_promotion_evidence,
16};
17pub use selector_references::{
18    SelectorEditableDirectReferenceSiteV0, SelectorReferenceEngineSummaryV0,
19    SelectorReferenceSiteV0, SelectorReferenceSummaryV0,
20    summarize_omena_bridge_selector_reference_engine,
21};
22pub use source_evidence::{
23    BindingOriginEvidenceV0, CertaintyReasonEvidenceV0, ReferenceSiteIdentityEvidenceV0,
24    SourceInputPromotionEvidenceSummaryV0, StyleModuleEdgeEvidenceV0,
25    ValueDomainExplanationEvidenceV0, summarize_omena_bridge_source_input_evidence,
26};
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub struct OmenaBridgeBoundarySummaryV0 {
31    pub schema_version: &'static str,
32    pub product: &'static str,
33    pub bridge_name: &'static str,
34    pub graph_product: &'static str,
35    pub delegated_semantic_boundary_product: &'static str,
36    pub selector_reference_product: &'static str,
37    pub source_input_evidence_product: &'static str,
38    pub bridge_owned_surfaces: Vec<&'static str>,
39    pub cme_coupled_surfaces: Vec<&'static str>,
40    pub next_decoupling_targets: Vec<&'static str>,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
44#[serde(rename_all = "camelCase")]
45pub struct StyleSemanticGraphSummaryV0 {
46    pub schema_version: &'static str,
47    pub product: &'static str,
48    pub language: &'static str,
49    pub parser_facts: ParserBoundarySyntaxFactsV0,
50    pub semantic_facts: StyleSemanticFactsV0,
51    pub selector_identity_engine: SelectorIdentityEngineSummaryV0,
52    pub selector_reference_engine: SelectorReferenceEngineSummaryV0,
53    pub source_input_evidence: SourceInputPromotionEvidenceSummaryV0,
54    pub promotion_evidence: SemanticPromotionEvidenceSummaryV0,
55    pub lossless_cst_contract: LosslessCstContractV0,
56}
57
58pub fn summarize_omena_bridge_boundary() -> OmenaBridgeBoundarySummaryV0 {
59    OmenaBridgeBoundarySummaryV0 {
60        schema_version: "0",
61        product: "omena-bridge.cme-semantic-bridge",
62        bridge_name: "cme-semantic-bridge",
63        graph_product: "omena-semantic.style-semantic-graph",
64        delegated_semantic_boundary_product: "omena-semantic.style-semantic-boundary",
65        selector_reference_product: "omena-semantic.selector-references",
66        source_input_evidence_product: "omena-semantic.source-input-evidence",
67        bridge_owned_surfaces: vec![
68            "styleSemanticGraph",
69            "styleSemanticGraphFromSource",
70            "selectorReferenceEngine",
71            "sourceInputEvidence",
72            "promotionEvidenceWithSourceInput",
73        ],
74        cme_coupled_surfaces: vec![
75            "EngineInputV2",
76            "sourceInputEvidence",
77            "selectorReferenceEngine",
78            "promotionEvidenceWithSourceInput",
79            "styleSemanticGraphFromSource",
80        ],
81        next_decoupling_targets: Vec::new(),
82    }
83}
84
85pub fn summarize_omena_bridge_style_semantic_graph(
86    sheet: &Stylesheet,
87    input: &EngineInputV2,
88) -> StyleSemanticGraphSummaryV0 {
89    summarize_omena_bridge_style_semantic_graph_for_path(sheet, input, None)
90}
91
92pub fn summarize_omena_bridge_style_semantic_graph_for_path(
93    sheet: &Stylesheet,
94    input: &EngineInputV2,
95    style_path: Option<&str>,
96) -> StyleSemanticGraphSummaryV0 {
97    let boundary = omena_semantic::summarize_style_semantic_boundary(sheet);
98    let parser_facts = boundary.parser_facts;
99    let semantic_facts = boundary.semantic_facts;
100    let selector_identity_engine = boundary.selector_identity_engine;
101    let selector_reference_engine =
102        summarize_omena_bridge_selector_reference_engine(input, style_path);
103    let source_input_evidence = summarize_omena_bridge_source_input_evidence(input);
104    let promotion_evidence = summarize_omena_bridge_promotion_evidence_with_source_input(
105        &parser_facts,
106        &semantic_facts,
107        input,
108    );
109    let lossless_cst_contract = boundary.lossless_cst_contract;
110
111    StyleSemanticGraphSummaryV0 {
112        schema_version: "0",
113        product: "omena-semantic.style-semantic-graph",
114        language: boundary.language,
115        parser_facts,
116        semantic_facts,
117        selector_identity_engine,
118        selector_reference_engine,
119        source_input_evidence,
120        promotion_evidence,
121        lossless_cst_contract,
122    }
123}
124
125pub fn summarize_omena_bridge_style_semantic_graph_from_source(
126    style_path: &str,
127    style_source: &str,
128    input: &EngineInputV2,
129) -> Option<StyleSemanticGraphSummaryV0> {
130    let sheet = parse_style_module(style_path, style_source)?;
131    Some(summarize_omena_bridge_style_semantic_graph_for_path(
132        &sheet,
133        input,
134        Some(style_path),
135    ))
136}
137
138#[cfg(test)]
139mod tests {
140    use engine_input_producers::{
141        ClassExpressionInputV2, EngineInputV2, PositionV2, RangeV2, SourceAnalysisInputV2,
142        SourceDocumentV2, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
143        StyleSelectorV2, TypeFactEntryV2,
144    };
145    use engine_style_parser::parse_style_module;
146
147    use super::{
148        summarize_omena_bridge_boundary,
149        summarize_omena_bridge_promotion_evidence_with_source_input,
150        summarize_omena_bridge_selector_reference_engine,
151        summarize_omena_bridge_source_input_evidence, summarize_omena_bridge_style_semantic_graph,
152        summarize_omena_bridge_style_semantic_graph_from_source,
153    };
154
155    #[test]
156    fn declares_cme_coupled_bridge_boundary() {
157        let boundary = summarize_omena_bridge_boundary();
158
159        assert_eq!(boundary.schema_version, "0");
160        assert_eq!(boundary.product, "omena-bridge.cme-semantic-bridge");
161        assert_eq!(
162            boundary.graph_product,
163            "omena-semantic.style-semantic-graph"
164        );
165        assert_eq!(
166            boundary.delegated_semantic_boundary_product,
167            "omena-semantic.style-semantic-boundary"
168        );
169        assert_eq!(
170            boundary.selector_reference_product,
171            "omena-semantic.selector-references"
172        );
173        assert_eq!(
174            boundary.source_input_evidence_product,
175            "omena-semantic.source-input-evidence"
176        );
177        assert!(
178            boundary
179                .bridge_owned_surfaces
180                .contains(&"styleSemanticGraphFromSource")
181        );
182        assert!(
183            boundary
184                .bridge_owned_surfaces
185                .contains(&"selectorReferenceEngine")
186        );
187        assert!(
188            boundary
189                .bridge_owned_surfaces
190                .contains(&"sourceInputEvidence")
191        );
192        assert!(
193            boundary
194                .bridge_owned_surfaces
195                .contains(&"promotionEvidenceWithSourceInput")
196        );
197        assert!(
198            boundary
199                .cme_coupled_surfaces
200                .contains(&"promotionEvidenceWithSourceInput")
201        );
202        assert!(
203            boundary.next_decoupling_targets.is_empty(),
204            "all current omena-bridge decoupling targets should be bridge-owned"
205        );
206    }
207
208    #[test]
209    fn exposes_source_input_evidence_through_bridge() {
210        let evidence = summarize_omena_bridge_source_input_evidence(&sample_engine_input());
211
212        assert_eq!(evidence.product, "omena-semantic.source-input-evidence");
213        assert_eq!(evidence.reference_site_identity.status, "ready");
214        assert_eq!(evidence.reference_site_identity.reference_site_count, 1);
215        assert_eq!(evidence.certainty_reason.status, "ready");
216        assert_eq!(evidence.binding_origin.status, "ready");
217        assert!(evidence.blocking_gaps.is_empty());
218    }
219
220    #[test]
221    fn exposes_style_semantic_graph_through_bridge() -> Result<(), String> {
222        let sheet = parse_style_module("Component.module.scss", ".button { color: red; }")
223            .ok_or_else(|| "SCSS module path should parse".to_string())?;
224        let graph = summarize_omena_bridge_style_semantic_graph(&sheet, &sample_engine_input());
225
226        assert_eq!(graph.product, "omena-semantic.style-semantic-graph");
227        assert_eq!(graph.selector_reference_engine.selector_count, 1);
228        assert_eq!(graph.selector_reference_engine.referenced_selector_count, 1);
229        assert_eq!(
230            graph.source_input_evidence.product,
231            "omena-semantic.source-input-evidence"
232        );
233        assert!(graph.promotion_evidence.blocking_gaps.is_empty());
234        Ok(())
235    }
236
237    #[test]
238    fn exposes_style_semantic_graph_from_source_through_bridge() -> Result<(), String> {
239        let graph = summarize_omena_bridge_style_semantic_graph_from_source(
240            "/tmp/Component.module.scss",
241            ".button { color: red; }",
242            &sample_engine_input(),
243        )
244        .ok_or_else(|| "bridge should parse SCSS module source".to_string())?;
245
246        assert_eq!(graph.product, "omena-semantic.style-semantic-graph");
247        assert_eq!(
248            graph.selector_reference_engine.style_path,
249            Some("/tmp/Component.module.scss".to_string())
250        );
251        assert_eq!(
252            graph.source_input_evidence.reference_site_identity.status,
253            "ready"
254        );
255        Ok(())
256    }
257
258    #[test]
259    fn owns_selector_reference_engine_without_changing_host_product() {
260        let bridge_references = summarize_omena_bridge_selector_reference_engine(
261            &sample_engine_input(),
262            Some("/tmp/Component.module.scss"),
263        );
264        let semantic_references = omena_semantic::summarize_selector_reference_engine(
265            &sample_engine_input(),
266            Some("/tmp/Component.module.scss"),
267        );
268
269        assert_eq!(
270            bridge_references.product,
271            "omena-semantic.selector-references"
272        );
273        assert_eq!(bridge_references.product, semantic_references.product);
274        assert_eq!(bridge_references.style_path, semantic_references.style_path);
275        assert_eq!(
276            bridge_references.selector_count,
277            semantic_references.selector_count
278        );
279        assert_eq!(
280            bridge_references.referenced_selector_count,
281            semantic_references.referenced_selector_count
282        );
283        assert_eq!(
284            bridge_references.total_reference_sites,
285            semantic_references.total_reference_sites
286        );
287        assert_eq!(
288            bridge_references.selectors[0].canonical_id,
289            semantic_references.selectors[0].canonical_id
290        );
291        assert_eq!(
292            bridge_references.selectors[0].editable_direct_reference_count,
293            semantic_references.selectors[0].editable_direct_reference_count
294        );
295    }
296
297    #[test]
298    fn owns_source_input_evidence_without_changing_host_product() {
299        let bridge_evidence = summarize_omena_bridge_source_input_evidence(&sample_engine_input());
300        let semantic_evidence =
301            omena_semantic::summarize_source_input_evidence(&sample_engine_input());
302
303        assert_bridge_source_input_evidence_matches_semantic(&bridge_evidence, &semantic_evidence);
304    }
305
306    #[test]
307    fn owns_source_backed_promotion_evidence_without_changing_host_product() -> Result<(), String> {
308        let sheet = parse_style_module("Component.module.scss", ".button { color: red; }")
309            .ok_or_else(|| "SCSS module path should parse".to_string())?;
310        let boundary = omena_semantic::summarize_style_semantic_boundary(&sheet);
311        let input = sample_engine_input();
312        let bridge_evidence = summarize_omena_bridge_promotion_evidence_with_source_input(
313            &boundary.parser_facts,
314            &boundary.semantic_facts,
315            &input,
316        );
317        let semantic_evidence =
318            omena_semantic::summarize_semantic_promotion_evidence_with_source_input(
319                &boundary.parser_facts,
320                &boundary.semantic_facts,
321                &input,
322            );
323
324        assert_bridge_promotion_evidence_matches_semantic(&bridge_evidence, &semantic_evidence);
325        Ok(())
326    }
327
328    #[test]
329    fn owns_graph_assembly_without_changing_host_product() -> Result<(), String> {
330        let sheet = parse_style_module("Component.module.scss", ".button { color: red; }")
331            .ok_or_else(|| "SCSS module path should parse".to_string())?;
332        let bridge_graph =
333            summarize_omena_bridge_style_semantic_graph(&sheet, &sample_engine_input());
334        let semantic_graph =
335            omena_semantic::summarize_style_semantic_graph(&sheet, &sample_engine_input());
336
337        assert_eq!(bridge_graph.product, "omena-semantic.style-semantic-graph");
338        assert_eq!(bridge_graph.product, semantic_graph.product);
339        assert_eq!(bridge_graph.language, semantic_graph.language);
340        assert_eq!(
341            bridge_graph.selector_reference_engine.product,
342            semantic_graph.selector_reference_engine.product
343        );
344        assert_eq!(
345            bridge_graph.selector_reference_engine.selector_count,
346            semantic_graph.selector_reference_engine.selector_count
347        );
348        assert_eq!(
349            bridge_graph.selector_reference_engine.total_reference_sites,
350            semantic_graph
351                .selector_reference_engine
352                .total_reference_sites
353        );
354        assert_bridge_source_input_evidence_matches_semantic(
355            &bridge_graph.source_input_evidence,
356            &semantic_graph.source_input_evidence,
357        );
358        assert_bridge_promotion_evidence_matches_semantic(
359            &bridge_graph.promotion_evidence,
360            &semantic_graph.promotion_evidence,
361        );
362        Ok(())
363    }
364
365    fn assert_bridge_promotion_evidence_matches_semantic(
366        bridge: &super::SemanticPromotionEvidenceSummaryV0,
367        semantic: &omena_semantic::SemanticPromotionEvidenceSummaryV0,
368    ) {
369        assert_eq!(bridge.schema_version, semantic.schema_version);
370        assert_eq!(bridge.product, semantic.product);
371        assert_eq!(bridge.blocking_gaps, semantic.blocking_gaps);
372        assert_eq!(bridge.next_priorities, semantic.next_priorities);
373        assert_eq!(bridge.items.len(), semantic.items.len());
374
375        for (bridge_item, semantic_item) in bridge.items.iter().zip(&semantic.items) {
376            assert_eq!(bridge_item.evidence, semantic_item.evidence);
377            assert_eq!(bridge_item.status, semantic_item.status);
378            assert_eq!(bridge_item.provider, semantic_item.provider);
379            assert_eq!(bridge_item.observed_count, semantic_item.observed_count);
380            assert_eq!(bridge_item.reason, semantic_item.reason);
381        }
382    }
383
384    fn assert_bridge_source_input_evidence_matches_semantic(
385        bridge: &super::SourceInputPromotionEvidenceSummaryV0,
386        semantic: &omena_semantic::SourceInputPromotionEvidenceSummaryV0,
387    ) {
388        assert_eq!(bridge.schema_version, semantic.schema_version);
389        assert_eq!(bridge.product, semantic.product);
390        assert_eq!(bridge.input_version, semantic.input_version);
391        assert_eq!(
392            bridge.reference_site_identity.status,
393            semantic.reference_site_identity.status
394        );
395        assert_eq!(
396            bridge.reference_site_identity.selector_count,
397            semantic.reference_site_identity.selector_count
398        );
399        assert_eq!(
400            bridge.reference_site_identity.reference_site_count,
401            semantic.reference_site_identity.reference_site_count
402        );
403        assert_eq!(
404            bridge.reference_site_identity.direct_reference_site_count,
405            semantic.reference_site_identity.direct_reference_site_count
406        );
407        assert_eq!(
408            bridge.reference_site_identity.expanded_reference_site_count,
409            semantic
410                .reference_site_identity
411                .expanded_reference_site_count
412        );
413        assert_eq!(
414            bridge
415                .reference_site_identity
416                .style_dependency_reference_site_count,
417            semantic
418                .reference_site_identity
419                .style_dependency_reference_site_count
420        );
421        assert_eq!(
422            bridge.reference_site_identity.editable_direct_site_count,
423            semantic.reference_site_identity.editable_direct_site_count
424        );
425        assert_eq!(
426            bridge.reference_site_identity.reference_kind_counts,
427            semantic.reference_site_identity.reference_kind_counts
428        );
429        assert_eq!(
430            bridge.certainty_reason.status,
431            semantic.certainty_reason.status
432        );
433        assert_eq!(
434            bridge.certainty_reason.expression_count,
435            semantic.certainty_reason.expression_count
436        );
437        assert_eq!(
438            bridge.certainty_reason.exact_count,
439            semantic.certainty_reason.exact_count
440        );
441        assert_eq!(
442            bridge.certainty_reason.inferred_count,
443            semantic.certainty_reason.inferred_count
444        );
445        assert_eq!(
446            bridge.certainty_reason.possible_count,
447            semantic.certainty_reason.possible_count
448        );
449        assert_eq!(
450            bridge.certainty_reason.missing_reason_count,
451            semantic.certainty_reason.missing_reason_count
452        );
453        assert_eq!(
454            bridge.certainty_reason.reason_counts,
455            semantic.certainty_reason.reason_counts
456        );
457        assert_eq!(
458            bridge.certainty_reason.shape_kind_counts,
459            semantic.certainty_reason.shape_kind_counts
460        );
461        assert_eq!(
462            bridge.certainty_reason.shape_label_counts,
463            semantic.certainty_reason.shape_label_counts
464        );
465        assert_eq!(bridge.binding_origin.status, semantic.binding_origin.status);
466        assert_eq!(
467            bridge.binding_origin.expression_count,
468            semantic.binding_origin.expression_count
469        );
470        assert_eq!(
471            bridge.binding_origin.direct_class_name_count,
472            semantic.binding_origin.direct_class_name_count
473        );
474        assert_eq!(
475            bridge.binding_origin.root_binding_count,
476            semantic.binding_origin.root_binding_count
477        );
478        assert_eq!(
479            bridge.binding_origin.access_path_count,
480            semantic.binding_origin.access_path_count
481        );
482        assert_eq!(
483            bridge.binding_origin.access_path_segment_count,
484            semantic.binding_origin.access_path_segment_count
485        );
486        assert_eq!(
487            bridge.binding_origin.expression_kind_counts,
488            semantic.binding_origin.expression_kind_counts
489        );
490        assert_eq!(
491            bridge.style_module_edge.status,
492            semantic.style_module_edge.status
493        );
494        assert_eq!(
495            bridge.style_module_edge.source_style_edge_count,
496            semantic.style_module_edge.source_style_edge_count
497        );
498        assert_eq!(
499            bridge.style_module_edge.distinct_style_module_count,
500            semantic.style_module_edge.distinct_style_module_count
501        );
502        assert_eq!(
503            bridge.style_module_edge.missing_style_document_edge_count,
504            semantic.style_module_edge.missing_style_document_edge_count
505        );
506        assert_eq!(
507            bridge.style_module_edge.composed_edge_count,
508            semantic.style_module_edge.composed_edge_count
509        );
510        assert_eq!(
511            bridge.style_module_edge.imported_composed_edge_count,
512            semantic.style_module_edge.imported_composed_edge_count
513        );
514        assert_eq!(
515            bridge.style_module_edge.global_composed_edge_count,
516            semantic.style_module_edge.global_composed_edge_count
517        );
518        assert_eq!(
519            bridge.value_domain_explanation.status,
520            semantic.value_domain_explanation.status
521        );
522        assert_eq!(
523            bridge.value_domain_explanation.expression_count,
524            semantic.value_domain_explanation.expression_count
525        );
526        assert_eq!(
527            bridge.value_domain_explanation.exact_expression_count,
528            semantic.value_domain_explanation.exact_expression_count
529        );
530        assert_eq!(
531            bridge
532                .value_domain_explanation
533                .finite_value_expression_count,
534            semantic
535                .value_domain_explanation
536                .finite_value_expression_count
537        );
538        assert_eq!(
539            bridge.value_domain_explanation.constrained_expression_count,
540            semantic
541                .value_domain_explanation
542                .constrained_expression_count
543        );
544        assert_eq!(
545            bridge.value_domain_explanation.unknown_expression_count,
546            semantic.value_domain_explanation.unknown_expression_count
547        );
548        assert_eq!(
549            bridge.value_domain_explanation.finite_value_count,
550            semantic.value_domain_explanation.finite_value_count
551        );
552        assert_eq!(
553            bridge.value_domain_explanation.derivation_count,
554            semantic.value_domain_explanation.derivation_count
555        );
556        assert_eq!(
557            bridge.value_domain_explanation.derivation_step_count,
558            semantic.value_domain_explanation.derivation_step_count
559        );
560        assert_eq!(
561            bridge.value_domain_explanation.value_domain_kind_counts,
562            semantic.value_domain_explanation.value_domain_kind_counts
563        );
564        assert_eq!(
565            bridge.value_domain_explanation.constraint_kind_counts,
566            semantic.value_domain_explanation.constraint_kind_counts
567        );
568        assert_eq!(
569            bridge.value_domain_explanation.derivation_product_counts,
570            semantic.value_domain_explanation.derivation_product_counts
571        );
572        assert_eq!(
573            bridge
574                .value_domain_explanation
575                .derivation_reduced_kind_counts,
576            semantic
577                .value_domain_explanation
578                .derivation_reduced_kind_counts
579        );
580        assert_eq!(
581            bridge.value_domain_explanation.derivation_operation_counts,
582            semantic
583                .value_domain_explanation
584                .derivation_operation_counts
585        );
586        assert_eq!(bridge.blocking_gaps, semantic.blocking_gaps);
587        assert_eq!(bridge.next_priorities, semantic.next_priorities);
588    }
589
590    fn sample_engine_input() -> EngineInputV2 {
591        EngineInputV2 {
592            version: "2".to_string(),
593            sources: vec![SourceAnalysisInputV2 {
594                document: SourceDocumentV2 {
595                    class_expressions: vec![ClassExpressionInputV2 {
596                        id: "expr-literal".to_string(),
597                        kind: "literal".to_string(),
598                        scss_module_path: "/tmp/Component.module.scss".to_string(),
599                        range: range(4, 12, 4, 18),
600                        class_name: Some("button".to_string()),
601                        root_binding_decl_id: None,
602                        access_path: None,
603                    }],
604                },
605            }],
606            styles: vec![StyleAnalysisInputV2 {
607                file_path: "/tmp/Component.module.scss".to_string(),
608                document: StyleDocumentV2 {
609                    selectors: vec![StyleSelectorV2 {
610                        name: "button".to_string(),
611                        view_kind: "canonical".to_string(),
612                        canonical_name: Some("button".to_string()),
613                        range: range(0, 1, 0, 7),
614                        nested_safety: Some("flat".to_string()),
615                        composes: None,
616                        bem_suffix: None,
617                    }],
618                },
619            }],
620            type_facts: vec![TypeFactEntryV2 {
621                file_path: "/tmp/Component.tsx".to_string(),
622                expression_id: "expr-literal".to_string(),
623                facts: StringTypeFactsV2 {
624                    kind: "exact".to_string(),
625                    constraint_kind: None,
626                    values: Some(vec!["button".to_string()]),
627                    prefix: None,
628                    suffix: None,
629                    min_len: None,
630                    max_len: None,
631                    char_must: None,
632                    char_may: None,
633                    may_include_other_chars: None,
634                },
635            }],
636        }
637    }
638
639    fn range(
640        start_line: usize,
641        start_character: usize,
642        end_line: usize,
643        end_character: usize,
644    ) -> RangeV2 {
645        RangeV2 {
646            start: PositionV2 {
647                line: start_line,
648                character: start_character,
649            },
650            end: PositionV2 {
651                line: end_line,
652                character: end_character,
653            },
654        }
655    }
656}