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