1#[cfg(feature = "test-support")]
8use std::cell::Cell;
9use std::collections::{BTreeMap, BTreeSet};
10
11pub use engine_input_producers::{
12 ClassExpressionInputV2, EngineInputV2, ExpressionDomainCallSiteFlowAnalysisV0,
13 ExpressionDomainControlFlowAnalysisV0, ExpressionDomainFlowAnalysisV0,
14 ExpressionDomainProvenanceExplanationsV0, ExpressionDomainReducedProductIterationV0,
15 ExpressionSemanticsCanonicalProducerSignalV0, ExpressionSemanticsQueryFragmentsV0, PositionV2,
16 RangeV2, SelectorUsageCanonicalProducerSignalV0, SelectorUsageQueryFragmentsV0,
17 SourceAnalysisInputV2, SourceDocumentV2, SourceResolutionCanonicalProducerSignalV0,
18 SourceResolutionQueryFragmentsV0, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
19 StyleSelectorV2, TypeFactEntryV2,
20};
21use engine_input_producers::{
22 collect_expression_domain_flow_graphs,
23 summarize_expression_domain_call_site_flow_analysis_input,
24 summarize_expression_domain_control_flow_analysis_input,
25 summarize_expression_domain_flow_analysis_input,
26 summarize_expression_domain_provenance_explanations_input,
27 summarize_expression_domain_reduced_product_iteration_input,
28 summarize_expression_semantics_canonical_producer_signal_input,
29 summarize_expression_semantics_query_fragments_input,
30 summarize_selector_usage_canonical_producer_signal_input,
31 summarize_selector_usage_query_fragments_input,
32};
33use omena_abstract_value::{
34 AbstractClassValueProvenanceV0, analyze_class_value_flow_incremental_with_database,
35 project_abstract_value_selectors, summarize_omena_abstract_value_domain,
36 summarize_reduced_class_value_product,
37};
38pub use omena_abstract_value::{
39 AbstractClassValueV0, AbstractPropertyValueCandidateV0, AbstractPropertyValueNarrowingV0,
40 AbstractPropertyValueV0, AbstractValueDomainSummaryV0, CascadeContextV0,
41 CascadeValueFamilyMemberV0, ClassBoundaryEffectV0, ClassValueFlowAnalysisV0,
42 ClassValueFlowIncrementalAnalysisV0, CssValueValidationClassV0, ExternalStringTypeFactsV0,
43 FactPrecision, FirstWitnessErrorV0, GuardAtomV0, GuardedTokenInputV0, GuardedTokenLanguageV0,
44 GuardedTokenMapInputV0, GuardedTokenMapV0, GuardedTokenObserverV0, Lin01ProvenanceSemiringV0,
45 LinearProvenancePathV0, LinearProvenanceV0, NaturalCountProvenanceSemiringV0,
46 OmenaAbstractValueCoverageDirectionV0, OmenaAbstractValuePrecisionBasisV0,
47 OmenaAbstractValuePrecisionWitnessV0, PolynomialProvenanceProjectionV0,
48 PolynomialProvenanceTermV0, PolynomialProvenanceV0, PolynomialProvenanceVariableV0,
49 ProvenanceSemiringLawReportV0, ReducedClassValueProductIterationV0, ReducedClassValueProductV0,
50 SelectorProjectionCertaintyV0, TokenObserverProjectionV0, abstract_class_value_from_facts,
51 abstract_class_value_kind, derive_context_indexed_cascade_restriction_maps_v0,
52 fact_precision_from_class_value, fact_precision_from_class_value_with_witness,
53 iterate_reduced_class_value_product_constraints, join_abstract_class_values,
54 narrow_abstract_property_value_for_cascade_branch,
55 narrow_abstract_property_value_for_pseudo_state, prefix_suffix_class_value,
56 summarize_context_indexed_cascade_value_family_v0,
57 summarize_polynomial_provenance_from_linear_v0, top_class_value,
58 validate_registered_property_value_v0, verify_provenance_semiring_laws_on_fixtures,
59};
60#[allow(deprecated)]
61#[deprecated(
62 since = "0.4.0",
63 note = "use the context-indexed cascade value family adapters; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
64)]
65pub use omena_abstract_value::{
66 derive_cascade_restriction_maps_v0, summarize_cascade_value_family_v0,
67};
68pub use omena_incremental::{
69 IncrementalEditDistancePriorityInputV0, IncrementalGraphInputV0,
70 IncrementalInvalidationPriorityPlanV0, IncrementalNodeInputV0, IncrementalRevisionV0,
71 OmenaIncrementalDatabaseV0, OmenaSalsaDatabaseV0, OmenaWorkspaceSnapshotIdV0,
72 snapshot_from_graph_input,
73};
74pub use omena_refinement::{
75 CascadeDimensionalRefinementBridgeV0, RefinementPropertyPredicateV0,
76 summarize_cascade_dimensional_refinement_bridge_v0,
77};
78pub use omena_resolver::OmenaResolverSourceResolutionRuntimeIndexV0;
79use omena_resolver::{
80 summarize_omena_resolver_canonical_producer_signal, summarize_omena_resolver_query_fragments,
81 summarize_omena_resolver_source_resolution_runtime,
82};
83pub use omena_value_lattice::{
84 canonicalize_css_value, split_top_level_value_arguments,
85 split_top_level_whitespace_value_components,
86};
87use serde::{Deserialize, Serialize};
88
89pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION: &str = "0";
90pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION_LABEL: &str = "V0";
91
92#[cfg(feature = "test-support")]
93thread_local! {
94 static SELECTOR_PROJECTION_EVALUATION_COUNT: Cell<usize> = const { Cell::new(0) };
95}
96
97#[cfg(feature = "test-support")]
98pub fn reset_selector_projection_evaluation_count_for_test() {
99 SELECTOR_PROJECTION_EVALUATION_COUNT.with(|counter| counter.set(0));
100}
101
102#[cfg(feature = "test-support")]
103pub fn selector_projection_evaluation_count_for_test() -> usize {
104 SELECTOR_PROJECTION_EVALUATION_COUNT.with(Cell::get)
105}
106
107#[cfg(feature = "test-support")]
108fn record_selector_projection_evaluation_for_test() {
109 SELECTOR_PROJECTION_EVALUATION_COUNT.with(|counter| counter.set(counter.get() + 1));
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct OmenaQueryAnalysisPrecisionV0 {
115 pub product: String,
116 pub value_domain: String,
117 pub flow_sensitivity: String,
118 pub context_sensitivity: String,
119 pub revision_axis: String,
120}
121
122const OMENA_QUERY_ANALYSIS_FACT_PRECISION_BY_VALUE_DOMAIN: &[(&str, FactPrecision)] = &[
123 ("cascadeAtPosition", FactPrecision::Exact),
124 ("styleModuleResolution", FactPrecision::Exact),
125 ("classValueResolution", FactPrecision::Conservative),
126 ("classValueUniverse", FactPrecision::Conservative),
127 ("classValueFlow", FactPrecision::Heuristic),
128 ("unknown", FactPrecision::Unknown),
129];
130
131pub fn fact_precision_from_analysis_precision(
132 precision: &OmenaQueryAnalysisPrecisionV0,
133) -> FactPrecision {
134 OMENA_QUERY_ANALYSIS_FACT_PRECISION_BY_VALUE_DOMAIN
135 .iter()
136 .find_map(|(value_domain, mapped)| {
137 (*value_domain == precision.value_domain).then_some(*mapped)
138 })
139 .unwrap_or(FactPrecision::Unknown)
140}
141
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct OmenaQueryAnalysisResultV0<TValue> {
145 pub schema_version: String,
146 pub product: String,
147 pub value: TValue,
148 pub precision: OmenaQueryAnalysisPrecisionV0,
149 pub provenance: Vec<String>,
150 pub revision: u64,
151}
152
153impl<TValue> OmenaQueryAnalysisResultV0<TValue> {
154 pub fn new(
155 value: TValue,
156 precision: OmenaQueryAnalysisPrecisionV0,
157 provenance: Vec<String>,
158 revision: u64,
159 ) -> Self {
160 Self {
161 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION.to_string(),
162 product: "omena-query.analysis-result".to_string(),
163 value,
164 precision,
165 provenance,
166 revision,
167 }
168 }
169}
170
171#[derive(Debug, Serialize)]
172#[serde(rename_all = "camelCase")]
173pub struct OmenaQueryFragmentBundleV0 {
174 pub schema_version: &'static str,
175 pub product: &'static str,
176 pub input_version: String,
177 pub expression_semantics: ExpressionSemanticsQueryFragmentsV0,
178 pub source_resolution: SourceResolutionQueryFragmentsV0,
179 pub selector_usage: SelectorUsageQueryFragmentsV0,
180}
181
182#[derive(Debug, Serialize)]
183#[serde(rename_all = "camelCase")]
184pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
185 pub schema_version: &'static str,
186 pub product: &'static str,
187 pub input_version: String,
188 pub revision: u64,
189 pub graph_count: usize,
190 pub dirty_graph_count: usize,
191 pub reused_graph_count: usize,
192 pub analyses: Vec<OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0>,
193}
194
195#[derive(Debug, Serialize)]
196#[serde(rename_all = "camelCase")]
197pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
198 pub graph_id: String,
199 pub file_path: String,
200 pub analysis: ClassValueFlowIncrementalAnalysisV0,
201}
202
203#[derive(Debug, Serialize)]
204#[serde(rename_all = "camelCase")]
205pub struct OmenaQueryExpressionDomainSelectorProjectionV0 {
206 pub schema_version: &'static str,
207 pub product: &'static str,
208 pub input_version: String,
209 pub projection_count: usize,
210 pub projections: Vec<OmenaQueryExpressionDomainSelectorProjectionEntryV0>,
211}
212
213#[derive(Debug, Serialize)]
214#[serde(rename_all = "camelCase")]
215pub struct OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
216 pub graph_id: String,
217 pub file_path: String,
218 pub node_id: String,
219 pub target_style_paths: Vec<String>,
220 pub value_kind: &'static str,
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub reduced_product: Option<ReducedClassValueProductV0>,
223 pub selector_names: Vec<String>,
224 pub certainty: SelectorProjectionCertaintyV0,
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
228#[serde(rename_all = "camelCase")]
229pub struct OmenaQueryExpressionDomainSelectorPrecisionV0 {
230 pub graph_id: String,
231 pub node_id: String,
232 pub precision: FactPrecision,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq)]
236struct ExpressionDomainSelectorCertaintyFlowHedgeV0 {
237 graph_converged: bool,
238 contains_flow_iteration_limit: bool,
239}
240
241#[derive(Default)]
242pub struct OmenaQueryExpressionDomainFlowRuntimeV0 {
243 revision: u64,
244 databases_by_graph_id: BTreeMap<String, OmenaIncrementalDatabaseV0>,
245 previous_analyses_by_graph_id: BTreeMap<String, ClassValueFlowAnalysisV0>,
246}
247
248impl OmenaQueryExpressionDomainFlowRuntimeV0 {
249 pub fn revision(&self) -> u64 {
250 self.revision
251 }
252
253 pub fn graph_count(&self) -> usize {
254 self.databases_by_graph_id.len()
255 }
256
257 pub fn analyze_input(
258 &mut self,
259 input: &EngineInputV2,
260 ) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
261 self.revision += 1;
262 let revision = self.revision;
263 let flow_graphs = collect_expression_domain_flow_graphs(input);
264 let live_graph_ids = flow_graphs
265 .iter()
266 .map(|entry| entry.graph_id.clone())
267 .collect::<BTreeSet<_>>();
268
269 self.databases_by_graph_id
270 .retain(|graph_id, _| live_graph_ids.contains(graph_id));
271 self.previous_analyses_by_graph_id
272 .retain(|graph_id, _| live_graph_ids.contains(graph_id));
273
274 let analyses = flow_graphs
275 .into_iter()
276 .map(|entry| {
277 let database = self
278 .databases_by_graph_id
279 .entry(entry.graph_id.clone())
280 .or_default();
281 let previous_analysis = self.previous_analyses_by_graph_id.get(&entry.graph_id);
282 let analysis = analyze_class_value_flow_incremental_with_database(
283 &entry.graph,
284 database,
285 previous_analysis,
286 revision,
287 );
288 self.previous_analyses_by_graph_id
289 .insert(entry.graph_id.clone(), analysis.analysis.clone());
290
291 OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
292 graph_id: entry.graph_id,
293 file_path: entry.file_path,
294 analysis,
295 }
296 })
297 .collect::<Vec<_>>();
298
299 let dirty_graph_count = analyses
300 .iter()
301 .filter(|entry| entry.analysis.incremental_plan.dirty_node_count > 0)
302 .count();
303 let reused_graph_count = analyses
304 .iter()
305 .filter(|entry| entry.analysis.reused_previous_analysis)
306 .count();
307
308 OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
309 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
310 product: "omena-query.expression-domain-incremental-flow-analysis",
311 input_version: input.version.clone(),
312 revision,
313 graph_count: analyses.len(),
314 dirty_graph_count,
315 reused_graph_count,
316 analyses,
317 }
318 }
319}
320
321pub fn summarize_omena_query_core_abstract_value_domain() -> AbstractValueDomainSummaryV0 {
322 summarize_omena_abstract_value_domain()
323}
324
325pub fn summarize_omena_query_fragment_bundle(input: &EngineInputV2) -> OmenaQueryFragmentBundleV0 {
326 OmenaQueryFragmentBundleV0 {
327 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
328 product: "omena-query.fragment-bundle",
329 input_version: input.version.clone(),
330 expression_semantics: summarize_omena_query_expression_semantics_query_fragments(input),
331 source_resolution: summarize_omena_query_source_resolution_query_fragments(input),
332 selector_usage: summarize_omena_query_selector_usage_query_fragments(input),
333 }
334}
335
336pub fn summarize_omena_query_expression_semantics_query_fragments(
337 input: &EngineInputV2,
338) -> ExpressionSemanticsQueryFragmentsV0 {
339 summarize_expression_semantics_query_fragments_input(input)
340}
341
342pub fn summarize_omena_query_expression_domain_flow_analysis(
343 input: &EngineInputV2,
344) -> ExpressionDomainFlowAnalysisV0 {
345 summarize_expression_domain_flow_analysis_input(input)
346}
347
348pub fn summarize_omena_query_expression_domain_control_flow_analysis(
349 input: &EngineInputV2,
350) -> ExpressionDomainControlFlowAnalysisV0 {
351 summarize_expression_domain_control_flow_analysis_input(input)
352}
353
354pub fn summarize_omena_query_expression_domain_call_site_flow_analysis(
355 input: &EngineInputV2,
356) -> ExpressionDomainCallSiteFlowAnalysisV0 {
357 summarize_expression_domain_call_site_flow_analysis_input(input)
358}
359
360pub fn summarize_omena_query_expression_domain_provenance_explanations(
361 input: &EngineInputV2,
362) -> ExpressionDomainProvenanceExplanationsV0 {
363 summarize_expression_domain_provenance_explanations_input(input)
364}
365
366pub fn summarize_omena_query_expression_domain_reduced_product_iteration(
367 input: &EngineInputV2,
368) -> ExpressionDomainReducedProductIterationV0 {
369 summarize_expression_domain_reduced_product_iteration_input(input)
370}
371
372pub fn summarize_omena_query_expression_domain_incremental_flow_analysis(
373 input: &EngineInputV2,
374 runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
375) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
376 runtime.analyze_input(input)
377}
378
379pub fn summarize_omena_query_expression_domain_incremental_flow_analysis_result(
380 input: &EngineInputV2,
381 runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
382) -> OmenaQueryAnalysisResultV0<OmenaQueryExpressionDomainIncrementalFlowAnalysisV0> {
383 let value = runtime.analyze_input(input);
384 let revision = value.revision;
385 OmenaQueryAnalysisResultV0::new(
386 value,
387 OmenaQueryAnalysisPrecisionV0 {
388 product: "omena-query.analysis-precision".to_string(),
389 value_domain: "classValueFlow".to_string(),
390 flow_sensitivity: "incrementalDataflow".to_string(),
391 context_sensitivity: "perExpressionGraph".to_string(),
392 revision_axis: "OmenaQueryExpressionDomainFlowRuntimeV0.revision".to_string(),
393 },
394 vec![
395 "omena-query-core.expression-domain-runtime".to_string(),
396 "omena-abstract-value.incremental-class-value-flow".to_string(),
397 ],
398 revision,
399 )
400}
401
402pub fn summarize_omena_query_expression_domain_selector_projection(
403 input: &EngineInputV2,
404) -> OmenaQueryExpressionDomainSelectorProjectionV0 {
405 summarize_omena_query_expression_domain_selector_projection_with_precision(input).0
406}
407
408pub fn summarize_omena_query_expression_domain_selector_projection_with_precision(
409 input: &EngineInputV2,
410) -> (
411 OmenaQueryExpressionDomainSelectorProjectionV0,
412 Vec<OmenaQueryExpressionDomainSelectorPrecisionV0>,
413) {
414 summarize_omena_query_expression_domain_selector_projection_with_precision_and_style_path_resolver(
415 input,
416 |target, _known| Some(target.to_string()),
417 )
418}
419
420pub fn summarize_omena_query_expression_domain_selector_projection_with_precision_and_style_path_resolver<
421 F,
422>(
423 input: &EngineInputV2,
424 resolve_style_path: F,
425) -> (
426 OmenaQueryExpressionDomainSelectorProjectionV0,
427 Vec<OmenaQueryExpressionDomainSelectorPrecisionV0>,
428)
429where
430 F: Fn(&str, &[String]) -> Option<String>,
431{
432 #[cfg(feature = "test-support")]
433 record_selector_projection_evaluation_for_test();
434 let selector_certainty_flow_hedges = expression_domain_selector_certainty_flow_hedges(input);
435 summarize_omena_query_expression_domain_selector_projection_with_flow_hedges(
436 input,
437 resolve_style_path,
438 &selector_certainty_flow_hedges,
439 )
440}
441
442fn summarize_omena_query_expression_domain_selector_projection_with_flow_hedges<F>(
443 input: &EngineInputV2,
444 resolve_style_path: F,
445 selector_certainty_flow_hedges: &BTreeMap<
446 (String, String),
447 ExpressionDomainSelectorCertaintyFlowHedgeV0,
448 >,
449) -> (
450 OmenaQueryExpressionDomainSelectorProjectionV0,
451 Vec<OmenaQueryExpressionDomainSelectorPrecisionV0>,
452)
453where
454 F: Fn(&str, &[String]) -> Option<String>,
455{
456 let style_selectors_by_path = style_selector_universe_by_path(input);
457 let known_style_paths = style_selectors_by_path.keys().cloned().collect::<Vec<_>>();
458 let expression_targets = expression_target_style_paths(input);
459 let flow_analysis = summarize_omena_query_expression_domain_flow_analysis(input);
460 let mut projections = Vec::new();
461 let mut precisions = Vec::new();
462
463 for graph in flow_analysis.analyses {
464 for node in graph.analysis.nodes {
465 let target_style_paths = target_style_paths_for_flow_node(
466 node.id.as_str(),
467 node.predecessor_ids.as_slice(),
468 &expression_targets,
469 );
470 let resolved_target_style_paths = target_style_paths
471 .iter()
472 .map(|path| resolve_style_path(path, known_style_paths.as_slice()))
473 .collect::<Option<Vec<_>>>();
474 let selector_universe = selector_universe_for_targets(
475 resolved_target_style_paths.as_deref(),
476 &style_selectors_by_path,
477 );
478 let projection = project_abstract_value_selectors(&node.value, &selector_universe);
479 let certainty = hedge_selector_projection_certainty(
480 projection.certainty,
481 selector_certainty_flow_hedges.get(&(graph.file_path.clone(), node.id.clone())),
482 );
483 precisions.push(OmenaQueryExpressionDomainSelectorPrecisionV0 {
484 graph_id: graph.graph_id.clone(),
485 node_id: node.id.clone(),
486 precision: fact_precision_from_class_value(&node.value),
487 });
488 projections.push(OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
489 graph_id: graph.graph_id.clone(),
490 file_path: graph.file_path.clone(),
491 node_id: node.id,
492 target_style_paths,
493 value_kind: node.value_kind,
494 reduced_product: summarize_reduced_class_value_product(&node.value),
495 selector_names: projection.selector_names,
496 certainty,
497 });
498 }
499 }
500
501 (
502 OmenaQueryExpressionDomainSelectorProjectionV0 {
503 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
504 product: "omena-query.expression-domain-selector-projection",
505 input_version: input.version.clone(),
506 projection_count: projections.len(),
507 projections,
508 },
509 precisions,
510 )
511}
512
513fn expression_domain_selector_certainty_flow_hedges(
514 input: &EngineInputV2,
515) -> BTreeMap<(String, String), ExpressionDomainSelectorCertaintyFlowHedgeV0> {
516 bind_expression_domain_selector_certainty_flow_hedges(
517 input,
518 summarize_omena_query_expression_domain_control_flow_analysis(input),
519 )
520}
521
522fn bind_expression_domain_selector_certainty_flow_hedges(
523 input: &EngineInputV2,
524 control_flow: ExpressionDomainControlFlowAnalysisV0,
525) -> BTreeMap<(String, String), ExpressionDomainSelectorCertaintyFlowHedgeV0> {
526 let control_flow_facts = input
527 .type_facts
528 .iter()
529 .filter(|entry| entry.control_flow_graph.is_some())
530 .collect::<Vec<_>>();
531 assert_eq!(
532 control_flow_facts.len(),
533 control_flow.analyses.len(),
534 "selector-certainty flow hedge requires one control analysis per control-flow type fact"
535 );
536
537 let mut hedges = BTreeMap::new();
538 for (entry, analyzed) in control_flow_facts.into_iter().zip(control_flow.analyses) {
539 let diagnostic_graph_id = format!(
540 "{}:{}:expression-domain-control-flow",
541 entry.file_path, entry.expression_id
542 );
543 assert_eq!(
544 analyzed.file_path, entry.file_path,
545 "selector-certainty flow hedge control analysis order/file mismatch"
546 );
547 assert_eq!(
548 analyzed.graph_id, diagnostic_graph_id,
549 "selector-certainty flow hedge control analysis order/graph mismatch"
550 );
551
552 let contains_flow_iteration_limit = analyzed
553 .analysis
554 .flow_analysis
555 .nodes
556 .iter()
557 .any(|node| abstract_value_contains_flow_iteration_limit(&node.value));
558 let key = (entry.file_path.clone(), entry.expression_id.clone());
559 let previous = hedges.insert(
560 key.clone(),
561 ExpressionDomainSelectorCertaintyFlowHedgeV0 {
562 graph_converged: analyzed.analysis.flow_analysis.converged,
563 contains_flow_iteration_limit,
564 },
565 );
566 assert!(
567 previous.is_none(),
568 "selector-certainty flow hedge duplicate type-fact key: file_path={:?} expression_id={:?}",
569 key.0,
570 key.1
571 );
572 }
573
574 hedges
575}
576
577fn hedge_selector_projection_certainty(
578 base: SelectorProjectionCertaintyV0,
579 flow_hedge: Option<&ExpressionDomainSelectorCertaintyFlowHedgeV0>,
580) -> SelectorProjectionCertaintyV0 {
581 flow_hedge.map_or(base, |hedge| {
582 hedge_selector_certainty_for_flow(
583 base,
584 hedge.graph_converged,
585 hedge.contains_flow_iteration_limit,
586 )
587 })
588}
589
590fn hedge_selector_certainty_for_flow(
591 base: SelectorProjectionCertaintyV0,
592 graph_converged: bool,
593 contains_flow_iteration_limit: bool,
594) -> SelectorProjectionCertaintyV0 {
595 if graph_converged && !contains_flow_iteration_limit {
596 base
597 } else {
598 SelectorProjectionCertaintyV0::Possible
599 }
600}
601
602fn abstract_value_contains_flow_iteration_limit(value: &AbstractClassValueV0) -> bool {
603 let provenance = match value {
604 AbstractClassValueV0::Automaton { provenance, .. }
605 | AbstractClassValueV0::Prefix { provenance, .. }
606 | AbstractClassValueV0::Suffix { provenance, .. }
607 | AbstractClassValueV0::PrefixSuffix { provenance, .. }
608 | AbstractClassValueV0::CharInclusion { provenance, .. }
609 | AbstractClassValueV0::Composite { provenance, .. }
610 | AbstractClassValueV0::Top { provenance } => *provenance,
611 AbstractClassValueV0::Bottom
612 | AbstractClassValueV0::Exact { .. }
613 | AbstractClassValueV0::FiniteSet { .. } => None,
614 };
615
616 provenance == Some(AbstractClassValueProvenanceV0::FlowIterationLimit)
617}
618
619fn expression_target_style_paths(input: &EngineInputV2) -> BTreeMap<String, String> {
620 input
621 .sources
622 .iter()
623 .flat_map(|source| source.document.class_expressions.iter())
624 .map(|expression| (expression.id.clone(), expression.scss_module_path.clone()))
625 .collect()
626}
627
628fn style_selector_universe_by_path(input: &EngineInputV2) -> BTreeMap<String, Vec<String>> {
629 input
630 .styles
631 .iter()
632 .map(|style| {
633 let selector_names = style
634 .document
635 .selectors
636 .iter()
637 .map(|selector| {
638 selector
639 .canonical_name
640 .clone()
641 .unwrap_or_else(|| selector.name.clone())
642 })
643 .collect::<BTreeSet<_>>()
644 .into_iter()
645 .collect::<Vec<_>>();
646 (style.file_path.clone(), selector_names)
647 })
648 .collect()
649}
650
651fn target_style_paths_for_flow_node(
652 node_id: &str,
653 predecessor_ids: &[String],
654 expression_targets: &BTreeMap<String, String>,
655) -> Vec<String> {
656 let mut targets = BTreeSet::new();
657 if let Some(target) = expression_targets.get(node_id) {
658 targets.insert(target.clone());
659 }
660 for predecessor_id in predecessor_ids {
661 if let Some(target) = expression_targets.get(predecessor_id) {
662 targets.insert(target.clone());
663 }
664 }
665 targets.into_iter().collect()
666}
667
668fn selector_universe_for_targets(
669 target_style_paths: Option<&[String]>,
670 style_selectors_by_path: &BTreeMap<String, Vec<String>>,
671) -> Vec<String> {
672 let mut selectors = BTreeSet::new();
673 if target_style_paths.is_none_or(<[String]>::is_empty) {
674 for selector_names in style_selectors_by_path.values() {
675 selectors.extend(selector_names.iter().cloned());
676 }
677 } else if let Some(target_style_paths) = target_style_paths {
678 for target_style_path in target_style_paths {
679 if let Some(selector_names) = style_selectors_by_path.get(target_style_path) {
680 selectors.extend(selector_names.iter().cloned());
681 }
682 }
683 }
684 selectors.into_iter().collect()
685}
686
687pub fn summarize_omena_query_source_resolution_query_fragments(
688 input: &EngineInputV2,
689) -> SourceResolutionQueryFragmentsV0 {
690 summarize_omena_resolver_query_fragments(input)
691}
692
693pub fn summarize_omena_query_selector_usage_query_fragments(
694 input: &EngineInputV2,
695) -> SelectorUsageQueryFragmentsV0 {
696 summarize_selector_usage_query_fragments_input(input)
697}
698
699pub fn summarize_omena_query_source_resolution_canonical_producer_signal(
700 input: &EngineInputV2,
701) -> SourceResolutionCanonicalProducerSignalV0 {
702 summarize_omena_resolver_canonical_producer_signal(input)
703}
704
705pub fn summarize_omena_query_source_resolution_runtime(
706 input: &EngineInputV2,
707) -> OmenaResolverSourceResolutionRuntimeIndexV0 {
708 summarize_omena_resolver_source_resolution_runtime(input)
709}
710
711pub fn summarize_omena_query_expression_semantics_canonical_producer_signal(
712 input: &EngineInputV2,
713) -> ExpressionSemanticsCanonicalProducerSignalV0 {
714 summarize_expression_semantics_canonical_producer_signal_input(input)
715}
716
717pub fn summarize_omena_query_selector_usage_canonical_producer_signal(
718 input: &EngineInputV2,
719) -> SelectorUsageCanonicalProducerSignalV0 {
720 summarize_selector_usage_canonical_producer_signal_input(input)
721}
722
723#[cfg(test)]
724mod tests {
725 use super::*;
726
727 fn selector_certainty_product_input() -> EngineInputV2 {
728 let range = RangeV2 {
729 start: PositionV2 {
730 line: 0,
731 character: 0,
732 },
733 end: PositionV2 {
734 line: 0,
735 character: 11,
736 },
737 };
738 EngineInputV2 {
739 version: "selector-certainty-product".to_string(),
740 sources: vec![SourceAnalysisInputV2 {
741 document: SourceDocumentV2 {
742 class_expressions: vec![ClassExpressionInputV2 {
743 id: "expr-certainty".to_string(),
744 kind: "styleAccess".to_string(),
745 scss_module_path: "/tmp/App.module.scss".to_string(),
746 range: range.clone(),
747 class_name: Some("x".to_string()),
748 root_binding_decl_id: None,
749 access_path: Some(vec!["styles".to_string(), "x".to_string()]),
750 }],
751 },
752 }],
753 styles: vec![StyleAnalysisInputV2 {
754 file_path: "/tmp/App.module.scss".to_string(),
755 source: None,
756 document: StyleDocumentV2 {
757 selectors: vec![StyleSelectorV2 {
758 name: "x".to_string(),
759 view_kind: "canonical".to_string(),
760 canonical_name: Some("x".to_string()),
761 range,
762 nested_safety: Some("safe".to_string()),
763 composes: None,
764 bem_suffix: None,
765 }],
766 },
767 }],
768 type_facts: vec![TypeFactEntryV2 {
769 file_path: "/tmp/App.tsx".to_string(),
770 expression_id: "expr-certainty".to_string(),
771 facts: StringTypeFactsV2 {
772 kind: "exact".to_string(),
773 constraint_kind: None,
774 values: Some(vec!["x".to_string()]),
775 prefix: None,
776 suffix: None,
777 min_len: None,
778 max_len: None,
779 char_must: None,
780 char_may: None,
781 may_include_other_chars: None,
782 provenance: None,
783 },
784 control_flow_graph: Some(engine_input_producers::TypeFactControlFlowGraphV2 {
785 entry_block_id: "seed".to_string(),
786 blocks: vec![
787 engine_input_producers::TypeFactControlFlowBlockV2 {
788 id: "seed".to_string(),
789 kind: "assignment".to_string(),
790 transfer_kind: "assignFacts".to_string(),
791 successor_block_ids: vec!["loop".to_string()],
792 symbol_ordinal: None,
793 variable_name: None,
794 expression_kind: None,
795 boundary_effect: "unknownBoundary".to_string(),
796 facts: Some(StringTypeFactsV2 {
797 kind: "finiteSet".to_string(),
798 constraint_kind: None,
799 values: Some(vec!["a".to_string(), "b".to_string()]),
800 prefix: None,
801 suffix: None,
802 min_len: None,
803 max_len: None,
804 char_must: None,
805 char_may: None,
806 may_include_other_chars: None,
807 provenance: None,
808 }),
809 },
810 engine_input_producers::TypeFactControlFlowBlockV2 {
811 id: "loop".to_string(),
812 kind: "loop".to_string(),
813 transfer_kind: "concatFacts".to_string(),
814 successor_block_ids: vec!["loop".to_string()],
815 symbol_ordinal: None,
816 variable_name: None,
817 expression_kind: None,
818 boundary_effect: "unknownBoundary".to_string(),
819 facts: None,
820 },
821 ],
822 }),
823 }],
824 }
825 }
826
827 fn selector_certainty_colon_collision_input() -> EngineInputV2 {
828 let mut input = selector_certainty_product_input();
829 input.sources[0].document.class_expressions[0].id = "b:c".to_string();
830 input.sources[0].document.class_expressions[0].class_name = Some("x".to_string());
831 input.sources[0].document.class_expressions[0].access_path =
832 Some(vec!["styles".to_string(), "x".to_string()]);
833 let second_expression = ClassExpressionInputV2 {
834 id: "c".to_string(),
835 kind: "styleAccess".to_string(),
836 scss_module_path: "/tmp/App.module.scss".to_string(),
837 range: input.sources[0].document.class_expressions[0].range.clone(),
838 class_name: Some("y".to_string()),
839 root_binding_decl_id: None,
840 access_path: Some(vec!["styles".to_string(), "y".to_string()]),
841 };
842 input.sources[0]
843 .document
844 .class_expressions
845 .push(second_expression);
846
847 let second_selector = StyleSelectorV2 {
848 name: "y".to_string(),
849 view_kind: "canonical".to_string(),
850 canonical_name: Some("y".to_string()),
851 range: input.styles[0].document.selectors[0].range.clone(),
852 nested_safety: Some("safe".to_string()),
853 composes: None,
854 bem_suffix: None,
855 };
856 input.styles[0].document.selectors.push(second_selector);
857
858 let mut first_fact = input.type_facts[0].clone();
859 first_fact.file_path = "/tmp/A".to_string();
860 first_fact.expression_id = "b:c".to_string();
861 let mut second_fact = first_fact.clone();
862 second_fact.file_path = "/tmp/A:b".to_string();
863 second_fact.expression_id = "c".to_string();
864 second_fact.facts.values = Some(vec!["y".to_string()]);
865 if let Some(second_graph) = second_fact.control_flow_graph.as_mut() {
866 second_graph.blocks.truncate(1);
867 second_graph.blocks[0].successor_block_ids.clear();
868 }
869 input.type_facts = vec![first_fact, second_fact];
870 input
871 }
872
873 #[test]
874 fn expression_domain_runtime_reuses_graph_databases_across_revisions() {
875 let input = EngineInputV2 {
876 version: "core-runtime".to_string(),
877 sources: Vec::new(),
878 styles: Vec::new(),
879 type_facts: Vec::new(),
880 };
881 let mut runtime = OmenaQueryExpressionDomainFlowRuntimeV0::default();
882
883 let first =
884 summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
885 let second =
886 summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
887
888 assert_eq!(first.revision, 1);
889 assert_eq!(second.revision, 2);
890 assert_eq!(runtime.revision(), 2);
891 }
892
893 #[test]
894 fn nonconverged_flow_hedge_demotes_typed_query_projection() {
895 let input = selector_certainty_product_input();
896 let graph_id = "/tmp/App.tsx:expr-certainty:expression-domain-control-flow";
897 let control_flow = summarize_omena_query_expression_domain_control_flow_analysis(&input);
898 let projection = summarize_omena_query_expression_domain_selector_projection(&input);
899 let control_entry = &control_flow.analyses[0];
900 let entry = &projection.projections[0];
901
902 println!(
903 "certainty-hedge-census graphId={graph_id} hedged=1 base=exact certainty=possible"
904 );
905 assert_eq!(control_entry.graph_id, graph_id);
906 assert!(!control_entry.analysis.flow_analysis.converged);
907 assert!(
908 control_entry
909 .analysis
910 .flow_analysis
911 .nodes
912 .iter()
913 .all(|node| {
914 matches!(
915 &node.value,
916 AbstractClassValueV0::Top {
917 provenance: Some(AbstractClassValueProvenanceV0::FlowIterationLimit)
918 }
919 )
920 })
921 );
922 assert_eq!(entry.value_kind, "exact");
923 assert_eq!(entry.selector_names, vec!["x".to_string()]);
924 assert_eq!(entry.certainty, SelectorProjectionCertaintyV0::Possible);
925 }
926
927 #[test]
928 fn colon_colliding_graph_ids_bind_selector_certainty_by_type_fact_tuple() {
929 let input = selector_certainty_colon_collision_input();
930 let control_flow = summarize_omena_query_expression_domain_control_flow_analysis(&input);
931 let projection = summarize_omena_query_expression_domain_selector_projection(&input);
932
933 assert_eq!(control_flow.analyses.len(), 2);
934 assert_eq!(
935 control_flow.analyses[0].graph_id, control_flow.analyses[1].graph_id,
936 "fixture must retain the diagnostic graph-id collision"
937 );
938 let nonconverged = projection
939 .projections
940 .iter()
941 .filter(|entry| entry.file_path == "/tmp/A" && entry.node_id == "b:c")
942 .collect::<Vec<_>>();
943 let converged = projection
944 .projections
945 .iter()
946 .filter(|entry| entry.file_path == "/tmp/A:b" && entry.node_id == "c")
947 .collect::<Vec<_>>();
948
949 assert_eq!(nonconverged.len(), 1);
950 assert_eq!(converged.len(), 1);
951 assert_eq!(
952 nonconverged[0].certainty,
953 SelectorProjectionCertaintyV0::Possible
954 );
955 assert_eq!(converged[0].certainty, SelectorProjectionCertaintyV0::Exact);
956 }
957
958 #[test]
959 #[should_panic(expected = "selector-certainty flow hedge duplicate type-fact key")]
960 fn duplicate_selector_certainty_type_fact_key_fails_closed() {
961 let mut input = selector_certainty_product_input();
962 input.type_facts.push(input.type_facts[0].clone());
963
964 let _ = summarize_omena_query_expression_domain_selector_projection(&input);
965 }
966
967 #[test]
968 #[should_panic(
969 expected = "selector-certainty flow hedge requires one control analysis per control-flow type fact"
970 )]
971 fn missing_selector_certainty_control_analysis_fails_closed() {
972 let input = selector_certainty_product_input();
973 let mut control_flow =
974 summarize_omena_query_expression_domain_control_flow_analysis(&input);
975 control_flow.analyses.clear();
976
977 let _ = bind_expression_domain_selector_certainty_flow_hedges(&input, control_flow);
978 }
979
980 #[test]
981 fn analysis_precision_view_maps_known_producers_and_fails_closed() {
982 let precision = |value_domain: &str| OmenaQueryAnalysisPrecisionV0 {
983 product: "omena-query.analysis-precision".to_string(),
984 value_domain: value_domain.to_string(),
985 flow_sensitivity: "fixture".to_string(),
986 context_sensitivity: "fixture".to_string(),
987 revision_axis: "fixture".to_string(),
988 };
989
990 assert_eq!(
991 fact_precision_from_analysis_precision(&precision("cascadeAtPosition")),
992 FactPrecision::Exact
993 );
994 assert_eq!(
995 fact_precision_from_analysis_precision(&precision("styleModuleResolution")),
996 FactPrecision::Exact
997 );
998 assert_eq!(
999 fact_precision_from_analysis_precision(&precision("classValueResolution")),
1000 FactPrecision::Conservative
1001 );
1002 assert_eq!(
1003 fact_precision_from_analysis_precision(&precision("classValueUniverse")),
1004 FactPrecision::Conservative
1005 );
1006 assert_eq!(
1007 fact_precision_from_analysis_precision(&precision("classValueFlow")),
1008 FactPrecision::Heuristic
1009 );
1010 assert_eq!(
1011 fact_precision_from_analysis_precision(&precision("unknown")),
1012 FactPrecision::Unknown
1013 );
1014 assert_eq!(
1015 fact_precision_from_analysis_precision(&precision("unregistered")),
1016 FactPrecision::Unknown
1017 );
1018 }
1019}