1use std::collections::{BTreeMap, BTreeSet};
8
9pub use engine_input_producers::{
10 ClassExpressionInputV2, EngineInputV2, ExpressionDomainCallSiteFlowAnalysisV0,
11 ExpressionDomainControlFlowAnalysisV0, ExpressionDomainFlowAnalysisV0,
12 ExpressionDomainProvenanceExplanationsV0, ExpressionDomainReducedProductIterationV0,
13 ExpressionSemanticsCanonicalProducerSignalV0, ExpressionSemanticsQueryFragmentsV0, PositionV2,
14 RangeV2, SelectorUsageCanonicalProducerSignalV0, SelectorUsageQueryFragmentsV0,
15 SourceAnalysisInputV2, SourceDocumentV2, SourceResolutionCanonicalProducerSignalV0,
16 SourceResolutionQueryFragmentsV0, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
17 StyleSelectorV2, TypeFactEntryV2,
18};
19use engine_input_producers::{
20 collect_expression_domain_flow_graphs,
21 summarize_expression_domain_call_site_flow_analysis_input,
22 summarize_expression_domain_control_flow_analysis_input,
23 summarize_expression_domain_flow_analysis_input,
24 summarize_expression_domain_provenance_explanations_input,
25 summarize_expression_domain_reduced_product_iteration_input,
26 summarize_expression_semantics_canonical_producer_signal_input,
27 summarize_expression_semantics_query_fragments_input,
28 summarize_selector_usage_canonical_producer_signal_input,
29 summarize_selector_usage_query_fragments_input,
30};
31pub use omena_abstract_value::{
32 AbstractClassValueV0, AbstractPropertyValueCandidateV0, AbstractPropertyValueNarrowingV0,
33 AbstractPropertyValueV0, AbstractValueDomainSummaryV0, CascadeContextV0,
34 CascadeValueFamilyMemberV0, ClassValueFlowAnalysisV0, ClassValueFlowIncrementalAnalysisV0,
35 CssValueValidationClassV0, ExternalStringTypeFactsV0, FactPrecision, Lin01ProvenanceSemiringV0,
36 LinearProvenancePathV0, LinearProvenanceV0, NaturalCountProvenanceSemiringV0,
37 OmenaAbstractValueCoverageDirectionV0, OmenaAbstractValuePrecisionBasisV0,
38 OmenaAbstractValuePrecisionWitnessV0, PolynomialProvenanceProjectionV0,
39 PolynomialProvenanceTermV0, PolynomialProvenanceV0, PolynomialProvenanceVariableV0,
40 ProvenanceSemiringLawReportV0, ReducedClassValueProductIterationV0, ReducedClassValueProductV0,
41 SelectorProjectionCertaintyV0, abstract_class_value_from_facts, abstract_class_value_kind,
42 derive_cascade_restriction_maps_v0, fact_precision_from_class_value,
43 fact_precision_from_class_value_with_witness, iterate_reduced_class_value_product_constraints,
44 join_abstract_class_values, narrow_abstract_property_value_for_cascade_branch,
45 narrow_abstract_property_value_for_pseudo_state, prefix_suffix_class_value,
46 summarize_cascade_value_family_v0, summarize_polynomial_provenance_from_linear_v0,
47 top_class_value, validate_registered_property_value_v0,
48 verify_provenance_semiring_laws_on_fixtures,
49};
50use omena_abstract_value::{
51 analyze_class_value_flow_incremental_with_database, project_abstract_value_selectors,
52 summarize_omena_abstract_value_domain, summarize_reduced_class_value_product,
53};
54pub use omena_incremental::{
55 IncrementalEditDistancePriorityInputV0, IncrementalGraphInputV0,
56 IncrementalInvalidationPriorityPlanV0, IncrementalNodeInputV0, IncrementalRevisionV0,
57 OmenaIncrementalDatabaseV0, OmenaSalsaDatabaseV0, OmenaWorkspaceSnapshotIdV0,
58 snapshot_from_graph_input,
59};
60pub use omena_refinement::{
61 CascadeDimensionalRefinementBridgeV0, RefinementPropertyPredicateV0,
62 summarize_cascade_dimensional_refinement_bridge_v0,
63};
64pub use omena_resolver::OmenaResolverSourceResolutionRuntimeIndexV0;
65use omena_resolver::{
66 summarize_omena_resolver_canonical_producer_signal, summarize_omena_resolver_query_fragments,
67 summarize_omena_resolver_source_resolution_runtime,
68};
69pub use omena_value_lattice::{
70 canonicalize_css_value, split_top_level_value_arguments,
71 split_top_level_whitespace_value_components,
72};
73use serde::{Deserialize, Serialize};
74
75pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION: &str = "0";
76pub const OMENA_QUERY_CURRENT_SCHEMA_VERSION_LABEL: &str = "V0";
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub struct OmenaQueryAnalysisPrecisionV0 {
81 pub product: String,
82 pub value_domain: String,
83 pub flow_sensitivity: String,
84 pub context_sensitivity: String,
85 pub revision_axis: String,
86}
87
88const OMENA_QUERY_ANALYSIS_FACT_PRECISION_BY_VALUE_DOMAIN: &[(&str, FactPrecision)] = &[
89 ("cascadeAtPosition", FactPrecision::Exact),
90 ("styleModuleResolution", FactPrecision::Exact),
91 ("classValueResolution", FactPrecision::Conservative),
92 ("classValueUniverse", FactPrecision::Conservative),
93 ("classValueFlow", FactPrecision::Heuristic),
94 ("unknown", FactPrecision::Unknown),
95];
96
97pub fn fact_precision_from_analysis_precision(
98 precision: &OmenaQueryAnalysisPrecisionV0,
99) -> FactPrecision {
100 OMENA_QUERY_ANALYSIS_FACT_PRECISION_BY_VALUE_DOMAIN
101 .iter()
102 .find_map(|(value_domain, mapped)| {
103 (*value_domain == precision.value_domain).then_some(*mapped)
104 })
105 .unwrap_or(FactPrecision::Unknown)
106}
107
108#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct OmenaQueryAnalysisResultV0<TValue> {
111 pub schema_version: String,
112 pub product: String,
113 pub value: TValue,
114 pub precision: OmenaQueryAnalysisPrecisionV0,
115 pub provenance: Vec<String>,
116 pub revision: u64,
117}
118
119impl<TValue> OmenaQueryAnalysisResultV0<TValue> {
120 pub fn new(
121 value: TValue,
122 precision: OmenaQueryAnalysisPrecisionV0,
123 provenance: Vec<String>,
124 revision: u64,
125 ) -> Self {
126 Self {
127 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION.to_string(),
128 product: "omena-query.analysis-result".to_string(),
129 value,
130 precision,
131 provenance,
132 revision,
133 }
134 }
135}
136
137#[derive(Debug, Serialize)]
138#[serde(rename_all = "camelCase")]
139pub struct OmenaQueryFragmentBundleV0 {
140 pub schema_version: &'static str,
141 pub product: &'static str,
142 pub input_version: String,
143 pub expression_semantics: ExpressionSemanticsQueryFragmentsV0,
144 pub source_resolution: SourceResolutionQueryFragmentsV0,
145 pub selector_usage: SelectorUsageQueryFragmentsV0,
146}
147
148#[derive(Debug, Serialize)]
149#[serde(rename_all = "camelCase")]
150pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
151 pub schema_version: &'static str,
152 pub product: &'static str,
153 pub input_version: String,
154 pub revision: u64,
155 pub graph_count: usize,
156 pub dirty_graph_count: usize,
157 pub reused_graph_count: usize,
158 pub analyses: Vec<OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0>,
159}
160
161#[derive(Debug, Serialize)]
162#[serde(rename_all = "camelCase")]
163pub struct OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
164 pub graph_id: String,
165 pub file_path: String,
166 pub analysis: ClassValueFlowIncrementalAnalysisV0,
167}
168
169#[derive(Debug, Serialize)]
170#[serde(rename_all = "camelCase")]
171pub struct OmenaQueryExpressionDomainSelectorProjectionV0 {
172 pub schema_version: &'static str,
173 pub product: &'static str,
174 pub input_version: String,
175 pub projection_count: usize,
176 pub projections: Vec<OmenaQueryExpressionDomainSelectorProjectionEntryV0>,
177}
178
179#[derive(Debug, Serialize)]
180#[serde(rename_all = "camelCase")]
181pub struct OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
182 pub graph_id: String,
183 pub file_path: String,
184 pub node_id: String,
185 pub target_style_paths: Vec<String>,
186 pub value_kind: &'static str,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub reduced_product: Option<ReducedClassValueProductV0>,
189 pub selector_names: Vec<String>,
190 pub certainty: SelectorProjectionCertaintyV0,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
194#[serde(rename_all = "camelCase")]
195pub struct OmenaQueryExpressionDomainSelectorPrecisionV0 {
196 pub graph_id: String,
197 pub node_id: String,
198 pub precision: FactPrecision,
199}
200
201#[derive(Default)]
202pub struct OmenaQueryExpressionDomainFlowRuntimeV0 {
203 revision: u64,
204 databases_by_graph_id: BTreeMap<String, OmenaIncrementalDatabaseV0>,
205 previous_analyses_by_graph_id: BTreeMap<String, ClassValueFlowAnalysisV0>,
206}
207
208impl OmenaQueryExpressionDomainFlowRuntimeV0 {
209 pub fn revision(&self) -> u64 {
210 self.revision
211 }
212
213 pub fn graph_count(&self) -> usize {
214 self.databases_by_graph_id.len()
215 }
216
217 pub fn analyze_input(
218 &mut self,
219 input: &EngineInputV2,
220 ) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
221 self.revision += 1;
222 let revision = self.revision;
223 let flow_graphs = collect_expression_domain_flow_graphs(input);
224 let live_graph_ids = flow_graphs
225 .iter()
226 .map(|entry| entry.graph_id.clone())
227 .collect::<BTreeSet<_>>();
228
229 self.databases_by_graph_id
230 .retain(|graph_id, _| live_graph_ids.contains(graph_id));
231 self.previous_analyses_by_graph_id
232 .retain(|graph_id, _| live_graph_ids.contains(graph_id));
233
234 let analyses = flow_graphs
235 .into_iter()
236 .map(|entry| {
237 let database = self
238 .databases_by_graph_id
239 .entry(entry.graph_id.clone())
240 .or_default();
241 let previous_analysis = self.previous_analyses_by_graph_id.get(&entry.graph_id);
242 let analysis = analyze_class_value_flow_incremental_with_database(
243 &entry.graph,
244 database,
245 previous_analysis,
246 revision,
247 );
248 self.previous_analyses_by_graph_id
249 .insert(entry.graph_id.clone(), analysis.analysis.clone());
250
251 OmenaQueryExpressionDomainIncrementalFlowAnalysisEntryV0 {
252 graph_id: entry.graph_id,
253 file_path: entry.file_path,
254 analysis,
255 }
256 })
257 .collect::<Vec<_>>();
258
259 let dirty_graph_count = analyses
260 .iter()
261 .filter(|entry| entry.analysis.incremental_plan.dirty_node_count > 0)
262 .count();
263 let reused_graph_count = analyses
264 .iter()
265 .filter(|entry| entry.analysis.reused_previous_analysis)
266 .count();
267
268 OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
269 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
270 product: "omena-query.expression-domain-incremental-flow-analysis",
271 input_version: input.version.clone(),
272 revision,
273 graph_count: analyses.len(),
274 dirty_graph_count,
275 reused_graph_count,
276 analyses,
277 }
278 }
279}
280
281pub fn summarize_omena_query_core_abstract_value_domain() -> AbstractValueDomainSummaryV0 {
282 summarize_omena_abstract_value_domain()
283}
284
285pub fn summarize_omena_query_fragment_bundle(input: &EngineInputV2) -> OmenaQueryFragmentBundleV0 {
286 OmenaQueryFragmentBundleV0 {
287 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
288 product: "omena-query.fragment-bundle",
289 input_version: input.version.clone(),
290 expression_semantics: summarize_omena_query_expression_semantics_query_fragments(input),
291 source_resolution: summarize_omena_query_source_resolution_query_fragments(input),
292 selector_usage: summarize_omena_query_selector_usage_query_fragments(input),
293 }
294}
295
296pub fn summarize_omena_query_expression_semantics_query_fragments(
297 input: &EngineInputV2,
298) -> ExpressionSemanticsQueryFragmentsV0 {
299 summarize_expression_semantics_query_fragments_input(input)
300}
301
302pub fn summarize_omena_query_expression_domain_flow_analysis(
303 input: &EngineInputV2,
304) -> ExpressionDomainFlowAnalysisV0 {
305 summarize_expression_domain_flow_analysis_input(input)
306}
307
308pub fn summarize_omena_query_expression_domain_control_flow_analysis(
309 input: &EngineInputV2,
310) -> ExpressionDomainControlFlowAnalysisV0 {
311 summarize_expression_domain_control_flow_analysis_input(input)
312}
313
314pub fn summarize_omena_query_expression_domain_call_site_flow_analysis(
315 input: &EngineInputV2,
316) -> ExpressionDomainCallSiteFlowAnalysisV0 {
317 summarize_expression_domain_call_site_flow_analysis_input(input)
318}
319
320pub fn summarize_omena_query_expression_domain_provenance_explanations(
321 input: &EngineInputV2,
322) -> ExpressionDomainProvenanceExplanationsV0 {
323 summarize_expression_domain_provenance_explanations_input(input)
324}
325
326pub fn summarize_omena_query_expression_domain_reduced_product_iteration(
327 input: &EngineInputV2,
328) -> ExpressionDomainReducedProductIterationV0 {
329 summarize_expression_domain_reduced_product_iteration_input(input)
330}
331
332pub fn summarize_omena_query_expression_domain_incremental_flow_analysis(
333 input: &EngineInputV2,
334 runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
335) -> OmenaQueryExpressionDomainIncrementalFlowAnalysisV0 {
336 runtime.analyze_input(input)
337}
338
339pub fn summarize_omena_query_expression_domain_incremental_flow_analysis_result(
340 input: &EngineInputV2,
341 runtime: &mut OmenaQueryExpressionDomainFlowRuntimeV0,
342) -> OmenaQueryAnalysisResultV0<OmenaQueryExpressionDomainIncrementalFlowAnalysisV0> {
343 let value = runtime.analyze_input(input);
344 let revision = value.revision;
345 OmenaQueryAnalysisResultV0::new(
346 value,
347 OmenaQueryAnalysisPrecisionV0 {
348 product: "omena-query.analysis-precision".to_string(),
349 value_domain: "classValueFlow".to_string(),
350 flow_sensitivity: "incrementalDataflow".to_string(),
351 context_sensitivity: "perExpressionGraph".to_string(),
352 revision_axis: "OmenaQueryExpressionDomainFlowRuntimeV0.revision".to_string(),
353 },
354 vec![
355 "omena-query-core.expression-domain-runtime".to_string(),
356 "omena-abstract-value.incremental-class-value-flow".to_string(),
357 ],
358 revision,
359 )
360}
361
362pub fn summarize_omena_query_expression_domain_selector_projection(
363 input: &EngineInputV2,
364) -> OmenaQueryExpressionDomainSelectorProjectionV0 {
365 summarize_omena_query_expression_domain_selector_projection_with_precision(input).0
366}
367
368pub fn summarize_omena_query_expression_domain_selector_projection_with_precision(
369 input: &EngineInputV2,
370) -> (
371 OmenaQueryExpressionDomainSelectorProjectionV0,
372 Vec<OmenaQueryExpressionDomainSelectorPrecisionV0>,
373) {
374 let style_selectors_by_path = style_selector_universe_by_path(input);
375 let expression_targets = expression_target_style_paths(input);
376 let flow_analysis = summarize_omena_query_expression_domain_flow_analysis(input);
377 let mut projections = Vec::new();
378 let mut precisions = Vec::new();
379
380 for graph in flow_analysis.analyses {
381 for node in graph.analysis.nodes {
382 let target_style_paths = target_style_paths_for_flow_node(
383 node.id.as_str(),
384 node.predecessor_ids.as_slice(),
385 &expression_targets,
386 );
387 let selector_universe = selector_universe_for_targets(
388 target_style_paths.as_slice(),
389 &style_selectors_by_path,
390 );
391 let projection = project_abstract_value_selectors(&node.value, &selector_universe);
392 precisions.push(OmenaQueryExpressionDomainSelectorPrecisionV0 {
393 graph_id: graph.graph_id.clone(),
394 node_id: node.id.clone(),
395 precision: fact_precision_from_class_value(&node.value),
396 });
397 projections.push(OmenaQueryExpressionDomainSelectorProjectionEntryV0 {
398 graph_id: graph.graph_id.clone(),
399 file_path: graph.file_path.clone(),
400 node_id: node.id,
401 target_style_paths,
402 value_kind: node.value_kind,
403 reduced_product: summarize_reduced_class_value_product(&node.value),
404 selector_names: projection.selector_names,
405 certainty: projection.certainty,
406 });
407 }
408 }
409
410 (
411 OmenaQueryExpressionDomainSelectorProjectionV0 {
412 schema_version: OMENA_QUERY_CURRENT_SCHEMA_VERSION,
413 product: "omena-query.expression-domain-selector-projection",
414 input_version: input.version.clone(),
415 projection_count: projections.len(),
416 projections,
417 },
418 precisions,
419 )
420}
421
422fn expression_target_style_paths(input: &EngineInputV2) -> BTreeMap<String, String> {
423 input
424 .sources
425 .iter()
426 .flat_map(|source| source.document.class_expressions.iter())
427 .map(|expression| (expression.id.clone(), expression.scss_module_path.clone()))
428 .collect()
429}
430
431fn style_selector_universe_by_path(input: &EngineInputV2) -> BTreeMap<String, Vec<String>> {
432 input
433 .styles
434 .iter()
435 .map(|style| {
436 let selector_names = style
437 .document
438 .selectors
439 .iter()
440 .map(|selector| {
441 selector
442 .canonical_name
443 .clone()
444 .unwrap_or_else(|| selector.name.clone())
445 })
446 .collect::<BTreeSet<_>>()
447 .into_iter()
448 .collect::<Vec<_>>();
449 (style.file_path.clone(), selector_names)
450 })
451 .collect()
452}
453
454fn target_style_paths_for_flow_node(
455 node_id: &str,
456 predecessor_ids: &[String],
457 expression_targets: &BTreeMap<String, String>,
458) -> Vec<String> {
459 let mut targets = BTreeSet::new();
460 if let Some(target) = expression_targets.get(node_id) {
461 targets.insert(target.clone());
462 }
463 for predecessor_id in predecessor_ids {
464 if let Some(target) = expression_targets.get(predecessor_id) {
465 targets.insert(target.clone());
466 }
467 }
468 targets.into_iter().collect()
469}
470
471fn selector_universe_for_targets(
472 target_style_paths: &[String],
473 style_selectors_by_path: &BTreeMap<String, Vec<String>>,
474) -> Vec<String> {
475 let mut selectors = BTreeSet::new();
476 if target_style_paths.is_empty() {
477 for selector_names in style_selectors_by_path.values() {
478 selectors.extend(selector_names.iter().cloned());
479 }
480 } else {
481 for target_style_path in target_style_paths {
482 if let Some(selector_names) = style_selectors_by_path.get(target_style_path) {
483 selectors.extend(selector_names.iter().cloned());
484 }
485 }
486 }
487 selectors.into_iter().collect()
488}
489
490pub fn summarize_omena_query_source_resolution_query_fragments(
491 input: &EngineInputV2,
492) -> SourceResolutionQueryFragmentsV0 {
493 summarize_omena_resolver_query_fragments(input)
494}
495
496pub fn summarize_omena_query_selector_usage_query_fragments(
497 input: &EngineInputV2,
498) -> SelectorUsageQueryFragmentsV0 {
499 summarize_selector_usage_query_fragments_input(input)
500}
501
502pub fn summarize_omena_query_source_resolution_canonical_producer_signal(
503 input: &EngineInputV2,
504) -> SourceResolutionCanonicalProducerSignalV0 {
505 summarize_omena_resolver_canonical_producer_signal(input)
506}
507
508pub fn summarize_omena_query_source_resolution_runtime(
509 input: &EngineInputV2,
510) -> OmenaResolverSourceResolutionRuntimeIndexV0 {
511 summarize_omena_resolver_source_resolution_runtime(input)
512}
513
514pub fn summarize_omena_query_expression_semantics_canonical_producer_signal(
515 input: &EngineInputV2,
516) -> ExpressionSemanticsCanonicalProducerSignalV0 {
517 summarize_expression_semantics_canonical_producer_signal_input(input)
518}
519
520pub fn summarize_omena_query_selector_usage_canonical_producer_signal(
521 input: &EngineInputV2,
522) -> SelectorUsageCanonicalProducerSignalV0 {
523 summarize_selector_usage_canonical_producer_signal_input(input)
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529
530 #[test]
531 fn expression_domain_runtime_reuses_graph_databases_across_revisions() {
532 let input = EngineInputV2 {
533 version: "core-runtime".to_string(),
534 sources: Vec::new(),
535 styles: Vec::new(),
536 type_facts: Vec::new(),
537 };
538 let mut runtime = OmenaQueryExpressionDomainFlowRuntimeV0::default();
539
540 let first =
541 summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
542 let second =
543 summarize_omena_query_expression_domain_incremental_flow_analysis(&input, &mut runtime);
544
545 assert_eq!(first.revision, 1);
546 assert_eq!(second.revision, 2);
547 assert_eq!(runtime.revision(), 2);
548 }
549
550 #[test]
551 fn analysis_precision_view_maps_known_producers_and_fails_closed() {
552 let precision = |value_domain: &str| OmenaQueryAnalysisPrecisionV0 {
553 product: "omena-query.analysis-precision".to_string(),
554 value_domain: value_domain.to_string(),
555 flow_sensitivity: "fixture".to_string(),
556 context_sensitivity: "fixture".to_string(),
557 revision_axis: "fixture".to_string(),
558 };
559
560 assert_eq!(
561 fact_precision_from_analysis_precision(&precision("cascadeAtPosition")),
562 FactPrecision::Exact
563 );
564 assert_eq!(
565 fact_precision_from_analysis_precision(&precision("styleModuleResolution")),
566 FactPrecision::Exact
567 );
568 assert_eq!(
569 fact_precision_from_analysis_precision(&precision("classValueResolution")),
570 FactPrecision::Conservative
571 );
572 assert_eq!(
573 fact_precision_from_analysis_precision(&precision("classValueUniverse")),
574 FactPrecision::Conservative
575 );
576 assert_eq!(
577 fact_precision_from_analysis_precision(&precision("classValueFlow")),
578 FactPrecision::Heuristic
579 );
580 assert_eq!(
581 fact_precision_from_analysis_precision(&precision("unknown")),
582 FactPrecision::Unknown
583 );
584 assert_eq!(
585 fact_precision_from_analysis_precision(&precision("unregistered")),
586 FactPrecision::Unknown
587 );
588 }
589}