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