sealmap_model/lib.rs
1//! # sealmap-model
2//!
3//! A language-neutral, **deterministic** model of a codebase: source files,
4//! the symbols they define, and the typed relations between those symbols.
5//!
6//! This crate is the shared vocabulary of the `sealmap` workspace. Language
7//! language adapters (such as `sealmap-rust`) *produce* a [`Codebase`]; projections
8//! (such as `sealmap-corpus`) *consume* one and render Mermaid diagrams from
9//! it. Because both sides only meet here, you can add a language adapter for another
10//! language, or a new diagram projection, without touching the other half.
11//!
12//! The navigation API is loosely inspired by
13//! [ts-morph](https://github.com/dsherret/ts-morph) (MIT, © David Sherret):
14//! a single [`Codebase`] value you can ask for files, symbols and relations,
15//! with an in-memory [`SourceSet`] standing in for the file system so tests and
16//! embedders never need to touch disk.
17//!
18//! ## Determinism contract
19//!
20//! Every type in this crate upholds the following, and downstream crates rely
21//! on it to produce byte-identical output across runs, machines and operating
22//! systems:
23//!
24//! 1. **Ordered collections only.** All maps and sets are `BTreeMap` /
25//! `BTreeSet`; iteration order is a function of the data, never of a hash
26//! seed or insertion order.
27//! 2. **Normalised paths.** [`SourcePath`] is always relative, `/`-separated
28//! and free of `.`/`..` components, so `src\lib.rs` on Windows and
29//! `src/lib.rs` on Linux are the same key.
30//! 3. **Stable hashing.** [`ContentHash`] is BLAKE3 over the source text with
31//! line endings normalised to `\n`; it never uses `std`'s randomly seeded
32//! hasher.
33//! 4. **No ambient data.** Nothing in the model records timestamps, absolute
34//! paths, user names or environment variables.
35//!
36//! ## Built for sequence diagrams
37//!
38//! Callables carry a [`Flow`]: the ordered tree of calls, branches, loops and
39//! early exits in their body. That is what makes dense, per-function sequence
40//! diagrams possible without running the code, and because every call target
41//! is a canonical [`SymbolId`], separately rendered sequences can be merged or
42//! inlined into each other by a downstream orchestrator.
43//!
44//! ## Symbol ids
45//!
46//! Every symbol is named by a `sym:` id (see [`sym`]): kind-explicit,
47//! SCIP-style, with no file path, so moving code between files never changes
48//! an id. Beside the id, every symbol carries two [`Fingerprint`]s:
49//! `sig_hash` over its contract and `body_hash` over its implementation, so a
50//! tool can tell "unchanged", "behaviour changed" (body only) and "contract
51//! changed" (signature) apart, and can match a renamed symbol by its body.
52//!
53//! ## Example
54//!
55//! Build a tiny model by hand and query it:
56//!
57//! ```
58//! use sealmap_model::*;
59//!
60//! let pkg = Package::current("cargo", "demo").unwrap();
61//! let root = SymbolId::package_root(pkg.clone());
62//! let id = |name: &str| root.child(Descriptor::r#type(name)).unwrap();
63//!
64//! let mut cb = Codebase::new("demo");
65//! let file = SourcePath::new("src/lib.rs").unwrap();
66//! cb.add_file(SourceFile::new(file.clone(), "rust", root.clone(), "pub struct A;"));
67//!
68//! cb.add_symbol(Symbol::new(id("A"), "A", SymbolKind::Struct, file.clone()));
69//! cb.add_symbol(Symbol::new(id("B"), "B", SymbolKind::Struct, file.clone()));
70//! cb.add_relation(Relation::new(id("A"), id("B"), RelationKind::FieldType, Confidence::Exact));
71//!
72//! assert_eq!(id("A").to_string(), "sym:cargo demo . A#");
73//! assert_eq!(cb.symbols_in_file(&file).count(), 2);
74//! assert_eq!(cb.relations_from(&id("A")).count(), 1);
75//! ```
76
77#![forbid(unsafe_code)]
78#![deny(missing_docs)]
79
80mod codebase;
81pub mod flow;
82mod hash;
83mod path;
84mod source;
85pub mod sym;
86mod symbol;
87
88pub use codebase::{Codebase, CodebaseStats, ModelJsonError};
89pub use flow::{Arm, Call, CallKind, Exit, Flow, Step};
90pub use hash::{ContentHash, Fingerprint};
91pub use path::{PathError, SourcePath};
92pub use source::{LoadOptions, SourceFile, SourceSet};
93pub use sym::{
94 Descriptor, DescriptorKind, DescriptorView, GlobalView, IdError, IdView, Package, Suffix, SymbolId, Version,
95};
96pub use symbol::{
97 Confidence, Member, MemberKind, PARSE_ERROR_TAG, Relation, RelationKind, Span, Symbol, SymbolKind, Visibility,
98};
99
100/// Version of the serialised model schema (the JSON shape of [`Codebase`]).
101///
102/// Bumped whenever a field is added, removed or changes meaning, so agents and
103/// tools reading a persisted model can refuse a schema they do not understand
104/// ([`Codebase::from_json`] does).
105///
106/// | Version | Change |
107/// |---|---|
108/// | 1 | `::` path ids, field `schema` (sealmap 0.1) |
109/// | 2 | `sym:` ids, `sig_hash` / `body_hash` on every symbol, field `schema_version` |
110///
111/// Version 1 has no reader: nothing outside this workspace consumed it.
112pub const MODEL_SCHEMA_VERSION: u32 = 2;
113
114/// Compiles and runs the README examples as doctests.
115#[cfg(doctest)]
116#[doc = include_str!("../README.md")]
117struct ReadmeDoctests;