1use super::*;
2
3pub(super) fn summarize_omena_query_cross_file_summary(
4 style_fact_entries: &[OmenaQueryStyleFactEntry],
5 css_modules_resolution: &OmenaQueryCssModulesCrossFileResolutionV0,
6 sass_module_resolution: &OmenaQuerySassModuleCrossFileResolutionV0,
7) -> OmenaQueryCrossFileSummaryV0 {
8 let mut edges = Vec::new();
9
10 for edge in &css_modules_resolution.edges {
11 let edge_kind = match edge.import_kind {
12 "composes" => "cssModulesComposesImport",
13 "value" => "cssModulesValueImport",
14 "icss" => "cssModulesIcssImport",
15 _ => "cssModulesImport",
16 };
17 let provenance = match edge.import_kind {
18 "composes" => vec![
19 "omena-query.css-modules-cross-file-resolution",
20 "omena-parser.css-module-composes-facts",
21 ],
22 "value" => vec![
23 "omena-query.css-modules-cross-file-resolution",
24 "omena-parser.css-module-value-facts",
25 ],
26 "icss" => vec![
27 "omena-query.css-modules-cross-file-resolution",
28 "omena-parser.icss-facts",
29 ],
30 _ => vec!["omena-query.css-modules-cross-file-resolution"],
31 };
32 edges.push(build_omena_query_cross_file_summary_edge(
33 OmenaQueryCrossFileSummaryEdgeInput {
34 edge_kind,
35 from_kind: "style",
36 from_path: edge.from_style_path.clone(),
37 target_kind: edge.resolved_style_path.as_ref().map(|_| "style"),
38 target_path: edge.resolved_style_path.clone(),
39 source: Some(edge.source.clone()),
40 owner_selector_name: None,
41 local_name: None,
42 remote_name: None,
43 target_names: edge.imported_names.clone(),
44 status: edge.status,
45 provenance,
46 },
47 ));
48 }
49
50 for edge in &css_modules_resolution.composes_closure_edges {
51 edges.push(build_omena_query_cross_file_summary_edge(
52 OmenaQueryCrossFileSummaryEdgeInput {
53 edge_kind: "cssModulesComposesClosure",
54 from_kind: "style",
55 from_path: edge.from_style_path.clone(),
56 target_kind: Some("style"),
57 target_path: Some(edge.target_style_path.clone()),
58 source: None,
59 owner_selector_name: Some(edge.owner_selector_name.clone()),
60 local_name: None,
61 remote_name: Some(edge.target_selector_name.clone()),
62 target_names: vec![edge.target_selector_name.clone()],
63 status: "reachable",
64 provenance: vec![
65 "omena-query.css-modules-cross-file-resolution",
66 "omena-parser.css-module-composes-facts",
67 ],
68 },
69 ));
70 }
71
72 for edge in &css_modules_resolution.value_closure_edges {
73 edges.push(build_omena_query_cross_file_summary_edge(
74 OmenaQueryCrossFileSummaryEdgeInput {
75 edge_kind: "cssModulesValueClosure",
76 from_kind: "style",
77 from_path: edge.from_style_path.clone(),
78 target_kind: Some("style"),
79 target_path: Some(edge.target_style_path.clone()),
80 source: None,
81 owner_selector_name: None,
82 local_name: Some(edge.value_name.clone()),
83 remote_name: Some(edge.target_value_name.clone()),
84 target_names: vec![edge.target_value_name.clone()],
85 status: "reachable",
86 provenance: vec![
87 "omena-query.css-modules-cross-file-resolution",
88 "omena-parser.css-module-value-facts",
89 ],
90 },
91 ));
92 }
93
94 for edge in &css_modules_resolution.icss_closure_edges {
95 edges.push(build_omena_query_cross_file_summary_edge(
96 OmenaQueryCrossFileSummaryEdgeInput {
97 edge_kind: "cssModulesIcssClosure",
98 from_kind: "style",
99 from_path: edge.from_style_path.clone(),
100 target_kind: Some("style"),
101 target_path: Some(edge.target_style_path.clone()),
102 source: None,
103 owner_selector_name: None,
104 local_name: Some(edge.name.clone()),
105 remote_name: Some(edge.target_name.clone()),
106 target_names: vec![edge.target_name.clone()],
107 status: "reachable",
108 provenance: vec![
109 "omena-query.css-modules-cross-file-resolution",
110 "omena-parser.icss-facts",
111 ],
112 },
113 ));
114 }
115
116 for edge in &sass_module_resolution.edges {
117 edges.push(build_omena_query_cross_file_summary_edge(
118 OmenaQueryCrossFileSummaryEdgeInput {
119 edge_kind: edge.edge_kind,
120 from_kind: "style",
121 from_path: edge.from_style_path.clone(),
122 target_kind: edge.resolved_style_path.as_ref().map(|_| "style"),
123 target_path: edge.resolved_style_path.clone(),
124 source: Some(edge.source.clone()),
125 owner_selector_name: None,
126 local_name: edge.namespace.clone(),
127 remote_name: edge.forward_prefix.clone(),
128 target_names: edge.visibility_filter_names.clone(),
129 status: edge.status,
130 provenance: vec![
131 "omena-query.sass-module-cross-file-resolution",
132 "omena-parser.sass-module-facts",
133 ],
134 },
135 ));
136 }
137
138 for edge in &sass_module_resolution.graph_closure_edges {
139 edges.push(build_omena_query_cross_file_summary_edge(
140 OmenaQueryCrossFileSummaryEdgeInput {
141 edge_kind: "sassModuleGraphClosure",
142 from_kind: "style",
143 from_path: edge.from_style_path.clone(),
144 target_kind: Some("style"),
145 target_path: Some(edge.target_style_path.clone()),
146 source: None,
147 owner_selector_name: None,
148 local_name: edge.namespace.clone(),
149 remote_name: edge.forward_prefix.clone(),
150 target_names: edge.visibility_filter_names.clone(),
151 status: "reachable",
152 provenance: vec![
153 "omena-query.sass-module-cross-file-resolution",
154 "omena-parser.sass-module-facts",
155 ],
156 },
157 ));
158 }
159
160 let design_token_declarations = collect_design_token_declarations_by_name(style_fact_entries);
161 let design_token_reachability =
162 collect_design_token_reachable_style_paths_by_origin(sass_module_resolution);
163
164 for entry in style_fact_entries {
165 let local_declarations = entry
166 .facts
167 .custom_property_decl_names
168 .iter()
169 .map(String::as_str)
170 .collect::<BTreeSet<_>>();
171 for name in &entry.facts.custom_property_ref_names {
172 let target = resolve_design_token_reference_target(
173 entry.style_path.as_str(),
174 name.as_str(),
175 &local_declarations,
176 &design_token_declarations,
177 &design_token_reachability,
178 );
179 let provenance = target.provenance();
180 let target_style_path = target.target_style_path;
181 let status = target.status;
182 edges.push(build_omena_query_cross_file_summary_edge(
183 OmenaQueryCrossFileSummaryEdgeInput {
184 edge_kind: "styleDesignTokenReference",
185 from_kind: "style",
186 from_path: entry.style_path.clone(),
187 target_kind: target_style_path.as_ref().map(|_| "style"),
188 target_path: target_style_path,
189 source: None,
190 owner_selector_name: None,
191 local_name: Some(name.clone()),
192 remote_name: None,
193 target_names: vec![name.clone()],
194 status,
195 provenance,
196 },
197 ));
198 }
199 }
200
201 edges.sort_by_key(|edge| edge.edge_id.clone());
202 let summary_hash = stable_omena_query_cross_file_summary_hash(&edges);
203
204 OmenaQueryCrossFileSummaryV0 {
205 schema_version: "0",
206 product: "omena-query.cross-file-summary",
207 status: "summaryEdgeSeed",
208 summary_scope: "styleSemanticGraphBatch",
209 style_count: style_fact_entries.len(),
210 summary_edge_count: edges.len(),
211 edge_kind_counts: summarize_omena_query_cross_file_summary_edge_kind_counts(&edges),
212 summary_hash,
213 edges,
214 capabilities: OmenaQueryCrossFileSummaryCapabilitiesV0 {
215 css_modules_composes_edges_ready: true,
216 css_modules_value_edges_ready: true,
217 css_modules_icss_edges_ready: true,
218 sass_module_edges_ready: true,
219 style_design_token_reference_edges_ready: true,
220 source_selector_reference_edges_ready: false,
221 stable_summary_hash_ready: true,
222 linear_provenance_ready: true,
223 linear_provenance_round_trip_ready: true,
224 linear_provenance_semiring_laws_hold:
225 summarize_omena_query_linear_provenance_semiring_laws().all_fixture_laws_hold,
226 },
227 next_priorities: vec![
228 "sourceSelectorReferenceSummaryEdges",
229 "summaryEdgeEquivalenceGate",
230 ],
231 }
232}
233
234#[derive(Debug, Clone)]
235struct DesignTokenReferenceTarget {
236 target_style_path: Option<String>,
237 status: &'static str,
238 resolution_provenance: Option<&'static str>,
239}
240
241impl DesignTokenReferenceTarget {
242 fn provenance(&self) -> Vec<&'static str> {
243 let mut provenance = vec![
244 "omena-query.style-semantic-graph-batch",
245 "omena-parser.custom-property-facts",
246 ];
247 if let Some(resolution_provenance) = self.resolution_provenance {
248 provenance.push(resolution_provenance);
249 }
250 provenance
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
255struct DesignTokenReachableStylePath {
256 distance: usize,
257 target_style_path: String,
258}
259
260fn collect_design_token_declarations_by_name(
261 style_fact_entries: &[OmenaQueryStyleFactEntry],
262) -> BTreeMap<String, BTreeSet<String>> {
263 let mut declarations_by_name = BTreeMap::<String, BTreeSet<String>>::new();
264 for entry in style_fact_entries {
265 for name in &entry.facts.custom_property_decl_names {
266 declarations_by_name
267 .entry(name.clone())
268 .or_default()
269 .insert(entry.style_path.clone());
270 }
271 }
272 declarations_by_name
273}
274
275fn collect_design_token_reachable_style_paths_by_origin(
276 sass_module_resolution: &OmenaQuerySassModuleCrossFileResolutionV0,
277) -> BTreeMap<String, Vec<DesignTokenReachableStylePath>> {
278 let mut reachable_by_origin =
279 BTreeMap::<String, BTreeSet<DesignTokenReachableStylePath>>::new();
280
281 for edge in &sass_module_resolution.edges {
282 if edge.status != "resolved" {
283 continue;
284 }
285 let Some(target_style_path) = edge.resolved_style_path.as_ref() else {
286 continue;
287 };
288 reachable_by_origin
289 .entry(edge.from_style_path.clone())
290 .or_default()
291 .insert(DesignTokenReachableStylePath {
292 distance: 1,
293 target_style_path: target_style_path.clone(),
294 });
295 }
296
297 for edge in &sass_module_resolution.graph_closure_edges {
298 reachable_by_origin
299 .entry(edge.from_style_path.clone())
300 .or_default()
301 .insert(DesignTokenReachableStylePath {
302 distance: edge.depth,
303 target_style_path: edge.target_style_path.clone(),
304 });
305 }
306
307 reachable_by_origin
308 .into_iter()
309 .map(|(origin, reachable)| (origin, reachable.into_iter().collect()))
310 .collect()
311}
312
313fn resolve_design_token_reference_target(
314 from_style_path: &str,
315 name: &str,
316 local_declarations: &BTreeSet<&str>,
317 declarations_by_name: &BTreeMap<String, BTreeSet<String>>,
318 reachable_by_origin: &BTreeMap<String, Vec<DesignTokenReachableStylePath>>,
319) -> DesignTokenReferenceTarget {
320 if local_declarations.contains(name) {
321 return DesignTokenReferenceTarget {
322 target_style_path: Some(from_style_path.to_string()),
323 status: "localResolved",
324 resolution_provenance: None,
325 };
326 }
327
328 let Some(declaration_paths) = declarations_by_name.get(name) else {
329 return DesignTokenReferenceTarget {
330 target_style_path: None,
331 status: "unresolvedReference",
332 resolution_provenance: None,
333 };
334 };
335
336 let Some(reachable_paths) = reachable_by_origin.get(from_style_path) else {
337 return DesignTokenReferenceTarget {
338 target_style_path: None,
339 status: "unresolvedReference",
340 resolution_provenance: None,
341 };
342 };
343
344 let target_style_path = reachable_paths
345 .iter()
346 .filter(|reachable| declaration_paths.contains(reachable.target_style_path.as_str()))
347 .min_by_key(|reachable| (reachable.distance, reachable.target_style_path.as_str()))
348 .map(|reachable| reachable.target_style_path.clone());
349
350 if let Some(target_style_path) = target_style_path {
351 DesignTokenReferenceTarget {
352 target_style_path: Some(target_style_path),
353 status: "importResolved",
354 resolution_provenance: Some("omena-query.sass-module-cross-file-resolution"),
355 }
356 } else {
357 DesignTokenReferenceTarget {
358 target_style_path: None,
359 status: "unresolvedReference",
360 resolution_provenance: None,
361 }
362 }
363}
364
365pub fn summarize_omena_query_source_selector_reference_cross_file_summary(
366 style_sources: &[OmenaQueryStyleSourceInputV0],
367 source_documents: &[OmenaQuerySourceDocumentInputV0],
368 package_manifests: &[OmenaQueryStylePackageManifestV0],
369) -> OmenaQueryCrossFileSummaryV0 {
370 let definitions =
371 super::source_refs::summarize_omena_query_style_selector_definitions(style_sources);
372 let references = super::source_refs::collect_omena_query_source_selector_references(
373 style_sources,
374 source_documents,
375 package_manifests,
376 );
377 let mut edges = references
378 .into_iter()
379 .map(|reference| {
380 let candidate = reference.candidate;
381 let source_candidate = OmenaQuerySourceSelectorCandidateV0 {
382 kind: candidate.kind,
383 name: candidate.name.clone(),
384 range: candidate.range,
385 source: candidate.source,
386 target_style_uri: candidate.target_style_uri.clone(),
387 };
388 let matched_definitions =
389 resolve_omena_query_style_selector_definitions_for_source_candidate(
390 &source_candidate,
391 definitions.as_slice(),
392 );
393 let target_names = if candidate.kind == "sourceSelectorPrefixReference" {
394 matched_definitions
395 .iter()
396 .map(|definition| definition.name.clone())
397 .collect::<Vec<_>>()
398 } else {
399 vec![candidate.name.clone()]
400 };
401 let status = if matched_definitions.is_empty() {
402 "unresolved"
403 } else {
404 "resolved"
405 };
406 build_omena_query_cross_file_summary_edge(OmenaQueryCrossFileSummaryEdgeInput {
407 edge_kind: candidate.kind,
408 from_kind: "source",
409 from_path: candidate.uri,
410 target_kind: candidate.target_style_uri.as_ref().map(|_| "style"),
411 target_path: candidate.target_style_uri,
412 source: None,
413 owner_selector_name: None,
414 local_name: Some(candidate.name),
415 remote_name: None,
416 target_names,
417 status,
418 provenance: vec![
419 "omena-query.source-selector-references",
420 "omena-query.style-selector-definitions",
421 ],
422 })
423 })
424 .collect::<Vec<_>>();
425
426 edges.sort_by_key(|edge| edge.edge_id.clone());
427 let summary_hash = stable_omena_query_cross_file_summary_hash(&edges);
428
429 OmenaQueryCrossFileSummaryV0 {
430 schema_version: "0",
431 product: "omena-query.cross-file-summary",
432 status: "sourceSelectorSummaryEdgeSeed",
433 summary_scope: "sourceSelectorReferences",
434 style_count: style_sources.len(),
435 summary_edge_count: edges.len(),
436 edge_kind_counts: summarize_omena_query_cross_file_summary_edge_kind_counts(&edges),
437 summary_hash,
438 edges,
439 capabilities: OmenaQueryCrossFileSummaryCapabilitiesV0 {
440 css_modules_composes_edges_ready: false,
441 css_modules_value_edges_ready: false,
442 css_modules_icss_edges_ready: false,
443 sass_module_edges_ready: false,
444 style_design_token_reference_edges_ready: false,
445 source_selector_reference_edges_ready: true,
446 stable_summary_hash_ready: true,
447 linear_provenance_ready: true,
448 linear_provenance_round_trip_ready: true,
449 linear_provenance_semiring_laws_hold:
450 summarize_omena_query_linear_provenance_semiring_laws().all_fixture_laws_hold,
451 },
452 next_priorities: vec!["sourceSelectorReferenceSummaryEquivalenceGate"],
453 }
454}
455
456pub fn summarize_omena_query_workspace_cross_file_summary(
457 style_sources: &[OmenaQueryStyleSourceInputV0],
458 source_documents: &[OmenaQuerySourceDocumentInputV0],
459 package_manifests: &[OmenaQueryStylePackageManifestV0],
460) -> OmenaQueryCrossFileSummaryV0 {
461 let style_pairs = style_sources
462 .iter()
463 .map(|source| (source.style_path.as_str(), source.style_source.as_str()))
464 .collect::<Vec<_>>();
465 let style_fact_entries = super::collect_omena_query_style_fact_entries(style_pairs.as_slice());
466 let css_modules_resolution =
467 super::summarize_css_modules_cross_file_resolution(&style_fact_entries, package_manifests);
468 let sass_module_resolution =
469 super::summarize_sass_module_cross_file_resolution(&style_fact_entries, package_manifests);
470 let style_summary = summarize_omena_query_cross_file_summary(
471 &style_fact_entries,
472 &css_modules_resolution,
473 &sass_module_resolution,
474 );
475 let source_summary = summarize_omena_query_source_selector_reference_cross_file_summary(
476 style_sources,
477 source_documents,
478 package_manifests,
479 );
480
481 merge_omena_query_cross_file_summaries(
482 "workspaceSummaryEdgeSeed",
483 "workspaceStyleAndSource",
484 style_sources.len(),
485 &[style_summary, source_summary],
486 )
487}
488
489const M4_AXIS_C_REQUIRED_EDGE_KINDS: [&str; 12] = [
490 "cssModulesComposesImport",
491 "cssModulesComposesClosure",
492 "cssModulesValueImport",
493 "cssModulesValueClosure",
494 "cssModulesIcssImport",
495 "cssModulesIcssClosure",
496 "sassUse",
497 "sassForward",
498 "sassImport",
499 "sassModuleGraphClosure",
500 "styleDesignTokenReference",
501 "sourceSelectorReference",
502];
503
504pub fn summarize_omena_query_m4_axis_c_readiness() -> OmenaQueryM4AxisCReadinessSummaryV0 {
505 let button_style_source = m4_axis_c_button_style_source("./base.module.scss");
506 let style_sources = m4_axis_c_style_sources(button_style_source.as_str());
507 let source_documents = m4_axis_c_source_documents("styles.root");
508 let package_manifests = m4_axis_c_package_manifests("./dist/theme.css");
509 let style_pairs = style_sources
510 .iter()
511 .map(|source| (source.style_path.as_str(), source.style_source.as_str()))
512 .collect::<Vec<_>>();
513 let style_fact_entries = super::collect_omena_query_style_fact_entries(style_pairs.as_slice());
514 let css_modules_resolution =
515 super::summarize_css_modules_cross_file_resolution(&style_fact_entries, &package_manifests);
516 let sass_module_resolution =
517 super::summarize_sass_module_cross_file_resolution(&style_fact_entries, &package_manifests);
518 let style_summary = summarize_omena_query_cross_file_summary(
519 &style_fact_entries,
520 &css_modules_resolution,
521 &sass_module_resolution,
522 );
523 let source_summary = summarize_omena_query_source_selector_reference_cross_file_summary(
524 style_sources.as_slice(),
525 source_documents.as_slice(),
526 package_manifests.as_slice(),
527 );
528 let workspace_summary = merge_omena_query_cross_file_summaries(
529 "workspaceSummaryEdgeSeed",
530 "workspaceStyleAndSource",
531 style_sources.len(),
532 &[style_summary.clone(), source_summary.clone()],
533 );
534
535 let required_edge_kind_counts =
536 m4_axis_c_required_edge_kind_counts(&workspace_summary.edge_kind_counts);
537 let required_edge_kinds_ready = required_edge_kind_counts
538 .iter()
539 .all(|entry| entry.count > 0);
540 let issue_63_provenance_round_trip_ready =
541 workspace_summary.capabilities.linear_provenance_ready
542 && workspace_summary
543 .capabilities
544 .linear_provenance_round_trip_ready
545 && workspace_summary.linear_provenance_round_trips_legacy_labels();
546 let issue_65_summary_edge_equivalence_ready = required_edge_kinds_ready
547 && style_summary.summary_edge_count
548 == m4_axis_c_expected_style_summary_edge_count(
549 &style_fact_entries,
550 &css_modules_resolution,
551 &sass_module_resolution,
552 )
553 && source_summary.edges.iter().any(|edge| {
554 edge.edge_kind == "sourceSelectorReference"
555 && edge.local_name.as_deref() == Some("root")
556 && edge.status == "resolved"
557 })
558 && workspace_summary.summary_edge_count
559 == m4_axis_c_merged_edge_id_count(&style_summary, &source_summary);
560
561 let source_changed = summarize_omena_query_workspace_cross_file_summary(
562 style_sources.as_slice(),
563 m4_axis_c_source_documents("styles.missing").as_slice(),
564 package_manifests.as_slice(),
565 );
566 let style_changed = summarize_omena_query_workspace_cross_file_summary(
567 m4_axis_c_style_sources(m4_axis_c_button_style_source("./missing.module.scss").as_str())
568 .as_slice(),
569 source_documents.as_slice(),
570 package_manifests.as_slice(),
571 );
572 let package_changed = summarize_omena_query_workspace_cross_file_summary(
573 style_sources.as_slice(),
574 source_documents.as_slice(),
575 m4_axis_c_package_manifests("./dist/alt.css").as_slice(),
576 );
577 let summary_hash_invalidation_ready = workspace_summary.summary_hash
578 != source_changed.summary_hash
579 && workspace_summary.summary_hash != style_changed.summary_hash
580 && workspace_summary.summary_hash != package_changed.summary_hash;
581 let ready = issue_63_provenance_round_trip_ready
582 && issue_65_summary_edge_equivalence_ready
583 && summary_hash_invalidation_ready;
584
585 OmenaQueryM4AxisCReadinessSummaryV0 {
586 schema_version: "0",
587 product: "omena-query.m4-axis-c-readiness",
588 status: if ready {
589 "m4AxisCReady"
590 } else {
591 "m4AxisCNeedsWork"
592 },
593 required_edge_kind_count: M4_AXIS_C_REQUIRED_EDGE_KINDS.len(),
594 required_edge_kind_counts,
595 workspace_edge_count: workspace_summary.summary_edge_count,
596 issue_63_provenance_round_trip_ready,
597 issue_65_summary_edge_equivalence_ready,
598 summary_hash_invalidation_ready,
599 summary_hash_samples: OmenaQueryM4AxisCSummaryHashSamplesV0 {
600 baseline: workspace_summary.summary_hash,
601 source_selector_change: source_changed.summary_hash,
602 style_edge_change: style_changed.summary_hash,
603 package_manifest_change: package_changed.summary_hash,
604 },
605 checked_surfaces: vec![
606 "linear-provenance-round-trip",
607 "summary-edge-resolution-equivalence",
608 "source-selector-summary-edge",
609 "workspace-summary-hash-source-invalidation",
610 "workspace-summary-hash-style-invalidation",
611 "workspace-summary-hash-package-manifest-invalidation",
612 ],
613 next_priorities: if ready {
614 vec![]
615 } else {
616 vec![
617 "completeM4AxisCProvenanceRoundTrip",
618 "completeM4AxisCSummaryEdgeEquivalence",
619 "completeM4AxisCSummaryHashInvalidation",
620 ]
621 },
622 }
623}
624
625fn m4_axis_c_button_style_source(composes_target: &str) -> String {
626 format!(
627 r#"@use "@design/tokens/theme";
628@forward "./palette";
629@import "./legacy";
630@value primary as localPrimary from "./tokens.module.scss";
631:import("./tokens.module.scss") {{ imported: exported; }}
632:export {{ forwarded: imported; }}
633.root {{ composes: base from "{composes_target}"; color: var(--brand); }}"#
634 )
635}
636
637fn m4_axis_c_style_sources(button_style_source: &str) -> Vec<OmenaQueryStyleSourceInputV0> {
638 vec![
639 OmenaQueryStyleSourceInputV0 {
640 style_path: "/fake/workspace/node_modules/@design/tokens/dist/theme.css".to_string(),
641 style_source: ":root { --brand: theme; }".to_string(),
642 },
643 OmenaQueryStyleSourceInputV0 {
644 style_path: "/fake/workspace/node_modules/@design/tokens/dist/alt.css".to_string(),
645 style_source: ":root { --brand: alt; }".to_string(),
646 },
647 OmenaQueryStyleSourceInputV0 {
648 style_path: "/fake/workspace/src/base.module.scss".to_string(),
649 style_source: ".base { display: block; }".to_string(),
650 },
651 OmenaQueryStyleSourceInputV0 {
652 style_path: "/fake/workspace/src/tokens.module.scss".to_string(),
653 style_source: "@value primary: red; :export { raw: red; exported: raw; }".to_string(),
654 },
655 OmenaQueryStyleSourceInputV0 {
656 style_path: "/fake/workspace/src/_palette.scss".to_string(),
657 style_source: "$tone: red;".to_string(),
658 },
659 OmenaQueryStyleSourceInputV0 {
660 style_path: "/fake/workspace/src/_legacy.scss".to_string(),
661 style_source: "$legacy: blue;".to_string(),
662 },
663 OmenaQueryStyleSourceInputV0 {
664 style_path: "/fake/workspace/src/Button.module.scss".to_string(),
665 style_source: button_style_source.to_string(),
666 },
667 ]
668}
669
670fn m4_axis_c_source_documents(class_expression: &str) -> Vec<OmenaQuerySourceDocumentInputV0> {
671 vec![OmenaQuerySourceDocumentInputV0 {
672 source_path: "/fake/workspace/src/Button.tsx".to_string(),
673 source_source: format!(
674 "import styles from './Button.module.scss';\nconst cls = {class_expression};\n"
675 ),
676 }]
677}
678
679fn m4_axis_c_package_manifests(style_export_target: &str) -> Vec<OmenaQueryStylePackageManifestV0> {
680 vec![OmenaQueryStylePackageManifestV0 {
681 package_json_path: "/fake/workspace/node_modules/@design/tokens/package.json".to_string(),
682 package_json_source: format!(
683 r#"{{"exports":{{"./theme":{{"style":"{style_export_target}"}}}}}}"#
684 ),
685 }]
686}
687
688fn m4_axis_c_required_edge_kind_counts(
689 edge_kind_counts: &[OmenaQueryCrossFileSummaryEdgeKindCountV0],
690) -> Vec<OmenaQueryCrossFileSummaryEdgeKindCountV0> {
691 let observed = edge_kind_counts
692 .iter()
693 .map(|entry| (entry.edge_kind, entry.count))
694 .collect::<BTreeMap<_, _>>();
695 M4_AXIS_C_REQUIRED_EDGE_KINDS
696 .iter()
697 .map(|edge_kind| OmenaQueryCrossFileSummaryEdgeKindCountV0 {
698 edge_kind,
699 count: observed.get(edge_kind).copied().unwrap_or(0),
700 })
701 .collect()
702}
703
704fn m4_axis_c_expected_style_summary_edge_count(
705 style_fact_entries: &[super::OmenaQueryStyleFactEntry],
706 css_modules_resolution: &OmenaQueryCssModulesCrossFileResolutionV0,
707 sass_module_resolution: &OmenaQuerySassModuleCrossFileResolutionV0,
708) -> usize {
709 let custom_property_reference_count = style_fact_entries
710 .iter()
711 .map(|entry| entry.facts.custom_property_ref_names.len())
712 .sum::<usize>();
713
714 css_modules_resolution.edges.len()
715 + css_modules_resolution.composes_closure_edges.len()
716 + css_modules_resolution.value_closure_edges.len()
717 + css_modules_resolution.icss_closure_edges.len()
718 + sass_module_resolution.edges.len()
719 + sass_module_resolution.graph_closure_edges.len()
720 + custom_property_reference_count
721}
722
723fn m4_axis_c_merged_edge_id_count(
724 style_summary: &OmenaQueryCrossFileSummaryV0,
725 source_summary: &OmenaQueryCrossFileSummaryV0,
726) -> usize {
727 style_summary
728 .edges
729 .iter()
730 .chain(source_summary.edges.iter())
731 .map(|edge| edge.edge_id.as_str())
732 .collect::<BTreeSet<_>>()
733 .len()
734}
735
736fn merge_omena_query_cross_file_summaries(
737 status: &'static str,
738 summary_scope: &'static str,
739 style_count: usize,
740 summaries: &[OmenaQueryCrossFileSummaryV0],
741) -> OmenaQueryCrossFileSummaryV0 {
742 let mut edges = summaries
743 .iter()
744 .flat_map(|summary| summary.edges.clone())
745 .collect::<Vec<_>>();
746 edges.sort_by_key(|edge| edge.edge_id.clone());
747 edges.dedup_by(|left, right| left.edge_id == right.edge_id);
748 let summary_hash = stable_omena_query_cross_file_summary_hash(edges.as_slice());
749
750 OmenaQueryCrossFileSummaryV0 {
751 schema_version: "0",
752 product: "omena-query.cross-file-summary",
753 status,
754 summary_scope,
755 style_count,
756 summary_edge_count: edges.len(),
757 edge_kind_counts: summarize_omena_query_cross_file_summary_edge_kind_counts(&edges),
758 summary_hash,
759 edges,
760 capabilities: merge_omena_query_cross_file_summary_capabilities(summaries),
761 next_priorities: vec!["workspaceSummaryHashInvalidationGate"],
762 }
763}
764
765fn merge_omena_query_cross_file_summary_capabilities(
766 summaries: &[OmenaQueryCrossFileSummaryV0],
767) -> OmenaQueryCrossFileSummaryCapabilitiesV0 {
768 OmenaQueryCrossFileSummaryCapabilitiesV0 {
769 css_modules_composes_edges_ready: summaries
770 .iter()
771 .any(|summary| summary.capabilities.css_modules_composes_edges_ready),
772 css_modules_value_edges_ready: summaries
773 .iter()
774 .any(|summary| summary.capabilities.css_modules_value_edges_ready),
775 css_modules_icss_edges_ready: summaries
776 .iter()
777 .any(|summary| summary.capabilities.css_modules_icss_edges_ready),
778 sass_module_edges_ready: summaries
779 .iter()
780 .any(|summary| summary.capabilities.sass_module_edges_ready),
781 style_design_token_reference_edges_ready: summaries.iter().any(|summary| {
782 summary
783 .capabilities
784 .style_design_token_reference_edges_ready
785 }),
786 source_selector_reference_edges_ready: summaries
787 .iter()
788 .any(|summary| summary.capabilities.source_selector_reference_edges_ready),
789 stable_summary_hash_ready: summaries
790 .iter()
791 .all(|summary| summary.capabilities.stable_summary_hash_ready),
792 linear_provenance_ready: summaries
793 .iter()
794 .all(|summary| summary.capabilities.linear_provenance_ready),
795 linear_provenance_round_trip_ready: summaries
796 .iter()
797 .all(|summary| summary.capabilities.linear_provenance_round_trip_ready)
798 && summaries
799 .iter()
800 .all(OmenaQueryCrossFileSummaryV0::linear_provenance_round_trips_legacy_labels),
801 linear_provenance_semiring_laws_hold: summaries
802 .iter()
803 .all(|summary| summary.capabilities.linear_provenance_semiring_laws_hold),
804 }
805}
806
807#[derive(Debug, Clone)]
808struct OmenaQueryCrossFileSummaryEdgeInput {
809 edge_kind: &'static str,
810 from_kind: &'static str,
811 target_kind: Option<&'static str>,
812 from_path: String,
813 target_path: Option<String>,
814 source: Option<String>,
815 owner_selector_name: Option<String>,
816 local_name: Option<String>,
817 remote_name: Option<String>,
818 target_names: Vec<String>,
819 status: &'static str,
820 provenance: Vec<&'static str>,
821}
822
823fn build_omena_query_cross_file_summary_edge(
824 input: OmenaQueryCrossFileSummaryEdgeInput,
825) -> OmenaQueryCrossFileSummaryEdgeV0 {
826 let edge_id = omena_query_cross_file_summary_edge_id(&input);
827 let linear_provenance = summarize_omena_query_linear_provenance_with_support_count(
828 input.provenance.as_slice(),
829 linear_provenance_support_count(&input),
830 );
831
832 OmenaQueryCrossFileSummaryEdgeV0 {
833 edge_id,
834 edge_kind: input.edge_kind,
835 from_kind: input.from_kind,
836 from_path: input.from_path,
837 target_kind: input.target_kind,
838 target_path: input.target_path,
839 source: input.source,
840 owner_selector_name: input.owner_selector_name,
841 local_name: input.local_name,
842 remote_name: input.remote_name,
843 target_names: input.target_names,
844 status: input.status,
845 provenance: input.provenance,
846 linear_provenance,
847 }
848}
849
850fn linear_provenance_support_count(input: &OmenaQueryCrossFileSummaryEdgeInput) -> u8 {
851 if !cross_file_summary_status_has_supported_target(input.status) {
852 return 0;
853 }
854 input.target_names.len().max(1).min(usize::from(u8::MAX)) as u8
855}
856
857fn cross_file_summary_status_has_supported_target(status: &str) -> bool {
858 matches!(
859 status,
860 "resolved" | "reachable" | "localResolved" | "importResolved" | "external"
861 )
862}
863
864fn omena_query_cross_file_summary_edge_id(input: &OmenaQueryCrossFileSummaryEdgeInput) -> String {
865 format!(
866 "{}|fromKind:{}|from:{}|targetKind:{}|target:{}|source:{}|owner:{}|local:{}|remote:{}|names:{}",
867 input.edge_kind,
868 input.from_kind,
869 input.from_path,
870 input.target_kind.unwrap_or("-"),
871 input.target_path.as_deref().unwrap_or("-"),
872 input.source.as_deref().unwrap_or("-"),
873 input.owner_selector_name.as_deref().unwrap_or("-"),
874 input.local_name.as_deref().unwrap_or("-"),
875 input.remote_name.as_deref().unwrap_or("-"),
876 input.target_names.join(",")
877 )
878}
879
880fn stable_omena_query_cross_file_summary_hash(
881 edges: &[OmenaQueryCrossFileSummaryEdgeV0],
882) -> String {
883 let mut hash = 0xcbf29ce484222325u64;
884 stable_omena_query_hash_piece(&mut hash, "omena-query.cross-file-summary");
885 stable_omena_query_hash_piece(&mut hash, "0");
886 for edge in edges {
887 stable_omena_query_hash_piece(&mut hash, edge.edge_id.as_str());
888 stable_omena_query_hash_piece(&mut hash, edge.status);
889 stable_omena_query_hash_piece(&mut hash, edge.linear_provenance.semiring_identifier());
890 let term_count = edge.linear_provenance.term_count.to_string();
891 stable_omena_query_hash_piece(&mut hash, term_count.as_str());
892 for term in &edge.linear_provenance.terms {
893 let coefficient = term.coefficient.to_string();
894 stable_omena_query_hash_piece(&mut hash, coefficient.as_str());
895 stable_omena_query_hash_piece(&mut hash, term.label);
896 }
897 }
898 format!("{hash:016x}")
899}
900
901impl OmenaQueryCrossFileSummaryV0 {
902 pub fn recompute_stable_summary_hash(&self) -> String {
903 stable_omena_query_cross_file_summary_hash(self.edges.as_slice())
904 }
905}
906
907fn summarize_omena_query_cross_file_summary_edge_kind_counts(
908 edges: &[OmenaQueryCrossFileSummaryEdgeV0],
909) -> Vec<OmenaQueryCrossFileSummaryEdgeKindCountV0> {
910 let mut counts = BTreeMap::<&'static str, usize>::new();
911 for edge in edges {
912 *counts.entry(edge.edge_kind).or_default() += 1;
913 }
914 counts
915 .into_iter()
916 .map(|(edge_kind, count)| OmenaQueryCrossFileSummaryEdgeKindCountV0 { edge_kind, count })
917 .collect()
918}
919
920fn stable_omena_query_hash_piece(hash: &mut u64, piece: &str) {
921 for byte in piece.as_bytes() {
922 *hash ^= u64::from(*byte);
923 *hash = hash.wrapping_mul(0x100000001b3);
924 }
925 *hash ^= 0xff;
926 *hash = hash.wrapping_mul(0x100000001b3);
927}