omena_query/style/cross_file_hypergraph/
mod.rs1#![cfg_attr(not(feature = "hypergraph-ifds"), allow(dead_code))]
2
3use std::collections::BTreeSet;
4
5use serde::Serialize;
6
7use crate::{OmenaQueryCrossFileSummaryEdgeV0, OmenaQueryCrossFileSummaryV0};
8
9mod edge;
10mod reachability;
11
12pub use edge::*;
13pub use reachability::*;
14pub(in crate::style) use reachability::{
15 HypergraphClosureMode, HypergraphClosurePath, collect_hypergraph_transitive_closure_paths,
16 collect_hypergraph_transitive_closure_paths_with_mode,
17};
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
20#[serde(rename_all = "camelCase")]
21pub struct OmenaQueryUnifiedCrossFileHypergraphV0 {
22 pub schema_version: &'static str,
23 pub product: &'static str,
24 pub status: &'static str,
25 pub layer_marker: &'static str,
26 pub feature_gate: &'static str,
27 pub node_count: usize,
28 pub hyperedge_count: usize,
29 pub summary_edge_count: usize,
30 pub projection_edge_ids: Vec<String>,
31 pub hyperedges: Vec<UnifiedHypergraphHyperedgeV0>,
32 pub summary_edges: Vec<HypergraphIFDSSummaryEdgeV0>,
33 pub gate_predicates: Vec<&'static str>,
34}
35
36pub fn summarize_omena_query_unified_cross_file_hypergraph(
37 summary: &OmenaQueryCrossFileSummaryV0,
38) -> OmenaQueryUnifiedCrossFileHypergraphV0 {
39 let mut builder = UnifiedCrossFileHypergraphBuilder::default();
40 for edge in &summary.edges {
41 builder.add_summary_edge(edge);
42 }
43 builder.finish()
44}
45
46#[derive(Default)]
47struct UnifiedCrossFileHypergraphBuilder {
48 node_ids: BTreeSet<String>,
49 hyperedges: Vec<UnifiedHypergraphHyperedgeV0>,
50 summary_edges: Vec<HypergraphIFDSSummaryEdgeV0>,
51}
52
53impl UnifiedCrossFileHypergraphBuilder {
54 fn add_summary_edge(&mut self, edge: &OmenaQueryCrossFileSummaryEdgeV0) {
55 let edge_kind = unified_edge_kind_for_summary_edge(edge);
56 let from_node_id = endpoint_node_id(edge, false);
57 let to_node_id = endpoint_node_id(edge, true);
58 let tail_node_ids = if edge_kind.is_order_significant() && !edge.target_names.is_empty() {
59 edge.target_names
60 .iter()
61 .map(|target_name| {
62 node_id(
63 "styleSymbol",
64 edge.target_path
65 .as_deref()
66 .unwrap_or(edge.from_path.as_str()),
67 Some(target_name),
68 )
69 })
70 .collect::<Vec<_>>()
71 } else {
72 vec![from_node_id.clone()]
73 };
74 self.node_ids.insert(from_node_id.clone());
75 self.node_ids.insert(to_node_id.clone());
76 self.node_ids.extend(tail_node_ids.iter().cloned());
77
78 let hyperedge_id = format!(
79 "hyperedge:{}|{}|{}",
80 edge_kind.as_wire_label(),
81 edge.edge_id,
82 tail_node_ids.join(">")
83 );
84 self.hyperedges.push(UnifiedHypergraphHyperedgeV0 {
85 schema_version: "0",
86 product: "omena-query.unified-hypergraph-hyperedge",
87 layer_marker: "hypergraph-ifds",
88 feature_gate: "hypergraph-ifds",
89 hyperedge_id: hyperedge_id.clone(),
90 edge_kind,
91 source_summary_edge_id: edge.edge_id.clone(),
92 source_edge_kind: edge.edge_kind,
93 source_status: edge.status,
94 tail_node_ids,
95 head_node_id: to_node_id.clone(),
96 order_significant_tail: edge_kind.is_order_significant(),
97 });
98 self.summary_edges.push(HypergraphIFDSSummaryEdgeV0 {
99 schema_version: "0",
100 product: "omena-query.hypergraph-ifds-summary-edge",
101 layer_marker: "hypergraph-ifds",
102 feature_gate: "hypergraph-ifds",
103 summary_edge_id: format!("ifds-summary:{}", edge.edge_id),
104 projection_edge_id: edge.edge_id.clone(),
105 hyperedge_id,
106 from_node_id,
107 to_node_id,
108 edge_kind,
109 status: edge.status,
110 provenance: edge.provenance.clone(),
111 linear_provenance: edge.linear_provenance.clone(),
112 });
113 }
114
115 fn finish(mut self) -> OmenaQueryUnifiedCrossFileHypergraphV0 {
116 self.hyperedges
117 .sort_by_key(|edge| edge.hyperedge_id.clone());
118 let summary_edges =
119 tabulate_hypergraph_ifds_summary_edges(&self.hyperedges, self.summary_edges);
120 let projection_edge_ids = summary_edges
121 .iter()
122 .map(|edge| edge.projection_edge_id.clone())
123 .collect::<Vec<_>>();
124
125 OmenaQueryUnifiedCrossFileHypergraphV0 {
126 schema_version: "0",
127 product: "omena-query.unified-cross-file-hypergraph",
128 status: "hypergraphIfdsProjection",
129 layer_marker: "hypergraph-ifds",
130 feature_gate: "hypergraph-ifds",
131 node_count: self.node_ids.len(),
132 hyperedge_count: self.hyperedges.len(),
133 summary_edge_count: summary_edges.len(),
134 projection_edge_ids,
135 hyperedges: self.hyperedges,
136 summary_edges,
137 gate_predicates: vec![
138 "P1.typeIntroduction",
139 "P2.byteEqualAdjacencyProjection",
140 "P3.sccUnification",
141 "P4.summaryEdgeSetEquality",
142 "P5.projectionHelper",
143 "P6.closureBodySwitchOver",
144 "P7.v0Publication",
145 "batchConnectivityOracle",
146 "streamingOracleWireCompatible",
147 "composesTailOrderingUsesVec",
148 ],
149 }
150 }
151}
152
153fn endpoint_node_id(edge: &OmenaQueryCrossFileSummaryEdgeV0, target: bool) -> String {
154 let (kind, path, symbol) = if target {
155 (
156 node_kind_for_summary_kind(edge.target_kind.unwrap_or(edge.from_kind), true),
157 edge.target_path
158 .as_deref()
159 .unwrap_or(edge.from_path.as_str()),
160 edge.remote_name
161 .as_deref()
162 .or_else(|| edge.target_names.first().map(String::as_str)),
163 )
164 } else {
165 (
166 node_kind_for_summary_kind(edge.from_kind, false),
167 edge.from_path.as_str(),
168 edge.owner_selector_name
169 .as_deref()
170 .or(edge.local_name.as_deref()),
171 )
172 };
173 node_id(kind, path, symbol)
174}
175
176fn node_id(kind: &'static str, path: &str, symbol: Option<&str>) -> String {
177 format!("{}|{}|{}", kind, path, symbol.unwrap_or("-"))
178}
179
180fn node_kind_for_summary_kind(kind: &str, target: bool) -> &'static str {
181 match (kind, target) {
182 ("style", false) => "styleModule",
183 ("style", true) => "styleSymbol",
184 ("source", false) => "sourceModule",
185 ("source", true) => "sourceSymbol",
186 _ => "foreignSymbol",
187 }
188}
189
190fn unified_edge_kind_for_summary_edge(
191 edge: &OmenaQueryCrossFileSummaryEdgeV0,
192) -> UnifiedHypergraphEdgeKindV0 {
193 match edge.edge_kind {
194 "composesLocal" => UnifiedHypergraphEdgeKindV0::ComposesLocal,
195 "composesGlobal" => UnifiedHypergraphEdgeKindV0::ComposesGlobal,
196 "cssModulesComposesImport" | "cssModulesComposesClosure" | "composesExternal" => {
197 UnifiedHypergraphEdgeKindV0::ComposesExternal
198 }
199 "sassUse" => UnifiedHypergraphEdgeKindV0::SassUse,
200 "sassForward" => UnifiedHypergraphEdgeKindV0::SassForward,
201 "sassImport" => UnifiedHypergraphEdgeKindV0::SassImport,
202 "cssModulesValueImport" | "cssModulesValueClosure" | "value" => {
203 UnifiedHypergraphEdgeKindV0::Value
204 }
205 "cssModulesIcssImport" | "cssModulesIcssClosure" | "icss" => {
206 UnifiedHypergraphEdgeKindV0::Icss
207 }
208 _ => UnifiedHypergraphEdgeKindV0::ForeignReference,
209 }
210}