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}