Skip to main content

devscout_rs/
query.rs

1// refs/impact query support: enum-member union, `impact_walk`, and
2// personalized PageRank. A `tests` block below pins the behavioral contract
3// with fixture cases exercising every code path.
4//
5// Design notes:
6//
7// - **Two-phase index build.** The index cannot be built in one call that
8//   also reads the graph and then hands back a struct borrowing that graph
9//   -- the result would be self-referential. So the caller reads the graph
10//   first with `graph::read_graph(root)`, which returns `None` when there is
11//   no graph, letting the caller check the `Option` before proceeding, then
12//   calls `load_graph_index(&graph, root)` here, which owns the manifest
13//   join and the index build.
14//
15// - **Manifest corrupt-JSON fail-open.** A corrupt manifest.json surfaces as
16//   `Err(ManifestError::InvalidJson)` from `manifest::read_manifest`.
17//   Panicking through a query path for an operator-error edge case (a
18//   hand-corrupted manifest) is worse than failing open, so
19//   `load_graph_index` treats `Err(_)` the same as "no manifest" -- the same
20//   fail-open-on-parse-error convention `graph::read_graph` follows. A
21//   manifest present but missing its `entries` key is handled the same way.
22//
23// - **Insertion-order-preserving scratch structures.** Several of
24//   `impact_walk`'s scratch maps/sets are NOT just membership/lookup
25//   structures -- their insertion order is directly observable in the final
26//   result: `Hit::symbols` feeds `top_symbols`'s first-3 truncation, and
27//   `visited` key order feeds the `nodes` array handed to
28//   `personalized_page_rank`, whose accumulation loop is float-addition over
29//   that exact order -- not associative, so a different iteration order can
30//   produce a bit-different (though not wrong) result.
31//   `std::collections::{HashMap,HashSet}` make no iteration-order guarantee,
32//   so this module has two tiny private order-preserving helpers,
33//   `SeqSet`/`SeqMap` (a `Vec` plus an index `HashMap`, first insertion wins
34//   the slot). They are pure algorithm scratch state, not artifact structs;
35//   `graph::Def`/`Edge`/`Graph` are reused directly from `graph.rs`.
36//
37// - **`str::cmp` ordering.** Location sorts use plain Unicode-codepoint
38//   `str::cmp` rather than locale-aware collation, the same accepted
39//   trade-off documented at `resolve.rs`'s `capped_candidates`: the two
40//   orderings coincide for every path/id shape this crate actually produces,
41//   and diverge only in a pathological mix of leading case or a
42//   `+`/`.`-adjacent tie -- flagged, not solved, no new ICU dependency.
43//
44// - **Result models.** `RefsModel`/`ImpactModel` (and their row/table
45//   sub-types) are the typed surface `render.rs` consumes. Field names are
46//   `snake_case` (e.g. `model.inbound.uses_type`). Nothing here is
47//   `Serialize` -- `--json` output byte-shape is `cli.rs`'s job, not this
48//   module's.
49//
50// Split by verb, plus the shared substrate the verbs sit on: `seq` (the
51// order-preserving scratch structures), `index` (`GraphIndex` and its
52// loading, plus `def_files`/`def_sites`/`symbol_refs`), `symbol`
53// (`resolve_symbol`'s ladder), and `rank` (personalized PageRank). The verbs
54// themselves: `find`, `refs`/`refs_tables` (the row/table shaping `read`
55// reuses), `read`, `coverage` (test coverage for a symbol -- named to avoid
56// colliding with this module's own `tests`), and `impact` (plus `infra`, the
57// hub-file name-pattern classification `impact` widens against). Every
58// public item keeps the path it had before the split via the `pub use`s
59// below.
60
61mod coverage;
62mod find;
63mod impact;
64mod impact_why;
65mod index;
66mod infra;
67// `--json` rendering of every query model, moved here from `cli.rs` to keep
68// that file under its size ratchet; `pub(crate)` so `cli.rs` can still call
69// it as `query::json::...`.
70pub(crate) mod json;
71mod member;
72mod outcome;
73mod rank;
74mod read;
75mod refs;
76mod refs_tables;
77mod seq;
78mod symbol;
79mod why;
80
81pub use coverage::{build_tests_model, TestRow, TestVia, TestsModel, TestsResult};
82pub use find::{file_inbound_counts, find_names, first_decl_line_by_file, name_tier, source_line};
83pub use impact::{
84    build_impact_model, impact_walk, looks_like_file_path, resolve_impact_seed, BrakedFile,
85    BrakedIface, ImpactModel, ImpactResult, ImpactRow, ImpactWalkResult, KindLines, SeedKind,
86    SeedResolution, VisitedEntry, DEFAULT_HOPS, DEFAULT_IFACE_MAX_FANIN,
87};
88pub use index::{
89    def_files, def_sites, load_graph_index, load_graph_index_with, symbol_refs, DefSite,
90    GraphIndex, HeuristicEntry, InboundEntry, IndexOptions, OutboundEntry, SymbolRefs,
91};
92pub use infra::{is_infra_file, DEFAULT_HUB_MAX_INDEGREE};
93pub use member::{
94    qualified_member_owners, qualified_seed, resolve_member_seed, MemberCandidate,
95    MemberSeedResolution,
96};
97pub use outcome::{Outcome, SCHEMA_VERSION};
98pub use rank::{personalized_page_rank, DEFAULT_DAMPING, DEFAULT_ITERATIONS};
99pub use read::{build_read_model, ReadModel, ReadResult, ReadSpan};
100pub use refs::{build_refs_model, LineCache, MemberRefEntry, MemberRefs, RefsModel, RefsResult};
101pub use refs_tables::{
102    AmbiguousRow, AmbiguousTables, ImportRow, InboundRow, InboundTables, OutboundRow,
103    OutboundTables, Table, DEFAULT_CAP, INBOUND_CAP, OUTBOUND_CAP, SOURCE_MAX,
104};
105pub use seq::{SeqMap, SeqSet};
106pub use symbol::{resolve_symbol, Resolution};
107pub use why::Why;
108
109#[cfg(test)]
110mod tests;