Skip to main content

engine_input_producers/
lib.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Deserializer, Serialize, de};
4
5pub mod engine_contract_v2_idl_generated;
6mod expression_domain;
7mod expression_semantics;
8mod query_plan;
9mod selector_usage;
10mod semantic;
11mod source_resolution;
12mod source_side;
13#[cfg(test)]
14mod test_support;
15mod type_facts;
16
17pub use expression_domain::collect_expression_domain_flow_graphs;
18pub use expression_domain::summarize_expression_domain_call_site_flow_analysis_input;
19pub use expression_domain::summarize_expression_domain_candidates_input;
20pub use expression_domain::summarize_expression_domain_canonical_candidate_bundle_input;
21pub use expression_domain::summarize_expression_domain_canonical_producer_signal_input;
22pub use expression_domain::summarize_expression_domain_control_flow_analysis_input;
23pub use expression_domain::summarize_expression_domain_evaluator_candidates_input;
24pub use expression_domain::summarize_expression_domain_flow_analysis_input;
25pub use expression_domain::summarize_expression_domain_fragments_input;
26pub use expression_domain::summarize_expression_domain_plan_input;
27pub use expression_domain::summarize_expression_domain_provenance_explanations_input;
28pub use expression_domain::summarize_expression_domain_reduced_product_iteration_input;
29pub use expression_semantics::summarize_expression_semantics_candidates_input;
30pub use expression_semantics::summarize_expression_semantics_canonical_candidate_bundle_input;
31pub use expression_semantics::summarize_expression_semantics_canonical_producer_signal_input;
32pub use expression_semantics::summarize_expression_semantics_evaluator_candidates_input;
33pub use expression_semantics::summarize_expression_semantics_fragments_input;
34pub use expression_semantics::summarize_expression_semantics_match_fragments_input;
35pub use expression_semantics::summarize_expression_semantics_query_fragments_input;
36pub use query_plan::summarize_query_plan_input;
37pub use selector_usage::summarize_selector_usage_candidates_input;
38pub use selector_usage::summarize_selector_usage_canonical_candidate_bundle_input;
39pub use selector_usage::summarize_selector_usage_canonical_producer_signal_input;
40pub use selector_usage::summarize_selector_usage_evaluator_candidates_input;
41pub use selector_usage::summarize_selector_usage_fragments_input;
42pub use selector_usage::summarize_selector_usage_plan_input;
43pub use selector_usage::summarize_selector_usage_query_fragments_input;
44pub use semantic::summarize_semantic_canonical_candidate_bundle_input;
45pub use semantic::summarize_semantic_canonical_producer_signal_input;
46pub use semantic::summarize_semantic_evaluator_candidates_input;
47pub use source_resolution::summarize_source_resolution_candidates_input;
48pub use source_resolution::summarize_source_resolution_canonical_candidate_bundle_input;
49pub use source_resolution::summarize_source_resolution_canonical_producer_signal_input;
50pub use source_resolution::summarize_source_resolution_evaluator_candidates_input;
51pub use source_resolution::summarize_source_resolution_fragments_input;
52pub use source_resolution::summarize_source_resolution_match_fragments_input;
53pub use source_resolution::summarize_source_resolution_plan_input;
54pub use source_resolution::summarize_source_resolution_query_fragments_input;
55pub use source_side::summarize_source_side_canonical_candidate_bundle_input;
56pub use source_side::summarize_source_side_canonical_producer_signal_input;
57pub use source_side::summarize_source_side_evaluator_candidates_input;
58pub use type_facts::summarize_type_fact_input;
59pub type EngineInputWireV2 = engine_contract_v2_idl_generated::EngineInputV2Json;
60pub type StringTypeFactsV2 = engine_contract_v2_idl_generated::StringTypeFactsV2Json;
61pub type TypeFactControlFlowBlockV2 =
62    engine_contract_v2_idl_generated::TypeFactControlFlowBlockV2Json;
63pub type TypeFactControlFlowGraphV2 =
64    engine_contract_v2_idl_generated::TypeFactControlFlowGraphV2Json;
65pub type TypeFactEntryV2 = engine_contract_v2_idl_generated::TypeFactEntryV2Json;
66
67#[derive(Debug)]
68pub struct EngineInputV2 {
69    pub version: String,
70    pub sources: Vec<SourceAnalysisInputV2>,
71    pub styles: Vec<StyleAnalysisInputV2>,
72    pub type_facts: Vec<TypeFactEntryV2>,
73}
74
75impl<'de> Deserialize<'de> for EngineInputV2 {
76    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77    where
78        D: Deserializer<'de>,
79    {
80        EngineInputWireV2::deserialize(deserializer)?
81            .try_into()
82            .map_err(de::Error::custom)
83    }
84}
85
86impl TryFrom<EngineInputWireV2> for EngineInputV2 {
87    type Error = serde_json::Error;
88
89    fn try_from(value: EngineInputWireV2) -> Result<Self, Self::Error> {
90        Ok(Self {
91            version: value.version,
92            sources: value
93                .sources
94                .into_iter()
95                .map(SourceAnalysisInputV2::try_from)
96                .collect::<Result<Vec<_>, _>>()?,
97            styles: value
98                .styles
99                .into_iter()
100                .map(StyleAnalysisInputV2::try_from)
101                .collect::<Result<Vec<_>, _>>()?,
102            type_facts: value.type_facts,
103        })
104    }
105}
106
107#[derive(Debug, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct SourceAnalysisInputV2 {
110    pub document: SourceDocumentV2,
111}
112
113impl TryFrom<engine_contract_v2_idl_generated::SourceAnalysisInputV2Json>
114    for SourceAnalysisInputV2
115{
116    type Error = serde_json::Error;
117
118    fn try_from(
119        value: engine_contract_v2_idl_generated::SourceAnalysisInputV2Json,
120    ) -> Result<Self, Self::Error> {
121        Ok(Self {
122            document: serde_json::from_value(value.document)?,
123        })
124    }
125}
126
127#[derive(Debug, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct SourceDocumentV2 {
130    pub class_expressions: Vec<ClassExpressionInputV2>,
131}
132
133#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
134#[serde(rename_all = "camelCase")]
135pub struct PositionV2 {
136    pub line: usize,
137    pub character: usize,
138}
139
140#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
141#[serde(rename_all = "camelCase")]
142pub struct RangeV2 {
143    pub start: PositionV2,
144    pub end: PositionV2,
145}
146
147#[derive(Debug, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct BemSuffixInfoV2 {
150    pub raw_token_range: RangeV2,
151}
152
153#[derive(Debug, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct ClassExpressionInputV2 {
156    pub id: String,
157    pub kind: String,
158    pub scss_module_path: String,
159    pub range: RangeV2,
160    pub class_name: Option<String>,
161    pub root_binding_decl_id: Option<String>,
162    pub access_path: Option<Vec<String>>,
163}
164
165#[derive(Debug, Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct StyleAnalysisInputV2 {
168    pub file_path: String,
169    #[serde(default)]
170    pub source: Option<String>,
171    pub document: StyleDocumentV2,
172}
173
174impl TryFrom<engine_contract_v2_idl_generated::StyleAnalysisInputV2Json> for StyleAnalysisInputV2 {
175    type Error = serde_json::Error;
176
177    fn try_from(
178        value: engine_contract_v2_idl_generated::StyleAnalysisInputV2Json,
179    ) -> Result<Self, Self::Error> {
180        Ok(Self {
181            file_path: value.file_path,
182            source: value.source,
183            document: serde_json::from_value(value.document)?,
184        })
185    }
186}
187
188#[derive(Debug, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct StyleDocumentV2 {
191    pub selectors: Vec<StyleSelectorV2>,
192}
193
194#[derive(Debug, Deserialize)]
195#[serde(rename_all = "camelCase")]
196pub struct StyleSelectorV2 {
197    pub name: String,
198    pub view_kind: String,
199    pub canonical_name: Option<String>,
200    pub range: RangeV2,
201    pub nested_safety: Option<String>,
202    pub composes: Option<Vec<serde_json::Value>>,
203    pub bem_suffix: Option<BemSuffixInfoV2>,
204}
205
206#[derive(Debug, Serialize)]
207#[serde(rename_all = "camelCase")]
208pub struct TypeFactInputSummaryV0 {
209    pub schema_version: &'static str,
210    pub input_version: String,
211    pub type_fact_count: usize,
212    pub distinct_fact_files: usize,
213    pub by_kind: BTreeMap<String, usize>,
214    pub constrained_kinds: BTreeMap<String, usize>,
215    pub finite_value_count: usize,
216}
217
218#[derive(Debug, Serialize)]
219#[serde(rename_all = "camelCase")]
220pub struct QueryPlanSummaryV0 {
221    schema_version: &'static str,
222    input_version: String,
223    expression_semantics_ids: Vec<String>,
224    source_expression_resolution_ids: Vec<String>,
225    selector_usage_ids: Vec<String>,
226    total_query_count: usize,
227}
228
229#[derive(Debug, Serialize, Clone)]
230#[serde(rename_all = "camelCase")]
231pub struct ExpressionDomainPlanSummaryV0 {
232    schema_version: &'static str,
233    input_version: String,
234    planned_expression_ids: Vec<String>,
235    value_domain_kinds: BTreeMap<String, usize>,
236    value_constraint_kinds: BTreeMap<String, usize>,
237    constraint_detail_counts: ConstraintDetailCounts,
238    finite_value_count: usize,
239}
240
241#[derive(Debug, Serialize, Clone)]
242#[serde(rename_all = "camelCase")]
243pub struct ExpressionDomainFragmentV0 {
244    pub expression_id: String,
245    pub file_path: String,
246    pub value_domain_kind: String,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub value_constraint_kind: Option<String>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub value_prefix: Option<String>,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub value_suffix: Option<String>,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub value_min_len: Option<usize>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub value_max_len: Option<usize>,
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub value_char_must: Option<String>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub value_char_may: Option<String>,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub value_may_include_other_chars: Option<bool>,
263    pub finite_value_count: usize,
264}
265
266#[derive(Debug, Serialize)]
267#[serde(rename_all = "camelCase")]
268pub struct ExpressionDomainFragmentsV0 {
269    pub schema_version: &'static str,
270    pub input_version: String,
271    pub fragments: Vec<ExpressionDomainFragmentV0>,
272}
273
274#[derive(Debug, Serialize, Clone)]
275#[serde(rename_all = "camelCase")]
276pub struct ExpressionDomainCandidateV0 {
277    pub expression_id: String,
278    pub file_path: String,
279    pub value_domain_kind: String,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub value_constraint_kind: Option<String>,
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub value_prefix: Option<String>,
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub value_suffix: Option<String>,
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub value_min_len: Option<usize>,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub value_max_len: Option<usize>,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub value_char_must: Option<String>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub value_char_may: Option<String>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub value_may_include_other_chars: Option<bool>,
296    pub finite_value_count: usize,
297}
298
299#[derive(Debug, Serialize)]
300#[serde(rename_all = "camelCase")]
301pub struct ExpressionDomainCandidatesV0 {
302    pub schema_version: &'static str,
303    pub input_version: String,
304    pub candidates: Vec<ExpressionDomainCandidateV0>,
305}
306
307#[derive(Debug, Serialize)]
308#[serde(rename_all = "camelCase")]
309pub struct ExpressionDomainCanonicalCandidateBundleV0 {
310    pub schema_version: &'static str,
311    pub input_version: String,
312    pub plan_summary: ExpressionDomainPlanSummaryV0,
313    pub fragments: Vec<ExpressionDomainFragmentV0>,
314    pub candidates: Vec<ExpressionDomainCandidateV0>,
315}
316
317#[derive(Debug, Serialize)]
318#[serde(rename_all = "camelCase")]
319pub struct ExpressionDomainEvaluatorCandidatePayloadV0 {
320    pub expression_id: String,
321    pub value_domain_kind: String,
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub value_constraint_kind: Option<String>,
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub value_prefix: Option<String>,
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub value_suffix: Option<String>,
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub value_min_len: Option<usize>,
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub value_max_len: Option<usize>,
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub value_char_must: Option<String>,
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub value_char_may: Option<String>,
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub value_may_include_other_chars: Option<bool>,
338    pub finite_value_count: usize,
339    pub value_domain_derivation: omena_abstract_value::ReducedClassValueDerivationV0,
340    pub value_domain_provenance_tree: omena_abstract_value::AbstractClassValueProvenanceTreeV0,
341}
342
343#[derive(Debug, Serialize)]
344#[serde(rename_all = "camelCase")]
345pub struct ExpressionDomainEvaluatorCandidateV0 {
346    pub kind: &'static str,
347    pub file_path: String,
348    pub query_id: String,
349    pub payload: ExpressionDomainEvaluatorCandidatePayloadV0,
350}
351
352#[derive(Debug, Serialize)]
353#[serde(rename_all = "camelCase")]
354pub struct ExpressionDomainEvaluatorCandidatesV0 {
355    pub schema_version: &'static str,
356    pub input_version: String,
357    pub results: Vec<ExpressionDomainEvaluatorCandidateV0>,
358}
359
360#[derive(Debug, Serialize)]
361#[serde(rename_all = "camelCase")]
362pub struct ExpressionDomainCanonicalProducerSignalV0 {
363    pub schema_version: &'static str,
364    pub input_version: String,
365    pub canonical_bundle: ExpressionDomainCanonicalCandidateBundleV0,
366    pub evaluator_candidates: ExpressionDomainEvaluatorCandidatesV0,
367}
368
369#[derive(Debug, Serialize)]
370#[serde(rename_all = "camelCase")]
371pub struct ExpressionDomainProvenanceExplanationsV0 {
372    pub schema_version: &'static str,
373    pub product: &'static str,
374    pub input_version: String,
375    pub explanation_count: usize,
376    pub explanations: Vec<ExpressionDomainProvenanceExplanationV0>,
377}
378
379#[derive(Debug, Serialize)]
380#[serde(rename_all = "camelCase")]
381pub struct ExpressionDomainProvenanceExplanationV0 {
382    pub expression_id: String,
383    pub file_path: String,
384    pub input_fact_kind: String,
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub input_constraint_kind: Option<String>,
387    pub reduced_kind: &'static str,
388    pub derivation: omena_abstract_value::ReducedClassValueDerivationV0,
389    pub provenance_tree: omena_abstract_value::AbstractClassValueProvenanceTreeV0,
390}
391
392#[derive(Debug, Serialize)]
393#[serde(rename_all = "camelCase")]
394pub struct ExpressionDomainFlowAnalysisV0 {
395    pub schema_version: &'static str,
396    pub product: &'static str,
397    pub input_version: String,
398    pub analyses: Vec<ExpressionDomainFlowAnalysisEntryV0>,
399}
400
401#[derive(Debug, Serialize)]
402#[serde(rename_all = "camelCase")]
403pub struct ExpressionDomainFlowAnalysisEntryV0 {
404    pub graph_id: String,
405    pub file_path: String,
406    pub analysis: omena_abstract_value::ClassValueFlowAnalysisV0,
407}
408
409#[derive(Debug, Clone, PartialEq, Eq)]
410pub struct ExpressionDomainFlowGraphEntryV0 {
411    pub graph_id: String,
412    pub file_path: String,
413    pub graph: omena_abstract_value::ClassValueFlowGraphV0,
414}
415
416#[derive(Debug, Serialize)]
417#[serde(rename_all = "camelCase")]
418pub struct ExpressionDomainControlFlowAnalysisV0 {
419    pub schema_version: &'static str,
420    pub product: &'static str,
421    pub input_version: String,
422    pub analyses: Vec<ExpressionDomainControlFlowAnalysisEntryV0>,
423}
424
425#[derive(Debug, Serialize)]
426#[serde(rename_all = "camelCase")]
427pub struct ExpressionDomainControlFlowAnalysisEntryV0 {
428    pub graph_id: String,
429    pub file_path: String,
430    pub analysis: omena_abstract_value::ClassValueControlFlowAnalysisV0,
431}
432
433#[derive(Debug, Serialize)]
434#[serde(rename_all = "camelCase")]
435pub struct ExpressionDomainCallSiteFlowAnalysisV0 {
436    pub schema_version: &'static str,
437    pub product: &'static str,
438    pub input_version: String,
439    pub zero_cfa: omena_abstract_value::KLimitedCallSiteFlowAnalysisV0,
440    pub one_cfa: omena_abstract_value::KLimitedCallSiteFlowAnalysisV0,
441}
442
443#[derive(Debug, Serialize)]
444#[serde(rename_all = "camelCase")]
445pub struct ExpressionDomainReducedProductIterationV0 {
446    pub schema_version: &'static str,
447    pub product: &'static str,
448    pub input_version: String,
449    pub iteration_count: usize,
450    pub iterations: Vec<ExpressionDomainReducedProductIterationEntryV0>,
451}
452
453#[derive(Debug, Serialize)]
454#[serde(rename_all = "camelCase")]
455pub struct ExpressionDomainReducedProductIterationEntryV0 {
456    pub expression_id: String,
457    pub file_path: String,
458    pub input_value_kind: String,
459    pub axis_constraint_count: usize,
460    pub iteration: omena_abstract_value::ReducedClassValueProductIterationV0,
461}
462
463#[derive(Debug, Serialize)]
464#[serde(rename_all = "camelCase")]
465pub struct SelectorUsagePlanSummaryV0 {
466    schema_version: &'static str,
467    input_version: String,
468    canonical_selector_names: Vec<String>,
469    view_kind_counts: BTreeMap<String, usize>,
470    nested_safety_counts: BTreeMap<String, usize>,
471    composed_selector_count: usize,
472    total_composes_refs: usize,
473}
474
475#[derive(Debug, Clone, Serialize)]
476#[serde(rename_all = "camelCase")]
477pub struct SelectorUsageFragmentV0 {
478    pub ordinal: usize,
479    pub view_kind: String,
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub canonical_name: Option<String>,
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub nested_safety: Option<String>,
484    pub composes_count: usize,
485}
486
487#[derive(Debug, Serialize)]
488#[serde(rename_all = "camelCase")]
489pub struct SelectorUsageFragmentsV0 {
490    pub schema_version: &'static str,
491    pub input_version: String,
492    pub fragments: Vec<SelectorUsageFragmentV0>,
493}
494
495#[derive(Debug, Clone, Serialize)]
496#[serde(rename_all = "camelCase")]
497pub struct SelectorUsageQueryFragmentV0 {
498    pub query_id: String,
499    pub canonical_name: String,
500    #[serde(skip_serializing_if = "Option::is_none")]
501    pub nested_safety: Option<String>,
502    pub composes_count: usize,
503}
504
505#[derive(Debug, Serialize)]
506#[serde(rename_all = "camelCase")]
507pub struct SelectorUsageQueryFragmentsV0 {
508    pub schema_version: &'static str,
509    pub input_version: String,
510    pub fragments: Vec<SelectorUsageQueryFragmentV0>,
511}
512
513#[derive(Debug, Serialize, Clone)]
514#[serde(rename_all = "camelCase")]
515pub struct SelectorUsageCandidateV0 {
516    pub query_id: String,
517    pub canonical_name: String,
518    pub file_path: String,
519    pub total_references: usize,
520    pub direct_reference_count: usize,
521    pub editable_direct_reference_count: usize,
522    pub exact_reference_count: usize,
523    pub inferred_or_better_reference_count: usize,
524    pub has_expanded_references: bool,
525    pub has_style_dependency_references: bool,
526    pub has_any_references: bool,
527}
528
529#[derive(Debug, Serialize)]
530#[serde(rename_all = "camelCase")]
531pub struct SelectorUsageCandidatesV0 {
532    pub schema_version: &'static str,
533    pub input_version: String,
534    pub candidates: Vec<SelectorUsageCandidateV0>,
535}
536
537#[derive(Debug, Serialize, Clone)]
538#[serde(rename_all = "camelCase")]
539pub struct SelectorUsageEvaluatorCandidatePayloadV0 {
540    pub canonical_name: String,
541    pub total_references: usize,
542    pub direct_reference_count: usize,
543    pub editable_direct_reference_count: usize,
544    pub exact_reference_count: usize,
545    pub inferred_or_better_reference_count: usize,
546    pub has_expanded_references: bool,
547    pub has_style_dependency_references: bool,
548    pub has_any_references: bool,
549    pub all_sites: Vec<SelectorUsageReferenceSiteV0>,
550    pub editable_direct_sites: Vec<SelectorUsageEditableDirectSiteV0>,
551}
552
553#[derive(Debug, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
554#[serde(rename_all = "camelCase")]
555pub struct SelectorUsageReferenceSiteV0 {
556    pub file_path: String,
557    pub range: RangeV2,
558    pub expansion: String,
559    pub reference_kind: String,
560}
561
562#[derive(Debug, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
563#[serde(rename_all = "camelCase")]
564pub struct SelectorUsageEditableDirectSiteV0 {
565    pub file_path: String,
566    pub range: RangeV2,
567    pub class_name: String,
568}
569
570#[derive(Debug, Serialize, Clone)]
571#[serde(rename_all = "camelCase")]
572pub struct SelectorUsageEvaluatorCandidateV0 {
573    pub kind: &'static str,
574    pub file_path: String,
575    pub query_id: String,
576    pub payload: SelectorUsageEvaluatorCandidatePayloadV0,
577}
578
579#[derive(Debug, Serialize)]
580#[serde(rename_all = "camelCase")]
581pub struct SelectorUsageEvaluatorCandidatesV0 {
582    pub schema_version: &'static str,
583    pub input_version: String,
584    pub results: Vec<SelectorUsageEvaluatorCandidateV0>,
585}
586
587#[derive(Debug, Serialize)]
588#[serde(rename_all = "camelCase")]
589pub struct SelectorUsageCanonicalCandidateBundleV0 {
590    pub schema_version: &'static str,
591    pub input_version: String,
592    pub query_fragments: Vec<SelectorUsageQueryFragmentV0>,
593    pub fragments: Vec<SelectorUsageFragmentV0>,
594    pub candidates: Vec<SelectorUsageCandidateV0>,
595}
596
597#[derive(Debug, Serialize)]
598#[serde(rename_all = "camelCase")]
599pub struct SelectorUsageCanonicalProducerSignalV0 {
600    pub schema_version: &'static str,
601    pub input_version: String,
602    pub canonical_bundle: SelectorUsageCanonicalCandidateBundleV0,
603    pub evaluator_candidates: SelectorUsageEvaluatorCandidatesV0,
604}
605
606#[derive(Debug, Serialize)]
607#[serde(rename_all = "camelCase")]
608pub struct SourceResolutionPlanSummaryV0 {
609    schema_version: &'static str,
610    input_version: String,
611    planned_expression_ids: Vec<String>,
612    expression_kind_counts: BTreeMap<String, usize>,
613    distinct_style_file_paths: Vec<String>,
614    symbol_ref_with_binding_count: usize,
615    style_access_count: usize,
616    style_access_path_depth_sum: usize,
617}
618
619#[derive(Debug, Serialize, Clone)]
620#[serde(rename_all = "camelCase")]
621pub struct SourceResolutionQueryFragmentV0 {
622    pub query_id: String,
623    pub expression_id: String,
624    pub expression_kind: String,
625    pub style_file_path: String,
626}
627
628#[derive(Debug, Serialize)]
629#[serde(rename_all = "camelCase")]
630pub struct SourceResolutionQueryFragmentsV0 {
631    pub schema_version: &'static str,
632    pub input_version: String,
633    pub fragments: Vec<SourceResolutionQueryFragmentV0>,
634}
635
636#[derive(Debug, Serialize, Clone)]
637#[serde(rename_all = "camelCase")]
638pub struct SourceResolutionMatchFragmentV0 {
639    pub query_id: String,
640    pub expression_id: String,
641    pub style_file_path: String,
642    pub selector_names: Vec<String>,
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub finite_values: Option<Vec<String>>,
645}
646
647#[derive(Debug, Serialize)]
648#[serde(rename_all = "camelCase")]
649pub struct SourceResolutionMatchFragmentsV0 {
650    pub schema_version: &'static str,
651    pub input_version: String,
652    pub fragments: Vec<SourceResolutionMatchFragmentV0>,
653}
654
655#[derive(Debug, Serialize, Clone)]
656#[serde(rename_all = "camelCase")]
657pub struct SourceResolutionCandidateV0 {
658    pub query_id: String,
659    pub expression_id: String,
660    pub style_file_path: String,
661    pub selector_names: Vec<String>,
662    #[serde(skip_serializing_if = "Option::is_none")]
663    pub finite_values: Option<Vec<String>>,
664    pub selector_certainty: String,
665    #[serde(skip_serializing_if = "Option::is_none")]
666    pub value_certainty: Option<String>,
667    pub selector_certainty_shape_kind: String,
668    pub selector_certainty_shape_label: String,
669    pub value_certainty_shape_kind: String,
670    pub value_certainty_shape_label: String,
671    #[serde(skip_serializing_if = "Option::is_none")]
672    pub selector_constraint_kind: Option<String>,
673    #[serde(skip_serializing_if = "Option::is_none")]
674    pub value_certainty_constraint_kind: Option<String>,
675    #[serde(skip_serializing_if = "Option::is_none")]
676    pub value_prefix: Option<String>,
677    #[serde(skip_serializing_if = "Option::is_none")]
678    pub value_suffix: Option<String>,
679    #[serde(skip_serializing_if = "Option::is_none")]
680    pub value_min_len: Option<usize>,
681    #[serde(skip_serializing_if = "Option::is_none")]
682    pub value_max_len: Option<usize>,
683    #[serde(skip_serializing_if = "Option::is_none")]
684    pub value_char_must: Option<String>,
685    #[serde(skip_serializing_if = "Option::is_none")]
686    pub value_char_may: Option<String>,
687    #[serde(skip_serializing_if = "Option::is_none")]
688    pub value_may_include_other_chars: Option<bool>,
689}
690
691#[derive(Debug, Serialize)]
692#[serde(rename_all = "camelCase")]
693pub struct SourceResolutionCandidatesV0 {
694    pub schema_version: &'static str,
695    pub input_version: String,
696    pub candidates: Vec<SourceResolutionCandidateV0>,
697}
698
699#[derive(Debug, Serialize)]
700#[serde(rename_all = "camelCase")]
701pub struct SourceResolutionCanonicalCandidateBundleV0 {
702    pub schema_version: &'static str,
703    pub input_version: String,
704    pub query_fragments: Vec<SourceResolutionQueryFragmentV0>,
705    pub fragments: Vec<SourceResolutionFragmentV0>,
706    pub match_fragments: Vec<SourceResolutionMatchFragmentV0>,
707    pub candidates: Vec<SourceResolutionCandidateV0>,
708}
709
710#[derive(Debug, Serialize)]
711#[serde(rename_all = "camelCase")]
712pub struct SourceResolutionEvaluatorCandidatePayloadV0 {
713    pub expression_id: String,
714    pub style_file_path: String,
715    pub selector_names: Vec<String>,
716    #[serde(skip_serializing_if = "Option::is_none")]
717    pub finite_values: Option<Vec<String>>,
718    pub selector_certainty: String,
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub value_certainty: Option<String>,
721    pub selector_certainty_shape_kind: String,
722    pub selector_certainty_shape_label: String,
723    pub value_certainty_shape_kind: String,
724    pub value_certainty_shape_label: String,
725    #[serde(skip_serializing_if = "Option::is_none")]
726    pub selector_constraint_kind: Option<String>,
727    #[serde(skip_serializing_if = "Option::is_none")]
728    pub value_certainty_constraint_kind: Option<String>,
729    #[serde(skip_serializing_if = "Option::is_none")]
730    pub value_prefix: Option<String>,
731    #[serde(skip_serializing_if = "Option::is_none")]
732    pub value_suffix: Option<String>,
733    #[serde(skip_serializing_if = "Option::is_none")]
734    pub value_min_len: Option<usize>,
735    #[serde(skip_serializing_if = "Option::is_none")]
736    pub value_max_len: Option<usize>,
737    #[serde(skip_serializing_if = "Option::is_none")]
738    pub value_char_must: Option<String>,
739    #[serde(skip_serializing_if = "Option::is_none")]
740    pub value_char_may: Option<String>,
741    #[serde(skip_serializing_if = "Option::is_none")]
742    pub value_may_include_other_chars: Option<bool>,
743}
744
745#[derive(Debug, Serialize)]
746#[serde(rename_all = "camelCase")]
747pub struct SourceResolutionEvaluatorCandidateV0 {
748    pub kind: &'static str,
749    pub file_path: String,
750    pub query_id: String,
751    pub payload: SourceResolutionEvaluatorCandidatePayloadV0,
752}
753
754#[derive(Debug, Serialize)]
755#[serde(rename_all = "camelCase")]
756pub struct SourceResolutionEvaluatorCandidatesV0 {
757    pub schema_version: &'static str,
758    pub input_version: String,
759    pub results: Vec<SourceResolutionEvaluatorCandidateV0>,
760}
761
762#[derive(Debug, Serialize)]
763#[serde(rename_all = "camelCase")]
764pub struct SourceResolutionCanonicalProducerSignalV0 {
765    pub schema_version: &'static str,
766    pub input_version: String,
767    pub canonical_bundle: SourceResolutionCanonicalCandidateBundleV0,
768    pub evaluator_candidates: SourceResolutionEvaluatorCandidatesV0,
769}
770
771#[derive(Debug, Serialize)]
772#[serde(rename_all = "camelCase")]
773pub struct SourceSideCanonicalCandidateBundleV0 {
774    pub schema_version: &'static str,
775    pub input_version: String,
776    pub expression_semantics: ExpressionSemanticsCanonicalCandidateBundleV0,
777    pub source_resolution: SourceResolutionCanonicalCandidateBundleV0,
778}
779
780#[derive(Debug, Serialize)]
781#[serde(rename_all = "camelCase")]
782pub struct SourceSideEvaluatorCandidatesV0 {
783    pub schema_version: &'static str,
784    pub input_version: String,
785    pub expression_semantics: ExpressionSemanticsEvaluatorCandidatesV0,
786    pub source_resolution: SourceResolutionEvaluatorCandidatesV0,
787}
788
789#[derive(Debug, Serialize)]
790#[serde(rename_all = "camelCase")]
791pub struct SourceSideCanonicalProducerSignalV0 {
792    pub schema_version: &'static str,
793    pub input_version: String,
794    pub canonical_bundle: SourceSideCanonicalCandidateBundleV0,
795    pub evaluator_candidates: SourceSideEvaluatorCandidatesV0,
796}
797
798#[derive(Debug, Serialize)]
799#[serde(rename_all = "camelCase")]
800pub struct SemanticCanonicalCandidateBundleV0 {
801    pub schema_version: &'static str,
802    pub input_version: String,
803    pub source_side: SourceSideCanonicalCandidateBundleV0,
804    pub expression_domain: ExpressionDomainCanonicalCandidateBundleV0,
805}
806
807#[derive(Debug, Serialize)]
808#[serde(rename_all = "camelCase")]
809pub struct SemanticEvaluatorCandidatesV0 {
810    pub schema_version: &'static str,
811    pub input_version: String,
812    pub source_side: SourceSideEvaluatorCandidatesV0,
813    pub expression_domain: ExpressionDomainEvaluatorCandidatesV0,
814}
815
816#[derive(Debug, Serialize)]
817#[serde(rename_all = "camelCase")]
818pub struct SemanticCanonicalProducerSignalV0 {
819    pub schema_version: &'static str,
820    pub input_version: String,
821    pub canonical_bundle: SemanticCanonicalCandidateBundleV0,
822    pub evaluator_candidates: SemanticEvaluatorCandidatesV0,
823}
824
825#[derive(Debug, Serialize, Clone)]
826#[serde(rename_all = "camelCase")]
827pub struct ExpressionSemanticsFragmentV0 {
828    query_id: String,
829    expression_id: String,
830    expression_kind: String,
831    style_file_path: String,
832    value_domain_kind: String,
833    #[serde(skip_serializing_if = "Option::is_none")]
834    value_constraint_kind: Option<String>,
835    #[serde(skip_serializing_if = "Option::is_none")]
836    value_prefix: Option<String>,
837    #[serde(skip_serializing_if = "Option::is_none")]
838    value_suffix: Option<String>,
839    #[serde(skip_serializing_if = "Option::is_none")]
840    value_min_len: Option<usize>,
841    #[serde(skip_serializing_if = "Option::is_none")]
842    value_max_len: Option<usize>,
843    #[serde(skip_serializing_if = "Option::is_none")]
844    value_char_must: Option<String>,
845    #[serde(skip_serializing_if = "Option::is_none")]
846    value_char_may: Option<String>,
847    #[serde(skip_serializing_if = "Option::is_none")]
848    value_may_include_other_chars: Option<bool>,
849}
850
851#[derive(Debug, Serialize)]
852#[serde(rename_all = "camelCase")]
853pub struct ExpressionSemanticsFragmentsV0 {
854    schema_version: &'static str,
855    input_version: String,
856    fragments: Vec<ExpressionSemanticsFragmentV0>,
857}
858
859#[derive(Debug, Serialize, Clone)]
860#[serde(rename_all = "camelCase")]
861pub struct ExpressionSemanticsQueryFragmentV0 {
862    pub query_id: String,
863    pub expression_id: String,
864    pub expression_kind: String,
865    pub style_file_path: String,
866}
867
868#[derive(Debug, Serialize)]
869#[serde(rename_all = "camelCase")]
870pub struct ExpressionSemanticsQueryFragmentsV0 {
871    pub schema_version: &'static str,
872    pub input_version: String,
873    pub fragments: Vec<ExpressionSemanticsQueryFragmentV0>,
874}
875
876#[derive(Debug, Serialize, Clone)]
877#[serde(rename_all = "camelCase")]
878pub struct ExpressionSemanticsMatchFragmentV0 {
879    pub query_id: String,
880    pub expression_id: String,
881    pub style_file_path: String,
882    pub selector_names: Vec<String>,
883    pub candidate_names: Vec<String>,
884    #[serde(skip_serializing_if = "Option::is_none")]
885    pub finite_values: Option<Vec<String>>,
886}
887
888#[derive(Debug, Serialize)]
889#[serde(rename_all = "camelCase")]
890pub struct ExpressionSemanticsMatchFragmentsV0 {
891    pub schema_version: &'static str,
892    pub input_version: String,
893    pub fragments: Vec<ExpressionSemanticsMatchFragmentV0>,
894}
895
896#[derive(Debug, Serialize, Clone)]
897#[serde(rename_all = "camelCase")]
898pub struct ExpressionSemanticsCandidateV0 {
899    pub query_id: String,
900    pub expression_id: String,
901    pub expression_kind: String,
902    pub style_file_path: String,
903    pub selector_names: Vec<String>,
904    pub candidate_names: Vec<String>,
905    #[serde(skip_serializing_if = "Option::is_none")]
906    pub finite_values: Option<Vec<String>>,
907    pub value_domain_kind: String,
908    pub selector_certainty: String,
909    #[serde(skip_serializing_if = "Option::is_none")]
910    pub value_certainty: Option<String>,
911    pub selector_certainty_shape_kind: String,
912    pub selector_certainty_shape_label: String,
913    pub value_certainty_shape_kind: String,
914    pub value_certainty_shape_label: String,
915    #[serde(skip_serializing_if = "Option::is_none")]
916    pub selector_constraint_kind: Option<String>,
917    #[serde(skip_serializing_if = "Option::is_none")]
918    pub value_certainty_constraint_kind: Option<String>,
919    #[serde(skip_serializing_if = "Option::is_none")]
920    pub value_constraint_kind: Option<String>,
921    #[serde(skip_serializing_if = "Option::is_none")]
922    pub value_prefix: Option<String>,
923    #[serde(skip_serializing_if = "Option::is_none")]
924    pub value_suffix: Option<String>,
925    #[serde(skip_serializing_if = "Option::is_none")]
926    pub value_min_len: Option<usize>,
927    #[serde(skip_serializing_if = "Option::is_none")]
928    pub value_max_len: Option<usize>,
929    #[serde(skip_serializing_if = "Option::is_none")]
930    pub value_char_must: Option<String>,
931    #[serde(skip_serializing_if = "Option::is_none")]
932    pub value_char_may: Option<String>,
933    #[serde(skip_serializing_if = "Option::is_none")]
934    pub value_may_include_other_chars: Option<bool>,
935}
936
937#[derive(Debug, Serialize)]
938#[serde(rename_all = "camelCase")]
939pub struct ExpressionSemanticsCandidatesV0 {
940    pub schema_version: &'static str,
941    pub input_version: String,
942    pub candidates: Vec<ExpressionSemanticsCandidateV0>,
943}
944
945#[derive(Debug, Serialize)]
946#[serde(rename_all = "camelCase")]
947pub struct ExpressionSemanticsCanonicalCandidateBundleV0 {
948    pub schema_version: &'static str,
949    pub input_version: String,
950    pub query_fragments: Vec<ExpressionSemanticsQueryFragmentV0>,
951    pub fragments: Vec<ExpressionSemanticsFragmentV0>,
952    pub match_fragments: Vec<ExpressionSemanticsMatchFragmentV0>,
953    pub candidates: Vec<ExpressionSemanticsCandidateV0>,
954}
955
956#[derive(Debug, Serialize)]
957#[serde(rename_all = "camelCase")]
958pub struct ExpressionSemanticsEvaluatorCandidatePayloadV0 {
959    pub expression_id: String,
960    pub expression_kind: String,
961    pub style_file_path: String,
962    pub selector_names: Vec<String>,
963    pub candidate_names: Vec<String>,
964    #[serde(skip_serializing_if = "Option::is_none")]
965    pub finite_values: Option<Vec<String>>,
966    pub value_domain_kind: String,
967    pub selector_certainty: String,
968    #[serde(skip_serializing_if = "Option::is_none")]
969    pub value_certainty: Option<String>,
970    pub selector_certainty_shape_kind: String,
971    pub selector_certainty_shape_label: String,
972    pub value_certainty_shape_kind: String,
973    pub value_certainty_shape_label: String,
974    #[serde(skip_serializing_if = "Option::is_none")]
975    pub selector_constraint_kind: Option<String>,
976    #[serde(skip_serializing_if = "Option::is_none")]
977    pub value_certainty_constraint_kind: Option<String>,
978    #[serde(skip_serializing_if = "Option::is_none")]
979    pub value_constraint_kind: Option<String>,
980    #[serde(skip_serializing_if = "Option::is_none")]
981    pub value_prefix: Option<String>,
982    #[serde(skip_serializing_if = "Option::is_none")]
983    pub value_suffix: Option<String>,
984    #[serde(skip_serializing_if = "Option::is_none")]
985    pub value_min_len: Option<usize>,
986    #[serde(skip_serializing_if = "Option::is_none")]
987    pub value_max_len: Option<usize>,
988    #[serde(skip_serializing_if = "Option::is_none")]
989    pub value_char_must: Option<String>,
990    #[serde(skip_serializing_if = "Option::is_none")]
991    pub value_char_may: Option<String>,
992    #[serde(skip_serializing_if = "Option::is_none")]
993    pub value_may_include_other_chars: Option<bool>,
994    pub value_domain_derivation: omena_abstract_value::ReducedClassValueDerivationV0,
995    pub value_domain_provenance_tree: omena_abstract_value::AbstractClassValueProvenanceTreeV0,
996}
997
998#[derive(Debug, Serialize)]
999#[serde(rename_all = "camelCase")]
1000pub struct ExpressionSemanticsEvaluatorCandidateV0 {
1001    pub kind: &'static str,
1002    pub file_path: String,
1003    pub query_id: String,
1004    pub payload: ExpressionSemanticsEvaluatorCandidatePayloadV0,
1005}
1006
1007#[derive(Debug, Serialize)]
1008#[serde(rename_all = "camelCase")]
1009pub struct ExpressionSemanticsEvaluatorCandidatesV0 {
1010    pub schema_version: &'static str,
1011    pub input_version: String,
1012    pub results: Vec<ExpressionSemanticsEvaluatorCandidateV0>,
1013}
1014
1015#[derive(Debug, Serialize)]
1016#[serde(rename_all = "camelCase")]
1017pub struct ExpressionSemanticsCanonicalProducerSignalV0 {
1018    pub schema_version: &'static str,
1019    pub input_version: String,
1020    pub canonical_bundle: ExpressionSemanticsCanonicalCandidateBundleV0,
1021    pub evaluator_candidates: ExpressionSemanticsEvaluatorCandidatesV0,
1022}
1023
1024#[derive(Debug, Serialize, Clone)]
1025#[serde(rename_all = "camelCase")]
1026pub struct SourceResolutionFragmentV0 {
1027    query_id: String,
1028    expression_id: String,
1029    style_file_path: String,
1030    value_certainty_shape_kind: String,
1031    #[serde(skip_serializing_if = "Option::is_none")]
1032    value_certainty_constraint_kind: Option<String>,
1033    #[serde(skip_serializing_if = "Option::is_none")]
1034    value_prefix: Option<String>,
1035    #[serde(skip_serializing_if = "Option::is_none")]
1036    value_suffix: Option<String>,
1037    #[serde(skip_serializing_if = "Option::is_none")]
1038    value_min_len: Option<usize>,
1039    #[serde(skip_serializing_if = "Option::is_none")]
1040    value_max_len: Option<usize>,
1041    #[serde(skip_serializing_if = "Option::is_none")]
1042    value_char_must: Option<String>,
1043    #[serde(skip_serializing_if = "Option::is_none")]
1044    value_char_may: Option<String>,
1045    #[serde(skip_serializing_if = "Option::is_none")]
1046    value_may_include_other_chars: Option<bool>,
1047}
1048
1049#[derive(Debug, Serialize)]
1050#[serde(rename_all = "camelCase")]
1051pub struct SourceResolutionFragmentsV0 {
1052    schema_version: &'static str,
1053    input_version: String,
1054    fragments: Vec<SourceResolutionFragmentV0>,
1055}
1056
1057#[derive(Debug, Serialize, Default, Clone)]
1058#[serde(rename_all = "camelCase")]
1059pub struct ConstraintDetailCounts {
1060    pub prefix_count: usize,
1061    pub suffix_count: usize,
1062    pub min_len_count: usize,
1063    pub min_len_sum: usize,
1064    pub max_len_count: usize,
1065    pub max_len_sum: usize,
1066    pub char_must_count: usize,
1067    pub char_must_len_sum: usize,
1068    pub char_may_count: usize,
1069    pub char_may_len_sum: usize,
1070    pub may_include_other_chars_count: usize,
1071}
1072
1073fn collect_constraint_detail_counts(
1074    counts: &mut ConstraintDetailCounts,
1075    details: ConstraintDetailInput<'_>,
1076) {
1077    if details.prefix.is_some() {
1078        counts.prefix_count += 1;
1079    }
1080    if details.suffix.is_some() {
1081        counts.suffix_count += 1;
1082    }
1083    if let Some(value) = details.min_len {
1084        counts.min_len_count += 1;
1085        counts.min_len_sum += value;
1086    }
1087    if let Some(value) = details.max_len {
1088        counts.max_len_count += 1;
1089        counts.max_len_sum += value;
1090    }
1091    if let Some(value) = details.char_must {
1092        counts.char_must_count += 1;
1093        counts.char_must_len_sum += value.len();
1094    }
1095    if let Some(value) = details.char_may {
1096        counts.char_may_count += 1;
1097        counts.char_may_len_sum += value.len();
1098    }
1099    if details.may_include_other_chars == Some(true) {
1100        counts.may_include_other_chars_count += 1;
1101    }
1102}
1103
1104pub(crate) struct ConstraintDetailInput<'a> {
1105    pub(crate) prefix: Option<&'a String>,
1106    pub(crate) suffix: Option<&'a String>,
1107    pub(crate) min_len: Option<usize>,
1108    pub(crate) max_len: Option<usize>,
1109    pub(crate) char_must: Option<&'a String>,
1110    pub(crate) char_may: Option<&'a String>,
1111    pub(crate) may_include_other_chars: Option<bool>,
1112}
1113
1114pub(crate) fn map_expression_value_domain_kind(facts: &StringTypeFactsV2) -> String {
1115    omena_abstract_value::expression_value_domain_kind_from_facts(&abstract_value_facts(facts))
1116}
1117
1118pub(crate) fn map_reduced_expression_value_domain_kind(facts: &StringTypeFactsV2) -> String {
1119    omena_abstract_value::reduced_value_domain_kind_from_facts(&abstract_value_facts(facts))
1120        .to_string()
1121}
1122
1123pub(crate) fn map_reduced_expression_value_domain_derivation(
1124    facts: &StringTypeFactsV2,
1125) -> omena_abstract_value::ReducedClassValueDerivationV0 {
1126    omena_abstract_value::reduced_class_value_derivation_from_facts(&abstract_value_facts(facts))
1127}
1128
1129pub(crate) fn map_reduced_expression_value_domain_provenance_tree(
1130    facts: &StringTypeFactsV2,
1131) -> omena_abstract_value::AbstractClassValueProvenanceTreeV0 {
1132    let value =
1133        omena_abstract_value::reduced_abstract_class_value_from_facts(&abstract_value_facts(facts));
1134    omena_abstract_value::summarize_abstract_class_value_provenance_tree(&value)
1135}
1136
1137pub(crate) fn map_value_certainty(facts: &StringTypeFactsV2) -> Option<String> {
1138    omena_abstract_value::value_certainty_from_facts(&abstract_value_facts(facts))
1139        .map(str::to_string)
1140}
1141
1142pub(crate) fn map_value_certainty_shape_kind(facts: &StringTypeFactsV2) -> String {
1143    omena_abstract_value::value_certainty_shape_kind_from_facts(&abstract_value_facts(facts))
1144        .to_string()
1145}
1146
1147pub(crate) fn map_value_certainty_shape_label(facts: &StringTypeFactsV2) -> String {
1148    omena_abstract_value::value_certainty_shape_label_from_facts(&abstract_value_facts(facts))
1149}
1150
1151pub(crate) fn map_selector_certainty_shape_kind(
1152    facts: &StringTypeFactsV2,
1153    matched_selector_count: usize,
1154    selector_universe_count: usize,
1155) -> String {
1156    omena_abstract_value::selector_certainty_shape_kind_from_facts(
1157        &abstract_value_facts(facts),
1158        matched_selector_count,
1159        selector_universe_count,
1160    )
1161    .to_string()
1162}
1163
1164pub(crate) fn map_selector_certainty_shape_label(
1165    facts: &StringTypeFactsV2,
1166    matched_selector_count: usize,
1167    selector_universe_count: usize,
1168) -> String {
1169    omena_abstract_value::selector_certainty_shape_label_from_facts(
1170        &abstract_value_facts(facts),
1171        matched_selector_count,
1172        selector_universe_count,
1173    )
1174}
1175
1176pub(crate) fn map_selector_certainty(
1177    facts: &StringTypeFactsV2,
1178    matched_selector_count: usize,
1179    selector_universe_count: usize,
1180) -> String {
1181    omena_abstract_value::selector_certainty_from_facts(
1182        &abstract_value_facts(facts),
1183        matched_selector_count,
1184        selector_universe_count,
1185    )
1186    .to_string()
1187}
1188
1189pub(crate) fn finite_values_for_facts(facts: &StringTypeFactsV2) -> Option<Vec<String>> {
1190    omena_abstract_value::finite_values_from_facts(&abstract_value_facts(facts))
1191}
1192
1193pub(crate) fn abstract_value_facts(
1194    facts: &StringTypeFactsV2,
1195) -> omena_abstract_value::ExternalStringTypeFactsV0 {
1196    omena_abstract_value::ExternalStringTypeFactsV0 {
1197        kind: facts.kind.clone(),
1198        constraint_kind: facts.constraint_kind.clone(),
1199        values: facts.values.clone(),
1200        prefix: facts.prefix.clone(),
1201        suffix: facts.suffix.clone(),
1202        min_len: facts.min_len,
1203        max_len: facts.max_len,
1204        char_must: facts.char_must.clone(),
1205        char_may: facts.char_may.clone(),
1206        may_include_other_chars: facts.may_include_other_chars,
1207    }
1208}
1209
1210pub(crate) fn resolve_selector_names(
1211    style: &StyleAnalysisInputV2,
1212    facts: &StringTypeFactsV2,
1213) -> Vec<String> {
1214    match facts.kind.as_str() {
1215        "unknown" => Vec::new(),
1216        "top" => canonical_selector_names(style),
1217        "exact" | "finiteSet" => {
1218            let mut names = Vec::new();
1219            for value in facts.values.as_ref().into_iter().flatten() {
1220                push_canonical_match(style, value, &mut names);
1221            }
1222            names
1223        }
1224        "constrained" => resolve_constrained_selector_names(style, facts),
1225        _ => Vec::new(),
1226    }
1227}
1228
1229fn resolve_constrained_selector_names(
1230    style: &StyleAnalysisInputV2,
1231    facts: &StringTypeFactsV2,
1232) -> Vec<String> {
1233    let mut names = Vec::new();
1234
1235    for selector in &style.document.selectors {
1236        if !matches_selector_constraints(selector, facts) {
1237            continue;
1238        }
1239        let canonical_name = canonical_name_for_selector(style, selector);
1240        if let Some(canonical_name) = canonical_name
1241            && !names.contains(&canonical_name)
1242        {
1243            names.push(canonical_name);
1244        }
1245    }
1246
1247    names
1248}
1249
1250fn matches_selector_constraints(selector: &StyleSelectorV2, facts: &StringTypeFactsV2) -> bool {
1251    match facts.constraint_kind.as_deref() {
1252        Some("prefix") => facts
1253            .prefix
1254            .as_ref()
1255            .is_some_and(|prefix| selector.name.starts_with(prefix)),
1256        Some("suffix") => facts
1257            .suffix
1258            .as_ref()
1259            .is_some_and(|suffix| selector.name.ends_with(suffix)),
1260        Some("prefixSuffix") => {
1261            let prefix_ok = facts
1262                .prefix
1263                .as_ref()
1264                .is_none_or(|prefix| selector.name.starts_with(prefix));
1265            let suffix_ok = facts
1266                .suffix
1267                .as_ref()
1268                .is_none_or(|suffix| selector.name.ends_with(suffix));
1269            let min_len_ok = facts
1270                .min_len
1271                .is_none_or(|min_len| selector.name.len() >= min_len);
1272            let max_len_ok = facts
1273                .max_len
1274                .is_none_or(|max_len| selector.name.len() <= max_len);
1275            prefix_ok && suffix_ok && min_len_ok && max_len_ok
1276        }
1277        Some("charInclusion") => matches_char_constraints(
1278            &selector.name,
1279            facts.char_must.as_deref().unwrap_or(""),
1280            facts.char_may.as_deref().unwrap_or(""),
1281            facts.may_include_other_chars.unwrap_or(false),
1282        ),
1283        Some("composite") => {
1284            let prefix_ok = facts
1285                .prefix
1286                .as_ref()
1287                .is_none_or(|prefix| selector.name.starts_with(prefix));
1288            let suffix_ok = facts
1289                .suffix
1290                .as_ref()
1291                .is_none_or(|suffix| selector.name.ends_with(suffix));
1292            let min_len_ok = facts
1293                .min_len
1294                .is_none_or(|min_len| selector.name.len() >= min_len);
1295            let max_len_ok = facts
1296                .max_len
1297                .is_none_or(|max_len| selector.name.len() <= max_len);
1298            prefix_ok
1299                && suffix_ok
1300                && min_len_ok
1301                && max_len_ok
1302                && matches_char_constraints(
1303                    &selector.name,
1304                    facts.char_must.as_deref().unwrap_or(""),
1305                    facts.char_may.as_deref().unwrap_or(""),
1306                    facts.may_include_other_chars.unwrap_or(false),
1307                )
1308        }
1309        _ => false,
1310    }
1311}
1312
1313fn matches_char_constraints(
1314    value: &str,
1315    must_chars: &str,
1316    may_chars: &str,
1317    may_include_other_chars: bool,
1318) -> bool {
1319    let value_chars: std::collections::BTreeSet<char> = value.chars().collect();
1320    let must_set: std::collections::BTreeSet<char> = must_chars.chars().collect();
1321    let may_set: std::collections::BTreeSet<char> = may_chars.chars().collect();
1322
1323    if must_set.iter().any(|char| !value_chars.contains(char)) {
1324        return false;
1325    }
1326    if !may_include_other_chars && value_chars.iter().any(|char| !may_set.contains(char)) {
1327        return false;
1328    }
1329    true
1330}
1331
1332fn push_canonical_match(style: &StyleAnalysisInputV2, view_name: &str, names: &mut Vec<String>) {
1333    if let Some(canonical_name) = canonical_name_for_view_name(style, view_name)
1334        && !names.contains(&canonical_name)
1335    {
1336        names.push(canonical_name);
1337    }
1338}
1339
1340fn canonical_selector_names(style: &StyleAnalysisInputV2) -> Vec<String> {
1341    let mut names = Vec::new();
1342    for selector in &style.document.selectors {
1343        if selector.view_kind == "canonical"
1344            && let Some(canonical_name) = selector.canonical_name.clone()
1345            && !names.contains(&canonical_name)
1346        {
1347            names.push(canonical_name);
1348        }
1349    }
1350    names
1351}
1352
1353pub(crate) fn canonical_selector_count(style: &StyleAnalysisInputV2) -> usize {
1354    canonical_selector_names(style).len()
1355}
1356
1357fn canonical_name_for_selector(
1358    style: &StyleAnalysisInputV2,
1359    selector: &StyleSelectorV2,
1360) -> Option<String> {
1361    canonical_name_for_view_name(style, &selector.name)
1362}
1363
1364fn canonical_name_for_view_name(style: &StyleAnalysisInputV2, view_name: &str) -> Option<String> {
1365    let matched = style
1366        .document
1367        .selectors
1368        .iter()
1369        .find(|selector| selector.name == view_name)?;
1370    let canonical = style.document.selectors.iter().find(|selector| {
1371        selector.view_kind == "canonical" && selector.canonical_name == matched.canonical_name
1372    });
1373    canonical
1374        .and_then(|selector| selector.canonical_name.clone())
1375        .or_else(|| matched.canonical_name.clone())
1376        .or_else(|| Some(matched.name.clone()))
1377}