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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Canonical rendering of a matched node — or, under `capture:`, of the parts
//! of it the pattern named: what an `ast:` probe actually hashes.
//!
//! # Why a rendering and not the matched text
//!
//! This is the same decision [`crate::json`] makes when it sorts object keys on
//! the way out, for the same reason. Hashing the matched *source text* would
//! make whitespace an input: a `cargo fmt` that reflows a signature across two
//! lines would report a changed public API, and the rule would re-run against a
//! file whose meaning did not move. Rendering the tree instead drops every byte
//! the parser dropped — indentation, line breaks, the spaces around `->` — and
//! keeps every byte it kept.
//!
//! # What it keeps, and why that is all of it
//!
//! [`render`] walks *every* child, named and anonymous, and prints each leaf's
//! exact text. Anonymous children are the operators and keywords, so `a + b`
//! and `a - b` render differently — a named-children-only walk (tree-sitter's
//! own `to_sexp`) collapses them, and a digest that cannot tell `+` from `-` is
//! precisely the lie this tool exists to refuse. Leaf text is exact rather than
//! whitespace-normalised, so `"a b"` and `"a b"` stay distinct string
//! literals.
//!
//! Between them those two rules make the rendering a lossless encoding of the
//! match's token sequence and tree shape. Nothing that changes the code can
//! leave the digest alone; only the inter-token whitespace, which no parse
//! records, is gone.
//!
//! # The cost this carries
//!
//! A rendering names node *kinds*, so it is pinned to the grammar that produced
//! them: upgrading mmz to a build with a newer tree-sitter grammar can change a
//! kind name and move every digest that mentions it. That is a false stale —
//! rules re-run once and settle — and it is the direction to fail in. Hashing
//! the matched text instead would be stable across grammar bumps and unstable
//! across reformatting, which trades a false stale for a *missed* one.
use Node;
use MetaVarEnv;
use StrDoc;
use SupportLang;
/// The document type every `ast:` probe parses into.
pub type Doc = ;
/// One step of the explicit walk in [`render`].
/// One matched node as canonical bytes.
///
/// The shape is an s-expression — `(kind child …)` for a node with children,
/// `(kind "text")` for a leaf — so a reader can eyeball what a probe measured
/// and see the tree rather than a hash.
///
/// The walk is an explicit stack rather than recursion because tree depth is
/// the input's to choose: a generated or minified source can nest thousands of
/// levels, and a recursive renderer would meet it with a stack overflow, which
/// aborts the process instead of failing the probe.
pub
/// [`render`]'s walk, writing into a buffer a caller may already be building.
/// [`render_captures`] needs exactly that, and going through `Vec<u8>` to get
/// there would re-validate bytes this module only ever wrote as UTF-8.
/// Puts a space before a node unless it opens the rendering or its parent, so
/// `(a(b))` never happens and `(a (b) (c))` reads as the tree it is.
/// One match's *captures* as canonical bytes: the shape a probe hashes when it
/// declares `capture:`.
///
/// Each named metavariable renders as `($NAME …)` wrapping the rendering of
/// every node bound to it, so the reading is the same s-expression as
/// [`render`] with the pattern's own names standing where the matched node
/// would have. The `$` is not decoration — it is what keeps a capture wrapper
/// from ever reading as a node of some grammar whose kinds happen to be
/// uppercase.
///
/// `names` arrives sorted and deduplicated (see [`crate::ast::select`]), which
/// is what makes reordering a `capture:` list not an edit to the input: the
/// list is the *set* of parts that matter, and the order it was typed in is the
/// author's presentation rather than anything the document said. That is the
/// same call [`crate::json`] makes on object keys, and the reason it is safe
/// here and not for match order is that no two capture sets differing in
/// content can normalise together — only two spellings of one set can.
///
/// A single capture (`$NAME`) binds one node, a multi capture (`$$$ARGS`) binds
/// a run of them, and one that bound none renders as a bare `($ARGS)` — which
/// is distinct from every count above it, so "the argument list emptied" stays
/// an edit. A name the pattern uses twice binds once: ast-grep keys its env by
/// name and requires the two occurrences to have matched identical nodes, so
/// there is nothing for a second mention to add.
pub
/// The nodes `name` is bound to in this match: one for a single capture, a run
/// for a multi capture, none for a multi capture that matched nothing.
///
/// The two are asked for in that order rather than distinguished up front
/// because ast-grep stores them in separate maps and a name is only ever in
/// one: a pattern cannot spell `$NAME` and `$$$NAME` as the same variable.