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