1use std::{
10 collections::{BTreeMap, BTreeSet},
11 sync::OnceLock,
12};
13
14use browserslist::{Distrib, Opts, resolve as resolve_browserslist};
15use omena_transform_cst::{TransformPassKind, transform_pass_sort_ordinal};
16use omena_transform_passes::{
17 TransformPassPlanV0, TransformVendorPrefixPolicyV0, plan_transform_passes,
18};
19use serde::{Deserialize, Serialize};
20
21const BROWSER_THRESHOLDS_SOURCE: &str = include_str!("../data/browser-thresholds.toml");
22const PASS_FEATURE_BINDINGS_SOURCE: &str = include_str!("../data/pass-feature-bindings.toml");
23const NATIVE_STAGE2_COVERAGE_SOURCE: &str = include_str!("../data/native-stage2-coverage.json");
24const TARGET_DATA_CONTRACT_ID: &str = "omena-transform-target-data-v0";
25const TARGET_DATA_SOURCE_FILES: &[&str] = &[
26 "data/compat-feature-selections.json",
27 "data/browser-thresholds.toml",
28 "data/pass-feature-bindings.toml",
29 "data/native-stage2-coverage.json",
30];
31const COMPAT_QUORUM_SOURCES: &[&str] = &["caniuse", "web-features", "mdn-bcd"];
32const CANIUSE_RESOLVER_WORKSPACE_DEPENDENCY: &str = "browserslist";
33const CANIUSE_RESOLVER_CARGO_PACKAGE: &str = "oxc-browserslist";
34const THRESHOLD_SOURCE_POLICY: &str = "mdnFullUnprefixedResolverCoveredV0";
35const VENDOR_PREFIX_MATRIX_SOURCE: &str = "curatedVendorPrefixMatrixTomlV0";
36const RUNTIME_FALLBACK_FEATURE_KEYS: &[&str] = &[];
37
38#[derive(Debug, Clone, PartialEq, Eq, Default)]
39struct BrowserThresholdDataV0 {
40 schema_version: String,
41 product: String,
42 refreshed_at: String,
43 threshold_source_policy: String,
44 quorum_min_sources: usize,
45 caniuse_resolver_workspace_dependency: String,
46 caniuse_resolver_cargo_package: String,
47 caniuse_resolver_cargo_version: String,
48 thresholds: Vec<BrowserFeatureThresholdV0>,
49 parse_error_count: usize,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Default)]
53struct BrowserFeatureThresholdV0 {
54 table: String,
55 browser: String,
56 min_major: u16,
57 min_minor: u16,
58 caniuse_key: String,
59 source_quorum: Vec<String>,
60 last_verified: String,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Default)]
64struct PassFeatureBindingDataV0 {
65 schema_version: String,
66 product: String,
67 refreshed_at: String,
68 caniuse_resolver_workspace_dependency: String,
69 caniuse_resolver_cargo_package: String,
70 caniuse_resolver_cargo_version: String,
71 bindings: Vec<PassFeatureBindingV0>,
72 parse_error_count: usize,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Default)]
76struct PassFeatureBindingV0 {
77 pass_id: String,
78 caniuse_keys: Vec<String>,
79 support_table: String,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
83#[serde(rename_all = "camelCase")]
84pub struct TargetFeatureSupportV0 {
85 pub vendor_prefix_required: bool,
86 pub supports_light_dark: bool,
87 pub supports_color_mix: bool,
88 pub supports_oklch_oklab: bool,
89 pub supports_color_function: bool,
90 pub supports_relative_color: bool,
91 pub supports_logical_properties: bool,
92 pub supports_css_nesting: bool,
93 pub supports_css_scope: bool,
94 pub supports_cascade_layers: bool,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
98#[serde(rename_all = "camelCase")]
99#[serde(default)]
100pub struct TargetTransformOptionsV0 {
101 pub allow_logical_to_physical: bool,
102 pub allow_scope_flatten: bool,
103 pub allow_layer_flatten: bool,
104 pub enable_supports_static_eval: bool,
105 pub enable_media_static_eval: bool,
106 pub enable_container_static_eval: bool,
107 pub drop_dark_mode_media_queries: bool,
108}
109
110impl Default for TargetTransformOptionsV0 {
111 fn default() -> Self {
112 conservative_target_options()
113 }
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
117#[serde(rename_all = "camelCase")]
118pub struct TransformTargetDataContractV0 {
119 pub product: &'static str,
120 pub contract_id: &'static str,
121 pub snapshot_id: String,
122 pub browser_threshold_schema_version: String,
123 pub pass_feature_binding_schema_version: String,
124 pub browser_threshold_refreshed_at: String,
125 pub pass_feature_binding_refreshed_at: String,
126 pub threshold_source_policy: String,
127 pub caniuse_resolver_workspace_dependency: String,
128 pub caniuse_resolver_cargo_package: String,
129 pub caniuse_resolver_cargo_version: String,
130 pub source_files: Vec<&'static str>,
131 pub parse_error_count: usize,
132 pub quorum_min_sources: usize,
133 pub browser_threshold_table_count: usize,
134 pub browser_threshold_entry_count: usize,
135 pub pass_feature_binding_count: usize,
136 pub stale_entry_count: usize,
137 pub unmapped_threshold_table_count: usize,
138 pub unresolvable_threshold_query_count: usize,
139 pub runtime_fallback_feature_keys: Vec<&'static str>,
140 pub runtime_fallback_feature_count: usize,
141 pub generated_coverage_complete: bool,
142 pub quorum_valid: bool,
143 pub bindings_valid: bool,
144 pub resolver_provenance_valid: bool,
145 pub valid: bool,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
149#[serde(rename_all = "camelCase")]
150pub struct TransformTargetDataEvidenceV0 {
151 pub product: &'static str,
152 pub pass_id: String,
153 pub support_table: String,
154 pub caniuse_keys: Vec<String>,
155 pub source_quorum: Vec<String>,
156 pub last_verified: Vec<String>,
157 pub resolved_targets: Vec<TransformTargetResolvedTargetEvidenceV0>,
158 pub all_resolved_targets_supported: bool,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
162#[serde(rename_all = "camelCase")]
163pub struct TransformTargetResolvedTargetEvidenceV0 {
164 pub browser: String,
165 pub version: String,
166 pub supported: bool,
167 pub matched_threshold: Option<TransformTargetThresholdEvidenceV0>,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
171#[serde(rename_all = "camelCase")]
172pub struct TransformTargetThresholdEvidenceV0 {
173 pub browser: String,
174 pub min_version: String,
175 pub caniuse_key: String,
176 pub source_quorum: Vec<String>,
177 pub last_verified: String,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
181#[serde(rename_all = "camelCase")]
182pub struct TransformTargetBoundarySummaryV0 {
183 pub schema_version: &'static str,
184 pub product: &'static str,
185 pub managed_pass_ids: Vec<&'static str>,
186 pub opt_in_pass_ids: Vec<&'static str>,
187 pub target_data_source: &'static str,
188 pub planner_surface: &'static str,
189 pub browser_threshold_table_count: usize,
190 pub browser_threshold_entry_count: usize,
191 pub pass_feature_binding_count: usize,
192 pub browser_data_source_files: Vec<&'static str>,
193 pub browser_data_parse_error_count: usize,
194 pub browser_data_quorum_valid: bool,
195 pub browser_data_bindings_valid: bool,
196 pub target_data_contract: TransformTargetDataContractV0,
197 pub vendor_prefix_matrix_source: &'static str,
198 pub runtime_fallback_feature_keys: Vec<&'static str>,
199 pub generated_coverage_complete: bool,
200 pub native_stage2_coverage: NativeStage2CoverageSummaryV0,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
204#[serde(rename_all = "camelCase")]
205pub struct NativeStage2CoverageFallbackV0 {
206 pub plugin_id: String,
207 pub policy: String,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
211#[serde(rename_all = "camelCase")]
212pub struct NativeStage2CoveredFeatureV0 {
213 pub feature_id: String,
214 pub category: String,
215 pub pass_ids: Vec<String>,
216 pub target_rule: String,
217 pub caniuse_keys: Vec<String>,
218 pub source_keys: BTreeMap<String, String>,
219 pub status: String,
220}
221
222#[derive(Debug, Clone, Deserialize, Serialize)]
223#[serde(rename_all = "camelCase")]
224pub struct NativeStage2UncoveredFeatureV0 {
225 pub feature_id: String,
226 pub category: String,
227 pub reason: String,
228 pub observed_properties: Vec<omena_syntax::ident::AuthoredPropertyTextV0>,
229 pub fallback: String,
230}
231
232impl PartialEq for NativeStage2UncoveredFeatureV0 {
233 fn eq(&self, other: &Self) -> bool {
234 self.feature_id == other.feature_id
235 && self.category == other.category
236 && self.reason == other.reason
237 && self.observed_properties.len() == other.observed_properties.len()
238 && self
239 .observed_properties
240 .iter()
241 .zip(&other.observed_properties)
242 .all(|(left, right)| left.to_standard_key() == right.to_standard_key())
243 && self.fallback == other.fallback
244 }
245}
246
247impl Eq for NativeStage2UncoveredFeatureV0 {}
248
249#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
250#[serde(rename_all = "camelCase")]
251struct NativeStage2CoverageDataV0 {
252 schema_version: String,
253 product: String,
254 binding_source: String,
255 selection_basis: Vec<String>,
256 stage1_fallback: NativeStage2CoverageFallbackV0,
257 covered_features: Vec<NativeStage2CoveredFeatureV0>,
258 uncovered_features: Vec<NativeStage2UncoveredFeatureV0>,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
262#[serde(rename_all = "camelCase")]
263pub struct NativeStage2CoverageSummaryV0 {
264 pub schema_version: String,
265 pub product: String,
266 pub binding_source: String,
267 pub selection_basis: Vec<String>,
268 pub stage1_fallback: NativeStage2CoverageFallbackV0,
269 pub covered_features: Vec<NativeStage2CoveredFeatureV0>,
270 pub uncovered_features: Vec<NativeStage2UncoveredFeatureV0>,
271 pub covered_feature_count: usize,
272 pub uncovered_feature_count: usize,
273 pub covered_pass_census_valid: bool,
274 pub binding_census_valid: bool,
275 pub valid: bool,
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
279#[serde(rename_all = "camelCase")]
280pub struct TransformTargetQueryPlanV0 {
281 pub schema_version: &'static str,
282 pub product: &'static str,
283 pub query: String,
284 pub normalized_query: String,
285 pub profile_id: &'static str,
286 pub recognized_profile: bool,
287 pub target_data_source: &'static str,
288 pub target_data_contract_id: &'static str,
289 pub target_data_snapshot_id: String,
290 pub target_data_evidence: Vec<TransformTargetDataEvidenceV0>,
291 pub vendor_prefix_matrix_source: &'static str,
292 pub vendor_prefix_policy: Option<TransformVendorPrefixPolicyV0>,
293 pub resolved_targets: Vec<String>,
294 pub resolution_error: Option<String>,
295 pub support: TargetFeatureSupportV0,
296 pub transform_plan: TransformTargetPlanV0,
297 pub native_stage2_coverage: NativeStage2CoverageSummaryV0,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
301#[serde(rename_all = "camelCase")]
302pub struct TransformTargetPlanV0 {
303 pub schema_version: &'static str,
304 pub product: &'static str,
305 pub target_label: String,
306 pub required_pass_ids: Vec<&'static str>,
307 pub blocked_pass_ids: Vec<&'static str>,
308 pub planned_pass_ids: Vec<&'static str>,
309 pub pass_plan: TransformPassPlanV0,
310}
311
312pub fn summarize_omena_transform_target_boundary() -> TransformTargetBoundarySummaryV0 {
313 let browser_data = browser_threshold_data();
314 let bindings = pass_feature_binding_data();
315 let target_data_contract = target_data_contract_summary(browser_data, bindings);
316
317 TransformTargetBoundarySummaryV0 {
318 schema_version: "0",
319 product: "omena-transform-target.boundary",
320 managed_pass_ids: target_managed_passes()
321 .iter()
322 .map(|pass| pass.id())
323 .collect(),
324 opt_in_pass_ids: vec![
325 TransformPassKind::LogicalToPhysical.id(),
326 TransformPassKind::ScopeFlatten.id(),
327 TransformPassKind::LayerFlatten.id(),
328 ],
329 target_data_source: "oxcBrowserslistV3+browserThresholdsTomlV0+staticTargetProfileV0+generatedFeatureMatrixV0",
330 planner_surface: "omena-transform-passes.plan",
331 browser_threshold_table_count: browser_threshold_table_count(browser_data),
332 browser_threshold_entry_count: browser_data.thresholds.len(),
333 pass_feature_binding_count: bindings.bindings.len(),
334 browser_data_source_files: target_data_source_files(),
335 browser_data_parse_error_count: browser_data.parse_error_count + bindings.parse_error_count,
336 browser_data_quorum_valid: browser_threshold_data_is_valid(browser_data),
337 browser_data_bindings_valid: pass_feature_binding_data_is_valid(browser_data, bindings),
338 target_data_contract,
339 vendor_prefix_matrix_source: VENDOR_PREFIX_MATRIX_SOURCE,
340 runtime_fallback_feature_keys: runtime_fallback_feature_keys(),
341 generated_coverage_complete: runtime_fallback_feature_keys().is_empty(),
342 native_stage2_coverage: summarize_native_stage2_coverage(),
343 }
344}
345
346pub fn summarize_native_stage2_coverage() -> NativeStage2CoverageSummaryV0 {
347 let data = native_stage2_coverage_data();
348 let implemented_pass_ids = omena_transform_passes::implemented_mutation_pass_ids()
349 .into_iter()
350 .collect::<BTreeSet<_>>();
351 let binding_pairs = pass_feature_binding_data()
352 .bindings
353 .iter()
354 .map(|binding| (binding.pass_id.as_str(), binding.support_table.as_str()))
355 .collect::<BTreeSet<_>>();
356 let covered_pass_census_valid = data.covered_features.iter().all(|feature| {
357 feature
358 .pass_ids
359 .iter()
360 .all(|pass_id| implemented_pass_ids.contains(pass_id.as_str()))
361 });
362 let binding_census_valid = data.covered_features.iter().all(|feature| {
363 let Some(support_table) = feature.target_rule.strip_prefix("allResolvedTargetsMeet:")
364 else {
365 return false;
366 };
367 feature
368 .pass_ids
369 .iter()
370 .all(|pass_id| binding_pairs.contains(&(pass_id.as_str(), support_table)))
371 });
372 let valid = data.schema_version == "0"
373 && data.product == "omena-transform-target.native-stage2-coverage"
374 && !data.covered_features.is_empty()
375 && !data.uncovered_features.is_empty()
376 && data
377 .uncovered_features
378 .iter()
379 .all(|feature| feature.fallback == "stage1")
380 && covered_pass_census_valid
381 && binding_census_valid;
382
383 NativeStage2CoverageSummaryV0 {
384 schema_version: data.schema_version.clone(),
385 product: data.product.clone(),
386 binding_source: data.binding_source.clone(),
387 selection_basis: data.selection_basis.clone(),
388 stage1_fallback: data.stage1_fallback.clone(),
389 covered_features: data.covered_features.clone(),
390 uncovered_features: data.uncovered_features.clone(),
391 covered_feature_count: data.covered_features.len(),
392 uncovered_feature_count: data.uncovered_features.len(),
393 covered_pass_census_valid,
394 binding_census_valid,
395 valid,
396 }
397}
398
399pub fn plan_target_transforms_from_query(
400 query: impl Into<String>,
401 options: TargetTransformOptionsV0,
402) -> TransformTargetQueryPlanV0 {
403 let query = query.into();
404 let normalized_query = normalize_target_query(&query);
405 let target_resolution = target_feature_support_for_query(&normalized_query);
406 let transform_plan = plan_target_transforms(
407 target_resolution.profile_id,
408 target_resolution.support,
409 options,
410 );
411
412 TransformTargetQueryPlanV0 {
413 schema_version: "0",
414 product: "omena-transform-target.query-plan",
415 query,
416 normalized_query,
417 profile_id: target_resolution.profile_id,
418 recognized_profile: target_resolution.recognized_profile,
419 target_data_source: target_resolution.target_data_source,
420 target_data_contract_id: target_resolution.target_data_contract_id,
421 target_data_snapshot_id: target_resolution.target_data_snapshot_id,
422 target_data_evidence: target_resolution.target_data_evidence,
423 vendor_prefix_matrix_source: target_resolution.vendor_prefix_matrix_source,
424 vendor_prefix_policy: target_resolution.vendor_prefix_policy,
425 resolved_targets: target_resolution.resolved_targets,
426 resolution_error: target_resolution.resolution_error,
427 support: target_resolution.support,
428 transform_plan,
429 native_stage2_coverage: summarize_native_stage2_coverage(),
430 }
431}
432
433pub fn plan_target_transforms(
434 target_label: impl Into<String>,
435 support: TargetFeatureSupportV0,
436 options: TargetTransformOptionsV0,
437) -> TransformTargetPlanV0 {
438 let target_label = target_label.into();
439 let mut required_passes = Vec::new();
440 let mut blocked_passes = Vec::new();
441
442 if support.vendor_prefix_required {
443 required_passes.push(TransformPassKind::VendorPrefixing);
444 } else {
445 required_passes.push(TransformPassKind::StalePrefixRemoval);
446 }
447 if !support.supports_light_dark {
448 required_passes.push(TransformPassKind::LightDarkLowering);
449 }
450 if !support.supports_color_mix {
451 required_passes.push(TransformPassKind::ColorMixLowering);
452 }
453 if !support.supports_oklch_oklab {
454 required_passes.push(TransformPassKind::OklchOklabLowering);
455 }
456 if !support.supports_color_function {
457 required_passes.push(TransformPassKind::ColorFunctionLowering);
458 }
459 if !support.supports_relative_color {
460 required_passes.push(TransformPassKind::RelativeColorLowering);
461 }
462 if !support.supports_logical_properties {
463 push_required_or_blocked(
464 TransformPassKind::LogicalToPhysical,
465 options.allow_logical_to_physical,
466 &mut required_passes,
467 &mut blocked_passes,
468 );
469 }
470 if !support.supports_css_nesting {
471 required_passes.push(TransformPassKind::NestingUnwrap);
472 }
473 if !support.supports_css_scope {
474 push_required_or_blocked(
475 TransformPassKind::ScopeFlatten,
476 options.allow_scope_flatten,
477 &mut required_passes,
478 &mut blocked_passes,
479 );
480 }
481 if !support.supports_cascade_layers {
482 push_required_or_blocked(
483 TransformPassKind::LayerFlatten,
484 options.allow_layer_flatten,
485 &mut required_passes,
486 &mut blocked_passes,
487 );
488 }
489 if options.enable_supports_static_eval {
490 required_passes.push(TransformPassKind::SupportsStaticEval);
491 }
492 if options.enable_media_static_eval {
493 required_passes.push(TransformPassKind::MediaStaticEval);
494 }
495 if options.enable_container_static_eval {
496 required_passes.push(TransformPassKind::ContainerStaticEval);
497 }
498 if options.drop_dark_mode_media_queries {
499 required_passes.push(TransformPassKind::DeadMediaBranchRemoval);
500 }
501
502 required_passes.sort_by_key(|pass| transform_pass_sort_ordinal(*pass));
503 required_passes.dedup();
504 blocked_passes.sort_by_key(|pass| transform_pass_sort_ordinal(*pass));
505 blocked_passes.dedup();
506
507 let pass_plan = plan_transform_passes(&required_passes);
508 let planned_pass_ids = pass_plan.ordered_pass_ids.clone();
509
510 TransformTargetPlanV0 {
511 schema_version: "0",
512 product: "omena-transform-target.plan",
513 target_label,
514 required_pass_ids: required_passes.iter().map(|pass| pass.id()).collect(),
515 blocked_pass_ids: blocked_passes.iter().map(|pass| pass.id()).collect(),
516 planned_pass_ids,
517 pass_plan,
518 }
519}
520
521pub const fn modern_feature_support() -> TargetFeatureSupportV0 {
522 TargetFeatureSupportV0 {
523 vendor_prefix_required: false,
524 supports_light_dark: true,
525 supports_color_mix: true,
526 supports_oklch_oklab: true,
527 supports_color_function: true,
528 supports_relative_color: true,
529 supports_logical_properties: true,
530 supports_css_nesting: true,
531 supports_css_scope: true,
532 supports_cascade_layers: true,
533 }
534}
535
536pub const fn legacy_webview_feature_support() -> TargetFeatureSupportV0 {
537 TargetFeatureSupportV0 {
538 vendor_prefix_required: true,
539 supports_light_dark: false,
540 supports_color_mix: false,
541 supports_oklch_oklab: false,
542 supports_color_function: false,
543 supports_relative_color: false,
544 supports_logical_properties: false,
545 supports_css_nesting: false,
546 supports_css_scope: false,
547 supports_cascade_layers: false,
548 }
549}
550
551pub const fn conservative_target_options() -> TargetTransformOptionsV0 {
552 TargetTransformOptionsV0 {
553 allow_logical_to_physical: false,
554 allow_scope_flatten: false,
555 allow_layer_flatten: false,
556 enable_supports_static_eval: false,
557 enable_media_static_eval: false,
558 enable_container_static_eval: false,
559 drop_dark_mode_media_queries: false,
560 }
561}
562
563struct TargetQueryResolutionV0 {
564 profile_id: &'static str,
565 recognized_profile: bool,
566 target_data_source: &'static str,
567 target_data_contract_id: &'static str,
568 target_data_snapshot_id: String,
569 target_data_evidence: Vec<TransformTargetDataEvidenceV0>,
570 vendor_prefix_matrix_source: &'static str,
571 vendor_prefix_policy: Option<TransformVendorPrefixPolicyV0>,
572 resolved_targets: Vec<String>,
573 resolution_error: Option<String>,
574 support: TargetFeatureSupportV0,
575}
576
577fn normalize_target_query(query: &str) -> String {
578 query
579 .split_whitespace()
580 .collect::<Vec<_>>()
581 .join(" ")
582 .trim()
583 .to_ascii_lowercase()
584}
585
586fn target_feature_support_for_query(normalized_query: &str) -> TargetQueryResolutionV0 {
587 if matches!(
588 normalized_query,
589 "" | "modern" | "baseline 2024" | "baseline-2024"
590 ) {
591 return TargetQueryResolutionV0 {
592 profile_id: "modern-evergreen",
593 recognized_profile: true,
594 target_data_source: "staticTargetProfileV0",
595 target_data_contract_id: TARGET_DATA_CONTRACT_ID,
596 target_data_snapshot_id: current_target_data_snapshot_id(),
597 target_data_evidence: Vec::new(),
598 vendor_prefix_matrix_source: VENDOR_PREFIX_MATRIX_SOURCE,
599 vendor_prefix_policy: Some(TransformVendorPrefixPolicyV0::none()),
600 resolved_targets: Vec::new(),
601 resolution_error: None,
602 support: modern_feature_support(),
603 };
604 }
605
606 if normalized_query == "legacy" || normalized_query == "legacy-webview" {
607 return TargetQueryResolutionV0 {
608 profile_id: "legacy-webview",
609 recognized_profile: true,
610 target_data_source: "staticTargetProfileV0",
611 target_data_contract_id: TARGET_DATA_CONTRACT_ID,
612 target_data_snapshot_id: current_target_data_snapshot_id(),
613 target_data_evidence: Vec::new(),
614 vendor_prefix_matrix_source: VENDOR_PREFIX_MATRIX_SOURCE,
615 vendor_prefix_policy: Some(TransformVendorPrefixPolicyV0::conservative()),
616 resolved_targets: Vec::new(),
617 resolution_error: None,
618 support: legacy_webview_feature_support(),
619 };
620 }
621
622 match resolve_browserslist(&[normalized_query], &Opts::default()) {
623 Ok(distribs) if !distribs.is_empty() => {
624 let resolved_targets = distribs.iter().map(distrib_key).collect::<Vec<_>>();
625 let target_data_evidence = target_data_evidence_for_resolved_targets(&distribs);
626 TargetQueryResolutionV0 {
627 profile_id: "browserslist-resolved",
628 recognized_profile: true,
629 target_data_source: "oxcBrowserslistV3+browserThresholdsTomlV0+generatedFeatureMatrixV0",
630 target_data_contract_id: TARGET_DATA_CONTRACT_ID,
631 target_data_snapshot_id: current_target_data_snapshot_id(),
632 target_data_evidence,
633 vendor_prefix_matrix_source: VENDOR_PREFIX_MATRIX_SOURCE,
634 vendor_prefix_policy: Some(vendor_prefix_policy_for_resolved_targets(&distribs)),
635 support: feature_support_for_resolved_targets(&distribs),
636 resolved_targets,
637 resolution_error: None,
638 }
639 }
640 Ok(_) => unknown_conservative_resolution(None),
641 Err(error) => unknown_conservative_resolution(Some(error.to_string())),
642 }
643}
644
645fn unknown_conservative_resolution(resolution_error: Option<String>) -> TargetQueryResolutionV0 {
646 TargetQueryResolutionV0 {
647 profile_id: "unknown-conservative",
648 recognized_profile: false,
649 target_data_source: "staticTargetProfileV0",
650 target_data_contract_id: TARGET_DATA_CONTRACT_ID,
651 target_data_snapshot_id: current_target_data_snapshot_id(),
652 target_data_evidence: Vec::new(),
653 vendor_prefix_matrix_source: VENDOR_PREFIX_MATRIX_SOURCE,
654 vendor_prefix_policy: Some(TransformVendorPrefixPolicyV0::conservative()),
655 resolved_targets: Vec::new(),
656 resolution_error,
657 support: legacy_webview_feature_support(),
658 }
659}
660
661fn vendor_prefix_policy_for_resolved_targets(
662 distribs: &[Distrib],
663) -> TransformVendorPrefixPolicyV0 {
664 let mut policy = TransformVendorPrefixPolicyV0::none();
665 for distrib in distribs {
666 let browser = distrib.name();
667 let version = distrib.version();
668 match browser {
669 "ie" => policy.ms = true,
670 "edge" if !browser_version_at_least(version, 79, 0) => policy.ms = true,
671 "safari" | "ios_saf" => policy.webkit = true,
672 "chrome" | "and_chr" | "android" | "opera" | "op_mob"
673 if !browser_version_at_least(version, 50, 0) =>
674 {
675 policy.webkit = true;
676 }
677 "firefox" if !browser_version_at_least(version, 70, 0) => policy.moz = true,
678 _ => {}
679 }
680 }
681 policy
682}
683
684fn feature_support_for_resolved_targets(distribs: &[Distrib]) -> TargetFeatureSupportV0 {
685 let flexbox_fully_supported =
686 target_set_is_subset_of_browser_threshold_table(distribs, "flexbox");
687 let sticky_fully_supported =
688 target_set_is_subset_of_browser_threshold_table(distribs, "sticky_positioning");
689
690 TargetFeatureSupportV0 {
691 vendor_prefix_required: !(flexbox_fully_supported && sticky_fully_supported),
692 supports_light_dark: target_set_is_subset_of_browser_threshold_table(
693 distribs,
694 "light_dark",
695 ),
696 supports_color_mix: target_set_is_subset_of_browser_threshold_table(distribs, "color_mix"),
697 supports_oklch_oklab: target_set_is_subset_of_browser_threshold_table(
698 distribs,
699 "oklch_oklab",
700 ),
701 supports_color_function: target_set_is_subset_of_browser_threshold_table(
702 distribs,
703 "color_function",
704 ),
705 supports_relative_color: target_set_is_subset_of_browser_threshold_table(
706 distribs,
707 "relative_color",
708 ),
709 supports_logical_properties: target_set_is_subset_of_browser_threshold_table(
710 distribs,
711 "logical_properties",
712 ),
713 supports_css_nesting: target_set_is_subset_of_browser_threshold_table(
714 distribs,
715 "css_nesting",
716 ),
717 supports_css_scope: target_set_is_subset_of_browser_threshold_table(distribs, "css_scope"),
718 supports_cascade_layers: target_set_is_subset_of_browser_threshold_table(
719 distribs,
720 "cascade_layers",
721 ),
722 }
723}
724
725fn target_set_is_subset_of_browser_threshold_table(distribs: &[Distrib], table: &str) -> bool {
726 !distribs.is_empty()
727 && distribs
728 .iter()
729 .all(|distrib| distrib_matches_browser_threshold_table(distrib, table))
730}
731
732fn distrib_matches_browser_threshold_table(distrib: &Distrib, table: &str) -> bool {
733 browser_threshold_data()
734 .thresholds
735 .iter()
736 .filter(|threshold| threshold.table == table)
737 .find(|threshold| threshold.browser == distrib.name())
738 .is_some_and(|threshold| {
739 browser_version_at_least(distrib.version(), threshold.min_major, threshold.min_minor)
740 })
741}
742
743fn browser_threshold_data() -> &'static BrowserThresholdDataV0 {
744 static DATA: OnceLock<BrowserThresholdDataV0> = OnceLock::new();
745 DATA.get_or_init(|| {
746 parse_browser_threshold_data(BROWSER_THRESHOLDS_SOURCE).unwrap_or_else(|_| {
747 BrowserThresholdDataV0 {
748 parse_error_count: 1,
749 ..BrowserThresholdDataV0::default()
750 }
751 })
752 })
753}
754
755fn pass_feature_binding_data() -> &'static PassFeatureBindingDataV0 {
756 static DATA: OnceLock<PassFeatureBindingDataV0> = OnceLock::new();
757 DATA.get_or_init(|| {
758 parse_pass_feature_binding_data(PASS_FEATURE_BINDINGS_SOURCE).unwrap_or_else(|_| {
759 PassFeatureBindingDataV0 {
760 parse_error_count: 1,
761 ..PassFeatureBindingDataV0::default()
762 }
763 })
764 })
765}
766
767fn native_stage2_coverage_data() -> &'static NativeStage2CoverageDataV0 {
768 static DATA: OnceLock<NativeStage2CoverageDataV0> = OnceLock::new();
769 DATA.get_or_init(|| {
770 serde_json::from_str(NATIVE_STAGE2_COVERAGE_SOURCE).unwrap_or_else(|_| {
771 NativeStage2CoverageDataV0 {
772 schema_version: "invalid".to_string(),
773 product: "invalid".to_string(),
774 binding_source: "invalid".to_string(),
775 selection_basis: Vec::new(),
776 stage1_fallback: NativeStage2CoverageFallbackV0 {
777 plugin_id: String::new(),
778 policy: String::new(),
779 },
780 covered_features: Vec::new(),
781 uncovered_features: Vec::new(),
782 }
783 })
784 })
785}
786
787fn target_data_source_files() -> Vec<&'static str> {
788 TARGET_DATA_SOURCE_FILES.to_vec()
789}
790
791fn runtime_fallback_feature_keys() -> Vec<&'static str> {
792 RUNTIME_FALLBACK_FEATURE_KEYS.to_vec()
793}
794
795fn current_target_data_snapshot_id() -> String {
796 target_data_snapshot_id(browser_threshold_data(), pass_feature_binding_data())
797}
798
799fn target_data_snapshot_id(
800 browser_data: &BrowserThresholdDataV0,
801 bindings: &PassFeatureBindingDataV0,
802) -> String {
803 format!(
804 "{TARGET_DATA_CONTRACT_ID}:thresholds-{}:bindings-{}",
805 browser_data.refreshed_at, bindings.refreshed_at
806 )
807}
808
809fn target_data_contract_summary(
810 browser_data: &BrowserThresholdDataV0,
811 bindings: &PassFeatureBindingDataV0,
812) -> TransformTargetDataContractV0 {
813 let parse_error_count = browser_data.parse_error_count + bindings.parse_error_count;
814 let stale_entry_count = browser_threshold_stale_entry_count(browser_data);
815 let unmapped_threshold_table_count = unmapped_threshold_table_count(browser_data, bindings);
816 let unresolvable_threshold_query_count = unresolvable_threshold_query_count(browser_data);
817 let quorum_valid = browser_threshold_data_is_valid(browser_data);
818 let bindings_valid = pass_feature_binding_data_is_valid(browser_data, bindings);
819 let resolver_provenance_valid = resolver_provenance_is_valid(browser_data, bindings);
820
821 TransformTargetDataContractV0 {
822 product: "omena-transform-target.data-contract",
823 contract_id: TARGET_DATA_CONTRACT_ID,
824 snapshot_id: target_data_snapshot_id(browser_data, bindings),
825 browser_threshold_schema_version: browser_data.schema_version.clone(),
826 pass_feature_binding_schema_version: bindings.schema_version.clone(),
827 browser_threshold_refreshed_at: browser_data.refreshed_at.clone(),
828 pass_feature_binding_refreshed_at: bindings.refreshed_at.clone(),
829 threshold_source_policy: browser_data.threshold_source_policy.clone(),
830 caniuse_resolver_workspace_dependency: browser_data
831 .caniuse_resolver_workspace_dependency
832 .clone(),
833 caniuse_resolver_cargo_package: browser_data.caniuse_resolver_cargo_package.clone(),
834 caniuse_resolver_cargo_version: browser_data.caniuse_resolver_cargo_version.clone(),
835 source_files: target_data_source_files(),
836 parse_error_count,
837 quorum_min_sources: browser_data.quorum_min_sources,
838 browser_threshold_table_count: browser_threshold_table_count(browser_data),
839 browser_threshold_entry_count: browser_data.thresholds.len(),
840 pass_feature_binding_count: bindings.bindings.len(),
841 stale_entry_count,
842 unmapped_threshold_table_count,
843 unresolvable_threshold_query_count,
844 runtime_fallback_feature_keys: runtime_fallback_feature_keys(),
845 runtime_fallback_feature_count: RUNTIME_FALLBACK_FEATURE_KEYS.len(),
846 generated_coverage_complete: RUNTIME_FALLBACK_FEATURE_KEYS.is_empty(),
847 quorum_valid,
848 bindings_valid,
849 resolver_provenance_valid,
850 valid: parse_error_count == 0
851 && stale_entry_count == 0
852 && unmapped_threshold_table_count == 0
853 && unresolvable_threshold_query_count == 0
854 && quorum_valid
855 && bindings_valid
856 && resolver_provenance_valid,
857 }
858}
859
860fn target_data_evidence_for_resolved_targets(
861 distribs: &[Distrib],
862) -> Vec<TransformTargetDataEvidenceV0> {
863 let browser_data = browser_threshold_data();
864 pass_feature_binding_data()
865 .bindings
866 .iter()
867 .map(|binding| {
868 let resolved_targets = distribs
869 .iter()
870 .map(|distrib| {
871 resolved_target_evidence_for_table(
872 browser_data,
873 distrib,
874 &binding.support_table,
875 )
876 })
877 .collect::<Vec<_>>();
878 let all_resolved_targets_supported = !resolved_targets.is_empty()
879 && resolved_targets.iter().all(|target| target.supported);
880
881 TransformTargetDataEvidenceV0 {
882 product: "omena-transform-target.data-evidence",
883 pass_id: binding.pass_id.clone(),
884 support_table: binding.support_table.clone(),
885 caniuse_keys: binding.caniuse_keys.clone(),
886 source_quorum: source_quorum_for_table(browser_data, &binding.support_table),
887 last_verified: last_verified_for_table(browser_data, &binding.support_table),
888 resolved_targets,
889 all_resolved_targets_supported,
890 }
891 })
892 .collect()
893}
894
895fn resolved_target_evidence_for_table(
896 browser_data: &BrowserThresholdDataV0,
897 distrib: &Distrib,
898 table: &str,
899) -> TransformTargetResolvedTargetEvidenceV0 {
900 let threshold = browser_threshold_for_table_and_browser(browser_data, table, distrib.name());
901 let supported = threshold.is_some_and(|threshold| {
902 browser_version_at_least(distrib.version(), threshold.min_major, threshold.min_minor)
903 });
904
905 TransformTargetResolvedTargetEvidenceV0 {
906 browser: distrib.name().to_string(),
907 version: distrib.version().to_string(),
908 supported,
909 matched_threshold: threshold.map(threshold_evidence),
910 }
911}
912
913fn threshold_evidence(threshold: &BrowserFeatureThresholdV0) -> TransformTargetThresholdEvidenceV0 {
914 TransformTargetThresholdEvidenceV0 {
915 browser: threshold.browser.clone(),
916 min_version: format!("{}.{}", threshold.min_major, threshold.min_minor),
917 caniuse_key: threshold.caniuse_key.clone(),
918 source_quorum: threshold.source_quorum.clone(),
919 last_verified: threshold.last_verified.clone(),
920 }
921}
922
923fn browser_threshold_for_table_and_browser<'a>(
924 browser_data: &'a BrowserThresholdDataV0,
925 table: &str,
926 browser: &str,
927) -> Option<&'a BrowserFeatureThresholdV0> {
928 browser_data
929 .thresholds
930 .iter()
931 .find(|threshold| threshold.table == table && threshold.browser == browser)
932}
933
934fn source_quorum_for_table(browser_data: &BrowserThresholdDataV0, table: &str) -> Vec<String> {
935 let mut sources = Vec::new();
936 for threshold in browser_data
937 .thresholds
938 .iter()
939 .filter(|threshold| threshold.table == table)
940 {
941 push_unique_strings(&mut sources, threshold.source_quorum.iter());
942 }
943 sources
944}
945
946fn last_verified_for_table(browser_data: &BrowserThresholdDataV0, table: &str) -> Vec<String> {
947 let mut dates = Vec::new();
948 for threshold in browser_data
949 .thresholds
950 .iter()
951 .filter(|threshold| threshold.table == table)
952 {
953 push_unique_strings(&mut dates, std::iter::once(&threshold.last_verified));
954 }
955 dates
956}
957
958fn push_unique_strings<'a>(output: &mut Vec<String>, values: impl IntoIterator<Item = &'a String>) {
959 for value in values {
960 if !output.iter().any(|existing| existing == value) {
961 output.push(value.clone());
962 }
963 }
964}
965
966fn parse_browser_threshold_data(source: &str) -> Result<BrowserThresholdDataV0, String> {
967 let mut data = BrowserThresholdDataV0::default();
968 let mut current_threshold: Option<BrowserFeatureThresholdV0> = None;
969
970 for line in significant_toml_lines(source) {
971 if line == "[[threshold]]" {
972 if let Some(threshold) = current_threshold.take() {
973 data.thresholds.push(threshold);
974 }
975 current_threshold = Some(BrowserFeatureThresholdV0::default());
976 continue;
977 }
978
979 let (key, value) = parse_toml_assignment(&line)?;
980 if let Some(threshold) = current_threshold.as_mut() {
981 match key {
982 "table" => threshold.table = parse_toml_string(value)?,
983 "browser" => threshold.browser = parse_toml_string(value)?,
984 "min_major" => threshold.min_major = parse_toml_u16(value)?,
985 "min_minor" => threshold.min_minor = parse_toml_u16(value)?,
986 "caniuse_key" => threshold.caniuse_key = parse_toml_string(value)?,
987 "source_quorum" => threshold.source_quorum = parse_toml_string_array(value)?,
988 "last_verified" => threshold.last_verified = parse_toml_string(value)?,
989 _ => return Err(format!("unknown browser threshold key `{key}`")),
990 }
991 } else {
992 match key {
993 "schema_version" => data.schema_version = parse_toml_string(value)?,
994 "product" => data.product = parse_toml_string(value)?,
995 "refreshed_at" => data.refreshed_at = parse_toml_string(value)?,
996 "threshold_source_policy" => {
997 data.threshold_source_policy = parse_toml_string(value)?
998 }
999 "quorum_min_sources" => data.quorum_min_sources = parse_toml_usize(value)?,
1000 "caniuse_resolver_workspace_dependency" => {
1001 data.caniuse_resolver_workspace_dependency = parse_toml_string(value)?
1002 }
1003 "caniuse_resolver_cargo_package" => {
1004 data.caniuse_resolver_cargo_package = parse_toml_string(value)?
1005 }
1006 "caniuse_resolver_cargo_version" => {
1007 data.caniuse_resolver_cargo_version = parse_toml_string(value)?
1008 }
1009 _ => return Err(format!("unknown browser threshold root key `{key}`")),
1010 }
1011 }
1012 }
1013
1014 if let Some(threshold) = current_threshold.take() {
1015 data.thresholds.push(threshold);
1016 }
1017
1018 Ok(data)
1019}
1020
1021fn parse_pass_feature_binding_data(source: &str) -> Result<PassFeatureBindingDataV0, String> {
1022 let mut data = PassFeatureBindingDataV0::default();
1023 let mut current_binding: Option<PassFeatureBindingV0> = None;
1024
1025 for line in significant_toml_lines(source) {
1026 if line == "[[binding]]" {
1027 if let Some(binding) = current_binding.take() {
1028 data.bindings.push(binding);
1029 }
1030 current_binding = Some(PassFeatureBindingV0::default());
1031 continue;
1032 }
1033
1034 let (key, value) = parse_toml_assignment(&line)?;
1035 if let Some(binding) = current_binding.as_mut() {
1036 match key {
1037 "pass_id" => binding.pass_id = parse_toml_string(value)?,
1038 "caniuse_keys" => binding.caniuse_keys = parse_toml_string_array(value)?,
1039 "support_table" => binding.support_table = parse_toml_string(value)?,
1040 _ => return Err(format!("unknown pass feature binding key `{key}`")),
1041 }
1042 } else {
1043 match key {
1044 "schema_version" => data.schema_version = parse_toml_string(value)?,
1045 "product" => data.product = parse_toml_string(value)?,
1046 "refreshed_at" => data.refreshed_at = parse_toml_string(value)?,
1047 "caniuse_resolver_workspace_dependency" => {
1048 data.caniuse_resolver_workspace_dependency = parse_toml_string(value)?
1049 }
1050 "caniuse_resolver_cargo_package" => {
1051 data.caniuse_resolver_cargo_package = parse_toml_string(value)?
1052 }
1053 "caniuse_resolver_cargo_version" => {
1054 data.caniuse_resolver_cargo_version = parse_toml_string(value)?
1055 }
1056 _ => return Err(format!("unknown pass feature binding root key `{key}`")),
1057 }
1058 }
1059 }
1060
1061 if let Some(binding) = current_binding.take() {
1062 data.bindings.push(binding);
1063 }
1064
1065 Ok(data)
1066}
1067
1068fn significant_toml_lines(source: &str) -> impl Iterator<Item = String> + '_ {
1069 source
1070 .lines()
1071 .map(|line| line.split('#').next().unwrap_or("").trim())
1072 .filter(|line| !line.is_empty())
1073 .map(ToOwned::to_owned)
1074}
1075
1076fn parse_toml_assignment(line: &str) -> Result<(&str, &str), String> {
1077 let Some((key, value)) = line.split_once('=') else {
1078 return Err(format!("invalid assignment `{line}`"));
1079 };
1080 Ok((key.trim(), value.trim()))
1081}
1082
1083fn parse_toml_string(value: &str) -> Result<String, String> {
1084 let value = value.trim();
1085 if value.len() < 2 || !value.starts_with('"') || !value.ends_with('"') {
1086 return Err(format!("expected quoted string, got `{value}`"));
1087 }
1088 Ok(value[1..value.len() - 1].to_string())
1089}
1090
1091fn parse_toml_u16(value: &str) -> Result<u16, String> {
1092 value
1093 .trim()
1094 .parse::<u16>()
1095 .map_err(|error| format!("invalid u16 `{value}`: {error}"))
1096}
1097
1098fn parse_toml_usize(value: &str) -> Result<usize, String> {
1099 value
1100 .trim()
1101 .parse::<usize>()
1102 .map_err(|error| format!("invalid usize `{value}`: {error}"))
1103}
1104
1105fn parse_toml_string_array(value: &str) -> Result<Vec<String>, String> {
1106 let value = value.trim();
1107 if value.len() < 2 || !value.starts_with('[') || !value.ends_with(']') {
1108 return Err(format!("expected string array, got `{value}`"));
1109 }
1110 let body = value[1..value.len() - 1].trim();
1111 if body.is_empty() {
1112 return Ok(Vec::new());
1113 }
1114
1115 body.split(',')
1116 .map(|item| parse_toml_string(item.trim()))
1117 .collect()
1118}
1119
1120fn browser_threshold_data_is_valid(data: &BrowserThresholdDataV0) -> bool {
1121 data.parse_error_count == 0
1122 && data.schema_version == "0"
1123 && data.product == "omena-transform-target.browser-thresholds"
1124 && is_iso_date(&data.refreshed_at)
1125 && data.threshold_source_policy == THRESHOLD_SOURCE_POLICY
1126 && data.quorum_min_sources == COMPAT_QUORUM_SOURCES.len()
1127 && data.caniuse_resolver_workspace_dependency == CANIUSE_RESOLVER_WORKSPACE_DEPENDENCY
1128 && data.caniuse_resolver_cargo_package == CANIUSE_RESOLVER_CARGO_PACKAGE
1129 && !data.caniuse_resolver_cargo_version.is_empty()
1130 && browser_threshold_table_count(data) >= 2
1131 && browser_threshold_stale_entry_count(data) == 0
1132 && unresolvable_threshold_query_count(data) == 0
1133 && data.thresholds.iter().all(|threshold| {
1134 !threshold.table.is_empty()
1135 && !threshold.browser.is_empty()
1136 && !threshold.caniuse_key.is_empty()
1137 && compat_source_quorum_is_complete(&threshold.source_quorum)
1138 && is_iso_date(&threshold.last_verified)
1139 })
1140}
1141
1142fn compat_source_quorum_is_complete(source_quorum: &[String]) -> bool {
1143 source_quorum.len() == COMPAT_QUORUM_SOURCES.len()
1144 && COMPAT_QUORUM_SOURCES
1145 .iter()
1146 .all(|expected| source_quorum.iter().any(|source| source == expected))
1147}
1148
1149fn pass_feature_binding_data_is_valid(
1150 browser_data: &BrowserThresholdDataV0,
1151 binding_data: &PassFeatureBindingDataV0,
1152) -> bool {
1153 binding_data.parse_error_count == 0
1154 && binding_data.schema_version == "0"
1155 && binding_data.product == "omena-transform-target.pass-feature-bindings"
1156 && is_iso_date(&binding_data.refreshed_at)
1157 && binding_data.caniuse_resolver_workspace_dependency
1158 == CANIUSE_RESOLVER_WORKSPACE_DEPENDENCY
1159 && binding_data.caniuse_resolver_cargo_package == CANIUSE_RESOLVER_CARGO_PACKAGE
1160 && !binding_data.caniuse_resolver_cargo_version.is_empty()
1161 && !binding_data.bindings.is_empty()
1162 && unmapped_threshold_table_count(browser_data, binding_data) == 0
1163 && binding_data.bindings.iter().all(|binding| {
1164 !binding.pass_id.is_empty()
1165 && target_managed_passes()
1166 .iter()
1167 .any(|pass| pass.id() == binding.pass_id)
1168 && !binding.caniuse_keys.is_empty()
1169 && binding.caniuse_keys.iter().all(|key| {
1170 browser_data.thresholds.iter().any(|threshold| {
1171 threshold.table == binding.support_table && threshold.caniuse_key == *key
1172 })
1173 })
1174 })
1175}
1176
1177fn resolver_provenance_is_valid(
1178 browser_data: &BrowserThresholdDataV0,
1179 binding_data: &PassFeatureBindingDataV0,
1180) -> bool {
1181 browser_data.caniuse_resolver_workspace_dependency == CANIUSE_RESOLVER_WORKSPACE_DEPENDENCY
1182 && browser_data.caniuse_resolver_cargo_package == CANIUSE_RESOLVER_CARGO_PACKAGE
1183 && !browser_data.caniuse_resolver_cargo_version.is_empty()
1184 && browser_data.caniuse_resolver_workspace_dependency
1185 == binding_data.caniuse_resolver_workspace_dependency
1186 && browser_data.caniuse_resolver_cargo_package
1187 == binding_data.caniuse_resolver_cargo_package
1188 && browser_data.caniuse_resolver_cargo_version
1189 == binding_data.caniuse_resolver_cargo_version
1190}
1191
1192fn browser_threshold_stale_entry_count(data: &BrowserThresholdDataV0) -> usize {
1193 data.thresholds
1194 .iter()
1195 .filter(|threshold| threshold.last_verified != data.refreshed_at)
1196 .count()
1197}
1198
1199fn unmapped_threshold_table_count(
1200 browser_data: &BrowserThresholdDataV0,
1201 binding_data: &PassFeatureBindingDataV0,
1202) -> usize {
1203 let bound_tables = binding_data
1204 .bindings
1205 .iter()
1206 .map(|binding| binding.support_table.as_str())
1207 .collect::<BTreeSet<_>>();
1208 browser_data
1209 .thresholds
1210 .iter()
1211 .map(|threshold| threshold.table.as_str())
1212 .collect::<BTreeSet<_>>()
1213 .difference(&bound_tables)
1214 .count()
1215}
1216
1217fn unresolvable_threshold_query_count(browser_data: &BrowserThresholdDataV0) -> usize {
1218 browser_data
1219 .thresholds
1220 .iter()
1221 .filter(|threshold| !threshold_browser_query_resolves(threshold))
1222 .count()
1223}
1224
1225fn threshold_browser_query_resolves(threshold: &BrowserFeatureThresholdV0) -> bool {
1226 let query = threshold_browser_query(threshold);
1227 resolve_browserslist(&[query.as_str()], &Opts::default()).is_ok_and(|distribs| {
1228 distribs.iter().any(|distrib| {
1229 distrib.name() == threshold.browser
1230 && browser_version_at_least(
1231 distrib.version(),
1232 threshold.min_major,
1233 threshold.min_minor,
1234 )
1235 })
1236 })
1237}
1238
1239fn threshold_browser_query(threshold: &BrowserFeatureThresholdV0) -> String {
1240 browser_version_query(
1241 threshold.browser.as_str(),
1242 threshold.min_major,
1243 threshold.min_minor,
1244 )
1245}
1246
1247fn browser_version_query(browser: &str, major: u16, minor: u16) -> String {
1248 if minor == 0 {
1249 format!("{browser} {major}")
1250 } else {
1251 format!("{browser} {major}.{minor}")
1252 }
1253}
1254
1255fn browser_threshold_table_count(data: &BrowserThresholdDataV0) -> usize {
1256 data.thresholds
1257 .iter()
1258 .map(|threshold| threshold.table.as_str())
1259 .collect::<BTreeSet<_>>()
1260 .len()
1261}
1262
1263fn is_iso_date(value: &str) -> bool {
1264 let bytes = value.as_bytes();
1265 bytes.len() == 10
1266 && bytes[4] == b'-'
1267 && bytes[7] == b'-'
1268 && bytes
1269 .iter()
1270 .enumerate()
1271 .all(|(index, byte)| matches!(index, 4 | 7) || byte.is_ascii_digit())
1272}
1273
1274fn browser_version_at_least(version: &str, min_major: u16, min_minor: u16) -> bool {
1275 if version.eq_ignore_ascii_case("tp") {
1276 return true;
1277 }
1278
1279 let version = version.split('-').next().unwrap_or(version);
1280 let mut parts = version.split('.');
1281 let Some(major) = parts.next().and_then(|part| part.parse::<u16>().ok()) else {
1282 return false;
1283 };
1284 let minor = parts
1285 .next()
1286 .and_then(|part| part.parse::<u16>().ok())
1287 .unwrap_or(0);
1288
1289 major > min_major || (major == min_major && minor >= min_minor)
1290}
1291
1292fn distrib_key(distrib: &Distrib) -> String {
1293 distrib.to_string()
1294}
1295
1296fn push_required_or_blocked(
1297 pass: TransformPassKind,
1298 allowed: bool,
1299 required_passes: &mut Vec<TransformPassKind>,
1300 blocked_passes: &mut Vec<TransformPassKind>,
1301) {
1302 if allowed {
1303 required_passes.push(pass);
1304 } else {
1305 blocked_passes.push(pass);
1306 }
1307}
1308
1309fn target_managed_passes() -> [TransformPassKind; 14] {
1310 [
1311 TransformPassKind::VendorPrefixing,
1312 TransformPassKind::StalePrefixRemoval,
1313 TransformPassKind::LightDarkLowering,
1314 TransformPassKind::ColorMixLowering,
1315 TransformPassKind::OklchOklabLowering,
1316 TransformPassKind::ColorFunctionLowering,
1317 TransformPassKind::RelativeColorLowering,
1318 TransformPassKind::LogicalToPhysical,
1319 TransformPassKind::NestingUnwrap,
1320 TransformPassKind::ScopeFlatten,
1321 TransformPassKind::LayerFlatten,
1322 TransformPassKind::SupportsStaticEval,
1323 TransformPassKind::MediaStaticEval,
1324 TransformPassKind::DeadMediaBranchRemoval,
1325 ]
1326}
1327
1328#[cfg(test)]
1329mod tests {
1330 use super::{
1331 NativeStage2UncoveredFeatureV0, TargetFeatureSupportV0, TargetTransformOptionsV0,
1332 conservative_target_options, plan_target_transforms, plan_target_transforms_from_query,
1333 summarize_omena_transform_target_boundary,
1334 };
1335
1336 #[test]
1337 fn uncovered_feature_identity_uses_standard_property_keys() {
1338 let fixture = |property: &str| NativeStage2UncoveredFeatureV0 {
1339 feature_id: "logical".to_string(),
1340 category: "declaration".to_string(),
1341 reason: "fallback".to_string(),
1342 observed_properties: vec![omena_syntax::ident::AuthoredPropertyTextV0::new(property)],
1343 fallback: "stage1".to_string(),
1344 };
1345
1346 assert_eq!(fixture(r"C\4f LOR"), fixture("color"));
1347 }
1348
1349 #[test]
1350 fn exposes_target_lowering_boundary() {
1351 let boundary = summarize_omena_transform_target_boundary();
1352
1353 assert_eq!(boundary.product, "omena-transform-target.boundary");
1354 assert_eq!(boundary.managed_pass_ids.len(), 14);
1355 assert_eq!(
1356 boundary.target_data_source,
1357 "oxcBrowserslistV3+browserThresholdsTomlV0+staticTargetProfileV0+generatedFeatureMatrixV0"
1358 );
1359 assert_eq!(boundary.browser_threshold_table_count, 11);
1360 assert_eq!(boundary.browser_threshold_entry_count, 119);
1361 assert_eq!(boundary.pass_feature_binding_count, 13);
1362 assert_eq!(boundary.browser_data_parse_error_count, 0);
1363 assert!(boundary.browser_data_quorum_valid);
1364 assert!(boundary.browser_data_bindings_valid);
1365 assert_eq!(
1366 boundary.target_data_contract.contract_id,
1367 super::TARGET_DATA_CONTRACT_ID
1368 );
1369 assert_eq!(
1370 boundary.target_data_contract.snapshot_id,
1371 "omena-transform-target-data-v0:thresholds-2026-08-27:bindings-2026-08-27"
1372 );
1373 assert!(boundary.target_data_contract.valid);
1374 assert_eq!(boundary.target_data_contract.pass_feature_binding_count, 13);
1375 assert_eq!(
1376 boundary
1377 .target_data_contract
1378 .caniuse_resolver_workspace_dependency,
1379 super::CANIUSE_RESOLVER_WORKSPACE_DEPENDENCY
1380 );
1381 assert_eq!(
1382 boundary.target_data_contract.caniuse_resolver_cargo_package,
1383 super::CANIUSE_RESOLVER_CARGO_PACKAGE
1384 );
1385 assert_eq!(
1386 boundary.target_data_contract.caniuse_resolver_cargo_version,
1387 "3.0.5"
1388 );
1389 assert_eq!(
1390 boundary.target_data_contract.threshold_source_policy,
1391 super::THRESHOLD_SOURCE_POLICY
1392 );
1393 assert!(boundary.target_data_contract.resolver_provenance_valid);
1394 assert_eq!(boundary.target_data_contract.stale_entry_count, 0);
1395 assert_eq!(
1396 boundary.target_data_contract.unmapped_threshold_table_count,
1397 0
1398 );
1399 assert_eq!(
1400 boundary
1401 .target_data_contract
1402 .unresolvable_threshold_query_count,
1403 0
1404 );
1405 assert_eq!(
1406 boundary.target_data_contract.runtime_fallback_feature_keys,
1407 Vec::<&'static str>::new()
1408 );
1409 assert_eq!(
1410 boundary.target_data_contract.runtime_fallback_feature_count,
1411 0
1412 );
1413 assert!(boundary.target_data_contract.generated_coverage_complete);
1414 assert_eq!(
1415 boundary.runtime_fallback_feature_keys,
1416 boundary.target_data_contract.runtime_fallback_feature_keys
1417 );
1418 assert!(boundary.generated_coverage_complete);
1419 assert_eq!(boundary.native_stage2_coverage.covered_feature_count, 11);
1420 assert_eq!(boundary.native_stage2_coverage.uncovered_feature_count, 6);
1421 assert!(boundary.native_stage2_coverage.covered_pass_census_valid);
1422 assert!(boundary.native_stage2_coverage.binding_census_valid);
1423 assert!(boundary.native_stage2_coverage.valid);
1424 assert_eq!(
1425 boundary.vendor_prefix_matrix_source,
1426 super::VENDOR_PREFIX_MATRIX_SOURCE
1427 );
1428 assert!(boundary.managed_pass_ids.contains(&"vendor-prefixing"));
1429 assert!(boundary.managed_pass_ids.contains(&"stale-prefix-removal"));
1430 assert!(boundary.managed_pass_ids.contains(&"media-static-eval"));
1431 assert!(boundary.opt_in_pass_ids.contains(&"scope-flatten"));
1432 }
1433
1434 #[test]
1435 fn native_stage2_coverage_is_complete_for_declared_native_features() {
1436 let coverage = super::summarize_native_stage2_coverage();
1437
1438 assert!(coverage.valid);
1439 assert_eq!(coverage.covered_feature_count, 11);
1440 assert_eq!(coverage.uncovered_feature_count, 6);
1441 assert_eq!(
1442 coverage.stage1_fallback.plugin_id,
1443 "autoprefixer-legacy-browsers"
1444 );
1445 assert!(coverage.covered_features.iter().any(|feature| {
1446 feature.feature_id == "relative-color"
1447 && feature.pass_ids == ["relative-color-lowering"]
1448 && feature.target_rule == "allResolvedTargetsMeet:relative_color"
1449 }));
1450 assert!(coverage.uncovered_features.iter().any(|feature| {
1451 feature.feature_id == "vendor-prefixing.user-select" && feature.fallback == "stage1"
1452 }));
1453 }
1454
1455 #[test]
1456 fn relative_color_lowering_tracks_its_own_browser_support_table() {
1457 let unsupported =
1458 plan_target_transforms_from_query("chrome 121", conservative_target_options());
1459 let supported =
1460 plan_target_transforms_from_query("chrome 122", conservative_target_options());
1461
1462 assert!(!unsupported.support.supports_relative_color);
1463 assert!(
1464 unsupported
1465 .transform_plan
1466 .planned_pass_ids
1467 .contains(&"relative-color-lowering")
1468 );
1469 assert!(supported.support.supports_relative_color);
1470 assert!(
1471 !supported
1472 .transform_plan
1473 .planned_pass_ids
1474 .contains(&"relative-color-lowering")
1475 );
1476 }
1477
1478 #[test]
1479 fn browser_data_governance_externalizes_thresholds_and_bindings() {
1480 let boundary = summarize_omena_transform_target_boundary();
1481
1482 assert!(
1483 boundary
1484 .browser_data_source_files
1485 .contains(&"data/browser-thresholds.toml")
1486 );
1487 assert!(
1488 boundary
1489 .browser_data_source_files
1490 .contains(&"data/pass-feature-bindings.toml")
1491 );
1492 assert!(boundary.browser_data_quorum_valid);
1493 assert!(boundary.browser_data_bindings_valid);
1494 assert_eq!(
1495 boundary
1496 .target_data_contract
1497 .unresolvable_threshold_query_count,
1498 0
1499 );
1500 assert_eq!(
1501 boundary.target_data_contract.source_files,
1502 boundary.browser_data_source_files
1503 );
1504 assert_eq!(
1505 boundary
1506 .target_data_contract
1507 .browser_threshold_schema_version,
1508 "0"
1509 );
1510 assert_eq!(
1511 boundary
1512 .target_data_contract
1513 .pass_feature_binding_schema_version,
1514 "0"
1515 );
1516 assert_eq!(
1517 boundary.target_data_contract.threshold_source_policy,
1518 super::THRESHOLD_SOURCE_POLICY
1519 );
1520 assert!(boundary.target_data_contract.resolver_provenance_valid);
1521 assert_eq!(
1522 boundary.target_data_contract.runtime_fallback_feature_count,
1523 0
1524 );
1525 assert!(boundary.target_data_contract.generated_coverage_complete);
1526 }
1527
1528 #[test]
1529 fn generated_browser_threshold_rows_resolve_and_match_pass_boundaries() {
1530 let browser_data = super::browser_threshold_data();
1531 let bindings = super::pass_feature_binding_data();
1532 let options = TargetTransformOptionsV0 {
1533 allow_logical_to_physical: true,
1534 allow_scope_flatten: true,
1535 allow_layer_flatten: true,
1536 enable_supports_static_eval: false,
1537 enable_media_static_eval: false,
1538 enable_container_static_eval: false,
1539 drop_dark_mode_media_queries: false,
1540 };
1541
1542 for binding in &bindings.bindings {
1543 let pass_id = binding.pass_id.as_str();
1544 for threshold in browser_data
1545 .thresholds
1546 .iter()
1547 .filter(|threshold| threshold.table == binding.support_table)
1548 {
1549 let query = super::threshold_browser_query(threshold);
1550 let plan = plan_target_transforms_from_query(query.clone(), options);
1551 assert_eq!(
1552 plan.profile_id, "browserslist-resolved",
1553 "{query} must resolve through oxc-browserslist"
1554 );
1555
1556 let evidence = plan.target_data_evidence.iter().find(|evidence| {
1557 evidence.support_table == threshold.table && evidence.pass_id == pass_id
1558 });
1559 assert!(
1560 evidence.is_some(),
1561 "{query} missing evidence for {}",
1562 threshold.table
1563 );
1564 let Some(evidence) = evidence else {
1565 continue;
1566 };
1567 let resolved_target = evidence
1568 .resolved_targets
1569 .iter()
1570 .find(|target| target.browser == threshold.browser);
1571 assert!(
1572 resolved_target.is_some(),
1573 "{query} missing resolved target {}",
1574 threshold.browser
1575 );
1576 let Some(resolved_target) = resolved_target else {
1577 continue;
1578 };
1579 assert!(
1580 resolved_target.supported,
1581 "{query} should satisfy {}",
1582 threshold.table
1583 );
1584 if pass_id != super::TransformPassKind::VendorPrefixing.id()
1585 && pass_id != super::TransformPassKind::StalePrefixRemoval.id()
1586 {
1587 assert!(
1588 !plan.transform_plan.required_pass_ids.contains(&pass_id),
1589 "{query} should not require {pass_id} at the support threshold"
1590 );
1591 assert!(
1592 !plan.transform_plan.blocked_pass_ids.contains(&pass_id),
1593 "{query} should not block {pass_id} at the support threshold"
1594 );
1595 }
1596
1597 if let Some((previous_major, previous_minor)) =
1598 previous_browser_version(threshold.min_major, threshold.min_minor)
1599 {
1600 let previous_query = super::browser_version_query(
1601 threshold.browser.as_str(),
1602 previous_major,
1603 previous_minor,
1604 );
1605 let previous_plan =
1606 plan_target_transforms_from_query(previous_query.clone(), options);
1607 if previous_plan.profile_id == "browserslist-resolved"
1608 && previous_plan
1609 .resolved_targets
1610 .iter()
1611 .any(|target| target == &previous_query)
1612 {
1613 let previous_evidence =
1614 previous_plan.target_data_evidence.iter().find(|evidence| {
1615 evidence.support_table == threshold.table
1616 && evidence.pass_id == pass_id
1617 });
1618 assert!(
1619 previous_evidence.is_some(),
1620 "{previous_query} missing evidence for {}",
1621 threshold.table
1622 );
1623 let Some(previous_evidence) = previous_evidence else {
1624 continue;
1625 };
1626 let previous_target = previous_evidence
1627 .resolved_targets
1628 .iter()
1629 .find(|target| target.browser == threshold.browser);
1630 assert!(
1631 previous_target.is_some(),
1632 "{previous_query} missing resolved target {}",
1633 threshold.browser
1634 );
1635 let Some(previous_target) = previous_target else {
1636 continue;
1637 };
1638 assert!(
1639 !previous_target.supported,
1640 "{previous_query} should not satisfy {}",
1641 threshold.table
1642 );
1643 if pass_id != super::TransformPassKind::VendorPrefixing.id()
1644 && pass_id != super::TransformPassKind::StalePrefixRemoval.id()
1645 {
1646 assert!(
1647 previous_plan
1648 .transform_plan
1649 .required_pass_ids
1650 .contains(&pass_id)
1651 || previous_plan
1652 .transform_plan
1653 .blocked_pass_ids
1654 .contains(&pass_id),
1655 "{previous_query} should require or block {pass_id} below the support threshold"
1656 );
1657 }
1658 }
1659 }
1660 }
1661 }
1662 }
1663
1664 fn previous_browser_version(major: u16, minor: u16) -> Option<(u16, u16)> {
1665 if minor > 0 {
1666 return Some((major, minor - 1));
1667 }
1668 major.checked_sub(1).map(|previous| (previous, 0))
1669 }
1670
1671 #[test]
1672 fn plans_target_lowering_with_vendor_prefix_after_lowering_edges() {
1673 let support = TargetFeatureSupportV0 {
1674 vendor_prefix_required: true,
1675 supports_light_dark: false,
1676 supports_color_mix: false,
1677 supports_oklch_oklab: true,
1678 supports_color_function: true,
1679 supports_relative_color: true,
1680 supports_logical_properties: true,
1681 supports_css_nesting: false,
1682 supports_css_scope: false,
1683 supports_cascade_layers: false,
1684 };
1685 let options = TargetTransformOptionsV0 {
1686 allow_logical_to_physical: false,
1687 allow_scope_flatten: true,
1688 allow_layer_flatten: true,
1689 enable_supports_static_eval: true,
1690 enable_media_static_eval: true,
1691 enable_container_static_eval: false,
1692 drop_dark_mode_media_queries: false,
1693 };
1694
1695 let plan = plan_target_transforms("legacy-webview", support, options);
1696
1697 assert_eq!(plan.pass_plan.violated_dag_edge_count, 0);
1698 assert!(plan.required_pass_ids.contains(&"light-dark-lowering"));
1699 assert!(plan.required_pass_ids.contains(&"color-mix-lowering"));
1700 assert!(plan.required_pass_ids.contains(&"vendor-prefixing"));
1701 assert!(plan.required_pass_ids.contains(&"scope-flatten"));
1702 assert!(plan.required_pass_ids.contains(&"layer-flatten"));
1703 let vendor_index = plan
1704 .planned_pass_ids
1705 .iter()
1706 .position(|id| *id == "vendor-prefixing");
1707 let light_dark_index = plan
1708 .planned_pass_ids
1709 .iter()
1710 .position(|id| *id == "light-dark-lowering");
1711 assert!(light_dark_index < vendor_index);
1712 }
1713
1714 #[test]
1715 fn blocks_opt_in_flattening_when_not_explicitly_enabled() {
1716 let support = TargetFeatureSupportV0 {
1717 supports_css_scope: false,
1718 supports_cascade_layers: false,
1719 ..super::modern_feature_support()
1720 };
1721
1722 let plan = plan_target_transforms(
1723 "modern-without-scope",
1724 support,
1725 conservative_target_options(),
1726 );
1727
1728 assert!(plan.blocked_pass_ids.contains(&"scope-flatten"));
1729 assert!(plan.blocked_pass_ids.contains(&"layer-flatten"));
1730 assert!(!plan.required_pass_ids.contains(&"scope-flatten"));
1731 assert!(!plan.required_pass_ids.contains(&"layer-flatten"));
1732 }
1733
1734 #[test]
1735 fn plans_dark_mode_media_drop_as_dead_media_branch_pass() {
1736 let mut options = conservative_target_options();
1737 options.drop_dark_mode_media_queries = true;
1738
1739 let plan = plan_target_transforms("modern", super::modern_feature_support(), options);
1740
1741 assert!(
1742 plan.required_pass_ids
1743 .contains(&"dead-media-branch-removal")
1744 );
1745 assert!(plan.planned_pass_ids.contains(&"dead-media-branch-removal"));
1746 }
1747
1748 #[test]
1749 fn plans_stale_prefix_removal_for_targets_that_do_not_need_prefixes() {
1750 let plan = plan_target_transforms(
1751 "modern",
1752 super::modern_feature_support(),
1753 conservative_target_options(),
1754 );
1755
1756 assert!(plan.required_pass_ids.contains(&"stale-prefix-removal"));
1757 assert!(!plan.required_pass_ids.contains(&"vendor-prefixing"));
1758 assert!(plan.planned_pass_ids.contains(&"stale-prefix-removal"));
1759 }
1760
1761 #[test]
1762 fn plans_target_lowering_from_static_target_query_profiles() {
1763 let options = TargetTransformOptionsV0 {
1764 allow_logical_to_physical: true,
1765 allow_scope_flatten: true,
1766 allow_layer_flatten: true,
1767 enable_supports_static_eval: true,
1768 enable_media_static_eval: true,
1769 enable_container_static_eval: false,
1770 drop_dark_mode_media_queries: false,
1771 };
1772 let plan = plan_target_transforms_from_query("legacy-webview", options);
1773
1774 assert!(plan.recognized_profile);
1775 assert_eq!(plan.normalized_query, "legacy-webview");
1776 assert_eq!(plan.profile_id, "legacy-webview");
1777 assert!(plan.support.vendor_prefix_required);
1778 assert_eq!(
1779 plan.vendor_prefix_policy,
1780 Some(super::TransformVendorPrefixPolicyV0::conservative())
1781 );
1782 assert_eq!(plan.transform_plan.pass_plan.violated_dag_edge_count, 0);
1783 assert!(
1784 plan.transform_plan
1785 .required_pass_ids
1786 .contains(&"vendor-prefixing")
1787 );
1788 assert!(
1789 plan.transform_plan
1790 .required_pass_ids
1791 .contains(&"nesting-unwrap")
1792 );
1793 assert!(
1794 plan.transform_plan
1795 .required_pass_ids
1796 .contains(&"logical-to-physical")
1797 );
1798
1799 let modern = plan_target_transforms_from_query("modern", conservative_target_options());
1800 assert!(modern.recognized_profile);
1801 assert_eq!(modern.profile_id, "modern-evergreen");
1802 assert_eq!(modern.target_data_source, "staticTargetProfileV0");
1803 assert_eq!(
1804 modern.target_data_contract_id,
1805 super::TARGET_DATA_CONTRACT_ID
1806 );
1807 assert_eq!(
1808 modern.target_data_snapshot_id,
1809 "omena-transform-target-data-v0:thresholds-2026-08-27:bindings-2026-08-27"
1810 );
1811 assert!(modern.target_data_evidence.is_empty());
1812 assert_eq!(
1813 modern.vendor_prefix_policy,
1814 Some(super::TransformVendorPrefixPolicyV0::none())
1815 );
1816 assert_eq!(
1817 modern.transform_plan.required_pass_ids,
1818 vec!["stale-prefix-removal"]
1819 );
1820 }
1821
1822 #[test]
1823 fn plans_target_lowering_from_resolved_browserslist_query() {
1824 let options = TargetTransformOptionsV0 {
1825 allow_logical_to_physical: true,
1826 allow_scope_flatten: true,
1827 allow_layer_flatten: true,
1828 enable_supports_static_eval: false,
1829 enable_media_static_eval: false,
1830 enable_container_static_eval: false,
1831 drop_dark_mode_media_queries: false,
1832 };
1833 let plan = plan_target_transforms_from_query("ie 11", options);
1834
1835 assert!(plan.recognized_profile);
1836 assert_eq!(plan.profile_id, "browserslist-resolved");
1837 assert_eq!(
1838 plan.target_data_source,
1839 "oxcBrowserslistV3+browserThresholdsTomlV0+generatedFeatureMatrixV0"
1840 );
1841 assert_eq!(plan.resolved_targets, vec!["ie 11"]);
1842 assert_eq!(plan.resolution_error, None);
1843 assert_eq!(plan.target_data_contract_id, super::TARGET_DATA_CONTRACT_ID);
1844 assert_eq!(
1845 plan.target_data_snapshot_id,
1846 "omena-transform-target-data-v0:thresholds-2026-08-27:bindings-2026-08-27"
1847 );
1848 assert_eq!(plan.target_data_evidence.len(), 13);
1849 assert_eq!(
1850 plan.vendor_prefix_policy,
1851 Some(super::TransformVendorPrefixPolicyV0 {
1852 webkit: false,
1853 moz: false,
1854 ms: true,
1855 })
1856 );
1857 assert!(
1858 plan.target_data_evidence
1859 .iter()
1860 .any(|evidence| evidence.support_table == "light_dark"
1861 && evidence.caniuse_keys == vec!["css-light-dark-function".to_string()]
1862 && evidence.source_quorum
1863 == vec![
1864 "caniuse".to_string(),
1865 "web-features".to_string(),
1866 "mdn-bcd".to_string()
1867 ]
1868 && evidence.last_verified == vec!["2026-08-27".to_string()])
1869 );
1870 assert!(
1871 plan.target_data_evidence
1872 .iter()
1873 .any(|evidence| evidence.support_table == "css_nesting"
1874 && evidence.caniuse_keys == vec!["css-nesting".to_string()]
1875 && !evidence.all_resolved_targets_supported)
1876 );
1877 assert!(
1878 plan.target_data_evidence
1879 .iter()
1880 .any(|evidence| evidence.support_table == "cascade_layers"
1881 && evidence.caniuse_keys == vec!["css-cascade-layers".to_string()]
1882 && !evidence.all_resolved_targets_supported)
1883 );
1884 assert!(
1885 plan.target_data_evidence
1886 .iter()
1887 .any(|evidence| evidence.support_table == "color_function"
1888 && evidence.caniuse_keys == vec!["css-color-function".to_string()]
1889 && !evidence.all_resolved_targets_supported)
1890 );
1891 assert!(
1892 plan.target_data_evidence
1893 .iter()
1894 .any(|evidence| evidence.support_table == "css_scope"
1895 && evidence.caniuse_keys == vec!["css-cascade-scope".to_string()]
1896 && !evidence.all_resolved_targets_supported)
1897 );
1898 assert!(
1899 plan.target_data_evidence
1900 .iter()
1901 .any(|evidence| evidence.pass_id == "stale-prefix-removal"
1902 && evidence.support_table == "flexbox"
1903 && evidence.caniuse_keys == vec!["flexbox".to_string()]
1904 && evidence.source_quorum
1905 == vec![
1906 "caniuse".to_string(),
1907 "web-features".to_string(),
1908 "mdn-bcd".to_string()
1909 ])
1910 );
1911 assert!(
1912 plan.target_data_evidence
1913 .iter()
1914 .any(|evidence| evidence.pass_id == "stale-prefix-removal"
1915 && evidence.support_table == "sticky_positioning"
1916 && evidence.caniuse_keys == vec!["css-sticky".to_string()]
1917 && evidence.source_quorum
1918 == vec![
1919 "caniuse".to_string(),
1920 "web-features".to_string(),
1921 "mdn-bcd".to_string()
1922 ])
1923 );
1924 assert!(plan.support.vendor_prefix_required);
1925 assert!(
1926 plan.transform_plan
1927 .required_pass_ids
1928 .contains(&"vendor-prefixing")
1929 );
1930 assert!(
1931 plan.transform_plan
1932 .required_pass_ids
1933 .contains(&"nesting-unwrap")
1934 );
1935 assert!(
1936 plan.transform_plan
1937 .required_pass_ids
1938 .contains(&"logical-to-physical")
1939 );
1940 }
1941
1942 #[test]
1943 fn resolves_target_features_from_static_compatibility_matrix() {
1944 let chrome_110 =
1945 plan_target_transforms_from_query("chrome 110", conservative_target_options());
1946 assert!(!chrome_110.support.supports_color_function);
1947 assert!(
1948 chrome_110
1949 .transform_plan
1950 .required_pass_ids
1951 .contains(&"color-function-lowering")
1952 );
1953
1954 let chrome_111 =
1955 plan_target_transforms_from_query("chrome 111", conservative_target_options());
1956 assert!(chrome_111.support.supports_color_function);
1957 assert!(!chrome_111.support.supports_css_nesting);
1958 assert!(
1959 !chrome_111
1960 .transform_plan
1961 .required_pass_ids
1962 .contains(&"color-function-lowering")
1963 );
1964 assert!(
1965 chrome_111
1966 .transform_plan
1967 .required_pass_ids
1968 .contains(&"nesting-unwrap")
1969 );
1970
1971 let chrome_119 =
1972 plan_target_transforms_from_query("chrome 119", conservative_target_options());
1973 assert!(!chrome_119.support.supports_css_nesting);
1974 assert!(
1975 chrome_119
1976 .transform_plan
1977 .required_pass_ids
1978 .contains(&"nesting-unwrap")
1979 );
1980
1981 let chrome_120 =
1982 plan_target_transforms_from_query("chrome 120", conservative_target_options());
1983 assert!(chrome_120.support.supports_css_nesting);
1984 assert!(
1985 !chrome_120
1986 .transform_plan
1987 .required_pass_ids
1988 .contains(&"nesting-unwrap")
1989 );
1990
1991 let chrome_98 =
1992 plan_target_transforms_from_query("chrome 98", conservative_target_options());
1993 assert!(!chrome_98.support.supports_cascade_layers);
1994
1995 let chrome_99 =
1996 plan_target_transforms_from_query("chrome 99", conservative_target_options());
1997 assert!(chrome_99.support.supports_cascade_layers);
1998
1999 let chrome_117 =
2000 plan_target_transforms_from_query("chrome 117", conservative_target_options());
2001 assert!(!chrome_117.support.supports_css_scope);
2002
2003 let chrome_118 =
2004 plan_target_transforms_from_query("chrome 118", conservative_target_options());
2005 assert!(chrome_118.support.supports_css_scope);
2006
2007 let chrome_122 =
2008 plan_target_transforms_from_query("chrome 122", conservative_target_options());
2009 assert_eq!(chrome_122.profile_id, "browserslist-resolved");
2010 assert!(!chrome_122.support.supports_light_dark);
2011 assert!(chrome_122.support.supports_color_mix);
2012 assert!(
2013 chrome_122
2014 .transform_plan
2015 .required_pass_ids
2016 .contains(&"light-dark-lowering")
2017 );
2018 assert!(
2019 !chrome_122
2020 .transform_plan
2021 .required_pass_ids
2022 .contains(&"color-mix-lowering")
2023 );
2024
2025 let chrome_123 =
2026 plan_target_transforms_from_query("chrome 123", conservative_target_options());
2027 assert!(chrome_123.support.supports_light_dark);
2028 assert!(chrome_123.support.supports_color_mix);
2029 assert!(
2030 !chrome_123
2031 .transform_plan
2032 .required_pass_ids
2033 .contains(&"light-dark-lowering")
2034 );
2035 assert!(
2036 !chrome_123
2037 .transform_plan
2038 .required_pass_ids
2039 .contains(&"color-mix-lowering")
2040 );
2041
2042 let safari_16_2 =
2043 plan_target_transforms_from_query("safari 16.2", conservative_target_options());
2044 assert!(!safari_16_2.support.supports_light_dark);
2045 assert!(safari_16_2.support.supports_color_mix);
2046 assert!(!safari_16_2.support.supports_css_nesting);
2047
2048 let safari_17_5 =
2049 plan_target_transforms_from_query("safari 17.5", conservative_target_options());
2050 assert!(safari_17_5.support.supports_light_dark);
2051 assert!(safari_17_5.support.supports_color_mix);
2052 assert!(safari_17_5.support.supports_css_nesting);
2053 }
2054
2055 #[test]
2056 fn query_plan_evidence_explains_threshold_support_for_resolved_targets() {
2057 let chrome_122 =
2058 plan_target_transforms_from_query("chrome 122", conservative_target_options());
2059
2060 let light_dark = chrome_122
2061 .target_data_evidence
2062 .iter()
2063 .find(|evidence| evidence.support_table == "light_dark");
2064 assert!(
2065 light_dark.is_some(),
2066 "light-dark target data evidence should be present"
2067 );
2068 let Some(light_dark) = light_dark else {
2069 return;
2070 };
2071 assert_eq!(light_dark.pass_id, "light-dark-lowering");
2072 assert_eq!(
2073 light_dark.caniuse_keys,
2074 vec!["css-light-dark-function".to_string()]
2075 );
2076 assert_eq!(
2077 light_dark.source_quorum,
2078 vec![
2079 "caniuse".to_string(),
2080 "web-features".to_string(),
2081 "mdn-bcd".to_string()
2082 ]
2083 );
2084 assert_eq!(light_dark.last_verified, vec!["2026-08-27".to_string()]);
2085 assert!(!light_dark.all_resolved_targets_supported);
2086
2087 let chrome_light_dark = light_dark
2088 .resolved_targets
2089 .iter()
2090 .find(|target| target.browser == "chrome");
2091 assert!(
2092 chrome_light_dark.is_some(),
2093 "chrome target evidence should be present"
2094 );
2095 let Some(chrome_light_dark) = chrome_light_dark else {
2096 return;
2097 };
2098 assert_eq!(chrome_light_dark.version, "122");
2099 assert!(!chrome_light_dark.supported);
2100 let light_dark_threshold = chrome_light_dark.matched_threshold.as_ref();
2101 assert!(
2102 light_dark_threshold.is_some(),
2103 "chrome light-dark threshold should be present"
2104 );
2105 let Some(light_dark_threshold) = light_dark_threshold else {
2106 return;
2107 };
2108 assert_eq!(light_dark_threshold.min_version, "123.0");
2109 assert_eq!(light_dark_threshold.caniuse_key, "css-light-dark-function");
2110
2111 let color_mix = chrome_122
2112 .target_data_evidence
2113 .iter()
2114 .find(|evidence| evidence.support_table == "color_mix");
2115 assert!(
2116 color_mix.is_some(),
2117 "color-mix target data evidence should be present"
2118 );
2119 let Some(color_mix) = color_mix else {
2120 return;
2121 };
2122 assert!(color_mix.all_resolved_targets_supported);
2123 let chrome_color_mix = color_mix
2124 .resolved_targets
2125 .iter()
2126 .find(|target| target.browser == "chrome");
2127 assert!(
2128 chrome_color_mix.is_some(),
2129 "chrome target evidence should be present"
2130 );
2131 let Some(chrome_color_mix) = chrome_color_mix else {
2132 return;
2133 };
2134 assert!(chrome_color_mix.supported);
2135 let color_mix_threshold = chrome_color_mix.matched_threshold.as_ref();
2136 assert!(
2137 color_mix_threshold.is_some(),
2138 "chrome color-mix threshold should be present"
2139 );
2140 let Some(color_mix_threshold) = color_mix_threshold else {
2141 return;
2142 };
2143 assert_eq!(color_mix_threshold.min_version, "111.0");
2144 assert_eq!(color_mix_threshold.caniuse_key, "css-color-mix");
2145
2146 let color_function = chrome_122
2147 .target_data_evidence
2148 .iter()
2149 .find(|evidence| evidence.support_table == "color_function");
2150 assert!(
2151 color_function.is_some(),
2152 "color() target data evidence should be present"
2153 );
2154 let Some(color_function) = color_function else {
2155 return;
2156 };
2157 assert_eq!(color_function.pass_id, "color-function-lowering");
2158 assert_eq!(
2159 color_function.caniuse_keys,
2160 vec!["css-color-function".to_string()]
2161 );
2162 assert!(color_function.all_resolved_targets_supported);
2163 let chrome_color_function = color_function
2164 .resolved_targets
2165 .iter()
2166 .find(|target| target.browser == "chrome");
2167 assert!(
2168 chrome_color_function.is_some(),
2169 "chrome color() target evidence should be present"
2170 );
2171 let Some(chrome_color_function) = chrome_color_function else {
2172 return;
2173 };
2174 assert!(chrome_color_function.supported);
2175 let color_function_threshold = chrome_color_function.matched_threshold.as_ref();
2176 assert!(
2177 color_function_threshold.is_some(),
2178 "chrome color() threshold should be present"
2179 );
2180 let Some(color_function_threshold) = color_function_threshold else {
2181 return;
2182 };
2183 assert_eq!(color_function_threshold.min_version, "111.0");
2184 assert_eq!(color_function_threshold.caniuse_key, "css-color-function");
2185
2186 let css_nesting = chrome_122
2187 .target_data_evidence
2188 .iter()
2189 .find(|evidence| evidence.support_table == "css_nesting");
2190 assert!(
2191 css_nesting.is_some(),
2192 "css nesting target data evidence should be present"
2193 );
2194 let Some(css_nesting) = css_nesting else {
2195 return;
2196 };
2197 assert_eq!(css_nesting.pass_id, "nesting-unwrap");
2198 assert_eq!(css_nesting.caniuse_keys, vec!["css-nesting".to_string()]);
2199 assert!(css_nesting.all_resolved_targets_supported);
2200 let chrome_css_nesting = css_nesting
2201 .resolved_targets
2202 .iter()
2203 .find(|target| target.browser == "chrome");
2204 assert!(
2205 chrome_css_nesting.is_some(),
2206 "chrome nesting target evidence should be present"
2207 );
2208 let Some(chrome_css_nesting) = chrome_css_nesting else {
2209 return;
2210 };
2211 assert!(chrome_css_nesting.supported);
2212 let css_nesting_threshold = chrome_css_nesting.matched_threshold.as_ref();
2213 assert!(
2214 css_nesting_threshold.is_some(),
2215 "chrome nesting threshold should be present"
2216 );
2217 let Some(css_nesting_threshold) = css_nesting_threshold else {
2218 return;
2219 };
2220 assert_eq!(css_nesting_threshold.min_version, "120.0");
2221 assert_eq!(css_nesting_threshold.caniuse_key, "css-nesting");
2222
2223 let cascade_layers = chrome_122
2224 .target_data_evidence
2225 .iter()
2226 .find(|evidence| evidence.support_table == "cascade_layers");
2227 assert!(
2228 cascade_layers.is_some(),
2229 "cascade layers target data evidence should be present"
2230 );
2231 let Some(cascade_layers) = cascade_layers else {
2232 return;
2233 };
2234 assert_eq!(cascade_layers.pass_id, "layer-flatten");
2235 assert_eq!(
2236 cascade_layers.caniuse_keys,
2237 vec!["css-cascade-layers".to_string()]
2238 );
2239 assert!(cascade_layers.all_resolved_targets_supported);
2240 let chrome_cascade_layers = cascade_layers
2241 .resolved_targets
2242 .iter()
2243 .find(|target| target.browser == "chrome");
2244 assert!(
2245 chrome_cascade_layers.is_some(),
2246 "chrome cascade layers target evidence should be present"
2247 );
2248 let Some(chrome_cascade_layers) = chrome_cascade_layers else {
2249 return;
2250 };
2251 assert!(chrome_cascade_layers.supported);
2252 let cascade_layers_threshold = chrome_cascade_layers.matched_threshold.as_ref();
2253 assert!(
2254 cascade_layers_threshold.is_some(),
2255 "chrome cascade layers threshold should be present"
2256 );
2257 let Some(cascade_layers_threshold) = cascade_layers_threshold else {
2258 return;
2259 };
2260 assert_eq!(cascade_layers_threshold.min_version, "99.0");
2261 assert_eq!(cascade_layers_threshold.caniuse_key, "css-cascade-layers");
2262
2263 let css_scope = chrome_122
2264 .target_data_evidence
2265 .iter()
2266 .find(|evidence| evidence.support_table == "css_scope");
2267 assert!(
2268 css_scope.is_some(),
2269 "css scope target data evidence should be present"
2270 );
2271 let Some(css_scope) = css_scope else {
2272 return;
2273 };
2274 assert_eq!(css_scope.pass_id, "scope-flatten");
2275 assert_eq!(
2276 css_scope.caniuse_keys,
2277 vec!["css-cascade-scope".to_string()]
2278 );
2279 assert!(css_scope.all_resolved_targets_supported);
2280 let chrome_css_scope = css_scope
2281 .resolved_targets
2282 .iter()
2283 .find(|target| target.browser == "chrome");
2284 assert!(
2285 chrome_css_scope.is_some(),
2286 "chrome scope target evidence should be present"
2287 );
2288 let Some(chrome_css_scope) = chrome_css_scope else {
2289 return;
2290 };
2291 assert!(chrome_css_scope.supported);
2292 let css_scope_threshold = chrome_css_scope.matched_threshold.as_ref();
2293 assert!(
2294 css_scope_threshold.is_some(),
2295 "chrome scope threshold should be present"
2296 );
2297 let Some(css_scope_threshold) = css_scope_threshold else {
2298 return;
2299 };
2300 assert_eq!(css_scope_threshold.min_version, "118.0");
2301 assert_eq!(css_scope_threshold.caniuse_key, "css-cascade-scope");
2302 }
2303
2304 #[test]
2305 fn resolved_multi_target_queries_fold_to_the_least_supported_feature_set() {
2306 let mixed_targets = plan_target_transforms_from_query(
2307 "chrome 123, safari 16.2",
2308 conservative_target_options(),
2309 );
2310
2311 assert_eq!(mixed_targets.profile_id, "browserslist-resolved");
2312 assert_eq!(
2313 mixed_targets.resolved_targets,
2314 vec!["chrome 123", "safari 16.2"]
2315 );
2316 assert!(
2317 !mixed_targets.support.supports_light_dark,
2318 "safari 16.2 keeps the multi-target set below the light-dark threshold"
2319 );
2320 assert!(mixed_targets.support.supports_color_mix);
2321 assert!(!mixed_targets.support.supports_css_nesting);
2322 assert!(
2323 mixed_targets
2324 .transform_plan
2325 .required_pass_ids
2326 .contains(&"light-dark-lowering")
2327 );
2328 assert!(
2329 !mixed_targets
2330 .transform_plan
2331 .required_pass_ids
2332 .contains(&"color-mix-lowering")
2333 );
2334 assert!(
2335 mixed_targets
2336 .transform_plan
2337 .required_pass_ids
2338 .contains(&"nesting-unwrap")
2339 );
2340 }
2341
2342 #[test]
2343 fn invalid_target_query_uses_conservative_profile_without_claiming_recognition() {
2344 let plan = plan_target_transforms_from_query("yuru 1.0", conservative_target_options());
2345
2346 assert!(!plan.recognized_profile);
2347 assert_eq!(plan.profile_id, "unknown-conservative");
2348 assert!(plan.resolution_error.is_some());
2349 assert!(plan.support.vendor_prefix_required);
2350 assert!(
2351 plan.transform_plan
2352 .required_pass_ids
2353 .contains(&"vendor-prefixing")
2354 );
2355 assert!(
2356 plan.transform_plan
2357 .blocked_pass_ids
2358 .contains(&"scope-flatten")
2359 );
2360 }
2361
2362 #[test]
2363 fn browser_data_contract_rejects_stale_threshold_entries() {
2364 let mut browser_data = super::browser_threshold_data().clone();
2365 browser_data.thresholds[0].last_verified = "2026-05-01".to_string();
2366
2367 assert_eq!(super::browser_threshold_stale_entry_count(&browser_data), 1);
2368 assert!(!super::browser_threshold_data_is_valid(&browser_data));
2369
2370 let contract =
2371 super::target_data_contract_summary(&browser_data, super::pass_feature_binding_data());
2372 assert_eq!(contract.stale_entry_count, 1);
2373 assert!(!contract.valid);
2374 }
2375
2376 #[test]
2377 fn browser_data_contract_rejects_unresolvable_threshold_queries() {
2378 let mut browser_data = super::browser_threshold_data().clone();
2379 browser_data.thresholds[0].browser = "android".to_string();
2380 browser_data.thresholds[0].min_major = 148;
2381 browser_data.thresholds[0].min_minor = 0;
2382
2383 assert_eq!(super::unresolvable_threshold_query_count(&browser_data), 1);
2384 assert!(!super::browser_threshold_data_is_valid(&browser_data));
2385
2386 let contract =
2387 super::target_data_contract_summary(&browser_data, super::pass_feature_binding_data());
2388 assert_eq!(contract.unresolvable_threshold_query_count, 1);
2389 assert!(!contract.valid);
2390 }
2391
2392 #[test]
2393 fn browser_data_contract_requires_three_source_quorum_minimum() {
2394 let mut browser_data = super::browser_threshold_data().clone();
2395 browser_data.quorum_min_sources = 2;
2396
2397 assert!(!super::browser_threshold_data_is_valid(&browser_data));
2398
2399 let contract =
2400 super::target_data_contract_summary(&browser_data, super::pass_feature_binding_data());
2401 assert_eq!(contract.quorum_min_sources, 2);
2402 assert!(!contract.quorum_valid);
2403 assert!(!contract.valid);
2404 }
2405
2406 #[test]
2407 fn browser_data_contract_rejects_incomplete_threshold_source_quorum() {
2408 let mut browser_data = super::browser_threshold_data().clone();
2409 browser_data.thresholds[0].source_quorum =
2410 vec!["caniuse".to_string(), "web-features".to_string()];
2411
2412 assert!(!super::browser_threshold_data_is_valid(&browser_data));
2413
2414 let contract =
2415 super::target_data_contract_summary(&browser_data, super::pass_feature_binding_data());
2416 assert!(!contract.quorum_valid);
2417 assert!(!contract.valid);
2418 }
2419
2420 #[test]
2421 fn browser_data_contract_rejects_threshold_source_policy_drift() {
2422 let mut browser_data = super::browser_threshold_data().clone();
2423 browser_data.threshold_source_policy = "manualThresholdSelection".to_string();
2424
2425 assert!(!super::browser_threshold_data_is_valid(&browser_data));
2426
2427 let contract =
2428 super::target_data_contract_summary(&browser_data, super::pass_feature_binding_data());
2429 assert_eq!(contract.threshold_source_policy, "manualThresholdSelection");
2430 assert!(!contract.quorum_valid);
2431 assert!(!contract.valid);
2432 }
2433
2434 #[test]
2435 fn browser_data_contract_rejects_resolver_provenance_drift() {
2436 let browser_data = super::browser_threshold_data().clone();
2437 let mut bindings = super::pass_feature_binding_data().clone();
2438 bindings.caniuse_resolver_cargo_version = "0.0.0".to_string();
2439
2440 assert!(!super::resolver_provenance_is_valid(
2441 &browser_data,
2442 &bindings
2443 ));
2444
2445 let contract = super::target_data_contract_summary(&browser_data, &bindings);
2446 assert!(!contract.resolver_provenance_valid);
2447 assert!(!contract.valid);
2448 }
2449
2450 #[test]
2451 fn browser_data_contract_rejects_unmapped_threshold_tables() {
2452 let mut browser_data = super::browser_threshold_data().clone();
2453 browser_data
2454 .thresholds
2455 .push(super::BrowserFeatureThresholdV0 {
2456 table: "unmapped_feature".to_string(),
2457 browser: "chrome".to_string(),
2458 min_major: 123,
2459 min_minor: 0,
2460 caniuse_key: "css-unmapped-feature".to_string(),
2461 source_quorum: vec![
2462 "caniuse".to_string(),
2463 "web-features".to_string(),
2464 "mdn-bcd".to_string(),
2465 ],
2466 last_verified: browser_data.refreshed_at.clone(),
2467 });
2468 let bindings = super::pass_feature_binding_data().clone();
2469
2470 assert_eq!(
2471 super::unmapped_threshold_table_count(&browser_data, &bindings),
2472 1
2473 );
2474 assert!(super::browser_threshold_data_is_valid(&browser_data));
2475 assert!(!super::pass_feature_binding_data_is_valid(
2476 &browser_data,
2477 &bindings
2478 ));
2479
2480 let contract = super::target_data_contract_summary(&browser_data, &bindings);
2481 assert_eq!(contract.unmapped_threshold_table_count, 1);
2482 assert!(!contract.valid);
2483 }
2484}