Skip to main content

engine_input_producers/
lib.rs

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