1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Artifact build/load, fragments cache, incremental reuse.
// Writes are atomic (tmp+rename); fragments cache keying is load-bearing --
// a mismatch makes reuse break silently.
//
// This module owns every serde struct for graph.json + the fragments-cache
// pair (fragments-v44.json, fragments-index-v44.json), plus their path resolution,
// atomic I/O, and the cache-then-resolve-then-write orchestration
// (`rebuild_graph`). The pure resolution ladder that
// turns fragments into `defs`/`edges` lives in `resolve.rs` and returns the
// `Graph` value this module serializes -- see that module for the ladder
// itself.
//
// Schema notes (the on-disk format graph.json must keep):
// - graph.json is compact JSON with NO indentation:
// `{"key":value,...}`, no spaces. `serde_json::to_vec` (non-pretty)
// matches this by default.
// - Key order is insertion order, not sorted. Defs keep first-insertion
// order (partial-class duplicates land in
// `also_in`, not a second top-level entry) -- backed here by a
// Vec<Def> + HashMap<id, index> pair (`build_def_index` in resolve.rs)
// rather than a HashMap alone, which has no defined iteration order.
// - `also_in` is omitted entirely (not `[]`) when a def has no additional
// declaring sites -- `#[serde(skip_serializing_if = "Vec::is_empty")]`.
// - `edges` are shape-heterogeneous by `kind`: inherits/uses-type/
// uses-member share `{kind, from_file, from_line, to, to_file}`;
// imports is `{kind, from_file, from_line, target}`; ambiguous is
// `{kind, origin, from_file, from_line, raw, candidates,
// candidate_count}`. Modeled as an internally-tagged enum
// (`#[serde(tag = "kind")]`) -- serde always emits the tag field first,
// matching every one of these shapes' field order.
// - a `uses-member` edge appends three more keys AFTER that shared prefix,
// in this exact order: `heuristic` (omitted when precise), `tier`, then
// `member`. `tier` names WHICH guess tier emitted the row and is present
// exactly when `heuristic` is; `member` names the member the reference
// reads or calls and is written on EVERY uses-member edge, the precise
// ones included -- it is the one fact the row never carried. Both are
// omit-when-`None`, so the append-last rule every other added key follows
// holds here too; the other two flagged kinds (inherits, uses-type) stay
// exactly as they were.
// - `schema_version` is `GRAPH_SCHEMA_VERSION`, which the `tier`/`member`
// append above moved to 2, and which the `implements`/`overrides` edge
// kinds moved to 3. `rebuild_graph`'s unchanged fast path reads
// the first bytes of an existing graph.json and refuses to reuse one
// written at an older version, so a stale artifact is rebuilt on the next
// `map` even when not one fragment moved.
// - `units` is the LAST key of the whole object, appended after `names`
// and omitted entirely (not `[]`) when the repo declares no `.csproj` --
// so a tree without one serializes byte-for-byte as it did before the
// project model existed. Each row's own key order is fixed too: `id`,
// `name`, then `refs` (omitted when the project references nothing) and
// `test` (omitted when false). A unit's DIRECTORY is not persisted: it
// is `id`'s parent, recomputed on read (`project::units_from_graph`),
// and neither is per-file/per-def membership -- that is derived from the
// unit list by `ProjectModel` rather than stored.
// - `stats.edges_by_kind` has a FIXED key order (inherits, uses-type,
// imports, uses-member, ctor-di, implements, overrides) -- not
// alphabetical, not insertion order of first edge seen. A plain struct
// with that declared field order reproduces it.
// - `stats.ambiguous_pct` is `round(x*1000)/10`: a number that prints
// WITHOUT a decimal point when whole (`20`, not `20.0`) and with
// exactly one decimal digit otherwise (`33.3`). Rust's f64 Serialize
// (ryu-backed) does not reproduce this -- see `Percent1` below, which
// stores tenths as an integer and branches serialize_i64/serialize_f64
// so whole values come out as bare integers, without needing serde_json's
// `arbitrary_precision`/`raw_value` features (neither is enabled).
// - fragments.json / fragments-index.json are objects keyed by
// repo-relative path, in file-walk order (fragments-index.json is
// rebuilt by iterating the same cache object fragments.json used).
// A `HashMap` has no serialization order guarantee and `serde_json`'s
// default `Map` (no `preserve_order` feature, which is off here) sorts
// by key -- neither matches. `OrderedMap<V>` below hand-rolls
// Serialize/Deserialize (using serde's own `SerializeMap` / `MapAccess`
// traits, not a bespoke JSON writer) to preserve insertion order.
// - `built_at_head` records `git -C <root> rev-parse HEAD` (`null` on any
// failure incl. no commits yet). `manifest.rs` already owns that
// (`manifest::git_head`) -- `resolve_graph` (resolve.rs) calls THAT, so
// this module does not duplicate it.
pub use ;
// Test-only: production code writes these artifacts through `map`, never through
// a direct writer.
pub use write_graph;
pub use write_fragments_cache;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use atomic_write_json;
pub use fragment_generation;
pub use fragments_cache_path;
pub use ;
pub use ;