1use plsql_core::Span;
21
22use crate::ast::{AstDecl, SourceFile};
23
24pub trait Visitor: Sized {
52 fn visit_source_file(&mut self, source_file: &SourceFile) {
56 walk::walk_source_file(self, source_file);
57 }
58
59 fn visit_decl(&mut self, decl: &AstDecl) {
63 walk::walk_decl(self, decl);
64 }
65
66 fn visit_package_spec(&mut self, _name: &str, _span: &Span) {}
68
69 fn visit_package_body(&mut self, _name: &str, _span: &Span) {}
71
72 fn visit_procedure(&mut self, _name: &str, _span: &Span) {}
74
75 fn visit_function(&mut self, _name: &str, _span: &Span) {}
77
78 fn visit_trigger(&mut self, _name: &str, _span: &Span) {}
80
81 fn visit_view(&mut self, _name: &str, _span: &Span) {}
83
84 fn visit_type_spec(&mut self, _name: &str, _span: &Span) {}
86
87 fn visit_type_body(&mut self, _name: &str, _span: &Span) {}
89
90 fn visit_ddl(&mut self, _kind: &str, _span: &Span) {}
92
93 fn visit_unknown(&mut self, _span: &Span) {}
95}
96
97pub mod walk {
107 use super::*;
108
109 pub fn walk_source_file<V: Visitor>(visitor: &mut V, source_file: &SourceFile) {
111 for decl in &source_file.declarations {
112 visitor.visit_decl(decl);
113 }
114 }
115
116 pub fn walk_decl<V: Visitor>(visitor: &mut V, decl: &AstDecl) {
118 match decl {
119 AstDecl::PackageSpec { name, span } => visitor.visit_package_spec(name, span),
120 AstDecl::PackageBody { name, span } => visitor.visit_package_body(name, span),
121 AstDecl::Procedure { name, span } => visitor.visit_procedure(name, span),
122 AstDecl::Function { name, span } => visitor.visit_function(name, span),
123 AstDecl::Trigger { name, span } => visitor.visit_trigger(name, span),
124 AstDecl::View { name, span } => visitor.visit_view(name, span),
125 AstDecl::TypeSpec { name, span } => visitor.visit_type_spec(name, span),
126 AstDecl::TypeBody { name, span } => visitor.visit_type_body(name, span),
127 AstDecl::Ddl { kind, span, .. } => visitor.visit_ddl(kind, span),
128 AstDecl::Unknown { span, .. } => visitor.visit_unknown(span),
129 }
130 }
131}
132
133pub fn visit_source_file<V: Visitor>(visitor: &mut V, source_file: &SourceFile) {
139 visitor.visit_source_file(source_file);
140}
141
142#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::ast::AstDecl;
150 use plsql_core::{FileId, Position};
151
152 fn span(offset: u32, len: u32) -> Span {
153 Span::new(
154 FileId::new(0),
155 Position::new(1, 1, offset),
156 Position::new(1, 1, offset + len),
157 )
158 }
159
160 struct DeclCounter {
162 count: usize,
163 package_count: usize,
164 unknown_count: usize,
165 }
166
167 impl DeclCounter {
168 fn new() -> Self {
169 Self {
170 count: 0,
171 package_count: 0,
172 unknown_count: 0,
173 }
174 }
175 }
176
177 impl Visitor for DeclCounter {
178 fn visit_decl(&mut self, decl: &AstDecl) {
179 self.count += 1;
180 walk::walk_decl(self, decl);
181 }
182
183 fn visit_package_spec(&mut self, _name: &str, _span: &Span) {
184 self.package_count += 1;
185 }
186
187 fn visit_package_body(&mut self, _name: &str, _span: &Span) {
188 self.package_count += 1;
189 }
190
191 fn visit_unknown(&mut self, _span: &Span) {
192 self.unknown_count += 1;
193 }
194 }
195
196 #[test]
197 fn visitor_counts_declarations() {
198 let sf = SourceFile {
199 span: span(0, 200),
200 declarations: vec![
201 AstDecl::PackageSpec {
202 name: "pkg_a".into(),
203 span: span(0, 50),
204 },
205 AstDecl::PackageBody {
206 name: "pkg_a".into(),
207 span: span(50, 50),
208 },
209 AstDecl::Procedure {
210 name: "standalone_p".into(),
211 span: span(100, 50),
212 },
213 AstDecl::Unknown {
214 span: span(150, 50),
215 antlr_rule_path: None,
216 },
217 ],
218 };
219
220 let mut counter = DeclCounter::new();
221 visit_source_file(&mut counter, &sf);
222
223 assert_eq!(counter.count, 4);
224 assert_eq!(counter.package_count, 2);
225 assert_eq!(counter.unknown_count, 1);
226 }
227
228 struct NameCollector {
230 names: Vec<String>,
231 }
232
233 impl NameCollector {
234 fn new() -> Self {
235 Self { names: Vec::new() }
236 }
237 }
238
239 impl Visitor for NameCollector {
240 fn visit_package_spec(&mut self, name: &str, _span: &Span) {
241 self.names.push(format!("package_spec:{name}"));
242 }
243
244 fn visit_package_body(&mut self, name: &str, _span: &Span) {
245 self.names.push(format!("package_body:{name}"));
246 }
247
248 fn visit_procedure(&mut self, name: &str, _span: &Span) {
249 self.names.push(format!("procedure:{name}"));
250 }
251
252 fn visit_function(&mut self, name: &str, _span: &Span) {
253 self.names.push(format!("function:{name}"));
254 }
255
256 fn visit_trigger(&mut self, name: &str, _span: &Span) {
257 self.names.push(format!("trigger:{name}"));
258 }
259
260 fn visit_view(&mut self, name: &str, _span: &Span) {
261 self.names.push(format!("view:{name}"));
262 }
263 }
264
265 #[test]
266 fn visitor_collects_names() {
267 let sf = SourceFile {
268 span: span(0, 100),
269 declarations: vec![
270 AstDecl::PackageSpec {
271 name: "emp_pkg".into(),
272 span: span(0, 20),
273 },
274 AstDecl::Function {
275 name: "get_name".into(),
276 span: span(20, 20),
277 },
278 AstDecl::View {
279 name: "v_emp".into(),
280 span: span(40, 20),
281 },
282 AstDecl::Trigger {
283 name: "trg_audit".into(),
284 span: span(60, 20),
285 },
286 ],
287 };
288
289 let mut collector = NameCollector::new();
290 visit_source_file(&mut collector, &sf);
291
292 assert_eq!(
293 collector.names,
294 vec![
295 "package_spec:emp_pkg",
296 "function:get_name",
297 "view:v_emp",
298 "trigger:trg_audit",
299 ]
300 );
301 }
302
303 struct DdlCounter {
305 creates: usize,
306 alters: usize,
307 drops: usize,
308 }
309
310 impl DdlCounter {
311 fn new() -> Self {
312 Self {
313 creates: 0,
314 alters: 0,
315 drops: 0,
316 }
317 }
318 }
319
320 impl Visitor for DdlCounter {
321 fn visit_ddl(&mut self, kind: &str, _span: &Span) {
322 match kind {
323 "CREATE" => self.creates += 1,
324 "ALTER" => self.alters += 1,
325 "DROP" => self.drops += 1,
326 _ => {}
327 }
328 }
329 }
330
331 #[test]
332 fn visitor_counts_ddl_by_kind() {
333 let sf = SourceFile {
334 span: span(0, 100),
335 declarations: vec![
336 AstDecl::Ddl {
337 kind: "CREATE".into(),
338 span: span(0, 20),
339 antlr_rule_path: None,
340 },
341 AstDecl::Ddl {
342 kind: "ALTER".into(),
343 span: span(20, 20),
344 antlr_rule_path: None,
345 },
346 AstDecl::Ddl {
347 kind: "DROP".into(),
348 span: span(40, 20),
349 antlr_rule_path: None,
350 },
351 AstDecl::Ddl {
352 kind: "CREATE".into(),
353 span: span(60, 20),
354 antlr_rule_path: None,
355 },
356 ],
357 };
358
359 let mut counter = DdlCounter::new();
360 visit_source_file(&mut counter, &sf);
361
362 assert_eq!(counter.creates, 2);
363 assert_eq!(counter.alters, 1);
364 assert_eq!(counter.drops, 1);
365 }
366
367 #[test]
368 fn default_visitor_recurse_all_variants() {
369 let sf = SourceFile {
370 span: span(0, 200),
371 declarations: vec![
372 AstDecl::PackageSpec {
373 name: "a".into(),
374 span: span(0, 10),
375 },
376 AstDecl::PackageBody {
377 name: "a".into(),
378 span: span(10, 10),
379 },
380 AstDecl::Procedure {
381 name: "p".into(),
382 span: span(20, 10),
383 },
384 AstDecl::Function {
385 name: "f".into(),
386 span: span(30, 10),
387 },
388 AstDecl::Trigger {
389 name: "t".into(),
390 span: span(40, 10),
391 },
392 AstDecl::View {
393 name: "v".into(),
394 span: span(50, 10),
395 },
396 AstDecl::TypeSpec {
397 name: "ty".into(),
398 span: span(60, 10),
399 },
400 AstDecl::TypeBody {
401 name: "ty".into(),
402 span: span(70, 10),
403 },
404 AstDecl::Ddl {
405 kind: "CREATE".into(),
406 span: span(80, 10),
407 antlr_rule_path: None,
408 },
409 AstDecl::Unknown {
410 span: span(90, 10),
411 antlr_rule_path: None,
412 },
413 ],
414 };
415
416 struct NoOpVisitor;
418 impl Visitor for NoOpVisitor {}
419
420 let mut visitor = NoOpVisitor;
421 visit_source_file(&mut visitor, &sf);
422 }
423}