Skip to main content

zen_expression/intellisense/
dependency.rs

1use crate::functions::{ClosureFunction, FunctionKind};
2use crate::lexer::{LogicalOperator, Operator};
3use crate::parser::{Node, NodeMetadata};
4use crate::variable::Variable;
5use ahash::HashSet;
6use nohash_hasher::BuildNoHashHasher;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::rc::Rc;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12#[serde(tag = "type", rename_all = "snake_case")]
13pub enum ReadDependency {
14    Direct {
15        path: Vec<Rc<str>>,
16        #[serde(default)]
17        span: (u32, u32),
18        #[serde(default, skip_serializing_if = "is_false")]
19        via_index: bool,
20    },
21    Iteration {
22        collection: Vec<Rc<str>>,
23        #[serde(default)]
24        span: (u32, u32),
25        #[serde(skip_serializing_if = "Option::is_none")]
26        alias: Option<Rc<str>>,
27        reads: Vec<ReadDependency>,
28    },
29    Unresolved {
30        path: Vec<Rc<str>>,
31        #[serde(default)]
32        span: (u32, u32),
33    },
34}
35
36fn is_false(b: &bool) -> bool {
37    !*b
38}
39
40impl ReadDependency {
41    pub fn without_spans(&self) -> Self {
42        match self {
43            ReadDependency::Direct {
44                path, via_index, ..
45            } => ReadDependency::Direct {
46                path: path.clone(),
47                span: (0, 0),
48                via_index: *via_index,
49            },
50            ReadDependency::Iteration {
51                collection,
52                alias,
53                reads,
54                ..
55            } => ReadDependency::Iteration {
56                collection: collection.clone(),
57                span: (0, 0),
58                alias: alias.clone(),
59                reads: reads.iter().map(|r| r.without_spans()).collect(),
60            },
61            ReadDependency::Unresolved { path, .. } => ReadDependency::Unresolved {
62                path: path.clone(),
63                span: (0, 0),
64            },
65        }
66    }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70pub struct Reference {
71    pub path: Vec<Rc<str>>,
72    pub spans: Vec<(u32, u32)>,
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub via_alias: Option<AliasBinding>,
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub via_index: Option<Vec<Rc<str>>>,
77}
78
79impl Reference {
80    pub fn without_via_alias(&self) -> Self {
81        Self {
82            path: self.path.clone(),
83            spans: self.spans.clone(),
84            via_alias: None,
85            via_index: None,
86        }
87    }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
91pub struct AliasBinding {
92    pub alias: Rc<str>,
93    pub collection: Vec<Rc<str>>,
94}
95
96#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
97pub struct DependencyResult {
98    pub reads: Vec<ReadDependency>,
99    pub references: Vec<Reference>,
100}
101
102type MetadataMap = HashMap<usize, NodeMetadata, BuildNoHashHasher<usize>>;
103
104enum ChainSegment<'a> {
105    Field { name: Rc<str>, prop: &'a Node<'a> },
106    Dynamic { prop: &'a Node<'a> },
107}
108
109struct FlatChain<'a> {
110    root: &'a Node<'a>,
111    segments: Vec<ChainSegment<'a>>,
112}
113
114pub(crate) struct DependencyResolutionWalker<'a> {
115    reads: Vec<ReadDependency>,
116    references: Vec<Reference>,
117    metadata: &'a MetadataMap,
118}
119
120#[derive(Debug, Clone, Default)]
121struct Scope {
122    locals: HashSet<Rc<str>>,
123    bindings: Vec<Vec<Rc<str>>>,
124    aliases: HashMap<Rc<str>, Vec<Rc<str>>>,
125    unresolved_aliases: HashSet<Rc<str>>,
126    pointer_collection: Option<Vec<Rc<str>>>,
127}
128
129impl Scope {
130    fn is_local(&self, path: &[Rc<str>]) -> bool {
131        if path.is_empty() {
132            return false;
133        }
134
135        if self.locals.contains(&path[0]) {
136            return true;
137        }
138
139        self.bindings
140            .iter()
141            .any(|b| path.len() >= b.len() && path.iter().zip(b.iter()).all(|(a, b)| a == b))
142    }
143
144    fn alias_binding_for(&self, path: &[Rc<str>]) -> Option<AliasBinding> {
145        let root = path.first()?;
146        let collection = self.aliases.get(root)?;
147
148        Some(AliasBinding {
149            alias: root.clone(),
150            collection: collection.clone(),
151        })
152    }
153
154    fn expand_alias_root(&self, path: &[Rc<str>]) -> Vec<Rc<str>> {
155        match path.first().and_then(|root| self.aliases.get(root)) {
156            Some(prefix) => prefix.iter().chain(path.iter().skip(1)).cloned().collect(),
157            None => path.to_vec(),
158        }
159    }
160}
161
162fn node_address(node: &Node) -> usize {
163    node as *const Node as usize
164}
165
166impl<'a> DependencyResolutionWalker<'a> {
167    pub fn walk(root: &Node, metadata: &'a MetadataMap) -> DependencyResult {
168        Self::walk_with_locals(root, metadata, &[])
169    }
170
171    pub fn walk_with_locals(
172        root: &Node,
173        metadata: &'a MetadataMap,
174        locals: &[&str],
175    ) -> DependencyResult {
176        let mut walker = Self {
177            reads: Vec::new(),
178            references: Vec::new(),
179            metadata,
180        };
181
182        let mut scope = Scope::default();
183        scope.locals.extend(locals.iter().map(|l| Rc::from(*l)));
184        walker.resolve(root, &mut scope);
185
186        DependencyResult {
187            reads: walker.reads,
188            references: walker.references,
189        }
190    }
191
192    pub fn field_dependencies(
193        root: &Node,
194        metadata: &'a MetadataMap,
195        field_path: &[&str],
196    ) -> Option<Vec<ReadDependency>> {
197        let mut bindings: Vec<(Rc<str>, Vec<Rc<str>>)> = Vec::new();
198        let mut values: Vec<&Node> = Vec::new();
199        Self::navigate_field(root, field_path, &mut bindings, &mut values);
200        if values.is_empty() {
201            return None;
202        }
203
204        let mut reads: Vec<ReadDependency> = Vec::new();
205        for value in values {
206            let (r, _refs) = Self::walk_inner(value, &mut Scope::default(), metadata);
207            reads.extend(r);
208        }
209
210        let mut wrapped = reads;
211        for (alias, collection) in bindings.into_iter().rev() {
212            wrapped = vec![ReadDependency::Iteration {
213                collection,
214                span: (0, 0),
215                alias: Some(alias),
216                reads: wrapped,
217            }];
218        }
219        Some(wrapped)
220    }
221
222    fn navigate_field<'n>(
223        node: &'n Node<'n>,
224        field_path: &[&str],
225        bindings: &mut Vec<(Rc<str>, Vec<Rc<str>>)>,
226        out: &mut Vec<&'n Node<'n>>,
227    ) {
228        match node {
229            Node::Parenthesized(inner) => Self::navigate_field(inner, field_path, bindings, out),
230            Node::Assignments {
231                output: Some(output),
232                ..
233            } => Self::navigate_field(output, field_path, bindings, out),
234            Node::Conditional {
235                on_true, on_false, ..
236            } => {
237                Self::navigate_field(on_true, field_path, bindings, out);
238                Self::navigate_field(on_false, field_path, bindings, out);
239            }
240            Node::FunctionCall {
241                kind: FunctionKind::Closure(ClosureFunction::Map | ClosureFunction::FlatMap),
242                arguments,
243            } if arguments.len() >= 2 => {
244                if let Node::Closure { body, alias } = arguments[1] {
245                    if let (Some(a), Some(src)) =
246                        (alias, Self::collection_source_path(arguments[0]))
247                    {
248                        bindings.push((Rc::from(*a), src));
249                    }
250                    Self::navigate_field(body, field_path, bindings, out);
251                }
252            }
253            Node::Object(pairs) => {
254                let Some((first, rest)) = field_path.split_first() else {
255                    return;
256                };
257                let value = pairs.iter().find_map(|(k, v)| match k {
258                    Node::String(s) if s == first => Some(*v),
259                    _ => None,
260                });
261                if let Some(value) = value {
262                    if rest.is_empty() {
263                        out.push(value);
264                    } else {
265                        Self::navigate_field(value, rest, bindings, out);
266                    }
267                }
268            }
269            _ => {}
270        }
271    }
272
273    fn walk_inner(
274        node: &Node,
275        scope: &mut Scope,
276        metadata: &'a MetadataMap,
277    ) -> (Vec<ReadDependency>, Vec<Reference>) {
278        let mut walker = Self {
279            reads: Vec::new(),
280            references: Vec::new(),
281            metadata,
282        };
283        walker.resolve(node, scope);
284        (walker.reads, walker.references)
285    }
286
287    fn node_span(&self, node: &Node) -> (u32, u32) {
288        node.span()
289            .or_else(|| self.metadata.get(&node_address(node)).map(|m| m.span))
290            .unwrap_or_default()
291    }
292
293    fn extract_path_with_spans(&self, node: &Node) -> Option<(Vec<Rc<str>>, Vec<(u32, u32)>)> {
294        match node {
295            Node::Identifier(name) => Some((vec![Rc::from(*name)], vec![self.node_span(node)])),
296            Node::Root => Some((vec![Variable::root_key_rc()], vec![self.node_span(node)])),
297            Node::Member { node: n, property } => {
298                let (mut path, mut spans) = self.extract_path_with_spans(n)?;
299                match property {
300                    Node::String(key) => {
301                        path.push(Rc::from(*key));
302                        spans.push(self.node_span(property));
303                        Some((path, spans))
304                    }
305                    _ => None,
306                }
307            }
308            _ => None,
309        }
310    }
311
312    fn collection_source_path(node: &Node) -> Option<Vec<Rc<str>>> {
313        match node {
314            Node::Identifier(name) => Some(vec![Rc::from(*name)]),
315            Node::Root => Some(vec![Variable::root_key_rc()]),
316            Node::Member { .. } => Self::extract_read_path(node),
317            Node::Parenthesized(inner) => Self::collection_source_path(inner),
318            Node::Binary {
319                left,
320                operator: Operator::Logical(LogicalOperator::NullishCoalescing),
321                ..
322            } => Self::collection_source_path(left),
323            Node::FunctionCall {
324                kind: FunctionKind::Closure(ClosureFunction::Filter),
325                arguments,
326            } if !arguments.is_empty() => Self::collection_source_path(arguments[0]),
327            _ => None,
328        }
329    }
330
331    fn flatten_member_chain<'n>(node: &'n Node<'n>) -> Option<FlatChain<'n>> {
332        let mut segments: Vec<ChainSegment<'n>> = Vec::new();
333        let mut current = node;
334        loop {
335            match current {
336                Node::Member { node: n, property } => {
337                    segments.push(match property {
338                        Node::String(key) => ChainSegment::Field {
339                            name: Rc::from(*key),
340                            prop: property,
341                        },
342                        _ => ChainSegment::Dynamic { prop: property },
343                    });
344                    current = n;
345                }
346                Node::Identifier(_) | Node::Root | Node::Pointer => {
347                    segments.reverse();
348                    return Some(FlatChain {
349                        root: current,
350                        segments,
351                    });
352                }
353                _ => return None,
354            }
355        }
356    }
357
358    fn resolve_member_chain(&mut self, node: &Node, scope: &mut Scope) {
359        let Some(chain) = Self::flatten_member_chain(node) else {
360            if let Node::Member { node: n, property } = node {
361                self.resolve(n, scope);
362                self.resolve(property, scope);
363            }
364
365            return;
366        };
367
368        let root_name: Rc<str> = match chain.root {
369            Node::Identifier(name) if *name == "$" => {
370                if scope.pointer_collection.is_some() {
371                    self.resolve_pointer_chain(&chain, scope);
372                } else {
373                    self.reference_dollar_chain(&chain, scope);
374                }
375                return;
376            }
377            Node::Identifier(name) => Rc::from(*name),
378            Node::Root => Variable::root_key_rc(),
379            Node::Pointer => {
380                self.resolve_pointer_chain(&chain, scope);
381                return;
382            }
383            _ => return,
384        };
385        let root_span = self.node_span(chain.root);
386
387        if scope.unresolved_aliases.contains(&root_name) {
388            let mut path = vec![root_name.clone()];
389            let mut spans = vec![root_span];
390            for segment in &chain.segments {
391                match segment {
392                    ChainSegment::Field { name, prop } => {
393                        path.push(name.clone());
394                        spans.push(self.node_span(prop));
395                    }
396                    ChainSegment::Dynamic { prop } => self.resolve(prop, scope),
397                }
398            }
399            let span = match (spans.first(), spans.last()) {
400                (Some(first), Some(last)) => (first.0, last.1),
401                _ => Default::default(),
402            };
403            self.reads.push(ReadDependency::Unresolved {
404                path: path.clone(),
405                span,
406            });
407            self.references.push(Reference {
408                path,
409                spans,
410                via_alias: None,
411                via_index: None,
412            });
413
414            return;
415        }
416
417        let via_alias_first =
418            scope
419                .aliases
420                .get(&root_name)
421                .cloned()
422                .map(|collection| AliasBinding {
423                    alias: root_name.clone(),
424                    collection,
425                });
426
427        if via_alias_first.is_none() && scope.is_local(std::slice::from_ref(&root_name)) {
428            for segment in &chain.segments {
429                if let ChainSegment::Dynamic { prop } = segment {
430                    self.resolve(prop, scope);
431                }
432            }
433            return;
434        }
435
436        let mut group_path: Vec<Rc<str>> = vec![root_name.clone()];
437        let mut group_spans: Vec<(u32, u32)> = vec![root_span];
438        let mut cumulative: Vec<Rc<str>> = vec![root_name];
439        let mut via_index_for_group: Option<Vec<Rc<str>>> = None;
440        let mut is_first_group = true;
441
442        for segment in &chain.segments {
443            match segment {
444                ChainSegment::Field { name, prop } => {
445                    group_path.push(name.clone());
446                    group_spans.push(self.node_span(prop));
447                    cumulative.push(name.clone());
448                }
449                ChainSegment::Dynamic { prop } => {
450                    let emittable = is_first_group || !group_path.is_empty();
451                    if emittable && !group_path.is_empty() {
452                        let via_alias = if is_first_group {
453                            via_alias_first.clone()
454                        } else {
455                            None
456                        };
457                        self.emit_group(
458                            std::mem::take(&mut group_path),
459                            std::mem::take(&mut group_spans),
460                            via_alias,
461                            via_index_for_group.clone(),
462                            scope,
463                        );
464                    }
465                    group_path.clear();
466                    group_spans.clear();
467                    self.resolve(prop, scope);
468                    via_index_for_group = Some(cumulative.clone());
469                    is_first_group = false;
470                }
471            }
472        }
473
474        if !group_path.is_empty() {
475            let via_alias = if is_first_group {
476                via_alias_first
477            } else {
478                None
479            };
480            self.emit_group(
481                group_path,
482                group_spans,
483                via_alias,
484                via_index_for_group,
485                scope,
486            );
487        }
488    }
489
490    fn reference_dollar_chain(&mut self, chain: &FlatChain, scope: &mut Scope) {
491        let mut path: Vec<Rc<str>> = vec![Variable::dollar_key_rc()];
492        let mut spans = vec![self.node_span(chain.root)];
493        let mut grouping = true;
494        for segment in &chain.segments {
495            match segment {
496                ChainSegment::Field { name, prop } if grouping => {
497                    path.push(name.clone());
498                    spans.push(self.node_span(prop));
499                }
500                ChainSegment::Field { .. } => {}
501                ChainSegment::Dynamic { prop } => {
502                    grouping = false;
503                    self.resolve(prop, scope);
504                }
505            }
506        }
507        if path.len() > 1 {
508            self.references.push(Reference {
509                path,
510                spans,
511                via_alias: None,
512                via_index: None,
513            });
514        }
515    }
516
517    fn resolve_pointer_chain(&mut self, chain: &FlatChain, scope: &mut Scope) {
518        let Some(collection) = scope.pointer_collection.clone() else {
519            for segment in &chain.segments {
520                if let ChainSegment::Dynamic { prop } = segment {
521                    self.resolve(prop, scope);
522                }
523            }
524            return;
525        };
526
527        let mut group_path: Vec<Rc<str>> = Vec::new();
528        let mut group_spans: Vec<(u32, u32)> = Vec::new();
529        let mut cumulative: Vec<Rc<str>> = collection.clone();
530        let mut via_index_for_group = collection;
531
532        for segment in &chain.segments {
533            match segment {
534                ChainSegment::Field { name, prop } => {
535                    group_path.push(name.clone());
536                    group_spans.push(self.node_span(prop));
537                    cumulative.push(name.clone());
538                }
539                ChainSegment::Dynamic { prop } => {
540                    if !group_path.is_empty() {
541                        self.references.push(Reference {
542                            path: std::mem::take(&mut group_path),
543                            spans: std::mem::take(&mut group_spans),
544                            via_alias: None,
545                            via_index: Some(via_index_for_group.clone()),
546                        });
547                    }
548                    self.resolve(prop, scope);
549                    via_index_for_group = cumulative.clone();
550                }
551            }
552        }
553
554        if !group_path.is_empty() {
555            self.references.push(Reference {
556                path: group_path,
557                spans: group_spans,
558                via_alias: None,
559                via_index: Some(via_index_for_group),
560            });
561        }
562    }
563
564    fn emit_group(
565        &mut self,
566        path: Vec<Rc<str>>,
567        spans: Vec<(u32, u32)>,
568        via_alias: Option<AliasBinding>,
569        via_index: Option<Vec<Rc<str>>>,
570        scope: &Scope,
571    ) {
572        if path.is_empty() {
573            return;
574        }
575        if via_alias.is_none() && scope.is_local(&path) {
576            return;
577        }
578        let span = match (spans.first(), spans.last()) {
579            (Some(first), Some(last)) => (first.0, last.1),
580            _ => Default::default(),
581        };
582        let read_path = match &via_index {
583            Some(prefix) => prefix.iter().chain(path.iter()).cloned().collect(),
584            None => path.clone(),
585        };
586        self.reads.push(ReadDependency::Direct {
587            path: read_path,
588            span,
589            via_index: via_index.is_some(),
590        });
591        self.references.push(Reference {
592            path,
593            spans,
594            via_alias,
595            via_index,
596        });
597    }
598
599    #[cfg_attr(not(target_family = "wasm"), recursive::recursive)]
600    fn resolve(&mut self, node: &Node, scope: &mut Scope) {
601        match node {
602            Node::Identifier(name) => {
603                let path = vec![Rc::from(*name)];
604                let span = self.node_span(node);
605                if scope.unresolved_aliases.contains(&path[0]) {
606                    self.reads.push(ReadDependency::Unresolved {
607                        path: path.clone(),
608                        span,
609                    });
610                    self.references.push(Reference {
611                        path,
612                        spans: vec![span],
613                        via_alias: None,
614                        via_index: None,
615                    });
616                    return;
617                }
618                let via_alias =
619                    scope
620                        .aliases
621                        .get(&path[0])
622                        .cloned()
623                        .map(|collection| AliasBinding {
624                            alias: path[0].clone(),
625                            collection,
626                        });
627                if via_alias.is_some() || !scope.is_local(&path) {
628                    self.reads.push(ReadDependency::Direct {
629                        path: path.clone(),
630                        span,
631                        via_index: false,
632                    });
633                    self.references.push(Reference {
634                        path,
635                        spans: vec![span],
636                        via_alias,
637                        via_index: None,
638                    });
639                }
640            }
641
642            Node::Member { .. } => self.resolve_member_chain(node, scope),
643
644            Node::Assignments { list, output } => {
645                for (key, value) in list.iter() {
646                    self.resolve(value, scope);
647
648                    if let Some(path) = Self::extract_binding_path(key) {
649                        if path.len() == 1 {
650                            scope.locals.insert(path[0].clone());
651                        } else if path.len() > 1 {
652                            scope.bindings.push(path);
653                        }
654                    }
655                }
656
657                if let Some(output) = output {
658                    self.resolve(output, scope);
659                }
660            }
661
662            Node::FunctionCall { kind, arguments } => {
663                if let FunctionKind::Closure(_) = kind {
664                    if arguments.len() >= 2 {
665                        let collection_node = arguments[0];
666                        let closure_node = arguments[1];
667
668                        let collection_info = self.extract_path_with_spans(collection_node);
669                        let collection_source = collection_info
670                            .as_ref()
671                            .map(|(p, _)| p.clone())
672                            .or_else(|| Self::collection_source_path(collection_node));
673
674                        let (alias, inner_reads, inner_refs) = match closure_node {
675                            Node::Closure { body, alias } => {
676                                let mut inner_scope = scope.clone();
677                                inner_scope.locals.insert(Variable::dollar_key_rc());
678                                inner_scope.pointer_collection = collection_source
679                                    .as_ref()
680                                    .filter(|source| !scope.is_local(source))
681                                    .map(|source| scope.expand_alias_root(source));
682
683                                match (alias, collection_source.as_ref()) {
684                                    (Some(alias_name), Some(source)) => {
685                                        let expanded = scope.expand_alias_root(source);
686                                        if scope.is_local(&expanded) {
687                                            inner_scope
688                                                .unresolved_aliases
689                                                .insert(Rc::from(*alias_name));
690                                        } else {
691                                            inner_scope
692                                                .aliases
693                                                .insert(Rc::from(*alias_name), expanded);
694                                        }
695                                    }
696                                    (Some(alias_name), None) => {
697                                        inner_scope
698                                            .unresolved_aliases
699                                            .insert(Rc::from(*alias_name));
700                                    }
701                                    _ => {}
702                                }
703                                let (reads, refs) =
704                                    Self::walk_inner(body, &mut inner_scope, self.metadata);
705                                (alias.map(|a| Rc::from(a)), reads, refs)
706                            }
707                            _ => {
708                                self.resolve(closure_node, scope);
709                                return;
710                            }
711                        };
712
713                        match collection_info {
714                            Some((collection, spans)) if !scope.is_local(&collection) => {
715                                let span = self.node_span(collection_node);
716                                self.references.push(Reference {
717                                    path: collection.clone(),
718                                    spans,
719                                    via_alias: scope.alias_binding_for(&collection),
720                                    via_index: None,
721                                });
722                                self.references.extend(inner_refs);
723                                self.reads.push(ReadDependency::Iteration {
724                                    collection,
725                                    span,
726                                    alias,
727                                    reads: inner_reads,
728                                });
729                            }
730                            _ => {
731                                self.resolve(collection_node, scope);
732                                match collection_source {
733                                    Some(source)
734                                        if !source.is_empty() && !scope.is_local(&source) =>
735                                    {
736                                        let span = self.node_span(collection_node);
737                                        self.references.extend(inner_refs);
738                                        self.reads.push(ReadDependency::Iteration {
739                                            collection: source,
740                                            span,
741                                            alias,
742                                            reads: inner_reads,
743                                        });
744                                    }
745                                    _ => {
746                                        self.reads.extend(inner_reads);
747                                        self.references.extend(inner_refs);
748                                    }
749                                }
750                            }
751                        }
752                    } else {
753                        for arg in arguments.iter() {
754                            self.resolve(arg, scope);
755                        }
756                    }
757                } else {
758                    for arg in arguments.iter() {
759                        self.resolve(arg, scope);
760                    }
761                }
762            }
763
764            Node::Closure { body, alias: _ } => {
765                let mut inner = scope.clone();
766                inner.locals.insert(Variable::dollar_key_rc());
767                inner.pointer_collection = None;
768                self.resolve(body, &mut inner);
769            }
770
771            Node::MethodCall {
772                this, arguments, ..
773            } => {
774                self.resolve(this, scope);
775                for arg in arguments.iter() {
776                    self.resolve(arg, scope);
777                }
778            }
779
780            Node::Binary { left, right, .. } => {
781                self.resolve(left, scope);
782                self.resolve(right, scope);
783            }
784
785            Node::Unary { node, .. } => self.resolve(node, scope),
786
787            Node::Conditional {
788                condition,
789                on_true,
790                on_false,
791            } => {
792                self.resolve(condition, scope);
793                self.resolve(on_true, scope);
794                self.resolve(on_false, scope);
795            }
796
797            Node::Parenthesized(n) => self.resolve(n, scope),
798
799            Node::Array(items) => {
800                for item in items.iter() {
801                    self.resolve(item, scope);
802                }
803            }
804
805            Node::Object(pairs) => {
806                for (k, v) in pairs.iter() {
807                    self.resolve(k, scope);
808                    self.resolve(v, scope);
809                }
810            }
811
812            Node::TemplateString(parts) => {
813                for part in parts.iter() {
814                    self.resolve(part, scope);
815                }
816            }
817
818            Node::Slice { node, from, to } => {
819                self.resolve(node, scope);
820                if let Some(f) = from {
821                    self.resolve(f, scope);
822                }
823                if let Some(t) = to {
824                    self.resolve(t, scope);
825                }
826            }
827
828            Node::Interval { left, right, .. } => {
829                self.resolve(left, scope);
830                self.resolve(right, scope);
831            }
832
833            Node::Error { node, .. } => {
834                if let Some(n) = node {
835                    self.resolve(n, scope);
836                }
837            }
838
839            Node::Root => {
840                let path: Vec<Rc<str>> = vec![Variable::root_key_rc()];
841                let span = self.node_span(node);
842                self.reads.push(ReadDependency::Direct {
843                    path: path.clone(),
844                    span,
845                    via_index: false,
846                });
847                self.references.push(Reference {
848                    path,
849                    spans: vec![span],
850                    via_alias: None,
851                    via_index: None,
852                });
853            }
854
855            Node::Null | Node::Bool(_) | Node::Number(_) | Node::String(_) | Node::Pointer => {}
856        }
857    }
858
859    fn extract_binding_path(node: &Node) -> Option<Vec<Rc<str>>> {
860        match node {
861            Node::String(s) => Some(s.split('.').map(Rc::from).collect()),
862            _ => Self::extract_read_path(node),
863        }
864    }
865
866    fn extract_read_path(node: &Node) -> Option<Vec<Rc<str>>> {
867        match node {
868            Node::Identifier(name) => Some(vec![Rc::from(*name)]),
869            Node::Root => Some(vec![Variable::root_key_rc()]),
870            Node::Member { node, property } => {
871                let mut path = Self::extract_read_path(node)?;
872                match property {
873                    Node::String(key) => {
874                        path.push(Rc::from(*key));
875                        Some(path)
876                    }
877                    _ => None,
878                }
879            }
880            _ => None,
881        }
882    }
883}