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