hcl_edit/
visit.rs

1//! HCL language item traversal.
2//!
3//! Each method of the [`Visit`] trait is a hook that can be overridden to customize the behavior
4//! when visiting the corresponding type of language item. By default, every method recursively
5//! visits the substructure of the AST by invoking the right visitor method of each of its fields.
6//!
7//! The API is modeled after [`syn::visit`](https://docs.rs/syn/latest/syn/visit/index.html). For a
8//! mutable alternative, see [`hcl_edit::visit_mut`](crate::visit_mut).
9//!
10//! # Examples
11//!
12//! Collect all referenced variables from a HCL document:
13//!
14//! ```
15//! # use std::error::Error;
16//! #
17//! # fn main() -> Result<(), Box<dyn Error>> {
18//! use hcl_edit::expr::Expression;
19//! use hcl_edit::structure::Body;
20//! use hcl_edit::visit::{visit_expr, Visit};
21//! use std::collections::HashSet;
22//! use std::str::FromStr;
23//!
24//! #[derive(Default)]
25//! struct VariableNameVisitor {
26//!     variable_names: HashSet<String>,
27//! }
28//!
29//! impl Visit for VariableNameVisitor {
30//!     fn visit_expr(&mut self, expr: &Expression) {
31//!         if let Expression::Variable(var) = expr {
32//!             self.variable_names.insert(var.to_string());
33//!         } else {
34//!             // Recurse further down the AST.
35//!             visit_expr(self, expr);
36//!         }
37//!     }
38//! }
39//!
40//! let input = r#"
41//!     // A service definition.
42//!     service {
43//!         fullname        = "${namespace}/${name}"
44//!         health_endpoint = "${base_url}/health"
45//!     }
46//! "#;
47//!
48//! let body = input.parse::<Body>()?;
49//!
50//! let mut visitor = VariableNameVisitor::default();
51//!
52//! visitor.visit_body(&body);
53//!
54//! let expected = HashSet::from(["namespace".into(), "name".into(), "base_url".into()]);
55//!
56//! assert_eq!(visitor.variable_names, expected);
57//! #   Ok(())
58//! # }
59//! ```
60
61#![allow(missing_docs)]
62
63use crate::expr::{
64    Array, BinaryOp, BinaryOperator, Conditional, Expression, ForCond, ForExpr, ForIntro, FuncArgs,
65    FuncCall, FuncName, Null, Object, ObjectKey, ObjectValue, Parenthesis, Splat, Traversal,
66    TraversalOperator, UnaryOp, UnaryOperator,
67};
68use crate::structure::{Attribute, Block, BlockLabel, Body, Structure};
69use crate::template::{
70    Directive, Element, ElseTemplateExpr, EndforTemplateExpr, EndifTemplateExpr, ForDirective,
71    ForTemplateExpr, HeredocTemplate, IfDirective, IfTemplateExpr, Interpolation, StringTemplate,
72    Template,
73};
74use crate::{Decorated, Formatted, Ident, Number, Spanned};
75
76macro_rules! empty_visit_methods {
77    ($($name: ident => $t: ty),+ $(,)?) => {
78        $(
79            fn $name(&mut self, node: &$t) {
80                let _ = node;
81            }
82        )*
83    };
84}
85
86macro_rules! visit_methods {
87    ($($name: ident => $t: ty),+ $(,)?) => {
88        $(
89            fn $name(&mut self, node: &$t) {
90                $name(self, node);
91            }
92        )*
93    };
94}
95
96/// Traversal to walk a shared borrow of an HCL language item.
97///
98/// See the [module documentation](crate::visit) for details.
99pub trait Visit {
100    empty_visit_methods! {
101        visit_ident => Decorated<Ident>,
102        visit_null => Decorated<Null>,
103        visit_bool => Decorated<bool>,
104        visit_u64 => Decorated<u64>,
105        visit_number => Formatted<Number>,
106        visit_string => Decorated<String>,
107        visit_splat => Decorated<Splat>,
108        visit_literal => Spanned<String>,
109        visit_unary_operator => Spanned<UnaryOperator>,
110        visit_binary_operator => Spanned<BinaryOperator>,
111        visit_endif_template_expr => EndifTemplateExpr,
112        visit_endfor_template_expr => EndforTemplateExpr,
113    }
114
115    visit_methods! {
116        visit_body => Body,
117        visit_structure => Structure,
118        visit_attr => Attribute,
119        visit_block => Block,
120        visit_block_label => BlockLabel,
121        visit_expr => Expression,
122        visit_array => Array,
123        visit_object => Object,
124        visit_object_key => ObjectKey,
125        visit_object_value => ObjectValue,
126        visit_parenthesis => Parenthesis,
127        visit_conditional => Conditional,
128        visit_unary_op => UnaryOp,
129        visit_binary_op => BinaryOp,
130        visit_traversal => Traversal,
131        visit_traversal_operator => TraversalOperator,
132        visit_func_call => FuncCall,
133        visit_func_name => FuncName,
134        visit_func_args => FuncArgs,
135        visit_for_expr => ForExpr,
136        visit_for_intro => ForIntro,
137        visit_for_cond => ForCond,
138        visit_string_template => StringTemplate,
139        visit_heredoc_template => HeredocTemplate,
140        visit_template => Template,
141        visit_element => Element,
142        visit_interpolation => Interpolation,
143        visit_directive => Directive,
144        visit_if_directive => IfDirective,
145        visit_for_directive => ForDirective,
146        visit_if_template_expr => IfTemplateExpr,
147        visit_else_template_expr => ElseTemplateExpr,
148        visit_for_template_expr => ForTemplateExpr,
149    }
150
151    fn visit_object_item(&mut self, key: &ObjectKey, value: &ObjectValue) {
152        visit_object_item(self, key, value);
153    }
154}
155
156pub fn visit_body<V>(v: &mut V, node: &Body)
157where
158    V: Visit + ?Sized,
159{
160    for structure in node {
161        v.visit_structure(structure);
162    }
163}
164
165pub fn visit_structure<V>(v: &mut V, node: &Structure)
166where
167    V: Visit + ?Sized,
168{
169    match node {
170        Structure::Attribute(attr) => v.visit_attr(attr),
171        Structure::Block(block) => v.visit_block(block),
172    }
173}
174
175pub fn visit_attr<V>(v: &mut V, node: &Attribute)
176where
177    V: Visit + ?Sized,
178{
179    v.visit_ident(&node.key);
180    v.visit_expr(&node.value);
181}
182
183pub fn visit_block<V>(v: &mut V, node: &Block)
184where
185    V: Visit + ?Sized,
186{
187    v.visit_ident(&node.ident);
188    for label in &node.labels {
189        v.visit_block_label(label);
190    }
191    v.visit_body(&node.body);
192}
193
194pub fn visit_block_label<V>(v: &mut V, node: &BlockLabel)
195where
196    V: Visit + ?Sized,
197{
198    match node {
199        BlockLabel::String(string) => v.visit_string(string),
200        BlockLabel::Ident(ident) => v.visit_ident(ident),
201    }
202}
203
204pub fn visit_expr<V>(v: &mut V, node: &Expression)
205where
206    V: Visit + ?Sized,
207{
208    match node {
209        Expression::Null(null) => v.visit_null(null),
210        Expression::Bool(b) => v.visit_bool(b),
211        Expression::Number(number) => v.visit_number(number),
212        Expression::String(string) => v.visit_string(string),
213        Expression::Array(array) => v.visit_array(array),
214        Expression::Object(object) => v.visit_object(object),
215        Expression::StringTemplate(template) => v.visit_string_template(template),
216        Expression::HeredocTemplate(template) => v.visit_heredoc_template(template),
217        Expression::Parenthesis(parens) => v.visit_parenthesis(parens),
218        Expression::Variable(var) => v.visit_ident(var),
219        Expression::ForExpr(for_expr) => v.visit_for_expr(for_expr),
220        Expression::Conditional(conditional) => v.visit_conditional(conditional),
221        Expression::FuncCall(func_call) => v.visit_func_call(func_call),
222        Expression::UnaryOp(unary_op) => v.visit_unary_op(unary_op),
223        Expression::BinaryOp(binary_op) => v.visit_binary_op(binary_op),
224        Expression::Traversal(traversal) => v.visit_traversal(traversal),
225    }
226}
227
228pub fn visit_array<V>(v: &mut V, node: &Array)
229where
230    V: Visit + ?Sized,
231{
232    for expr in node {
233        v.visit_expr(expr);
234    }
235}
236
237pub fn visit_object<V>(v: &mut V, node: &Object)
238where
239    V: Visit + ?Sized,
240{
241    for (key, value) in node {
242        v.visit_object_item(key, value);
243    }
244}
245
246pub fn visit_object_item<V>(v: &mut V, key: &ObjectKey, value: &ObjectValue)
247where
248    V: Visit + ?Sized,
249{
250    v.visit_object_key(key);
251    v.visit_object_value(value);
252}
253
254pub fn visit_object_key<V>(v: &mut V, node: &ObjectKey)
255where
256    V: Visit + ?Sized,
257{
258    match node {
259        ObjectKey::Ident(ident) => v.visit_ident(ident),
260        ObjectKey::Expression(expr) => v.visit_expr(expr),
261    }
262}
263
264pub fn visit_object_value<V>(v: &mut V, node: &ObjectValue)
265where
266    V: Visit + ?Sized,
267{
268    v.visit_expr(node.expr());
269}
270
271pub fn visit_parenthesis<V>(v: &mut V, node: &Parenthesis)
272where
273    V: Visit + ?Sized,
274{
275    v.visit_expr(node.inner());
276}
277
278pub fn visit_conditional<V>(v: &mut V, node: &Conditional)
279where
280    V: Visit + ?Sized,
281{
282    v.visit_expr(&node.cond_expr);
283    v.visit_expr(&node.true_expr);
284    v.visit_expr(&node.false_expr);
285}
286
287pub fn visit_unary_op<V>(v: &mut V, node: &UnaryOp)
288where
289    V: Visit + ?Sized,
290{
291    v.visit_unary_operator(&node.operator);
292    v.visit_expr(&node.expr);
293}
294
295pub fn visit_binary_op<V>(v: &mut V, node: &BinaryOp)
296where
297    V: Visit + ?Sized,
298{
299    v.visit_expr(&node.lhs_expr);
300    v.visit_binary_operator(&node.operator);
301    v.visit_expr(&node.rhs_expr);
302}
303
304pub fn visit_traversal<V>(v: &mut V, node: &Traversal)
305where
306    V: Visit + ?Sized,
307{
308    v.visit_expr(&node.expr);
309    for operator in &node.operators {
310        v.visit_traversal_operator(operator);
311    }
312}
313
314pub fn visit_traversal_operator<V>(v: &mut V, node: &TraversalOperator)
315where
316    V: Visit + ?Sized,
317{
318    match node {
319        TraversalOperator::AttrSplat(splat) | TraversalOperator::FullSplat(splat) => {
320            v.visit_splat(splat);
321        }
322        TraversalOperator::GetAttr(ident) => v.visit_ident(ident),
323        TraversalOperator::Index(expr) => v.visit_expr(expr),
324        TraversalOperator::LegacyIndex(u) => v.visit_u64(u),
325    }
326}
327
328pub fn visit_func_call<V>(v: &mut V, node: &FuncCall)
329where
330    V: Visit + ?Sized,
331{
332    v.visit_func_name(&node.name);
333    v.visit_func_args(&node.args);
334}
335
336pub fn visit_func_name<V>(v: &mut V, node: &FuncName)
337where
338    V: Visit + ?Sized,
339{
340    for component in &node.namespace {
341        v.visit_ident(component);
342    }
343    v.visit_ident(&node.name);
344}
345
346pub fn visit_func_args<V>(v: &mut V, node: &FuncArgs)
347where
348    V: Visit + ?Sized,
349{
350    for arg in node {
351        v.visit_expr(arg);
352    }
353}
354
355pub fn visit_for_expr<V>(v: &mut V, node: &ForExpr)
356where
357    V: Visit + ?Sized,
358{
359    v.visit_for_intro(&node.intro);
360    if let Some(key_expr) = &node.key_expr {
361        v.visit_expr(key_expr);
362    }
363    v.visit_expr(&node.value_expr);
364    if let Some(cond) = &node.cond {
365        v.visit_for_cond(cond);
366    }
367}
368
369pub fn visit_for_intro<V>(v: &mut V, node: &ForIntro)
370where
371    V: Visit + ?Sized,
372{
373    if let Some(key_var) = &node.key_var {
374        v.visit_ident(key_var);
375    }
376    v.visit_ident(&node.value_var);
377    v.visit_expr(&node.collection_expr);
378}
379
380pub fn visit_for_cond<V>(v: &mut V, node: &ForCond)
381where
382    V: Visit + ?Sized,
383{
384    v.visit_expr(&node.expr);
385}
386
387pub fn visit_string_template<V>(v: &mut V, node: &StringTemplate)
388where
389    V: Visit + ?Sized,
390{
391    for element in node {
392        v.visit_element(element);
393    }
394}
395
396pub fn visit_heredoc_template<V>(v: &mut V, node: &HeredocTemplate)
397where
398    V: Visit + ?Sized,
399{
400    v.visit_template(&node.template);
401}
402
403pub fn visit_template<V>(v: &mut V, node: &Template)
404where
405    V: Visit + ?Sized,
406{
407    for element in node {
408        v.visit_element(element);
409    }
410}
411
412pub fn visit_element<V>(v: &mut V, node: &Element)
413where
414    V: Visit + ?Sized,
415{
416    match node {
417        Element::Literal(literal) => v.visit_literal(literal),
418        Element::Interpolation(interpolation) => v.visit_interpolation(interpolation),
419        Element::Directive(directive) => v.visit_directive(directive),
420    }
421}
422
423pub fn visit_interpolation<V>(v: &mut V, node: &Interpolation)
424where
425    V: Visit + ?Sized,
426{
427    v.visit_expr(&node.expr);
428}
429
430pub fn visit_directive<V>(v: &mut V, node: &Directive)
431where
432    V: Visit + ?Sized,
433{
434    match node {
435        Directive::If(if_directive) => v.visit_if_directive(if_directive),
436        Directive::For(for_directive) => v.visit_for_directive(for_directive),
437    }
438}
439
440pub fn visit_if_directive<V>(v: &mut V, node: &IfDirective)
441where
442    V: Visit + ?Sized,
443{
444    v.visit_if_template_expr(&node.if_expr);
445    if let Some(else_template_expr) = &node.else_expr {
446        v.visit_else_template_expr(else_template_expr);
447    }
448    v.visit_endif_template_expr(&node.endif_expr);
449}
450
451pub fn visit_for_directive<V>(v: &mut V, node: &ForDirective)
452where
453    V: Visit + ?Sized,
454{
455    v.visit_for_template_expr(&node.for_expr);
456    v.visit_endfor_template_expr(&node.endfor_expr);
457}
458
459pub fn visit_if_template_expr<V>(v: &mut V, node: &IfTemplateExpr)
460where
461    V: Visit + ?Sized,
462{
463    v.visit_expr(&node.cond_expr);
464    v.visit_template(&node.template);
465}
466
467pub fn visit_else_template_expr<V>(v: &mut V, node: &ElseTemplateExpr)
468where
469    V: Visit + ?Sized,
470{
471    v.visit_template(&node.template);
472}
473
474pub fn visit_for_template_expr<V>(v: &mut V, node: &ForTemplateExpr)
475where
476    V: Visit + ?Sized,
477{
478    if let Some(key_var) = &node.key_var {
479        v.visit_ident(key_var);
480    }
481    v.visit_ident(&node.value_var);
482    v.visit_template(&node.template);
483}