1use std::{
4 collections::{BTreeSet, hash_map::DefaultHasher},
5 hash::{Hash, Hasher},
6};
7
8use omena_cascade::StaticSupportsAssumptionV0;
9use omena_incremental::IncrementalRevisionV0;
10use omena_parser::{
11 ClosedWorldBundleV0, ClosedWorldLinkedModuleV0, ConfigurationHashV0, ModuleIdV0,
12 ModuleInstanceKeyV0, StyleDialect, parse, summarize_omena_parser_parity_lite,
13 summarize_omena_parser_style_facts,
14};
15use omena_syntax::SyntaxKind;
16use omena_transform_cst::{
17 IrNodeIdV0, IrNodeKindV0, TransformIrV0, TransformPassClassV0, TransformPassKind,
18 default_transform_pass_descriptors, lower_transform_ir_from_source, print_transform_ir_css,
19};
20
21use super::planner::{plan_transform_passes, transform_pass_kind_from_id};
22use super::provenance::derive_transform_mutation_spans;
23use super::semantic_preservation::{
24 SemanticObservationProjectionV0, SemanticObservationScopeV0,
25 compare_semantic_observation_for_pass_with_scopes, semantic_preservation_applies,
26};
27use crate::runtime::cascade_proof::collect_cascade_proof_obligations_for_pass_input;
28use crate::{
29 TransformProvenanceMutationSpanV0, TransformSemanticRemovalCandidate,
30 TransformStructuralIrShadowEquivalenceReportV0, TransformStructuralIrShadowFieldReportV0,
31 TransformStructuralIrShadowFixtureReportV0,
32 domains::{
33 cascade_flatten::{
34 collect_layer_flatten_proof_candidates_with_lexer,
35 collect_scope_flatten_proof_candidates_with_lexer, flatten_css_layers_with_lexer,
36 flatten_css_scopes_with_lexer,
37 },
38 css_modules_classes::{
39 local_css_module_composes_resolutions_with_lexer,
40 rewrite_css_module_class_names_with_lexer,
41 strip_resolved_css_module_composes_with_lexer, tree_shake_css_class_rules_with_lexer,
42 },
43 css_modules_values::tree_shake_css_modules_values_with_lexer,
44 custom_property::tree_shake_css_custom_properties_with_lexer,
45 design_token::route_design_token_values_with_lexer,
46 import_inline::inline_css_imports_with_lexer,
47 keyframes::tree_shake_css_keyframes_with_lexer,
48 nesting::unwrap_css_nesting_with_lexer,
49 rule_cleanup::{dedupe_exact_css_rules_with_lexer, remove_empty_css_rules_with_lexer},
50 rule_merge::{
51 merge_adjacent_same_block_css_selectors_with_lexer,
52 merge_adjacent_same_selector_css_rules_with_lexer,
53 },
54 static_eval::{
55 StaticMediaEvaluationOptions, evaluate_static_container_rules_with_lexer,
56 evaluate_static_media_rules_with_lexer, evaluate_static_supports_rules_with_lexer,
57 },
58 },
59 helpers::ir_transaction::{
60 reset_structural_ir_transaction_telemetry, structural_ir_transaction_telemetry_snapshot,
61 },
62 model::{
63 TransformClassNameRewriteV0, TransformCssModuleComposesResolutionV0,
64 TransformDesignTokenRouteV0, TransformExecutionContextV0, TransformExecutionSummaryV0,
65 TransformImportInlineV0, TransformSemanticRemovalV0,
66 TransformStructuralIrTransactionTelemetryV0,
67 },
68 registry::{evaluate_native_css_static_values_with_plan, unwrap_css_nesting_in_ir},
69 runtime::executor::{
70 execute_transform_passes_on_source_with_dialect_and_context,
71 execute_transform_passes_on_source_with_dialect_context_and_closed_world_bundle,
72 },
73};
74
75const COMPARED_FIELDS: [&str; 12] = [
76 "canonicalCssBytes",
77 "selectorSet",
78 "declarationSet",
79 "cascadeOutcome",
80 "mutationSpanRanges",
81 "mutationCount",
82 "semanticRemovals",
83 "cssImportInlines",
84 "cssModuleComposesExports",
85 "cssModuleEvaluation",
86 "designTokenRoutes",
87 "irTransactionCommitCount",
88];
89
90#[derive(Debug, Clone, Copy)]
91pub struct TransformStructuralIrShadowFixtureInputV0<'source> {
92 pub fixture: &'source str,
93 pub pass: TransformPassKind,
94 pub dialect: StyleDialect,
95 pub source: &'source str,
96 pub closed_bundle: bool,
97}
98
99#[derive(Debug, Clone, Copy)]
100pub struct TransformStructuralIrPipelineShadowFixtureInputV0<'source> {
101 pub fixture: &'source str,
102 pub dialect: StyleDialect,
103 pub source: &'source str,
104 pub closed_bundle: bool,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
108struct StructuralShadowPathSnapshotV0 {
109 output_css: String,
110 mutation_count: usize,
111 selector_values: Vec<String>,
112 declaration_values: Vec<String>,
113 cascade_values: Vec<String>,
114 mutation_span_values: Vec<String>,
115 semantic_removal_values: Vec<String>,
116 css_import_inline_values: Vec<String>,
117 css_module_composes_values: Vec<String>,
118 css_module_evaluation_values: Vec<String>,
119 design_token_route_values: Vec<String>,
120 ir_transaction_telemetry: TransformStructuralIrTransactionTelemetryV0,
121 typed_payload_telemetry: StructuralShadowTypedPayloadTelemetryV0,
122}
123
124#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
125struct StructuralShadowTypedPayloadTelemetryV0 {
126 projections_consumed: usize,
127 memo_hits: usize,
128}
129
130#[derive(Debug, Clone)]
131struct TypedPayloadProjectionV0 {
132 node_id: IrNodeIdV0,
133 revision: IncrementalRevisionV0,
134 content_signature: u64,
135 typed_node_count: usize,
136 style_rule_count: usize,
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140struct TypedPayloadProjectionKeyV0 {
141 node_id: IrNodeIdV0,
142 revision: IncrementalRevisionV0,
143 content_signature: u64,
144}
145
146#[derive(Default)]
147struct TypedPayloadProjectionMemoV0 {
148 entries: Vec<(TypedPayloadProjectionKeyV0, TypedPayloadProjectionV0)>,
149 telemetry: StructuralShadowTypedPayloadTelemetryV0,
150}
151
152struct StructuralShadowReachabilityV0 {
153 class_names: Vec<String>,
154 keyframe_names: Vec<String>,
155 value_names: Vec<String>,
156 custom_property_names: Vec<String>,
157}
158
159#[derive(Debug, Clone, Default)]
160struct StructuralShadowModuleContextV0 {
161 import_inlines: Vec<TransformImportInlineV0>,
162 class_name_rewrites: Vec<TransformClassNameRewriteV0>,
163 css_module_composes_resolutions: Vec<TransformCssModuleComposesResolutionV0>,
164 design_token_routes: Vec<TransformDesignTokenRouteV0>,
165}
166
167#[derive(Debug, Clone, Default)]
168struct StructuralShadowModuleEgressValuesV0 {
169 css_import_inline_values: Vec<String>,
170 css_module_composes_values: Vec<String>,
171 css_module_evaluation_values: Vec<String>,
172 design_token_route_values: Vec<String>,
173}
174
175pub fn summarize_structural_ir_shadow_equivalence_v0()
176-> TransformStructuralIrShadowEquivalenceReportV0 {
177 let fixtures = structural_shadow_fixtures();
178 summarize_structural_ir_shadow_equivalence_for_fixtures_v0(fixtures.as_slice())
179}
180
181pub fn summarize_structural_ir_shadow_equivalence_for_fixtures_v0(
182 fixtures: &[TransformStructuralIrShadowFixtureInputV0<'_>],
183) -> TransformStructuralIrShadowEquivalenceReportV0 {
184 let reports = fixtures
185 .iter()
186 .copied()
187 .map(structural_shadow_report_for_fixture)
188 .collect::<Vec<_>>();
189 let all_fields_match = reports.iter().all(|report| report.all_fields_match);
190 let all_typed_path_fields_match = reports
191 .iter()
192 .all(|report| report.all_typed_path_fields_match);
193 let typed_payload_projections_consumed = reports
194 .iter()
195 .map(|report| report.typed_payload_projections_consumed)
196 .sum::<usize>();
197 let typed_payload_memo_hits = reports
198 .iter()
199 .map(|report| report.typed_payload_memo_hits)
200 .sum::<usize>();
201
202 TransformStructuralIrShadowEquivalenceReportV0 {
203 schema_version: "0",
204 product: "omena-transform-passes.structural-ir-shadow-equivalence",
205 fixture_count: reports.len(),
206 compared_pass_ids: compared_pass_ids(),
207 compared_fields: COMPARED_FIELDS.to_vec(),
208 reports,
209 all_fields_match,
210 all_typed_path_fields_match,
211 typed_payload_projections_consumed,
212 typed_payload_memo_hits,
213 }
214}
215
216pub fn summarize_structural_ir_pipeline_shadow_equivalence_for_fixtures_v0(
217 fixtures: &[TransformStructuralIrPipelineShadowFixtureInputV0<'_>],
218) -> TransformStructuralIrShadowEquivalenceReportV0 {
219 let reports = fixtures
220 .iter()
221 .copied()
222 .map(structural_pipeline_shadow_report_for_fixture)
223 .collect::<Vec<_>>();
224 let all_fields_match = reports.iter().all(|report| report.all_fields_match);
225 let all_typed_path_fields_match = reports
226 .iter()
227 .all(|report| report.all_typed_path_fields_match);
228 let typed_payload_projections_consumed = reports
229 .iter()
230 .map(|report| report.typed_payload_projections_consumed)
231 .sum::<usize>();
232 let typed_payload_memo_hits = reports
233 .iter()
234 .map(|report| report.typed_payload_memo_hits)
235 .sum::<usize>();
236
237 TransformStructuralIrShadowEquivalenceReportV0 {
238 schema_version: "0",
239 product: "omena-transform-passes.structural-ir-pipeline-shadow-equivalence",
240 fixture_count: reports.len(),
241 compared_pass_ids: compared_pass_ids(),
242 compared_fields: COMPARED_FIELDS.to_vec(),
243 reports,
244 all_fields_match,
245 all_typed_path_fields_match,
246 typed_payload_projections_consumed,
247 typed_payload_memo_hits,
248 }
249}
250
251fn structural_shadow_report_for_fixture(
252 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
253) -> TransformStructuralIrShadowFixtureReportV0 {
254 let string_snapshot = string_path_snapshot(fixture);
255 let ir_snapshot = ir_path_snapshot(fixture);
256 let typed_snapshot = typed_path_snapshot(fixture);
257 let expected_commit_flag = expected_ir_transaction_commit_flag(string_snapshot.mutation_count);
258 let (
259 ir_path_mutation_count,
260 typed_path_mutation_count,
261 ir_path_transaction_commit_count,
262 typed_payload_projections_consumed,
263 typed_payload_memo_hits,
264 fields,
265 ) = match (ir_snapshot, typed_snapshot) {
266 (Ok(ir_snapshot), Ok(typed_snapshot)) => {
267 let telemetry = ir_snapshot.ir_transaction_telemetry;
268 let typed_payload_telemetry = typed_snapshot.typed_payload_telemetry;
269 let actual_commit_flag = if telemetry.transaction_commit_count > 0 {
270 "1".to_string()
271 } else {
272 "0".to_string()
273 };
274 (
275 Some(ir_snapshot.mutation_count),
276 Some(typed_snapshot.mutation_count),
277 Some(telemetry.transaction_commit_count),
278 typed_payload_telemetry.projections_consumed,
279 typed_payload_telemetry.memo_hits,
280 vec![
281 shadow_field_report_with_typed(
282 "canonicalCssBytes",
283 [string_snapshot.output_css.clone()],
284 [ir_snapshot.output_css],
285 [typed_snapshot.output_css],
286 ),
287 shadow_field_report_with_typed(
288 "selectorSet",
289 string_snapshot.selector_values,
290 ir_snapshot.selector_values,
291 typed_snapshot.selector_values,
292 ),
293 shadow_field_report_with_typed(
294 "declarationSet",
295 string_snapshot.declaration_values,
296 ir_snapshot.declaration_values,
297 typed_snapshot.declaration_values,
298 ),
299 shadow_field_report_with_typed(
300 "cascadeOutcome",
301 string_snapshot.cascade_values,
302 ir_snapshot.cascade_values,
303 typed_snapshot.cascade_values,
304 ),
305 shadow_field_report_with_typed(
306 "mutationSpanRanges",
307 string_snapshot.mutation_span_values,
308 ir_snapshot.mutation_span_values,
309 typed_snapshot.mutation_span_values,
310 ),
311 shadow_field_report_with_typed(
312 "mutationCount",
313 [string_snapshot.mutation_count.to_string()],
314 [ir_snapshot.mutation_count.to_string()],
315 [typed_snapshot.mutation_count.to_string()],
316 ),
317 shadow_field_report_with_typed(
318 "semanticRemovals",
319 string_snapshot.semantic_removal_values,
320 ir_snapshot.semantic_removal_values,
321 typed_snapshot.semantic_removal_values,
322 ),
323 shadow_field_report_with_typed(
324 "cssImportInlines",
325 string_snapshot.css_import_inline_values,
326 ir_snapshot.css_import_inline_values,
327 typed_snapshot.css_import_inline_values,
328 ),
329 shadow_field_report_with_typed(
330 "cssModuleComposesExports",
331 string_snapshot.css_module_composes_values,
332 ir_snapshot.css_module_composes_values,
333 typed_snapshot.css_module_composes_values,
334 ),
335 shadow_field_report_with_typed(
336 "cssModuleEvaluation",
337 string_snapshot.css_module_evaluation_values,
338 ir_snapshot.css_module_evaluation_values,
339 typed_snapshot.css_module_evaluation_values,
340 ),
341 shadow_field_report_with_typed(
342 "designTokenRoutes",
343 string_snapshot.design_token_route_values,
344 ir_snapshot.design_token_route_values,
345 typed_snapshot.design_token_route_values,
346 ),
347 shadow_field_report_with_typed(
348 "irTransactionCommitCount",
349 [expected_commit_flag.clone()],
350 [actual_commit_flag],
351 [expected_ir_transaction_commit_flag(
352 typed_snapshot.mutation_count,
353 )],
354 ),
355 ],
356 )
357 }
358 (Err(ir_error), typed_result) => {
359 let ir_error = format!("irPathError:{ir_error}");
360 let typed_error = typed_result
361 .err()
362 .map(|error| format!("typedPathError:{error}"))
363 .unwrap_or_else(|| "typedPathUnavailableAfterIrError".to_string());
364 (
365 None,
366 None,
367 None,
368 0,
369 0,
370 vec![
371 shadow_field_report_with_typed(
372 "canonicalCssBytes",
373 [string_snapshot.output_css],
374 [ir_error.clone()],
375 [typed_error.clone()],
376 ),
377 shadow_field_report_with_typed(
378 "selectorSet",
379 string_snapshot.selector_values,
380 [ir_error.clone()],
381 [typed_error.clone()],
382 ),
383 shadow_field_report_with_typed(
384 "declarationSet",
385 string_snapshot.declaration_values,
386 [ir_error.clone()],
387 [typed_error.clone()],
388 ),
389 shadow_field_report_with_typed(
390 "cascadeOutcome",
391 string_snapshot.cascade_values,
392 [ir_error.clone()],
393 [typed_error.clone()],
394 ),
395 shadow_field_report_with_typed(
396 "mutationSpanRanges",
397 string_snapshot.mutation_span_values,
398 [ir_error.clone()],
399 [typed_error.clone()],
400 ),
401 shadow_field_report_with_typed(
402 "mutationCount",
403 [string_snapshot.mutation_count.to_string()],
404 [ir_error.clone()],
405 [typed_error.clone()],
406 ),
407 shadow_field_report_with_typed(
408 "semanticRemovals",
409 string_snapshot.semantic_removal_values,
410 [ir_error.clone()],
411 [typed_error.clone()],
412 ),
413 shadow_field_report_with_typed(
414 "cssImportInlines",
415 string_snapshot.css_import_inline_values,
416 [ir_error.clone()],
417 [typed_error.clone()],
418 ),
419 shadow_field_report_with_typed(
420 "cssModuleComposesExports",
421 string_snapshot.css_module_composes_values,
422 [ir_error.clone()],
423 [typed_error.clone()],
424 ),
425 shadow_field_report_with_typed(
426 "cssModuleEvaluation",
427 string_snapshot.css_module_evaluation_values,
428 [ir_error.clone()],
429 [typed_error.clone()],
430 ),
431 shadow_field_report_with_typed(
432 "designTokenRoutes",
433 string_snapshot.design_token_route_values,
434 [ir_error.clone()],
435 [typed_error.clone()],
436 ),
437 shadow_field_report_with_typed(
438 "irTransactionCommitCount",
439 [expected_commit_flag],
440 [ir_error.clone()],
441 [typed_error.clone()],
442 ),
443 ],
444 )
445 }
446 (Ok(ir_snapshot), Err(typed_error)) => {
447 let telemetry = ir_snapshot.ir_transaction_telemetry;
448 let actual_commit_flag = if telemetry.transaction_commit_count > 0 {
449 "1".to_string()
450 } else {
451 "0".to_string()
452 };
453 let typed_error = format!("typedPathError:{typed_error}");
454 (
455 Some(ir_snapshot.mutation_count),
456 None,
457 Some(telemetry.transaction_commit_count),
458 0,
459 0,
460 vec![
461 shadow_field_report_with_typed(
462 "canonicalCssBytes",
463 [string_snapshot.output_css],
464 [ir_snapshot.output_css],
465 [typed_error.clone()],
466 ),
467 shadow_field_report_with_typed(
468 "selectorSet",
469 string_snapshot.selector_values,
470 ir_snapshot.selector_values,
471 [typed_error.clone()],
472 ),
473 shadow_field_report_with_typed(
474 "declarationSet",
475 string_snapshot.declaration_values,
476 ir_snapshot.declaration_values,
477 [typed_error.clone()],
478 ),
479 shadow_field_report_with_typed(
480 "cascadeOutcome",
481 string_snapshot.cascade_values,
482 ir_snapshot.cascade_values,
483 [typed_error.clone()],
484 ),
485 shadow_field_report_with_typed(
486 "mutationSpanRanges",
487 string_snapshot.mutation_span_values,
488 ir_snapshot.mutation_span_values,
489 [typed_error.clone()],
490 ),
491 shadow_field_report_with_typed(
492 "mutationCount",
493 [string_snapshot.mutation_count.to_string()],
494 [ir_snapshot.mutation_count.to_string()],
495 [typed_error.clone()],
496 ),
497 shadow_field_report_with_typed(
498 "semanticRemovals",
499 string_snapshot.semantic_removal_values,
500 ir_snapshot.semantic_removal_values,
501 [typed_error.clone()],
502 ),
503 shadow_field_report_with_typed(
504 "cssImportInlines",
505 string_snapshot.css_import_inline_values,
506 ir_snapshot.css_import_inline_values,
507 [typed_error.clone()],
508 ),
509 shadow_field_report_with_typed(
510 "cssModuleComposesExports",
511 string_snapshot.css_module_composes_values,
512 ir_snapshot.css_module_composes_values,
513 [typed_error.clone()],
514 ),
515 shadow_field_report_with_typed(
516 "cssModuleEvaluation",
517 string_snapshot.css_module_evaluation_values,
518 ir_snapshot.css_module_evaluation_values,
519 [typed_error.clone()],
520 ),
521 shadow_field_report_with_typed(
522 "designTokenRoutes",
523 string_snapshot.design_token_route_values,
524 ir_snapshot.design_token_route_values,
525 [typed_error.clone()],
526 ),
527 shadow_field_report_with_typed(
528 "irTransactionCommitCount",
529 [expected_commit_flag],
530 [actual_commit_flag],
531 [typed_error],
532 ),
533 ],
534 )
535 }
536 };
537 let all_fields_match = fields.iter().all(|field| field.matches);
538 let all_typed_path_fields_match = all_fields_match;
539
540 TransformStructuralIrShadowFixtureReportV0 {
541 schema_version: "0",
542 product: "omena-transform-passes.structural-ir-shadow-fixture",
543 fixture: fixture.fixture.to_string(),
544 pass_id: fixture.pass.id(),
545 dialect: dialect_label(fixture.dialect),
546 string_path_mutation_count: Some(string_snapshot.mutation_count),
547 ir_path_mutation_count,
548 typed_path_mutation_count,
549 ir_path_transaction_commit_count,
550 typed_payload_projections_consumed,
551 typed_payload_memo_hits,
552 fields,
553 all_fields_match,
554 all_typed_path_fields_match,
555 }
556}
557
558fn structural_pipeline_shadow_report_for_fixture(
559 fixture: TransformStructuralIrPipelineShadowFixtureInputV0<'_>,
560) -> TransformStructuralIrShadowFixtureReportV0 {
561 let string_snapshot = string_pipeline_snapshot(fixture);
562 let expected_commit_flag = expected_ir_transaction_commit_flag(string_snapshot.mutation_count);
563 let (ir_path_mutation_count, ir_path_transaction_commit_count, fields) =
564 match ir_pipeline_snapshot(fixture) {
565 Ok(ir_snapshot) => {
566 let actual_commit_flag = if ir_snapshot
567 .ir_transaction_telemetry
568 .transaction_commit_count
569 > 0
570 {
571 "1".to_string()
572 } else {
573 "0".to_string()
574 };
575 (
576 Some(ir_snapshot.mutation_count),
577 Some(
578 ir_snapshot
579 .ir_transaction_telemetry
580 .transaction_commit_count,
581 ),
582 vec![
583 shadow_field_report(
584 "canonicalCssBytes",
585 [string_snapshot.output_css.clone()],
586 [ir_snapshot.output_css],
587 ),
588 shadow_field_report(
589 "selectorSet",
590 string_snapshot.selector_values,
591 ir_snapshot.selector_values,
592 ),
593 shadow_field_report(
594 "declarationSet",
595 string_snapshot.declaration_values,
596 ir_snapshot.declaration_values,
597 ),
598 shadow_field_report(
599 "cascadeOutcome",
600 string_snapshot.cascade_values,
601 ir_snapshot.cascade_values,
602 ),
603 shadow_field_report(
604 "mutationSpanRanges",
605 string_snapshot.mutation_span_values,
606 ir_snapshot.mutation_span_values,
607 ),
608 shadow_field_report(
609 "mutationCount",
610 [string_snapshot.mutation_count.to_string()],
611 [ir_snapshot.mutation_count.to_string()],
612 ),
613 shadow_field_report(
614 "semanticRemovals",
615 string_snapshot.semantic_removal_values,
616 ir_snapshot.semantic_removal_values,
617 ),
618 shadow_field_report(
619 "cssImportInlines",
620 string_snapshot.css_import_inline_values,
621 ir_snapshot.css_import_inline_values,
622 ),
623 shadow_field_report(
624 "cssModuleComposesExports",
625 string_snapshot.css_module_composes_values,
626 ir_snapshot.css_module_composes_values,
627 ),
628 shadow_field_report(
629 "cssModuleEvaluation",
630 string_snapshot.css_module_evaluation_values,
631 ir_snapshot.css_module_evaluation_values,
632 ),
633 shadow_field_report(
634 "designTokenRoutes",
635 string_snapshot.design_token_route_values,
636 ir_snapshot.design_token_route_values,
637 ),
638 shadow_field_report(
639 "irTransactionCommitCount",
640 [expected_commit_flag.clone()],
641 [actual_commit_flag],
642 ),
643 ],
644 )
645 }
646 Err(error) => {
647 let error = format!("irPipelinePathError:{error}");
648 (
649 None,
650 None,
651 vec![
652 shadow_field_report(
653 "canonicalCssBytes",
654 [string_snapshot.output_css.clone()],
655 [error.clone()],
656 ),
657 shadow_field_report(
658 "selectorSet",
659 string_snapshot.selector_values,
660 [error.clone()],
661 ),
662 shadow_field_report(
663 "declarationSet",
664 string_snapshot.declaration_values,
665 [error.clone()],
666 ),
667 shadow_field_report(
668 "cascadeOutcome",
669 string_snapshot.cascade_values,
670 [error.clone()],
671 ),
672 shadow_field_report(
673 "mutationSpanRanges",
674 string_snapshot.mutation_span_values,
675 [error.clone()],
676 ),
677 shadow_field_report(
678 "mutationCount",
679 [string_snapshot.mutation_count.to_string()],
680 [error.clone()],
681 ),
682 shadow_field_report(
683 "semanticRemovals",
684 string_snapshot.semantic_removal_values,
685 [error.clone()],
686 ),
687 shadow_field_report(
688 "cssImportInlines",
689 string_snapshot.css_import_inline_values,
690 [error.clone()],
691 ),
692 shadow_field_report(
693 "cssModuleComposesExports",
694 string_snapshot.css_module_composes_values,
695 [error.clone()],
696 ),
697 shadow_field_report(
698 "cssModuleEvaluation",
699 string_snapshot.css_module_evaluation_values,
700 [error.clone()],
701 ),
702 shadow_field_report(
703 "designTokenRoutes",
704 string_snapshot.design_token_route_values,
705 [error.clone()],
706 ),
707 shadow_field_report(
708 "irTransactionCommitCount",
709 [expected_commit_flag],
710 [error],
711 ),
712 ],
713 )
714 }
715 };
716 let all_fields_match = fields.iter().all(|field| field.matches);
717 let all_typed_path_fields_match = all_fields_match;
718
719 TransformStructuralIrShadowFixtureReportV0 {
720 schema_version: "0",
721 product: "omena-transform-passes.structural-ir-shadow-fixture",
722 fixture: fixture.fixture.to_string(),
723 pass_id: "structural-pipeline",
724 dialect: dialect_label(fixture.dialect),
725 string_path_mutation_count: Some(string_snapshot.mutation_count),
726 ir_path_mutation_count,
727 typed_path_mutation_count: ir_path_mutation_count,
728 ir_path_transaction_commit_count,
729 typed_payload_projections_consumed: 0,
730 typed_payload_memo_hits: 0,
731 fields,
732 all_fields_match,
733 all_typed_path_fields_match,
734 }
735}
736
737fn expected_ir_transaction_commit_flag(mutation_count: usize) -> String {
738 if mutation_count > 0 {
739 "1".to_string()
740 } else {
741 "0".to_string()
742 }
743}
744
745fn string_path_snapshot(
746 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
747) -> StructuralShadowPathSnapshotV0 {
748 let reachability = reachability_for_fixture(fixture);
749 let module_context = module_context_for_fixture(fixture);
750 let (output_css, mutation_count, semantic_removal_values) = match fixture.pass {
751 TransformPassKind::NestingUnwrap => {
752 let (output_css, mutation_count) =
753 unwrap_css_nesting_with_lexer(fixture.source, fixture.dialect);
754 (output_css, mutation_count, Vec::new())
755 }
756 TransformPassKind::ScopeFlatten => {
757 if string_path_flatten_precondition_allows(
758 fixture,
759 TransformPassKind::ScopeFlatten,
760 &reachability,
761 &module_context,
762 ) {
763 let (output_css, mutation_count) =
764 flatten_css_scopes_with_lexer(fixture.source, fixture.dialect);
765 (output_css, mutation_count, Vec::new())
766 } else {
767 (fixture.source.to_string(), 0, Vec::new())
768 }
769 }
770 TransformPassKind::LayerFlatten => {
771 if string_path_flatten_precondition_allows(
772 fixture,
773 TransformPassKind::LayerFlatten,
774 &reachability,
775 &module_context,
776 ) {
777 let (output_css, mutation_count) = flatten_css_layers_with_lexer(
778 fixture.source,
779 fixture.dialect,
780 fixture.closed_bundle,
781 );
782 (output_css, mutation_count, Vec::new())
783 } else {
784 (fixture.source.to_string(), 0, Vec::new())
785 }
786 }
787 TransformPassKind::RuleDeduplication => {
788 let (output_css, mutation_count) =
789 dedupe_exact_css_rules_with_lexer(fixture.source, fixture.dialect);
790 (output_css, mutation_count, Vec::new())
791 }
792 TransformPassKind::RuleMerging => {
793 let (output_css, mutation_count) =
794 merge_adjacent_same_selector_css_rules_with_lexer(fixture.source, fixture.dialect);
795 (output_css, mutation_count, Vec::new())
796 }
797 TransformPassKind::SelectorMerging => {
798 let (output_css, mutation_count) =
799 merge_adjacent_same_block_css_selectors_with_lexer(fixture.source, fixture.dialect);
800 (output_css, mutation_count, Vec::new())
801 }
802 TransformPassKind::EmptyRuleRemoval => {
803 let (output_css, mutation_count) =
804 remove_empty_css_rules_with_lexer(fixture.source, fixture.dialect);
805 (output_css, mutation_count, Vec::new())
806 }
807 TransformPassKind::SupportsStaticEval | TransformPassKind::DeadSupportsBranchRemoval => {
808 let (output_css, mutation_count) = evaluate_static_supports_rules_with_lexer(
809 fixture.source,
810 fixture.dialect,
811 StaticSupportsAssumptionV0::ModernBrowser,
812 );
813 (output_css, mutation_count, Vec::new())
814 }
815 TransformPassKind::MediaStaticEval | TransformPassKind::DeadMediaBranchRemoval => {
816 let (output_css, mutation_count) = evaluate_static_media_rules_with_lexer(
817 fixture.source,
818 fixture.dialect,
819 StaticMediaEvaluationOptions::default(),
820 );
821 (output_css, mutation_count, Vec::new())
822 }
823 TransformPassKind::ContainerStaticEval => {
824 let (output_css, mutation_count) =
825 evaluate_static_container_rules_with_lexer(fixture.source, fixture.dialect);
826 (output_css, mutation_count, Vec::new())
827 }
828 TransformPassKind::NativeCssStaticEval => {
829 let (output_css, mutation_count) =
830 evaluate_native_css_static_values_with_plan(fixture.source, fixture.dialect);
831 (output_css, mutation_count, Vec::new())
832 }
833 TransformPassKind::TreeShakeClass => {
834 let (output_css, removals) = tree_shake_css_class_rules_with_lexer(
835 fixture.source,
836 fixture.dialect,
837 reachability.class_names.as_slice(),
838 );
839 let mutation_count = removals.len();
840 (
841 output_css,
842 mutation_count,
843 semantic_removal_values(removals),
844 )
845 }
846 TransformPassKind::TreeShakeKeyframes => {
847 let (output_css, removals) = tree_shake_css_keyframes_with_lexer(
848 fixture.source,
849 fixture.dialect,
850 reachability.keyframe_names.as_slice(),
851 reachability.class_names.as_slice(),
852 );
853 let mutation_count = removals.len();
854 (
855 output_css,
856 mutation_count,
857 semantic_removal_values(removals),
858 )
859 }
860 TransformPassKind::TreeShakeValue => {
861 let (output_css, removals) = tree_shake_css_modules_values_with_lexer(
862 fixture.source,
863 fixture.dialect,
864 reachability.value_names.as_slice(),
865 reachability.keyframe_names.as_slice(),
866 reachability.class_names.as_slice(),
867 );
868 let mutation_count = removals.len();
869 (
870 output_css,
871 mutation_count,
872 semantic_removal_values(removals),
873 )
874 }
875 TransformPassKind::TreeShakeCustomProperty => {
876 let (output_css, removals) = tree_shake_css_custom_properties_with_lexer(
877 fixture.source,
878 fixture.dialect,
879 reachability.custom_property_names.as_slice(),
880 reachability.keyframe_names.as_slice(),
881 reachability.class_names.as_slice(),
882 );
883 let mutation_count = removals.len();
884 (
885 output_css,
886 mutation_count,
887 semantic_removal_values(removals),
888 )
889 }
890 TransformPassKind::ImportInline => {
891 let (output_css, mutation_count) = inline_css_imports_with_lexer(
892 fixture.source,
893 fixture.dialect,
894 module_context.import_inlines.as_slice(),
895 );
896 (output_css, mutation_count, Vec::new())
897 }
898 TransformPassKind::ResolveCssModulesComposes => {
899 let resolutions = css_module_composes_resolutions_for_fixture(fixture, &module_context);
900 let (output_css, mutation_count) = strip_resolved_css_module_composes_with_lexer(
901 fixture.source,
902 fixture.dialect,
903 resolutions.as_slice(),
904 );
905 (output_css, mutation_count, Vec::new())
906 }
907 TransformPassKind::HashCssModuleClassNames => {
908 let (output_css, mutation_count) = rewrite_css_module_class_names_with_lexer(
909 fixture.source,
910 fixture.dialect,
911 module_context.class_name_rewrites.as_slice(),
912 );
913 (output_css, mutation_count, Vec::new())
914 }
915 TransformPassKind::DesignTokenRouting => {
916 let (output_css, mutation_count) = route_design_token_values_with_lexer(
917 fixture.source,
918 fixture.dialect,
919 module_context.design_token_routes.as_slice(),
920 );
921 (output_css, mutation_count, Vec::new())
922 }
923 _ => (fixture.source.to_string(), 0, Vec::new()),
924 };
925 let (output_css, mutation_count, semantic_removal_values) =
926 if string_path_product_runtime_allows(
927 fixture,
928 mutation_count,
929 &reachability,
930 &module_context,
931 ) && string_path_semantic_preservation_allows(
932 fixture,
933 output_css.as_str(),
934 mutation_count,
935 &reachability,
936 ) {
937 (output_css, mutation_count, semantic_removal_values)
938 } else {
939 (fixture.source.to_string(), 0, Vec::new())
940 };
941
942 path_snapshot_from_output(
943 fixture,
944 output_css,
945 mutation_count,
946 semantic_removal_values,
947 module_egress_values_for_fixture(fixture, &module_context),
948 TransformStructuralIrTransactionTelemetryV0::default(),
949 )
950}
951
952fn string_path_semantic_preservation_allows(
953 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
954 output_css: &str,
955 mutation_count: usize,
956 reachability: &StructuralShadowReachabilityV0,
957) -> bool {
958 if mutation_count == 0 || !semantic_preservation_applies(fixture.pass) {
959 return true;
960 }
961 let input_ir = lower_transform_ir_from_source(
962 fixture.source,
963 fixture.dialect,
964 "omena-transform-passes.structural-shadow.input",
965 );
966 let output_ir = lower_transform_ir_from_source(
967 output_css,
968 fixture.dialect,
969 "omena-transform-passes.structural-shadow.output",
970 );
971 let closed_bundle = fixture_requires_closed_world_bundle(fixture)
972 .then(|| closed_world_bundle_for_shadow_fixture(fixture.fixture, reachability))
973 .transpose()
974 .ok()
975 .flatten();
976 let projection = SemanticObservationProjectionV0::for_pass_input(
977 fixture.pass,
978 &input_ir,
979 fixture.dialect,
980 closed_bundle.as_ref(),
981 None,
982 );
983 let input_scope = SemanticObservationScopeV0::for_pass(
984 fixture.pass,
985 fixture.dialect,
986 closed_bundle.as_ref(),
987 None,
988 &projection,
989 );
990 let output_scope = input_scope.without_ignored_source_ranges();
991 compare_semantic_observation_for_pass_with_scopes(
992 fixture.pass.id(),
993 &input_ir,
994 &output_ir,
995 input_scope,
996 output_scope,
997 )
998 .preserved
999}
1000
1001fn string_path_product_runtime_allows(
1002 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1003 mutation_count: usize,
1004 reachability: &StructuralShadowReachabilityV0,
1005 module_context: &StructuralShadowModuleContextV0,
1006) -> bool {
1007 if mutation_count == 0 {
1008 return true;
1009 }
1010 let context = execution_context_for_fixture(reachability, module_context);
1011 let passes = [fixture.pass];
1012 let summary = if fixture_requires_closed_world_bundle(fixture) {
1013 let Ok(bundle) = closed_world_bundle_for_shadow_fixture(fixture.fixture, reachability)
1014 else {
1015 return false;
1016 };
1017 execute_transform_passes_on_source_with_dialect_context_and_closed_world_bundle(
1018 fixture.source,
1019 fixture.dialect,
1020 &passes,
1021 &context,
1022 &bundle,
1023 )
1024 } else {
1025 execute_transform_passes_on_source_with_dialect_and_context(
1026 fixture.source,
1027 fixture.dialect,
1028 &passes,
1029 &context,
1030 )
1031 };
1032 summary.mutation_count > 0
1033 && !summary
1034 .planned_only_pass_ids
1035 .iter()
1036 .any(|pass_id| *pass_id == fixture.pass.id())
1037}
1038
1039fn string_path_flatten_precondition_allows(
1040 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1041 pass: TransformPassKind,
1042 reachability: &StructuralShadowReachabilityV0,
1043 module_context: &StructuralShadowModuleContextV0,
1044) -> bool {
1045 let context = execution_context_for_fixture(reachability, module_context);
1046 let closed_bundle = fixture
1047 .closed_bundle
1048 .then(|| closed_world_bundle_for_shadow_fixture(fixture.fixture, reachability))
1049 .transpose()
1050 .ok()
1051 .flatten();
1052 let obligations = collect_cascade_proof_obligations_for_pass_input(
1053 pass.id(),
1054 Some(pass),
1055 fixture.source,
1056 fixture.dialect,
1057 &context,
1058 closed_bundle.as_ref(),
1059 );
1060 obligations.is_empty()
1061 || obligations.iter().all(|obligation| {
1062 obligation.accepted
1063 && obligation
1064 .discharge_ledger_lookup
1065 .as_ref()
1066 .is_some_and(|lookup| lookup.can_apply_family_stamp())
1067 })
1068}
1069
1070fn string_pipeline_snapshot(
1071 fixture: TransformStructuralIrPipelineShadowFixtureInputV0<'_>,
1072) -> StructuralShadowPathSnapshotV0 {
1073 let mut current_source = fixture.source.to_string();
1074 let mut mutation_count = 0;
1075 let mut semantic_removal_values = Vec::new();
1076 let mut css_import_inline_values = Vec::new();
1077 let mut css_module_composes_values = Vec::new();
1078 let mut css_module_evaluation_values = Vec::new();
1079 let mut design_token_route_values = Vec::new();
1080 let planned_pipeline_pass_ids = product_pipeline_execution_summary(fixture)
1081 .map(|summary| summary.planned_only_pass_ids)
1082 .unwrap_or_default();
1083
1084 for pass in structural_pipeline_passes() {
1085 if planned_pipeline_pass_ids
1086 .iter()
1087 .any(|pass_id| *pass_id == pass.id())
1088 {
1089 continue;
1090 }
1091 let pass_fixture = TransformStructuralIrShadowFixtureInputV0 {
1092 fixture: fixture.fixture,
1093 pass,
1094 dialect: fixture.dialect,
1095 source: current_source.as_str(),
1096 closed_bundle: fixture.closed_bundle,
1097 };
1098 let snapshot = string_path_snapshot(pass_fixture);
1099 mutation_count += snapshot.mutation_count;
1100 semantic_removal_values.extend(snapshot.semantic_removal_values);
1101 css_import_inline_values.extend(snapshot.css_import_inline_values);
1102 css_module_composes_values.extend(snapshot.css_module_composes_values);
1103 css_module_evaluation_values.extend(snapshot.css_module_evaluation_values);
1104 design_token_route_values.extend(snapshot.design_token_route_values);
1105 current_source = snapshot.output_css;
1106 }
1107
1108 path_snapshot_from_output(
1109 TransformStructuralIrShadowFixtureInputV0 {
1110 fixture: fixture.fixture,
1111 pass: TransformPassKind::NestingUnwrap,
1112 dialect: fixture.dialect,
1113 source: fixture.source,
1114 closed_bundle: fixture.closed_bundle,
1115 },
1116 current_source,
1117 mutation_count,
1118 semantic_removal_values,
1119 StructuralShadowModuleEgressValuesV0 {
1120 css_import_inline_values,
1121 css_module_composes_values,
1122 css_module_evaluation_values,
1123 design_token_route_values,
1124 },
1125 TransformStructuralIrTransactionTelemetryV0::default(),
1126 )
1127}
1128
1129fn product_pipeline_execution_summary(
1130 fixture: TransformStructuralIrPipelineShadowFixtureInputV0<'_>,
1131) -> Result<TransformExecutionSummaryV0, String> {
1132 let reachability = reachability_for_pipeline_fixture(fixture);
1133 let module_context = module_context_for_pipeline_fixture(fixture);
1134 let context = TransformExecutionContextV0 {
1135 reachable_class_names: reachability.class_names.clone(),
1136 reachable_keyframe_names: reachability.keyframe_names.clone(),
1137 reachable_value_names: reachability.value_names.clone(),
1138 reachable_custom_property_names: reachability.custom_property_names.clone(),
1139 import_inlines: module_context.import_inlines,
1140 class_name_rewrites: module_context.class_name_rewrites,
1141 css_module_composes_resolutions: module_context.css_module_composes_resolutions,
1142 design_token_routes: module_context.design_token_routes,
1143 ..TransformExecutionContextV0::default()
1144 };
1145 let bundle = closed_world_bundle_for_shadow_fixture(fixture.fixture, &reachability)?;
1146 Ok(
1147 execute_transform_passes_on_source_with_dialect_context_and_closed_world_bundle(
1148 fixture.source,
1149 fixture.dialect,
1150 structural_pipeline_passes().as_slice(),
1151 &context,
1152 &bundle,
1153 ),
1154 )
1155}
1156
1157fn ir_path_snapshot(
1158 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1159) -> Result<StructuralShadowPathSnapshotV0, String> {
1160 let reachability = reachability_for_fixture(fixture);
1161 let module_context = module_context_for_fixture(fixture);
1162 let context = execution_context_for_fixture(&reachability, &module_context);
1163 let passes = [fixture.pass];
1164 let summary = if fixture_requires_closed_world_bundle(fixture) {
1165 let bundle = closed_world_bundle_for_shadow_fixture(fixture.fixture, &reachability)?;
1166 execute_transform_passes_on_source_with_dialect_context_and_closed_world_bundle(
1167 fixture.source,
1168 fixture.dialect,
1169 &passes,
1170 &context,
1171 &bundle,
1172 )
1173 } else {
1174 execute_transform_passes_on_source_with_dialect_and_context(
1175 fixture.source,
1176 fixture.dialect,
1177 &passes,
1178 &context,
1179 )
1180 };
1181
1182 Ok(path_snapshot_from_output(
1183 fixture,
1184 summary.output_css,
1185 summary.mutation_count,
1186 public_semantic_removal_values(summary.semantic_removals),
1187 StructuralShadowModuleEgressValuesV0 {
1188 css_import_inline_values: json_values(summary.css_import_inlines.as_slice()),
1189 css_module_composes_values: json_values(summary.css_module_composes_exports.as_slice()),
1190 css_module_evaluation_values: summary
1191 .css_module_evaluation
1192 .as_ref()
1193 .map(|evaluation| serde_json::to_string(evaluation).unwrap_or_default())
1194 .into_iter()
1195 .collect(),
1196 design_token_route_values: json_values(summary.design_token_routes.as_slice()),
1197 },
1198 summary.structural_ir_transaction_telemetry,
1199 ))
1200}
1201
1202fn typed_path_snapshot(
1203 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1204) -> Result<StructuralShadowPathSnapshotV0, String> {
1205 if fixture.pass != TransformPassKind::NestingUnwrap {
1206 return ir_path_snapshot(fixture);
1207 }
1208
1209 let mut ir = lower_transform_ir_from_source(
1210 fixture.source,
1211 fixture.dialect,
1212 "omena-transform-passes.typed-payload-shadow",
1213 );
1214 let mut memo = TypedPayloadProjectionMemoV0::default();
1215 let revision = IncrementalRevisionV0 { value: 1 };
1216 let mut typed_payload_ready = false;
1217 for node_id in top_level_style_rule_node_ids(&ir) {
1218 let Some(node_source) = node_source_for_typed_payload(&ir, node_id) else {
1219 continue;
1220 };
1221 if let Some(projection) =
1222 memo.project_style_rule_payload(node_id, node_source, fixture.dialect, revision)
1223 {
1224 typed_payload_ready |= projection_supports_nesting_unwrap(&projection, node_id);
1225 let _ =
1226 memo.project_style_rule_payload(node_id, node_source, fixture.dialect, revision);
1227 }
1228 }
1229
1230 reset_structural_ir_transaction_telemetry();
1231 let mutation_count = if typed_payload_ready {
1232 unwrap_css_nesting_in_ir(&mut ir, fixture.dialect)
1233 .map_err(|error| format!("typed payload structural rewrite failed: {error:?}"))?
1234 } else {
1235 0
1236 };
1237 let telemetry = structural_ir_transaction_telemetry_snapshot();
1238 let output_css = print_transform_ir_css(&ir)
1239 .map_err(|error| format!("typed payload structural print failed: {error:?}"))?;
1240 let mut snapshot = path_snapshot_from_output(
1241 fixture,
1242 output_css,
1243 mutation_count,
1244 Vec::new(),
1245 StructuralShadowModuleEgressValuesV0::default(),
1246 telemetry,
1247 );
1248 snapshot.typed_payload_telemetry = memo.telemetry;
1249 Ok(snapshot)
1250}
1251
1252impl TypedPayloadProjectionMemoV0 {
1253 fn project_style_rule_payload(
1254 &mut self,
1255 node_id: IrNodeIdV0,
1256 source: &str,
1257 dialect: StyleDialect,
1258 revision: IncrementalRevisionV0,
1259 ) -> Option<TypedPayloadProjectionV0> {
1260 let key = TypedPayloadProjectionKeyV0 {
1261 node_id,
1262 revision,
1263 content_signature: typed_payload_content_signature(source),
1264 };
1265 self.telemetry.projections_consumed += 1;
1266 if let Some((_, projection)) = self.entries.iter().find(|(candidate, _)| *candidate == key)
1267 {
1268 self.telemetry.memo_hits += 1;
1269 return Some(projection.clone());
1270 }
1271
1272 let parsed = parse(source, dialect);
1273 let syntax = parsed.syntax();
1274 let has_stylesheet = syntax
1275 .descendants()
1276 .any(|node| node.kind() == SyntaxKind::Stylesheet);
1277 if !has_stylesheet {
1278 return None;
1279 }
1280 let mut typed_node_count = 0usize;
1281 let mut style_rule_count = 0usize;
1282 for node in syntax.descendants() {
1283 if node.kind().is_node() || node.kind().is_bogus() {
1284 typed_node_count += 1;
1285 }
1286 if matches!(node.kind(), SyntaxKind::Rule | SyntaxKind::QualifiedRule) {
1287 style_rule_count += 1;
1288 }
1289 }
1290
1291 let projection = TypedPayloadProjectionV0 {
1292 node_id,
1293 revision,
1294 content_signature: key.content_signature,
1295 typed_node_count,
1296 style_rule_count,
1297 };
1298 self.entries.push((key, projection.clone()));
1299 Some(projection)
1300 }
1301}
1302
1303fn projection_supports_nesting_unwrap(
1304 projection: &TypedPayloadProjectionV0,
1305 node_id: IrNodeIdV0,
1306) -> bool {
1307 projection.node_id == node_id
1308 && projection.revision.value > 0
1309 && projection.content_signature != 0
1310 && projection.typed_node_count > 0
1311 && projection.style_rule_count > 0
1312}
1313
1314fn top_level_style_rule_node_ids(ir: &TransformIrV0) -> Vec<IrNodeIdV0> {
1315 ir.nodes
1316 .iter()
1317 .filter(|node| {
1318 !node.deleted
1319 && node.kind == IrNodeKindV0::StyleRule
1320 && node
1321 .parent
1322 .and_then(|parent_id| ir.nodes.get(parent_id.index()))
1323 .is_none_or(|parent| parent.deleted || parent.kind != IrNodeKindV0::StyleRule)
1324 })
1325 .map(|node| node.node_id)
1326 .collect()
1327}
1328
1329fn node_source_for_typed_payload(ir: &TransformIrV0, node_id: IrNodeIdV0) -> Option<&str> {
1330 let node = ir.nodes.get(node_id.index())?;
1331 ir.source_text()
1332 .get(node.source_span_start..node.source_span_end)
1333}
1334
1335fn typed_payload_content_signature(source: &str) -> u64 {
1336 let mut hasher = DefaultHasher::new();
1337 source.hash(&mut hasher);
1338 hasher.finish()
1339}
1340
1341fn ir_pipeline_snapshot(
1342 fixture: TransformStructuralIrPipelineShadowFixtureInputV0<'_>,
1343) -> Result<StructuralShadowPathSnapshotV0, String> {
1344 let summary = product_pipeline_execution_summary(fixture)?;
1345
1346 Ok(path_snapshot_from_output(
1347 TransformStructuralIrShadowFixtureInputV0 {
1348 fixture: fixture.fixture,
1349 pass: TransformPassKind::NestingUnwrap,
1350 dialect: fixture.dialect,
1351 source: fixture.source,
1352 closed_bundle: fixture.closed_bundle,
1353 },
1354 summary.output_css,
1355 summary.mutation_count,
1356 public_semantic_removal_values(summary.semantic_removals),
1357 StructuralShadowModuleEgressValuesV0 {
1358 css_import_inline_values: json_values(summary.css_import_inlines.as_slice()),
1359 css_module_composes_values: json_values(summary.css_module_composes_exports.as_slice()),
1360 css_module_evaluation_values: summary
1361 .css_module_evaluation
1362 .as_ref()
1363 .map(|evaluation| serde_json::to_string(evaluation).unwrap_or_default())
1364 .into_iter()
1365 .collect(),
1366 design_token_route_values: json_values(summary.design_token_routes.as_slice()),
1367 },
1368 summary.structural_ir_transaction_telemetry,
1369 ))
1370}
1371
1372fn execution_context_for_fixture(
1373 reachability: &StructuralShadowReachabilityV0,
1374 module_context: &StructuralShadowModuleContextV0,
1375) -> TransformExecutionContextV0 {
1376 TransformExecutionContextV0 {
1377 reachable_class_names: reachability.class_names.clone(),
1378 reachable_keyframe_names: reachability.keyframe_names.clone(),
1379 reachable_value_names: reachability.value_names.clone(),
1380 reachable_custom_property_names: reachability.custom_property_names.clone(),
1381 import_inlines: module_context.import_inlines.clone(),
1382 class_name_rewrites: module_context.class_name_rewrites.clone(),
1383 css_module_composes_resolutions: module_context.css_module_composes_resolutions.clone(),
1384 design_token_routes: module_context.design_token_routes.clone(),
1385 ..TransformExecutionContextV0::default()
1386 }
1387}
1388
1389fn fixture_requires_closed_world_bundle(
1390 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1391) -> bool {
1392 fixture.closed_bundle
1393 || matches!(
1394 fixture.pass,
1395 TransformPassKind::TreeShakeClass
1396 | TransformPassKind::TreeShakeKeyframes
1397 | TransformPassKind::TreeShakeValue
1398 | TransformPassKind::TreeShakeCustomProperty
1399 )
1400}
1401
1402fn closed_world_bundle_for_shadow_fixture(
1403 fixture_name: &str,
1404 reachability: &StructuralShadowReachabilityV0,
1405) -> Result<ClosedWorldBundleV0, String> {
1406 let instance = ModuleInstanceKeyV0::new(
1407 ModuleIdV0::new(format!("omena-transform-passes.shadow.{fixture_name}")),
1408 ConfigurationHashV0::none(),
1409 );
1410 let mut module = ClosedWorldLinkedModuleV0::new(instance.clone());
1411 for name in &reachability.class_names {
1412 module = module.with_class_name(name.clone());
1413 }
1414 for name in &reachability.keyframe_names {
1415 module = module.with_keyframe_name(name.clone());
1416 }
1417 for name in &reachability.value_names {
1418 module = module.with_value_name(name.clone());
1419 }
1420 for name in &reachability.custom_property_names {
1421 module = module.with_custom_property_name(name.clone());
1422 }
1423
1424 ClosedWorldBundleV0::try_from_linked_modules(vec![instance], vec![module])
1425 .map_err(|err| format!("closed-world bundle construction failed: {err:?}"))
1426}
1427
1428fn structural_pipeline_passes() -> Vec<TransformPassKind> {
1429 let structural_passes = default_transform_pass_descriptors()
1430 .into_iter()
1431 .filter(|descriptor| descriptor.pass_class == TransformPassClassV0::Structural)
1432 .map(|descriptor| descriptor.kind)
1433 .collect::<Vec<_>>();
1434 plan_transform_passes(structural_passes.as_slice())
1435 .ordered_pass_ids
1436 .iter()
1437 .filter_map(|pass_id| transform_pass_kind_from_id(pass_id))
1438 .collect()
1439}
1440
1441fn path_snapshot_from_output(
1442 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1443 output_css: String,
1444 mutation_count: usize,
1445 semantic_removal_values: Vec<String>,
1446 module_egress_values: StructuralShadowModuleEgressValuesV0,
1447 ir_transaction_telemetry: TransformStructuralIrTransactionTelemetryV0,
1448) -> StructuralShadowPathSnapshotV0 {
1449 let cascade_values = cascade_values_for_source(fixture, output_css.as_str());
1450 StructuralShadowPathSnapshotV0 {
1451 selector_values: selector_values_for_source(&output_css, fixture.dialect),
1452 declaration_values: declaration_values_for_source(&output_css, fixture.dialect),
1453 cascade_values,
1454 mutation_span_values: mutation_span_values(derive_transform_mutation_spans(
1455 fixture.source,
1456 output_css.as_str(),
1457 )),
1458 output_css,
1459 mutation_count,
1460 semantic_removal_values,
1461 css_import_inline_values: module_egress_values.css_import_inline_values,
1462 css_module_composes_values: module_egress_values.css_module_composes_values,
1463 css_module_evaluation_values: module_egress_values.css_module_evaluation_values,
1464 design_token_route_values: module_egress_values.design_token_route_values,
1465 ir_transaction_telemetry,
1466 typed_payload_telemetry: StructuralShadowTypedPayloadTelemetryV0::default(),
1467 }
1468}
1469
1470fn structural_shadow_fixtures() -> Vec<TransformStructuralIrShadowFixtureInputV0<'static>> {
1471 vec![
1472 TransformStructuralIrShadowFixtureInputV0 {
1473 fixture: "nesting-descendant-and-pseudo",
1474 pass: TransformPassKind::NestingUnwrap,
1475 dialect: StyleDialect::Css,
1476 source: ".card { color: red; & .title { color: blue; } &:hover { color: green; } }",
1477 closed_bundle: false,
1478 },
1479 TransformStructuralIrShadowFixtureInputV0 {
1480 fixture: "nesting-conditional-group",
1481 pass: TransformPassKind::NestingUnwrap,
1482 dialect: StyleDialect::Css,
1483 source: "@media (min-width: 40rem) { .card { color: red; & .title { color: blue; } } }",
1484 closed_bundle: false,
1485 },
1486 TransformStructuralIrShadowFixtureInputV0 {
1487 fixture: "scope-root-flatten",
1488 pass: TransformPassKind::ScopeFlatten,
1489 dialect: StyleDialect::Css,
1490 source: "@scope (:root) { .card { color: red; } }",
1491 closed_bundle: false,
1492 },
1493 TransformStructuralIrShadowFixtureInputV0 {
1494 fixture: "scope-limit-blocked",
1495 pass: TransformPassKind::ScopeFlatten,
1496 dialect: StyleDialect::Css,
1497 source: "@scope (.theme) to (.stop) { .card { color: red; } }",
1498 closed_bundle: false,
1499 },
1500 TransformStructuralIrShadowFixtureInputV0 {
1501 fixture: "layer-closed-bundle-flatten",
1502 pass: TransformPassKind::LayerFlatten,
1503 dialect: StyleDialect::Css,
1504 source: "@layer theme { .card { color: red; } }",
1505 closed_bundle: true,
1506 },
1507 TransformStructuralIrShadowFixtureInputV0 {
1508 fixture: "layer-open-bundle-blocked",
1509 pass: TransformPassKind::LayerFlatten,
1510 dialect: StyleDialect::Css,
1511 source: "@layer theme { .card { color: red; } }",
1512 closed_bundle: false,
1513 },
1514 TransformStructuralIrShadowFixtureInputV0 {
1515 fixture: "rule-dedup-overridden-declarations",
1516 pass: TransformPassKind::RuleDeduplication,
1517 dialect: StyleDialect::Css,
1518 source: ".a { color: red; color: blue; --tone: red; --tone: blue; color: green !important; color: black !important; } :export { token: red; token: blue; }",
1519 closed_bundle: false,
1520 },
1521 TransformStructuralIrShadowFixtureInputV0 {
1522 fixture: "rule-dedup-duplicate-rules",
1523 pass: TransformPassKind::RuleDeduplication,
1524 dialect: StyleDialect::Css,
1525 source: ".a { color: red; } .b { color: red; } .a { color: blue; } .a { color: red; }",
1526 closed_bundle: false,
1527 },
1528 TransformStructuralIrShadowFixtureInputV0 {
1529 fixture: "rule-merge-adjacent-ordinary",
1530 pass: TransformPassKind::RuleMerging,
1531 dialect: StyleDialect::Css,
1532 source: ".a { color: red; } .a { background: blue; } .a { outline: 0; } .b { color: red; }",
1533 closed_bundle: false,
1534 },
1535 TransformStructuralIrShadowFixtureInputV0 {
1536 fixture: "rule-merge-adjacent-conditional-wrappers",
1537 pass: TransformPassKind::RuleMerging,
1538 dialect: StyleDialect::Css,
1539 source: "@media (prefers-color-scheme: dark) { .card { color: white; } } @media (prefers-color-scheme: dark) { .card .title { color: #ddd; } } @supports (display: grid) { .grid { display: grid; } }",
1540 closed_bundle: false,
1541 },
1542 TransformStructuralIrShadowFixtureInputV0 {
1543 fixture: "selector-merge-adjacent-same-block",
1544 pass: TransformPassKind::SelectorMerging,
1545 dialect: StyleDialect::Css,
1546 source: ".a { color: red; } .b { color: red; } .c { color: red; } .d { color: blue; }",
1547 closed_bundle: false,
1548 },
1549 TransformStructuralIrShadowFixtureInputV0 {
1550 fixture: "selector-merge-nested-same-block",
1551 pass: TransformPassKind::SelectorMerging,
1552 dialect: StyleDialect::Css,
1553 source: "@media (min-width: 1px) { .m { color: black; } .n { color: black; } }",
1554 closed_bundle: false,
1555 },
1556 TransformStructuralIrShadowFixtureInputV0 {
1557 fixture: "empty-rule-ordinary-and-group",
1558 pass: TransformPassKind::EmptyRuleRemoval,
1559 dialect: StyleDialect::Css,
1560 source: ".a {} @media (min-width: 1px) { .b {} } @keyframes spin { from {} to { opacity: 1; } }",
1561 closed_bundle: false,
1562 },
1563 TransformStructuralIrShadowFixtureInputV0 {
1564 fixture: "empty-rule-preserves-comment-block",
1565 pass: TransformPassKind::EmptyRuleRemoval,
1566 dialect: StyleDialect::Css,
1567 source: ".a { /* keep */ } .b { color: red; }",
1568 closed_bundle: false,
1569 },
1570 TransformStructuralIrShadowFixtureInputV0 {
1571 fixture: "supports-static-true-unwrap",
1572 pass: TransformPassKind::SupportsStaticEval,
1573 dialect: StyleDialect::Css,
1574 source: "@supports (display: grid) { .a { display: grid; } }",
1575 closed_bundle: false,
1576 },
1577 TransformStructuralIrShadowFixtureInputV0 {
1578 fixture: "supports-static-false-remove",
1579 pass: TransformPassKind::DeadSupportsBranchRemoval,
1580 dialect: StyleDialect::Css,
1581 source: "@supports not (display: grid) { .a { display: grid; } } .b { color: red; }",
1582 closed_bundle: false,
1583 },
1584 TransformStructuralIrShadowFixtureInputV0 {
1585 fixture: "media-static-true-unwrap",
1586 pass: TransformPassKind::MediaStaticEval,
1587 dialect: StyleDialect::Css,
1588 source: "@media all { .a { color: red; } } @media (min-width: 40PX) { .b { color: blue; } }",
1589 closed_bundle: false,
1590 },
1591 TransformStructuralIrShadowFixtureInputV0 {
1592 fixture: "media-static-false-remove",
1593 pass: TransformPassKind::DeadMediaBranchRemoval,
1594 dialect: StyleDialect::Css,
1595 source: "@media not all { .a { color: red; } } .b { color: blue; }",
1596 closed_bundle: false,
1597 },
1598 TransformStructuralIrShadowFixtureInputV0 {
1599 fixture: "container-static-false-remove",
1600 pass: TransformPassKind::ContainerStaticEval,
1601 dialect: StyleDialect::Css,
1602 source: "@container (max-width: -1px) { .a { color: red; } } .b { color: blue; }",
1603 closed_bundle: false,
1604 },
1605 TransformStructuralIrShadowFixtureInputV0 {
1606 fixture: "native-css-static-when-fold",
1607 pass: TransformPassKind::NativeCssStaticEval,
1608 dialect: StyleDialect::Css,
1609 source: "@when supports(display: grid) { .grid { display: grid; } } @else { .fallback { display: block; } }",
1610 closed_bundle: false,
1611 },
1612 TransformStructuralIrShadowFixtureInputV0 {
1613 fixture: "tree-shake-class-reachable-owner",
1614 pass: TransformPassKind::TreeShakeClass,
1615 dialect: StyleDialect::Css,
1616 source: ".used { color: green; } .unused, .also-unused { color: red; } :global(.external) { color: black; }",
1617 closed_bundle: false,
1618 },
1619 TransformStructuralIrShadowFixtureInputV0 {
1620 fixture: "tree-shake-keyframes-referenced-animation",
1621 pass: TransformPassKind::TreeShakeKeyframes,
1622 dialect: StyleDialect::Css,
1623 source: "@keyframes spin { to { opacity: 1; } } @keyframes fade { to { opacity: 0; } } .used { animation: spin 1s; }",
1624 closed_bundle: false,
1625 },
1626 TransformStructuralIrShadowFixtureInputV0 {
1627 fixture: "tree-shake-css-modules-values",
1628 pass: TransformPassKind::TreeShakeValue,
1629 dialect: StyleDialect::Css,
1630 source: "@value keep: 1px; @value dead: 2px; @value imported, unused from \"./tokens.css\"; :export { keepExport: keep; deadExport: dead; }",
1631 closed_bundle: false,
1632 },
1633 TransformStructuralIrShadowFixtureInputV0 {
1634 fixture: "tree-shake-custom-properties",
1635 pass: TransformPassKind::TreeShakeCustomProperty,
1636 dialect: StyleDialect::Css,
1637 source: "@property --dead-reg { syntax: \"<color>\"; inherits: false; initial-value: red; } .used { color: var(--keep); --keep: green; --dead: red; } :export { keepExport: var(--keep); deadExport: var(--dead); }",
1638 closed_bundle: false,
1639 },
1640 TransformStructuralIrShadowFixtureInputV0 {
1641 fixture: "module-import-inline",
1642 pass: TransformPassKind::ImportInline,
1643 dialect: StyleDialect::Css,
1644 source: "@import \"./tokens.css\"; .used { color: var(--brand); }",
1645 closed_bundle: false,
1646 },
1647 TransformStructuralIrShadowFixtureInputV0 {
1648 fixture: "module-composes-resolution",
1649 pass: TransformPassKind::ResolveCssModulesComposes,
1650 dialect: StyleDialect::Css,
1651 source: ".button { composes: base utility; color: red; } .base { color: blue; } .utility { color: green; }",
1652 closed_bundle: false,
1653 },
1654 TransformStructuralIrShadowFixtureInputV0 {
1655 fixture: "module-class-hashing",
1656 pass: TransformPassKind::HashCssModuleClassNames,
1657 dialect: StyleDialect::Css,
1658 source: ".button { composes: base utility global(reset); color: red; } :local { .button { color: blue; } } @supports selector(.button) { .button { color: green; } }",
1659 closed_bundle: false,
1660 },
1661 TransformStructuralIrShadowFixtureInputV0 {
1662 fixture: "module-design-token-routing",
1663 pass: TransformPassKind::DesignTokenRouting,
1664 dialect: StyleDialect::Css,
1665 source: "@media (min-width: var(--pkg-breakpoint)) { .button { color: var(--pkg-brand); } }",
1666 closed_bundle: false,
1667 },
1668 ]
1669}
1670
1671fn compared_pass_ids() -> Vec<&'static str> {
1672 let mut pass_ids = default_transform_pass_descriptors()
1673 .into_iter()
1674 .filter(|descriptor| descriptor.pass_class == TransformPassClassV0::Structural)
1675 .map(|descriptor| descriptor.id)
1676 .collect::<Vec<_>>();
1677 pass_ids.sort_unstable();
1678 pass_ids
1679}
1680
1681fn reachability_for_fixture(
1682 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1683) -> StructuralShadowReachabilityV0 {
1684 match fixture.fixture {
1685 "tree-shake-class-reachable-owner" => StructuralShadowReachabilityV0 {
1686 class_names: string_vec(["used"]),
1687 keyframe_names: Vec::new(),
1688 value_names: Vec::new(),
1689 custom_property_names: Vec::new(),
1690 },
1691 "tree-shake-keyframes-referenced-animation" => StructuralShadowReachabilityV0 {
1692 class_names: string_vec(["used"]),
1693 keyframe_names: Vec::new(),
1694 value_names: Vec::new(),
1695 custom_property_names: Vec::new(),
1696 },
1697 "tree-shake-css-modules-values" => StructuralShadowReachabilityV0 {
1698 class_names: Vec::new(),
1699 keyframe_names: Vec::new(),
1700 value_names: string_vec(["keepExport"]),
1701 custom_property_names: Vec::new(),
1702 },
1703 "tree-shake-custom-properties" => StructuralShadowReachabilityV0 {
1704 class_names: string_vec(["used"]),
1705 keyframe_names: Vec::new(),
1706 value_names: Vec::new(),
1707 custom_property_names: string_vec(["keepExport"]),
1708 },
1709 "pipeline-module-structural-interpass" => StructuralShadowReachabilityV0 {
1710 class_names: string_vec(["card", "card__icon", "base", "utility"]),
1711 keyframe_names: string_vec(["spin"]),
1712 value_names: Vec::new(),
1713 custom_property_names: string_vec(["pkg-brand", "local-tone"]),
1714 },
1715 "pipeline-rule-structural-interpass" => StructuralShadowReachabilityV0 {
1716 class_names: string_vec(["card", "card__icon", "dup", "grid", "media"]),
1717 keyframe_names: Vec::new(),
1718 value_names: Vec::new(),
1719 custom_property_names: Vec::new(),
1720 },
1721 _ => StructuralShadowReachabilityV0 {
1722 class_names: Vec::new(),
1723 keyframe_names: Vec::new(),
1724 value_names: Vec::new(),
1725 custom_property_names: Vec::new(),
1726 },
1727 }
1728}
1729
1730fn reachability_for_pipeline_fixture(
1731 fixture: TransformStructuralIrPipelineShadowFixtureInputV0<'_>,
1732) -> StructuralShadowReachabilityV0 {
1733 reachability_for_fixture(TransformStructuralIrShadowFixtureInputV0 {
1734 fixture: fixture.fixture,
1735 pass: TransformPassKind::TreeShakeClass,
1736 dialect: fixture.dialect,
1737 source: fixture.source,
1738 closed_bundle: fixture.closed_bundle,
1739 })
1740}
1741
1742fn module_context_for_pipeline_fixture(
1743 fixture: TransformStructuralIrPipelineShadowFixtureInputV0<'_>,
1744) -> StructuralShadowModuleContextV0 {
1745 module_context_for_fixture(TransformStructuralIrShadowFixtureInputV0 {
1746 fixture: fixture.fixture,
1747 pass: TransformPassKind::ImportInline,
1748 dialect: fixture.dialect,
1749 source: fixture.source,
1750 closed_bundle: fixture.closed_bundle,
1751 })
1752}
1753
1754fn module_context_for_fixture(
1755 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1756) -> StructuralShadowModuleContextV0 {
1757 match fixture.fixture {
1758 "module-import-inline" => StructuralShadowModuleContextV0 {
1759 import_inlines: vec![TransformImportInlineV0 {
1760 import_source: "./tokens.css".to_string(),
1761 replacement_css: ":root { --brand: red; }".to_string(),
1762 }],
1763 ..StructuralShadowModuleContextV0::default()
1764 },
1765 "module-class-hashing" => StructuralShadowModuleContextV0 {
1766 class_name_rewrites: vec![
1767 TransformClassNameRewriteV0 {
1768 original_name: "button".to_string(),
1769 rewritten_name: "_button_hash".to_string(),
1770 },
1771 TransformClassNameRewriteV0 {
1772 original_name: "base".to_string(),
1773 rewritten_name: "_base_hash".to_string(),
1774 },
1775 TransformClassNameRewriteV0 {
1776 original_name: "utility".to_string(),
1777 rewritten_name: "_utility_hash".to_string(),
1778 },
1779 ],
1780 ..StructuralShadowModuleContextV0::default()
1781 },
1782 "module-design-token-routing" => StructuralShadowModuleContextV0 {
1783 design_token_routes: vec![
1784 TransformDesignTokenRouteV0 {
1785 token_name: "--pkg-breakpoint".to_string(),
1786 routed_value: "40rem".to_string(),
1787 },
1788 TransformDesignTokenRouteV0 {
1789 token_name: "--pkg-brand".to_string(),
1790 routed_value: "#123456".to_string(),
1791 },
1792 ],
1793 ..StructuralShadowModuleContextV0::default()
1794 },
1795 "pipeline-module-structural-interpass" => StructuralShadowModuleContextV0 {
1796 import_inlines: vec![TransformImportInlineV0 {
1797 import_source: "./tokens.css".to_string(),
1798 replacement_css: ":root { --pkg-brand: #123456; }".to_string(),
1799 }],
1800 class_name_rewrites: vec![
1801 TransformClassNameRewriteV0 {
1802 original_name: "card".to_string(),
1803 rewritten_name: "_card_hash".to_string(),
1804 },
1805 TransformClassNameRewriteV0 {
1806 original_name: "card__icon".to_string(),
1807 rewritten_name: "_card__icon_hash".to_string(),
1808 },
1809 TransformClassNameRewriteV0 {
1810 original_name: "base".to_string(),
1811 rewritten_name: "_base_hash".to_string(),
1812 },
1813 TransformClassNameRewriteV0 {
1814 original_name: "utility".to_string(),
1815 rewritten_name: "_utility_hash".to_string(),
1816 },
1817 ],
1818 css_module_composes_resolutions: vec![TransformCssModuleComposesResolutionV0 {
1819 local_class_name: "card".to_string(),
1820 exported_class_names: vec!["base".to_string(), "utility".to_string()],
1821 }],
1822 design_token_routes: vec![TransformDesignTokenRouteV0 {
1823 token_name: "--pkg-brand".to_string(),
1824 routed_value: "#123456".to_string(),
1825 }],
1826 },
1827 _ => StructuralShadowModuleContextV0::default(),
1828 }
1829}
1830
1831fn css_module_composes_resolutions_for_fixture(
1832 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1833 module_context: &StructuralShadowModuleContextV0,
1834) -> Vec<TransformCssModuleComposesResolutionV0> {
1835 let mut merged =
1836 local_css_module_composes_resolutions_with_lexer(fixture.source, fixture.dialect);
1837 for resolution in &module_context.css_module_composes_resolutions {
1838 let Some(existing) = merged
1839 .iter_mut()
1840 .find(|existing| existing.local_class_name == resolution.local_class_name)
1841 else {
1842 merged.push(resolution.clone());
1843 continue;
1844 };
1845 for exported_class_name in &resolution.exported_class_names {
1846 if !existing
1847 .exported_class_names
1848 .iter()
1849 .any(|existing| existing == exported_class_name)
1850 {
1851 existing
1852 .exported_class_names
1853 .push(exported_class_name.clone());
1854 }
1855 }
1856 }
1857 merged.sort_by(|left, right| left.local_class_name.cmp(&right.local_class_name));
1858 merged
1859}
1860
1861fn module_egress_values_for_fixture(
1862 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1863 module_context: &StructuralShadowModuleContextV0,
1864) -> StructuralShadowModuleEgressValuesV0 {
1865 match fixture.pass {
1866 TransformPassKind::ImportInline => StructuralShadowModuleEgressValuesV0 {
1867 css_import_inline_values: json_values(module_context.import_inlines.as_slice()),
1868 ..StructuralShadowModuleEgressValuesV0::default()
1869 },
1870 TransformPassKind::ResolveCssModulesComposes => StructuralShadowModuleEgressValuesV0 {
1871 css_module_composes_values: json_values(
1872 css_module_composes_resolutions_for_fixture(fixture, module_context).as_slice(),
1873 ),
1874 ..StructuralShadowModuleEgressValuesV0::default()
1875 },
1876 TransformPassKind::DesignTokenRouting => StructuralShadowModuleEgressValuesV0 {
1877 design_token_route_values: json_values(module_context.design_token_routes.as_slice()),
1878 ..StructuralShadowModuleEgressValuesV0::default()
1879 },
1880 _ => StructuralShadowModuleEgressValuesV0::default(),
1881 }
1882}
1883
1884fn json_values<T: serde::Serialize>(values: &[T]) -> Vec<String> {
1885 values
1886 .iter()
1887 .map(|value| serde_json::to_string(value).unwrap_or_default())
1888 .collect()
1889}
1890
1891fn selector_values_for_source(source: &str, dialect: StyleDialect) -> Vec<String> {
1892 let summary = summarize_omena_parser_style_facts(source, dialect);
1893 sorted_unique(
1894 summary
1895 .class_selector_names
1896 .into_iter()
1897 .map(|name| format!("class:{name}"))
1898 .chain(
1899 summary
1900 .id_selector_names
1901 .into_iter()
1902 .map(|name| format!("id:{name}")),
1903 )
1904 .chain(
1905 summary
1906 .placeholder_selector_names
1907 .into_iter()
1908 .map(|name| format!("placeholder:{name}")),
1909 )
1910 .collect::<Vec<_>>(),
1911 )
1912}
1913
1914fn declaration_values_for_source(source: &str, dialect: StyleDialect) -> Vec<String> {
1915 let summary = summarize_omena_parser_parity_lite(source, dialect);
1916 sorted_unique(vec![
1917 format!("ruleCount:{}", summary.rule_count),
1918 format!("declarationCount:{}", summary.declaration_count),
1919 format!(
1920 "declarationKindCounts:{}",
1921 serde_json::to_string(&summary.declaration_kind_counts).unwrap_or_default()
1922 ),
1923 format!(
1924 "atRuleKindCounts:{}",
1925 serde_json::to_string(&summary.at_rule_kind_counts).unwrap_or_default()
1926 ),
1927 ])
1928}
1929
1930fn cascade_values_for_source(
1931 fixture: TransformStructuralIrShadowFixtureInputV0<'_>,
1932 source: &str,
1933) -> Vec<String> {
1934 match fixture.pass {
1935 TransformPassKind::ScopeFlatten => sorted_unique(
1936 collect_scope_flatten_proof_candidates_with_lexer(source, fixture.dialect)
1937 .into_iter()
1938 .map(|candidate| {
1939 format!(
1940 "scope:{}..{}:accepted={}:blocked={:?}:root={}:witness={}",
1941 candidate.source_span_start,
1942 candidate.source_span_end,
1943 candidate.proof.accepted,
1944 candidate.proof.blocked_reason,
1945 candidate.proof.root_selector,
1946 candidate.proof.cascade_safe_witness
1947 )
1948 })
1949 .collect::<Vec<_>>(),
1950 ),
1951 TransformPassKind::LayerFlatten => sorted_unique(
1952 collect_layer_flatten_proof_candidates_with_lexer(
1953 source,
1954 fixture.dialect,
1955 fixture.closed_bundle,
1956 )
1957 .into_iter()
1958 .map(|candidate| {
1959 format!(
1960 "layer:{}..{}:accepted={}:blocked={:?}:name={:?}:witness={}",
1961 candidate.source_span_start,
1962 candidate.source_span_end,
1963 candidate.proof.accepted,
1964 candidate.proof.blocked_reason,
1965 candidate.proof.layer_name,
1966 candidate.proof.cascade_safe_witness
1967 )
1968 })
1969 .collect::<Vec<_>>(),
1970 ),
1971 _ => Vec::new(),
1972 }
1973}
1974
1975fn mutation_span_values(spans: Vec<TransformProvenanceMutationSpanV0>) -> Vec<String> {
1976 spans
1977 .into_iter()
1978 .map(|span| {
1979 format!(
1980 "{}..{}=>{}..{}",
1981 span.source_span_start,
1982 span.source_span_end,
1983 span.generated_span_start,
1984 span.generated_span_end
1985 )
1986 })
1987 .collect()
1988}
1989
1990fn semantic_removal_values(removals: Vec<TransformSemanticRemovalCandidate>) -> Vec<String> {
1991 removals
1992 .into_iter()
1993 .map(|removal| {
1994 format!(
1995 "{}:{}:{}..{}:{}",
1996 removal.symbol_kind,
1997 removal.name,
1998 removal.source_span_start,
1999 removal.source_span_end,
2000 removal.reason
2001 )
2002 })
2003 .collect()
2004}
2005
2006fn public_semantic_removal_values(removals: Vec<TransformSemanticRemovalV0>) -> Vec<String> {
2007 removals
2008 .into_iter()
2009 .map(|removal| {
2010 format!(
2011 "{}:{}:{}..{}:{}",
2012 removal.symbol_kind,
2013 removal.name,
2014 removal.source_span_start,
2015 removal.source_span_end,
2016 removal.reason
2017 )
2018 })
2019 .collect()
2020}
2021
2022fn string_vec<const N: usize>(values: [&str; N]) -> Vec<String> {
2023 values.into_iter().map(str::to_string).collect()
2024}
2025
2026fn shadow_field_report(
2027 field: &'static str,
2028 string_path_values: impl IntoIterator<Item = String>,
2029 ir_path_values: impl IntoIterator<Item = String>,
2030) -> TransformStructuralIrShadowFieldReportV0 {
2031 let ir_path_values = sorted_unique(ir_path_values);
2032 shadow_field_report_with_typed(
2033 field,
2034 string_path_values,
2035 ir_path_values.clone(),
2036 ir_path_values,
2037 )
2038}
2039
2040fn shadow_field_report_with_typed(
2041 field: &'static str,
2042 string_path_values: impl IntoIterator<Item = String>,
2043 ir_path_values: impl IntoIterator<Item = String>,
2044 typed_path_values: impl IntoIterator<Item = String>,
2045) -> TransformStructuralIrShadowFieldReportV0 {
2046 let string_path_values = sorted_unique(string_path_values);
2047 let ir_path_values = sorted_unique(ir_path_values);
2048 let typed_path_values = sorted_unique(typed_path_values);
2049 let matches = string_path_values == ir_path_values && string_path_values == typed_path_values;
2050 TransformStructuralIrShadowFieldReportV0 {
2051 field,
2052 string_path_values,
2053 ir_path_values,
2054 typed_path_values,
2055 matches,
2056 }
2057}
2058
2059fn sorted_unique(values: impl IntoIterator<Item = String>) -> Vec<String> {
2060 values
2061 .into_iter()
2062 .collect::<BTreeSet<_>>()
2063 .into_iter()
2064 .collect()
2065}
2066
2067fn dialect_label(dialect: StyleDialect) -> &'static str {
2068 match dialect {
2069 StyleDialect::Css => "css",
2070 StyleDialect::Scss => "scss",
2071 StyleDialect::Sass => "sass",
2072 StyleDialect::Less => "less",
2073 }
2074}
2075
2076#[cfg(test)]
2077mod tests {
2078 use std::collections::BTreeMap;
2079
2080 use serde::Deserialize;
2081
2082 use super::*;
2083
2084 const STRING_AUTHORITY_STRUCTURAL_GOLDEN: &str =
2085 include_str!("../../data/string-authority-structural-golden-v0.json");
2086
2087 #[derive(Debug, Deserialize)]
2088 #[serde(rename_all = "camelCase")]
2089 struct StringAuthorityStructuralGoldenEntryV0 {
2090 fixture: String,
2091 pass_id: String,
2092 dialect: String,
2093 output_css: String,
2094 }
2095
2096 #[test]
2097 fn structural_ir_output_matches_string_authority_golden() -> Result<(), String> {
2098 let entries = serde_json::from_str::<Vec<StringAuthorityStructuralGoldenEntryV0>>(
2099 STRING_AUTHORITY_STRUCTURAL_GOLDEN,
2100 )
2101 .map_err(|err| format!("String authority structural golden should parse: {err:?}"))?;
2102 let mut entries_by_key = BTreeMap::new();
2103 for entry in entries {
2104 let key = (entry.fixture.clone(), entry.pass_id.clone());
2105 if entries_by_key.insert(key.clone(), entry).is_some() {
2106 return Err(format!(
2107 "String authority structural golden contains duplicate fixture/pass key {key:?}"
2108 ));
2109 }
2110 }
2111
2112 let fixtures = structural_shadow_fixtures();
2113 assert_eq!(
2114 entries_by_key.len(),
2115 fixtures.len(),
2116 "String authority structural golden must cover every structural shadow fixture"
2117 );
2118
2119 for fixture in fixtures {
2120 let key = (fixture.fixture.to_string(), fixture.pass.id().to_string());
2121 let Some(golden) = entries_by_key.remove(&key) else {
2122 return Err(format!(
2123 "String authority structural golden is missing fixture/pass key {key:?}"
2124 ));
2125 };
2126 assert_eq!(golden.dialect, dialect_label(fixture.dialect));
2127 let snapshot = ir_path_snapshot(fixture)?;
2128 assert_eq!(
2129 snapshot.output_css, golden.output_css,
2130 "IR output drifted from String authority golden for {} / {}",
2131 fixture.fixture, golden.pass_id
2132 );
2133 }
2134
2135 assert!(
2136 entries_by_key.is_empty(),
2137 "String authority structural golden contains stale keys: {:?}",
2138 entries_by_key.keys().collect::<Vec<_>>()
2139 );
2140 Ok(())
2141 }
2142
2143 #[test]
2144 fn typed_payload_projection_memo_distinguishes_same_node_after_source_change() {
2145 let mut memo = TypedPayloadProjectionMemoV0::default();
2146 let node_id = IrNodeIdV0(7);
2147 let revision = IncrementalRevisionV0 { value: 1 };
2148
2149 let first = memo.project_style_rule_payload(
2150 node_id,
2151 ".card { &__icon { color: red; } }",
2152 StyleDialect::Scss,
2153 revision,
2154 );
2155 let second = memo.project_style_rule_payload(
2156 node_id,
2157 ".card { &__icon { color: red; } }",
2158 StyleDialect::Scss,
2159 revision,
2160 );
2161 let changed = memo.project_style_rule_payload(
2162 node_id,
2163 ".card { &__icon { color: blue; } }",
2164 StyleDialect::Scss,
2165 revision,
2166 );
2167
2168 assert!(first.is_some());
2169 assert!(second.is_some());
2170 assert!(changed.is_some());
2171 assert_eq!(memo.entries.len(), 2);
2172 assert_eq!(memo.telemetry.projections_consumed, 3);
2173 assert_eq!(memo.telemetry.memo_hits, 1);
2174 }
2175}