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}