weavatrix_parse/facts.rs
1//! Language-neutral facts a structural pass extracts from a token stream.
2//!
3//! These are the shapes repository intelligence actually consumes. Anything a
4//! consumer cannot use - operator precedence, expression trees, type
5//! inference - is deliberately absent, which is what keeps extraction linear
6//! in the token count.
7
8/// Position of a fact in its source file.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Span {
11 pub start: usize,
12 pub end: usize,
13 pub line: u32,
14 pub column: u32,
15 pub end_line: u32,
16 pub end_column: u32,
17}
18
19/// The GraphQL root operation a field exposes or an executable document calls.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum GraphqlOperation {
22 Query,
23 Mutation,
24 Subscription,
25}
26
27/// The schema role of a GraphQL named type.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum GraphqlType {
30 Object,
31 Interface,
32 Input,
33 Enum,
34 Scalar,
35 Union,
36}
37
38/// A typed API-contract fact.
39#[derive(Debug, Clone, PartialEq, Eq)]
40#[non_exhaustive]
41pub enum ContractKind {
42 GraphqlType(GraphqlType),
43 GraphqlField {
44 operation: Option<GraphqlOperation>,
45 return_type: String,
46 },
47 GraphqlOperation(GraphqlOperation),
48 GraphqlCall(GraphqlOperation),
49 GraphqlFragment {
50 on_type: String,
51 operation: Option<GraphqlOperation>,
52 },
53 GraphqlFragmentSpread,
54 ProtobufPackage,
55 ProtobufMessage,
56 ProtobufEnum,
57 ProtobufService,
58 ProtobufRpc {
59 input: String,
60 output: String,
61 client_streaming: bool,
62 server_streaming: bool,
63 },
64}
65
66/// A named contract element and its exact source location.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct Contract {
69 pub name: String,
70 pub kind: ContractKind,
71 pub span: Span,
72 pub owner: Option<String>,
73}
74
75/// A fail-closed diagnostic emitted instead of guessed structural facts.
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct ParseDiagnostic {
78 pub code: &'static str,
79 pub message: String,
80 pub span: Span,
81}
82
83/// What a declared name is.
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[non_exhaustive]
86pub enum DeclarationKind {
87 Function,
88 Method,
89 Class,
90 Interface,
91 Enum,
92 TypeAlias,
93 Field,
94 Constant,
95 Variable,
96 Module,
97 Struct,
98 Trait,
99 Table,
100 View,
101 Procedure,
102 /// A CSS class or id selector, named with its leading `.` or `#`.
103 Selector,
104 /// An infrastructure object: a Terraform resource, data source or output.
105 Resource,
106 /// A section heading in a document.
107 Heading,
108}
109
110/// A named declaration and where it was written.
111#[derive(Debug, Clone, PartialEq, Eq)]
112#[non_exhaustive]
113pub struct Declaration {
114 pub name: String,
115 pub kind: DeclarationKind,
116 pub span: Span,
117 /// Enclosing declaration, when the language nests them.
118 pub owner: Option<String>,
119 /// Whether the declaration leaves the module.
120 pub exported: bool,
121}
122
123/// A module this file pulls in.
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct ImportBinding {
126 /// The name exported by the imported module.
127 pub imported: String,
128 /// The name made available in this file.
129 pub local: String,
130}
131
132/// A module this file pulls in.
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct Import {
135 /// The specifier exactly as written, without quotes.
136 pub specifier: String,
137 pub span: Span,
138 /// A type-position import, which disappears when the code is compiled.
139 pub type_only: bool,
140 /// `export ... from`, which forwards another module's surface.
141 pub reexport: bool,
142 /// Local names this import binds.
143 ///
144 /// Without them a consumer meeting `router` in `app.use("/api", router)`
145 /// cannot tell which module it came from, and the mount resolves to
146 /// nothing.
147 pub names: Vec<String>,
148 /// Lossless exported-to-local binding pairs.
149 ///
150 /// `names` remains the backward-compatible list of local names. This field
151 /// preserves the source name too, so `import { original as local }` can be
152 /// resolved to `original` without a repository-wide guess for `local`.
153 pub bindings: Vec<ImportBinding>,
154}
155
156/// Why one name mentions another.
157///
158/// A call and an `extends` clause are both "this name depends on that name",
159/// and separating them into different fact types would force every consumer to
160/// walk two collections to answer one question.
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162#[non_exhaustive]
163pub enum ReferenceKind {
164 Call,
165 Inherits,
166 Implements,
167 /// A name used without being called, as an HTML `class` attribute uses a
168 /// CSS selector.
169 Uses,
170 /// A statement that reads the named object, as `SELECT ... FROM users`.
171 Reads,
172 /// A statement that writes it, as `INSERT INTO users`.
173 Writes,
174}
175
176/// One name mentioning another.
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct Reference {
179 /// The referenced name, without its receiver.
180 pub name: String,
181 pub kind: ReferenceKind,
182 /// Receiver written before the final dot, when there was one.
183 pub receiver: Option<String>,
184 pub span: Span,
185 /// Enclosing declaration the reference was written in.
186 pub owner: Option<String>,
187 /// Literal string arguments, which carry routes, topics and table names.
188 pub string_arguments: Vec<String>,
189 /// Names passed as arguments, in the order written.
190 ///
191 /// `app.use("/api", router)` mounts one module under a prefix, and the
192 /// prefix is a string while the module is a name - so a consumer that
193 /// only sees literals sees half the fact and can resolve neither end.
194 pub name_arguments: Vec<String>,
195}
196
197/// Everything one structural pass found in one file.
198#[derive(Debug, Clone, Default, PartialEq, Eq)]
199pub struct Facts {
200 pub declarations: Vec<Declaration>,
201 /// Exact spans of declarations that exist only in a test compilation.
202 ///
203 /// Keeping this sparse avoids enlarging every declaration in every
204 /// language. Rust fills it from `#[test]` and positive `#[cfg(test)]`
205 /// contexts; languages without compile-time test scopes leave it empty.
206 pub test_only_declarations: Vec<Span>,
207 pub imports: Vec<Import>,
208 pub references: Vec<Reference>,
209 pub contracts: Vec<Contract>,
210 pub diagnostics: Vec<ParseDiagnostic>,
211}
212
213impl Facts {
214 /// Whether the declaration at `span` only exists in a test compilation.
215 #[must_use]
216 pub fn declaration_is_test_only(&self, span: Span) -> bool {
217 self.test_only_declarations.contains(&span)
218 }
219
220 /// Just the call sites, for consumers that want a call graph and nothing
221 /// else.
222 pub fn calls(&self) -> impl Iterator<Item = &Reference> {
223 self.references
224 .iter()
225 .filter(|reference| reference.kind == ReferenceKind::Call)
226 }
227}