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