Skip to main content

pedant_graph/
lib.rs

1//! Deterministic, language-tagged code topology built from one snapshot-bound
2//! symbol-resolution result.
3//!
4//! The graph separates three relations a consumer would otherwise have to
5//! reconstruct itself: logical containment, source association, and
6//! certainty-carrying reference and dependency edges. It consumes already
7//! validated in-memory facts and widens nothing: it loads no project, reads no
8//! file, parses no source, invokes no build tool, and evaluates no policy.
9//!
10//! The snapshot must be the one the resolution was validated against, and a
11//! successful build serializes to the compact version-1 JSON described below
12//! through `serde_json::to_string`.
13//!
14//! ```no_run
15//! # use pedant_core::resolution::rust::{RustResolutionSnapshot, RustTargetResolution};
16//! # use pedant_graph::{CodeGraph, GraphBuildError};
17//! # fn topology(
18//! #     snapshot: &RustResolutionSnapshot,
19//! #     resolution: &RustTargetResolution,
20//! # ) -> Result<CodeGraph, GraphBuildError> {
21//! pedant_graph::build_rust_graph(snapshot, resolution)
22//! # }
23//! ```
24//!
25//! # The version-1 shape
26//!
27//! One object with six keys, in this order:
28//!
29//! - `schema_version`: the integer [`CodeGraph::SCHEMA_VERSION`].
30//! - `tier`: `"syntactic"` or `"semantic"`, taken from the supplied report.
31//! - `nodes`: `id`, `language`, `name`, `kind`, `location`. `kind` is tagged by
32//!   `category`; `location` is `null` or tagged by `kind` as `file` or `span`.
33//! - `containment`: `parent` and `child`, sorted by child then parent.
34//! - `references`: `id`, `source`, `language`, `kind`, `text`, `span`, `gaps`,
35//!   `edges`, in mint order.
36//! - `edges`: `id`, `source`, `target`, `kind`, `certainty`, `origin`, in mint
37//!   order. `origin` is tagged by `kind` as `reference` or `dependency`.
38//!
39//! Every identity is an integer equal to the record's position in its own
40//! slice, and every graph-owned enum is one lower-snake-case string. No key
41//! states a coordinate, a size, a color, or any other layout choice.
42
43//! # Bounded queries over a built graph
44//!
45//! A caller states which edges to admit and what the answer may cost, then
46//! asks. Nothing about the graph changes.
47//!
48//! ```no_run
49//! # use pedant_graph::{
50//! #     CodeGraph, GraphAnalysis, GraphAnalysisError, GraphAnalysisLimits, GraphDirection,
51//! #     GraphEdgeSelection, GraphNeighbor, GraphNodeId,
52//! # };
53//! # fn callers_of(
54//! #     graph: &CodeGraph,
55//! #     node: GraphNodeId,
56//! # ) -> Result<Box<[GraphNeighbor]>, GraphAnalysisError> {
57//! let analysis = GraphAnalysis::new(
58//!     graph,
59//!     GraphEdgeSelection::code_relations(),
60//!     GraphAnalysisLimits::new(100_000, 400_000, 8, 50_000_000),
61//! )?;
62//! analysis.neighbors(node, 2, GraphDirection::Incoming)
63//! # }
64//! ```
65//!
66//! The same view answers the derived questions. Cohesion, modularity, affinity,
67//! and boundary-crossing cycles are evidence about the partition the graph
68//! declares; layout metadata is that evidence beside the degrees and weights a
69//! ranker or a renderer would otherwise measure itself.
70//!
71//! ```no_run
72//! # use pedant_graph::{
73//! #     CodeGraph, GraphAnalysis, GraphAnalysisError, GraphAnalysisLimits, GraphEdgeSelection,
74//! #     LayoutAssistMetadata,
75//! # };
76//! # fn described(graph: &CodeGraph) -> Result<LayoutAssistMetadata, GraphAnalysisError> {
77//! let analysis = GraphAnalysis::new(
78//!     graph,
79//!     GraphEdgeSelection::code_relations(),
80//!     GraphAnalysisLimits::new(100_000, 400_000, 8, 50_000_000),
81//! )?;
82//! let evidence = analysis.divergence();
83//! for module in evidence.cohesion() {
84//!     let _ = (module.root(), module.internal_edges(), module.score());
85//! }
86//! analysis.layout_assist()
87//! # }
88//! ```
89//!
90//! # Optional bounded reuse
91//!
92//! A caller that inspects successive repository states may keep a cache of the
93//! work it already admitted. The cache builds nothing of its own: it takes the
94//! same snapshot and validated resolution the direct builder takes, returns the
95//! same graph, and states how many values it may retain. Zero disables a
96//! category without disabling the computation behind it.
97//!
98//! ```no_run
99//! # use pedant_core::resolution::rust::{RustResolutionSnapshot, RustTargetResolution};
100//! # use pedant_graph::{
101//! #     GraphAnalysisError, GraphAnalysisLimits, GraphBuildError, GraphCache, GraphCacheLimits,
102//! #     GraphEdgeSelection, GraphLimits,
103//! # };
104//! # #[derive(Debug)]
105//! # enum ReuseError {
106//! #     Build(GraphBuildError),
107//! #     Analysis(GraphAnalysisError),
108//! # }
109//! # impl From<GraphBuildError> for ReuseError {
110//! #     fn from(error: GraphBuildError) -> Self { Self::Build(error) }
111//! # }
112//! # impl From<GraphAnalysisError> for ReuseError {
113//! #     fn from(error: GraphAnalysisError) -> Self { Self::Analysis(error) }
114//! # }
115//! # fn reused(
116//! #     snapshot: &RustResolutionSnapshot,
117//! #     resolution: &RustTargetResolution,
118//! # ) -> Result<(), ReuseError> {
119//! let mut cache = GraphCache::new(GraphCacheLimits::new(4_096, 8, 4, 256));
120//! let held = cache.build_rust_graph(snapshot, resolution, GraphLimits::default())?;
121//! let analysis = held.analyze(
122//!     GraphEdgeSelection::code_relations(),
123//!     GraphAnalysisLimits::new(100_000, 400_000, 8, 50_000_000),
124//! )?;
125//! let _ = (analysis.divergence(), cache.stats().exact_graph_hits());
126//! # Ok(())
127//! # }
128//! ```
129//!
130//! The returned handles are the caller's: a graph, an analysis, and every
131//! product they answer with stay valid and immutable after eviction, after
132//! `clear`, and after the cache itself is dropped. Nothing about the version-1
133//! object above changes, and no cache value is serialized.
134
135#![deny(missing_docs)]
136
137/// Derived analysis over one borrowed graph.
138mod analysis;
139/// Optional caller-owned reuse of already admitted graph work.
140mod cache;
141/// The logical hierarchy relation.
142mod containment;
143/// Graph edges, their certainty, and their evidence.
144mod edge;
145/// Why a graph build refused.
146mod error;
147/// The immutable graph value and its checked record store.
148mod graph;
149/// Dense typed identities into one graph's collections.
150mod id;
151/// The configured ceilings a build refuses at.
152mod limits;
153/// Graph nodes, their categories, and their source locations.
154mod node;
155/// Reference records and what they denote.
156mod reference;
157/// The Rust projection adapter.
158mod rust;
159
160pub use analysis::{
161    BetweennessCentrality, BoundaryCrossingComponent, ComponentIdKind, CondensationEdge,
162    CondensationGraph, DeclaredModulePartition, DegreeCentrality, GraphAnalysis,
163    GraphAnalysisError, GraphAnalysisLimits, GraphComponent, GraphComponentId, GraphComponents,
164    GraphDirection, GraphDivergence, GraphEdgeSelection, GraphNeighbor, GraphPath, GraphSubgraph,
165    LayoutAssistMetadata, LayoutEdgeMetadata, LayoutNodeMetadata, MisplacementCandidate,
166    ModuleCohesion,
167};
168pub use cache::{CachedCodeGraph, CachedGraphAnalysis, GraphCacheLimits, GraphCacheStats};
169pub use containment::ContainmentEdge;
170pub use edge::{
171    DependencyEvidence, GraphCertainty, GraphDependencyKind, GraphEdge, GraphEdgeKind,
172    GraphEdgeOrigin,
173};
174pub use error::GraphBuildError;
175pub use graph::CodeGraph;
176pub use id::{
177    EdgeIdKind, GraphEdgeId, GraphId, GraphNodeId, GraphReferenceId, NodeIdKind, ReferenceIdKind,
178};
179pub use limits::{GraphCollection, GraphLimits};
180pub use node::{GraphNode, GraphNodeKind, GraphNodeLocation};
181pub use reference::{GraphReference, GraphReferenceKind};
182pub use rust::{GraphCache, build_rust_graph, build_rust_graph_with_limits};