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). `dispatch`
58// holds the `implements`/`overrides` plumbing `index.rs` and `refs.rs` both
59// call into (index-build-time adjacency, the bare-member fallback's
60// exact-match half) -- split out to keep those two files under this crate's
61// per-file line budget, not a verb of its own. `occurrence` is the same
62// kind of split: a query-time-only helper that tags two otherwise-identical
63// rows of one inbound/outbound table with a stable position, called from
64// `refs.rs` after ranking/capping and never touching the graph it reads.
65// Every public item keeps the path it had before the split via the
66// `pub use`s below.
67
68mod coverage;
69mod dispatch;
70mod find;
71mod impact;
72mod impact_why;
73mod imported;
74mod index;
75mod infra;
76// `--json` rendering of every query model, moved here from `cli.rs` to keep
77// that file under its size ratchet; `pub(crate)` so `cli.rs` can still call
78// it as `query::json::...`.
79pub(crate) mod json;
80mod member;
81mod occurrence;
82mod outcome;
83mod rank;
84mod read;
85mod refs;
86mod refs_tables;
87mod seq;
88mod symbol;
89mod why;
90
91pub use coverage::{build_tests_model, TestRow, TestVia, TestsModel, TestsResult};
92pub use find::{file_inbound_counts, find_names, first_decl_line_by_file, name_tier, source_line};
93pub use impact::{
94 build_impact_model, impact_walk, looks_like_file_path, resolve_impact_seed, BrakedFile,
95 BrakedIface, ImpactModel, ImpactResult, ImpactRow, ImpactWalkResult, KindLines, SeedKind,
96 SeedResolution, VisitedEntry, DEFAULT_HOPS, DEFAULT_IFACE_MAX_FANIN,
97};
98pub use imported::{build_imported_section, ImportedRow, ImportedSection};
99pub use index::{
100 def_files, def_sites, load_graph_index, load_graph_index_with, symbol_refs, DefSite,
101 GraphIndex, HeuristicEntry, InboundEntry, IndexOptions, OutboundEntry, SymbolRefs,
102};
103pub use infra::{is_infra_file, DEFAULT_HUB_MAX_INDEGREE};
104pub use member::{
105 qualified_member_owners, qualified_seed, resolve_member_seed, MemberCandidate,
106 MemberSeedResolution,
107};
108pub use outcome::{Outcome, SCHEMA_VERSION};
109pub use rank::{personalized_page_rank, DEFAULT_DAMPING, DEFAULT_ITERATIONS};
110pub use read::{build_read_model, ReadModel, ReadResult, ReadSpan};
111pub use refs::{build_refs_model, LineCache, MemberRefEntry, MemberRefs, RefsModel, RefsResult};
112pub use refs_tables::{
113 AmbiguousRow, AmbiguousTables, ImportRow, InboundRow, InboundTables, OutboundRow,
114 OutboundTables, Table, DEFAULT_CAP, INBOUND_CAP, OUTBOUND_CAP, SOURCE_MAX,
115};
116pub use seq::{SeqMap, SeqSet};
117pub use symbol::{resolve_symbol, Resolution};
118pub use why::Why;
119
120#[cfg(test)]
121mod tests;