1use std::collections::BTreeMap;
2
3use crate::{
4 EngineInputV2, ExpressionSemanticsCandidateV0, ExpressionSemanticsCandidatesV0,
5 ExpressionSemanticsCanonicalCandidateBundleV0, ExpressionSemanticsCanonicalProducerSignalV0,
6 ExpressionSemanticsEvaluatorCandidatePayloadV0, ExpressionSemanticsEvaluatorCandidateV0,
7 ExpressionSemanticsEvaluatorCandidatesV0, ExpressionSemanticsFragmentV0,
8 ExpressionSemanticsFragmentsV0, ExpressionSemanticsMatchFragmentV0,
9 ExpressionSemanticsMatchFragmentsV0, ExpressionSemanticsQueryFragmentV0,
10 ExpressionSemanticsQueryFragmentsV0, canonical_selector_count, finite_values_for_facts,
11 map_expression_value_domain_kind, map_reduced_expression_value_domain_derivation,
12 map_reduced_expression_value_domain_provenance_tree, map_selector_certainty,
13 map_selector_certainty_shape_kind, map_selector_certainty_shape_label, map_value_certainty,
14 map_value_certainty_shape_kind, map_value_certainty_shape_label, resolve_selector_names,
15};
16
17struct ExpressionSemanticsInputRows {
18 query_fragments: Vec<ExpressionSemanticsQueryFragmentV0>,
19 fragments: Vec<ExpressionSemanticsFragmentV0>,
20 match_fragments: Vec<ExpressionSemanticsMatchFragmentV0>,
21 candidates: Vec<ExpressionSemanticsCandidateV0>,
22 evaluator_candidates: Vec<ExpressionSemanticsEvaluatorCandidateV0>,
23}
24
25fn collect_expression_semantics_input_rows(input: &EngineInputV2) -> ExpressionSemanticsInputRows {
26 let mut expression_index = BTreeMap::new();
27 let mut style_index = BTreeMap::new();
28 let mut query_fragments = Vec::new();
29
30 for source in &input.sources {
31 for expression in &source.document.class_expressions {
32 expression_index.insert(expression.id.clone(), expression);
33 query_fragments.push(ExpressionSemanticsQueryFragmentV0 {
34 query_id: expression.id.clone(),
35 expression_id: expression.id.clone(),
36 expression_kind: expression.kind.clone(),
37 style_file_path: expression.scss_module_path.clone(),
38 });
39 }
40 }
41
42 for style in &input.styles {
43 style_index.insert(style.file_path.clone(), style);
44 }
45
46 let mut fragments = Vec::new();
47 let mut match_fragments = Vec::new();
48 let mut candidates = Vec::new();
49 let mut evaluator_candidates = Vec::new();
50
51 for entry in &input.type_facts {
52 let Some(expression) = expression_index.get(&entry.expression_id) else {
53 continue;
54 };
55 let Some(style) = style_index.get(&expression.scss_module_path) else {
56 continue;
57 };
58
59 let selector_names = resolve_selector_names(style, &entry.facts);
60 let finite_values = finite_values_for_facts(&entry.facts);
61 let candidate_names = finite_values
62 .clone()
63 .unwrap_or_else(|| selector_names.clone());
64 let selector_certainty = map_selector_certainty(
65 &entry.facts,
66 selector_names.len(),
67 canonical_selector_count(style),
68 );
69 let value_certainty = map_value_certainty(&entry.facts);
70 let selector_certainty_shape_label = map_selector_certainty_shape_label(
71 &entry.facts,
72 selector_names.len(),
73 canonical_selector_count(style),
74 );
75 let selector_certainty_shape_kind = map_selector_certainty_shape_kind(
76 &entry.facts,
77 selector_names.len(),
78 canonical_selector_count(style),
79 );
80 let value_certainty_shape_kind = map_value_certainty_shape_kind(&entry.facts);
81 let value_certainty_shape_label = map_value_certainty_shape_label(&entry.facts);
82
83 fragments.push(ExpressionSemanticsFragmentV0 {
84 query_id: entry.expression_id.clone(),
85 expression_id: entry.expression_id.clone(),
86 expression_kind: expression.kind.clone(),
87 style_file_path: expression.scss_module_path.clone(),
88 value_domain_kind: map_expression_value_domain_kind(&entry.facts),
89 value_constraint_kind: entry.facts.constraint_kind.clone(),
90 value_prefix: entry.facts.prefix.clone(),
91 value_suffix: entry.facts.suffix.clone(),
92 value_min_len: entry.facts.min_len,
93 value_max_len: entry.facts.max_len,
94 value_char_must: entry.facts.char_must.clone(),
95 value_char_may: entry.facts.char_may.clone(),
96 value_may_include_other_chars: entry.facts.may_include_other_chars,
97 });
98
99 match_fragments.push(ExpressionSemanticsMatchFragmentV0 {
100 query_id: entry.expression_id.clone(),
101 expression_id: entry.expression_id.clone(),
102 style_file_path: expression.scss_module_path.clone(),
103 selector_names: selector_names.clone(),
104 candidate_names: candidate_names.clone(),
105 finite_values: finite_values.clone(),
106 });
107
108 let candidate = ExpressionSemanticsCandidateV0 {
109 query_id: entry.expression_id.clone(),
110 expression_id: entry.expression_id.clone(),
111 expression_kind: expression.kind.clone(),
112 style_file_path: expression.scss_module_path.clone(),
113 selector_names,
114 candidate_names,
115 finite_values,
116 value_domain_kind: map_expression_value_domain_kind(&entry.facts),
117 selector_certainty,
118 value_certainty,
119 selector_certainty_shape_kind,
120 selector_certainty_shape_label,
121 value_certainty_shape_kind,
122 value_certainty_shape_label,
123 selector_constraint_kind: entry.facts.constraint_kind.clone(),
124 value_certainty_constraint_kind: entry.facts.constraint_kind.clone(),
125 value_constraint_kind: entry.facts.constraint_kind.clone(),
126 value_prefix: entry.facts.prefix.clone(),
127 value_suffix: entry.facts.suffix.clone(),
128 value_min_len: entry.facts.min_len,
129 value_max_len: entry.facts.max_len,
130 value_char_must: entry.facts.char_must.clone(),
131 value_char_may: entry.facts.char_may.clone(),
132 value_may_include_other_chars: entry.facts.may_include_other_chars,
133 };
134
135 candidates.push(candidate.clone());
136
137 evaluator_candidates.push(ExpressionSemanticsEvaluatorCandidateV0 {
138 kind: "expression-semantics",
139 file_path: entry.file_path.clone(),
140 query_id: entry.expression_id.clone(),
141 payload: ExpressionSemanticsEvaluatorCandidatePayloadV0 {
142 expression_id: entry.expression_id.clone(),
143 expression_kind: candidate.expression_kind.clone(),
144 style_file_path: candidate.style_file_path.clone(),
145 selector_names: candidate.selector_names.clone(),
146 candidate_names: candidate.candidate_names.clone(),
147 finite_values: candidate.finite_values.clone(),
148 value_domain_kind: candidate.value_domain_kind.clone(),
149 selector_certainty: candidate.selector_certainty.clone(),
150 value_certainty: candidate.value_certainty.clone(),
151 selector_certainty_shape_kind: candidate.selector_certainty_shape_kind.clone(),
152 selector_certainty_shape_label: candidate.selector_certainty_shape_label.clone(),
153 value_certainty_shape_kind: candidate.value_certainty_shape_kind.clone(),
154 value_certainty_shape_label: candidate.value_certainty_shape_label.clone(),
155 selector_constraint_kind: candidate.selector_constraint_kind.clone(),
156 value_certainty_constraint_kind: candidate.value_certainty_constraint_kind.clone(),
157 value_constraint_kind: candidate.value_constraint_kind.clone(),
158 value_prefix: candidate.value_prefix.clone(),
159 value_suffix: candidate.value_suffix.clone(),
160 value_min_len: candidate.value_min_len,
161 value_max_len: candidate.value_max_len,
162 value_char_must: candidate.value_char_must.clone(),
163 value_char_may: candidate.value_char_may.clone(),
164 value_may_include_other_chars: candidate.value_may_include_other_chars,
165 value_domain_derivation: map_reduced_expression_value_domain_derivation(
166 &entry.facts,
167 ),
168 value_domain_provenance_tree: map_reduced_expression_value_domain_provenance_tree(
169 &entry.facts,
170 ),
171 },
172 });
173 }
174
175 query_fragments.sort_by(|a, b| a.query_id.cmp(&b.query_id));
176 fragments.sort_by(|a, b| a.query_id.cmp(&b.query_id));
177 match_fragments.sort_by(|a, b| a.query_id.cmp(&b.query_id));
178 candidates.sort_by(|a, b| a.query_id.cmp(&b.query_id));
179 evaluator_candidates.sort_by(|a, b| a.query_id.cmp(&b.query_id));
180
181 ExpressionSemanticsInputRows {
182 query_fragments,
183 fragments,
184 match_fragments,
185 candidates,
186 evaluator_candidates,
187 }
188}
189
190pub fn summarize_expression_semantics_canonical_candidate_bundle_input(
191 input: &EngineInputV2,
192) -> ExpressionSemanticsCanonicalCandidateBundleV0 {
193 let rows = collect_expression_semantics_input_rows(input);
194
195 ExpressionSemanticsCanonicalCandidateBundleV0 {
196 schema_version: "0",
197 input_version: input.version.clone(),
198 query_fragments: rows.query_fragments,
199 fragments: rows.fragments,
200 match_fragments: rows.match_fragments,
201 candidates: rows.candidates,
202 }
203}
204
205pub fn summarize_expression_semantics_candidates_input(
206 input: &EngineInputV2,
207) -> ExpressionSemanticsCandidatesV0 {
208 let rows = collect_expression_semantics_input_rows(input);
209
210 ExpressionSemanticsCandidatesV0 {
211 schema_version: "0",
212 input_version: input.version.clone(),
213 candidates: rows.candidates,
214 }
215}
216
217pub fn summarize_expression_semantics_evaluator_candidates_input(
218 input: &EngineInputV2,
219) -> ExpressionSemanticsEvaluatorCandidatesV0 {
220 let rows = collect_expression_semantics_input_rows(input);
221
222 ExpressionSemanticsEvaluatorCandidatesV0 {
223 schema_version: "0",
224 input_version: input.version.clone(),
225 results: rows.evaluator_candidates,
226 }
227}
228
229pub fn summarize_expression_semantics_canonical_producer_signal_input(
230 input: &EngineInputV2,
231) -> ExpressionSemanticsCanonicalProducerSignalV0 {
232 let rows = collect_expression_semantics_input_rows(input);
233 let input_version = input.version.clone();
234
235 ExpressionSemanticsCanonicalProducerSignalV0 {
236 schema_version: "0",
237 input_version: input_version.clone(),
238 canonical_bundle: ExpressionSemanticsCanonicalCandidateBundleV0 {
239 schema_version: "0",
240 input_version: input_version.clone(),
241 query_fragments: rows.query_fragments.clone(),
242 fragments: rows.fragments.clone(),
243 match_fragments: rows.match_fragments.clone(),
244 candidates: rows.candidates.clone(),
245 },
246 evaluator_candidates: ExpressionSemanticsEvaluatorCandidatesV0 {
247 schema_version: "0",
248 input_version,
249 results: rows.evaluator_candidates,
250 },
251 }
252}
253
254pub fn summarize_expression_semantics_fragments_input(
255 input: &EngineInputV2,
256) -> ExpressionSemanticsFragmentsV0 {
257 let rows = collect_expression_semantics_input_rows(input);
258
259 ExpressionSemanticsFragmentsV0 {
260 schema_version: "0",
261 input_version: input.version.clone(),
262 fragments: rows.fragments,
263 }
264}
265
266pub fn summarize_expression_semantics_query_fragments_input(
267 input: &EngineInputV2,
268) -> ExpressionSemanticsQueryFragmentsV0 {
269 let rows = collect_expression_semantics_input_rows(input);
270
271 ExpressionSemanticsQueryFragmentsV0 {
272 schema_version: "0",
273 input_version: input.version.clone(),
274 fragments: rows.query_fragments,
275 }
276}
277
278pub fn summarize_expression_semantics_match_fragments_input(
279 input: &EngineInputV2,
280) -> ExpressionSemanticsMatchFragmentsV0 {
281 let rows = collect_expression_semantics_input_rows(input);
282
283 ExpressionSemanticsMatchFragmentsV0 {
284 schema_version: "0",
285 input_version: input.version.clone(),
286 fragments: rows.match_fragments,
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::{
293 summarize_expression_semantics_candidates_input,
294 summarize_expression_semantics_canonical_candidate_bundle_input,
295 summarize_expression_semantics_canonical_producer_signal_input,
296 summarize_expression_semantics_evaluator_candidates_input,
297 summarize_expression_semantics_fragments_input,
298 summarize_expression_semantics_match_fragments_input,
299 summarize_expression_semantics_query_fragments_input,
300 };
301 use crate::test_support::sample_input;
302
303 #[test]
304 fn builds_expression_semantics_fragment_from_type_fact() {
305 let summary = summarize_expression_semantics_fragments_input(&sample_input());
306
307 assert_eq!(summary.fragments.len(), 2);
308 let fragment = &summary.fragments[0];
309 assert_eq!(fragment.query_id, "expr-1");
310 assert_eq!(fragment.expression_id, "expr-1");
311 assert_eq!(fragment.expression_kind, "symbolRef");
312 assert_eq!(fragment.style_file_path, "/tmp/App.module.scss");
313 assert_eq!(fragment.value_domain_kind, "constrained");
314 assert_eq!(
315 fragment.value_constraint_kind.as_deref(),
316 Some("prefixSuffix")
317 );
318 assert_eq!(fragment.value_prefix.as_deref(), Some("btn-"));
319 assert_eq!(fragment.value_suffix.as_deref(), Some("-active"));
320 assert_eq!(fragment.value_min_len, Some(10));
321
322 let second = &summary.fragments[1];
323 assert_eq!(second.query_id, "expr-2");
324 assert_eq!(second.value_domain_kind, "finiteSet");
325 }
326
327 #[test]
328 fn builds_expression_semantics_query_fragments_from_input() {
329 let summary = summarize_expression_semantics_query_fragments_input(&sample_input());
330
331 assert_eq!(summary.fragments.len(), 2);
332 let first = &summary.fragments[0];
333 assert_eq!(first.query_id, "expr-1");
334 assert_eq!(first.expression_id, "expr-1");
335 assert_eq!(first.expression_kind, "symbolRef");
336 assert_eq!(first.style_file_path, "/tmp/App.module.scss");
337
338 let second = &summary.fragments[1];
339 assert_eq!(second.query_id, "expr-2");
340 assert_eq!(second.expression_kind, "styleAccess");
341 assert_eq!(second.style_file_path, "/tmp/Card.module.scss");
342 }
343
344 #[test]
345 fn builds_expression_semantics_match_fragments_from_input() {
346 let summary = summarize_expression_semantics_match_fragments_input(&sample_input());
347
348 assert_eq!(summary.fragments.len(), 2);
349 let first = &summary.fragments[0];
350 assert_eq!(first.query_id, "expr-1");
351 assert_eq!(first.expression_id, "expr-1");
352 assert_eq!(first.style_file_path, "/tmp/App.module.scss");
353 assert_eq!(first.selector_names, vec!["btn-active".to_string()]);
354 assert_eq!(first.candidate_names, vec!["btn-active".to_string()]);
355 assert!(first.finite_values.is_none());
356
357 let second = &summary.fragments[1];
358 assert_eq!(second.query_id, "expr-2");
359 assert_eq!(second.selector_names, vec!["card-header".to_string()]);
360 assert_eq!(
361 second.candidate_names,
362 vec!["card-header".to_string(), "card-body".to_string()]
363 );
364 assert_eq!(
365 second.finite_values,
366 Some(vec!["card-header".to_string(), "card-body".to_string()])
367 );
368 }
369
370 #[test]
371 fn builds_expression_semantics_candidates_from_input() {
372 let summary = summarize_expression_semantics_candidates_input(&sample_input());
373
374 assert_eq!(summary.candidates.len(), 2);
375 let first = &summary.candidates[0];
376 assert_eq!(first.query_id, "expr-1");
377 assert_eq!(first.expression_kind, "symbolRef");
378 assert_eq!(first.style_file_path, "/tmp/App.module.scss");
379 assert_eq!(first.selector_names, vec!["btn-active".to_string()]);
380 assert_eq!(first.candidate_names, vec!["btn-active".to_string()]);
381 assert_eq!(first.value_domain_kind, "constrained");
382 assert_eq!(first.selector_certainty, "inferred");
383 assert_eq!(first.value_certainty.as_deref(), Some("inferred"));
384 assert_eq!(first.selector_certainty_shape_kind, "constrained");
385 assert_eq!(
386 first.selector_certainty_shape_label,
387 "constrained edge selector set (1)"
388 );
389 assert_eq!(first.value_certainty_shape_kind, "constrained");
390 assert_eq!(
391 first.value_certainty_shape_label,
392 "constrained prefix `btn-` + suffix `-active`"
393 );
394 assert_eq!(
395 first.selector_constraint_kind.as_deref(),
396 Some("prefixSuffix")
397 );
398 assert_eq!(
399 first.value_certainty_constraint_kind.as_deref(),
400 Some("prefixSuffix")
401 );
402 assert_eq!(first.value_constraint_kind.as_deref(), Some("prefixSuffix"));
403 assert_eq!(first.value_prefix.as_deref(), Some("btn-"));
404 assert_eq!(first.value_suffix.as_deref(), Some("-active"));
405
406 let second = &summary.candidates[1];
407 assert_eq!(second.query_id, "expr-2");
408 assert_eq!(second.expression_kind, "styleAccess");
409 assert_eq!(second.selector_names, vec!["card-header".to_string()]);
410 assert_eq!(
411 second.finite_values,
412 Some(vec!["card-header".to_string(), "card-body".to_string()])
413 );
414 assert_eq!(second.value_domain_kind, "finiteSet");
415 assert_eq!(second.selector_certainty, "inferred");
416 assert_eq!(second.value_certainty.as_deref(), Some("inferred"));
417 assert_eq!(second.selector_certainty_shape_kind, "boundedFinite");
418 assert_eq!(
419 second.selector_certainty_shape_label,
420 "bounded selector set (1)"
421 );
422 assert_eq!(second.value_certainty_shape_kind, "boundedFinite");
423 assert_eq!(second.value_certainty_shape_label, "bounded finite (2)");
424 }
425
426 #[test]
427 fn builds_expression_semantics_canonical_candidate_bundle() {
428 let bundle =
429 summarize_expression_semantics_canonical_candidate_bundle_input(&sample_input());
430
431 assert_eq!(bundle.query_fragments.len(), 2);
432 assert_eq!(bundle.fragments.len(), 2);
433 assert_eq!(bundle.match_fragments.len(), 2);
434 assert_eq!(bundle.candidates.len(), 2);
435 assert_eq!(bundle.query_fragments[0].query_id, "expr-1");
436 assert_eq!(bundle.candidates[0].query_id, "expr-1");
437 }
438
439 #[test]
440 fn builds_expression_semantics_evaluator_candidates() {
441 let summary = summarize_expression_semantics_evaluator_candidates_input(&sample_input());
442
443 assert_eq!(summary.results.len(), 2);
444 let first = &summary.results[0];
445 assert_eq!(first.kind, "expression-semantics");
446 assert_eq!(first.file_path, "/tmp/App.tsx");
447 assert_eq!(first.query_id, "expr-1");
448 assert_eq!(first.payload.expression_kind, "symbolRef");
449 assert_eq!(first.payload.value_domain_kind, "constrained");
450 assert_eq!(
451 first.payload.value_constraint_kind.as_deref(),
452 Some("prefixSuffix")
453 );
454 assert_eq!(
455 first.payload.value_domain_derivation.product,
456 "omena-abstract-value.reduced-class-value-derivation"
457 );
458 assert_eq!(
459 first.payload.value_domain_derivation.reduced_kind,
460 "prefixSuffix"
461 );
462 assert_eq!(
463 first.payload.value_domain_provenance_tree.product,
464 "omena-abstract-value.provenance-tree"
465 );
466 assert_eq!(
467 first.payload.value_domain_provenance_tree.root.operation,
468 "constraintDomain"
469 );
470 }
471
472 #[test]
473 fn builds_expression_semantics_canonical_producer_signal() {
474 let summary =
475 summarize_expression_semantics_canonical_producer_signal_input(&sample_input());
476
477 assert_eq!(summary.canonical_bundle.candidates.len(), 2);
478 assert_eq!(summary.evaluator_candidates.results.len(), 2);
479 assert_eq!(summary.evaluator_candidates.results[0].query_id, "expr-1");
480 }
481}