Skip to main content

devscout_rs/query/
refs_tables.rs

1use crate::graph;
2
3// The three reference kinds and their fixed order. Not iterated generically
4// here -- each kind gets its own struct field (`inherits`/`uses_type`/
5// `uses_member`) throughout this module rather than a keyed collection. This
6// constant exists only as an anchor for the kind list and its order.
7#[allow(dead_code)]
8const REF_KINDS: [&str; 3] = ["inherits", "uses-type", "uses-member"];
9
10/// Default per-table row cap for the outbound and ambiguous tables. The three
11/// inbound tables share [`INBOUND_CAP`] instead.
12pub const DEFAULT_CAP: usize = 50;
13/// Default cap shared across all three inbound kinds -- one cap total, not one
14/// per table.
15pub const INBOUND_CAP: usize = 30;
16/// Default cap for the `--out` view: one cap shared across all four outbound
17/// kinds (inherits/uses-type/uses-member/imports), the mirror of
18/// [`INBOUND_CAP`]. `--all` lifts this cap.
19pub const OUTBOUND_CAP: usize = 30;
20/// Maximum length of a displayed source line, in UTF-16 code units.
21/// Truncation counts code units (see `clip_source`) so the cut is stable
22/// regardless of any astral characters on the line.
23pub const SOURCE_MAX: usize = 120;
24// ============================================================================
25// build_refs_model.
26// ============================================================================
27
28pub(super) fn cap_rows<T>(mut rows: Vec<T>, cap: usize) -> (Vec<T>, usize) {
29    if rows.len() <= cap {
30        (rows, 0)
31    } else {
32        let dropped = rows.len() - cap;
33        rows.truncate(cap);
34        (rows, dropped)
35    }
36}
37
38// The (from_file, from_line) an edge originates at, the key location sorts use.
39// See the module header re: `str::cmp` ordering.
40pub(super) fn edge_loc(e: &graph::Edge) -> (&str, usize) {
41    match e {
42        graph::Edge::Inherits { from_file, from_line, .. }
43        | graph::Edge::UsesType { from_file, from_line, .. }
44        | graph::Edge::UsesMember { from_file, from_line, .. }
45        | graph::Edge::Implements { from_file, from_line, .. }
46        | graph::Edge::Overrides { from_file, from_line, .. }
47        | graph::Edge::BusHop { from_file, from_line, .. }
48        | graph::Edge::Imports { from_file, from_line, .. }
49        | graph::Edge::Ambiguous { from_file, from_line, .. }
50        // Never actually reached: a 'ctor-di' edge is never pushed into any
51        // structure this helper sorts (see the query-index builder's own
52        // CtorDi arm). Included only for exhaustiveness, as are the four
53        // TS/TSX kinds below, for the same reason.
54        | graph::Edge::CtorDi { from_file, from_line, .. }
55        | graph::Edge::Import { from_file, from_line, .. }
56        | graph::Edge::Call { from_file, from_line, .. }
57        | graph::Edge::JsxUse { from_file, from_line, .. }
58        | graph::Edge::Dispatch { from_file, from_line, .. } => (from_file.as_str(), *from_line),
59    }
60}
61
62pub(super) fn loc_cmp(a: &graph::Edge, b: &graph::Edge) -> std::cmp::Ordering {
63    let (af, al) = edge_loc(a);
64    let (bf, bl) = edge_loc(b);
65    if af == bf {
66        al.cmp(&bl)
67    } else {
68        af.cmp(bf)
69    }
70}
71
72/// Which tier a ROW reports, folded from the tiers of the edges behind it.
73///
74/// One extension edge is enough to call the whole row an extension: that tier
75/// ran C#'s own lookup rule and only failed to check the receiver, so it is the
76/// stronger of the two and a name guess sitting beside it does not weaken it.
77/// Everything else heuristic is a guess, an edge carrying NO tier included -- a
78/// graph written before schema 2 cannot prove it was anything better, and
79/// reporting an unproven edge as the stronger tier is the one direction this
80/// surface must never round.
81pub(super) fn row_tier(heuristic: bool, ext_seen: bool) -> Option<graph::HeuristicTier> {
82    if !heuristic {
83        return None;
84    }
85    Some(if ext_seen {
86        graph::HeuristicTier::Ext
87    } else {
88        graph::HeuristicTier::Guess
89    })
90}
91
92/// One inbound-table row: `file` and `line` of the referencing site, then
93/// `heuristic` (whether the edge was guessed), `tier` (which guess tier said
94/// so) and `source` (the trimmed referencing line). An empty `source` is
95/// omitted from `--json`. `occurrence_index` distinguishes two rows that would
96/// otherwise be equal on every other field (see the `occurrence` module);
97/// it is `None` on every row outside such a collision.
98#[derive(Debug, Clone, PartialEq)]
99pub struct InboundRow {
100    /// The file value.
101    pub file: String,
102    /// The line value.
103    pub line: usize,
104    /// The heuristic value.
105    pub heuristic: bool,
106    /// Which heuristic tier stands behind this row; `None` exactly when
107    /// `heuristic` is false (see [`row_tier`]).
108    pub tier: Option<graph::HeuristicTier>,
109    /// The source value.
110    pub source: String,
111    /// A 0-based position among rows of this same table that are otherwise
112    /// identical, assigned in the table's existing stable emission order;
113    /// `None` for a row with no such collision. Scoped to one table of one
114    /// answer against one graph snapshot -- never a cross-query or
115    /// cross-snapshot identity. See the `occurrence` module.
116    pub occurrence_index: Option<usize>,
117}
118
119/// One outbound-table row: `file`/`line` of the referencing site, `to_file`/`to`
120/// of the target, then `heuristic` and `source` (same omit-when-empty rule as
121/// [`InboundRow`]). `source` is read at the site actually making the reference
122/// -- for an outbound edge that is a line in the def's own file, not the
123/// target's.
124#[derive(Debug, Clone, PartialEq)]
125pub struct OutboundRow {
126    /// The file value.
127    pub file: String,
128    /// The line value.
129    pub line: usize,
130    /// The to file value.
131    pub to_file: String,
132    /// The to value.
133    pub to: String,
134    /// The heuristic value.
135    pub heuristic: bool,
136    /// Which heuristic tier stands behind this row, same rule as
137    /// [`InboundRow::tier`].
138    pub tier: Option<graph::HeuristicTier>,
139    /// The source value.
140    pub source: String,
141    /// Same rule as [`InboundRow::occurrence_index`], scoped to this table.
142    pub occurrence_index: Option<usize>,
143}
144
145/// One imports-table row: `file`/`line`, the imported `target` namespace, and
146/// `source`. An imports edge is never a guess, so unlike [`OutboundRow`] this
147/// carries no `heuristic` field.
148#[derive(Debug, Clone, PartialEq)]
149pub struct ImportRow {
150    /// The file value.
151    pub file: String,
152    /// The line value.
153    pub line: usize,
154    /// The target value.
155    pub target: String,
156    /// The source value.
157    pub source: String,
158}
159
160/// One ambiguous-table row: the referencing `file`/`line`, the `origin` and
161/// `raw` text of the reference, and how many candidates it matched.
162#[derive(Debug, Clone, PartialEq)]
163pub struct AmbiguousRow {
164    /// The file value.
165    pub file: String,
166    /// The line value.
167    pub line: usize,
168    /// The origin value.
169    pub origin: String,
170    /// The raw value.
171    pub raw: String,
172    /// The candidate count value.
173    pub candidate_count: usize,
174}
175
176/// A capped table: `total` rows before capping, how many were `dropped`, and
177/// the surviving `rows`.
178#[derive(Debug, Clone, PartialEq)]
179pub struct Table<R> {
180    /// The total value.
181    pub total: usize,
182    /// The dropped value.
183    pub dropped: usize,
184    /// The rows value.
185    pub rows: Vec<R>,
186}
187
188pub(super) fn build_table<R>(
189    mut idxs: Vec<usize>,
190    edges: &[graph::Edge],
191    cap: usize,
192    map_row: fn(&graph::Edge) -> R,
193) -> Table<R> {
194    idxs.sort_by(|&a, &b| loc_cmp(&edges[a], &edges[b]));
195    let total = idxs.len();
196    let (shown, dropped) = cap_rows(idxs, cap);
197    let rows = shown.into_iter().map(|i| map_row(&edges[i])).collect();
198    Table {
199        total,
200        dropped,
201        rows,
202    }
203}
204
205// The per-table, per-kind outbound builder was removed here: the outbound
206// tables now go through `build_outbound_tables`'s shared cap and rank instead
207// (below `RankedOutbound`), the same way the inbound tables stopped going
208// through a per-kind builder. `build_table` above still backs the two
209// ambiguous tables.
210
211pub(super) fn ambiguous_row(e: &graph::Edge) -> AmbiguousRow {
212    match e {
213        graph::Edge::Ambiguous {
214            origin,
215            from_file,
216            from_line,
217            raw,
218            candidate_count,
219            ..
220        } => AmbiguousRow {
221            file: from_file.clone(),
222            line: *from_line,
223            origin: origin.clone(),
224            raw: raw.clone(),
225            candidate_count: *candidate_count,
226        },
227        _ => unreachable!("ambiguous table only ever holds ambiguous edge indices"),
228    }
229}
230
231/// The five inbound tables, one per kind.
232///
233/// `implements`/`overrides` are never heuristic, and a renderer shows
234/// either only when its own total is non-zero -- unlike the first three,
235/// which always show their header -- so a symbol untouched by dispatch
236/// edges renders byte-identical to before this pair existed.
237#[derive(Debug, Clone, PartialEq)]
238pub struct InboundTables {
239    /// The inherits value.
240    pub inherits: Table<InboundRow>,
241    /// The uses type value.
242    pub uses_type: Table<InboundRow>,
243    /// The uses member value.
244    pub uses_member: Table<InboundRow>,
245    /// The implements value.
246    pub implements: Table<InboundRow>,
247    /// The overrides value.
248    pub overrides: Table<InboundRow>,
249}
250
251/// The six outbound tables: three by kind, `implements`/`overrides`, plus
252/// imports.
253#[derive(Debug, Clone, PartialEq)]
254pub struct OutboundTables {
255    /// The inherits value.
256    pub inherits: Table<OutboundRow>,
257    /// The uses type value.
258    pub uses_type: Table<OutboundRow>,
259    /// The uses member value.
260    pub uses_member: Table<OutboundRow>,
261    /// The implements value.
262    pub implements: Table<OutboundRow>,
263    /// The overrides value.
264    pub overrides: Table<OutboundRow>,
265    /// The imports value.
266    pub imports: Table<ImportRow>,
267}
268
269/// The inbound and outbound ambiguous-reference tables.
270#[derive(Debug, Clone, PartialEq)]
271pub struct AmbiguousTables {
272    /// The inbound value.
273    pub inbound: Table<AmbiguousRow>,
274    /// The outbound value.
275    pub outbound: Table<AmbiguousRow>,
276}