Skip to main content

cairo_lang_syntax/node/
mod.rs

1use core::hash::Hash;
2
3use cairo_lang_filesystem::db::FilesGroup;
4use cairo_lang_filesystem::ids::{FileId, SmolStrId};
5use cairo_lang_filesystem::span::{TextOffset, TextPosition, TextSpan, TextWidth};
6use cairo_lang_proc_macros::{DebugWithDb, HeapSize};
7use cairo_lang_utils::require;
8use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
9use salsa::Database;
10use salsa::plumbing::AsId;
11
12use self::ast::TriviaGreen;
13use self::green::GreenNode;
14use self::ids::{GreenId, SyntaxStablePtrId};
15use self::kind::SyntaxKind;
16use crate::node::iter::{Preorder, WalkEvent};
17
18pub mod ast;
19pub mod db;
20pub mod element_list;
21pub mod green;
22pub mod helpers;
23pub mod ids;
24pub mod iter;
25pub mod key_fields;
26pub mod kind;
27pub mod stable_ptr;
28pub mod with_db;
29
30#[cfg(test)]
31mod ast_test;
32#[cfg(test)]
33mod test_utils;
34
35/// Private enum for syntax node id. This holds data used to identify the node.
36#[derive(Debug, Clone, PartialEq, Eq, Hash, DebugWithDb, salsa::SalsaValue)]
37#[debug_db(dyn Database)]
38pub enum SyntaxNodeId<'db> {
39    Root(FileId<'db>),
40    Child {
41        /// Parent of this node, either another node or if node is root, its file id.
42        parent: SyntaxNode<'db>,
43        /// The kind of this node. Part of the identity, as `index` below counts occurrences
44        /// per `(parent, kind, key_fields)`.
45        kind: SyntaxKind,
46        /// Chronological index among all nodes with the same (parent, kind, key_fields).
47        index: usize,
48        /// Which fields are used is determined by each SyntaxKind.
49        /// For example, a function item might use the name of the function.
50        key_fields: Box<[GreenId<'db>]>,
51    },
52}
53
54/// Private tracked struct containing the actual SyntaxNode data.
55/// This is kept private so the public `new` function generated by salsa can't be called directly.
56#[salsa::tracked]
57#[derive(Debug, HeapSize)]
58struct SyntaxNodeData<'a> {
59    #[tracked]
60    green: GreenId<'a>,
61    /// This node's span start relative to its parent's (for a root, to the file start). Keeping
62    /// it parent-relative makes `get_children` a pure function of the green tree, independent of
63    /// the node's absolute position. Recover the absolute offset via [`SyntaxNode::offset`].
64    #[tracked]
65    #[returns(copy)]
66    offset_in_parent: TextOffset,
67    /// Unique identifier for this node.
68    #[returns(ref)]
69    id: SyntaxNodeId<'a>,
70}
71
72impl<'db> SyntaxNodeData<'db> {
73    /// Gets the kind of the given node's parent if it exists.
74    pub fn parent(&self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
75        match self.id(db) {
76            SyntaxNodeId::Root(_) => None,
77            SyntaxNodeId::Child { parent, .. } => Some(*parent),
78        }
79    }
80}
81
82/// The absolute offset of the node from the beginning of the file: its offset within its parent
83/// plus the (recursively memoized) absolute offset of the parent. A tracked query rather than a
84/// cache inside the node data, as a cache would go stale when a shifted-but-unchanged subtree is
85/// backdated.
86#[salsa::tracked(returns(copy))]
87fn absolute_offset<'db>(db: &'db dyn Database, data: SyntaxNodeData<'db>) -> TextOffset {
88    match data.parent(db) {
89        Some(parent) => absolute_offset(db, parent.data)
90            .add_width(data.offset_in_parent(db) - TextOffset::START),
91        None => data.offset_in_parent(db),
92    }
93}
94
95/// The eagerly-materialized children of all non-terminal nodes of one syntax tree, stored flat:
96/// one contiguous nodes vector plus per-node (start, len) ranges, avoiding a heap allocation per
97/// node.
98#[derive(Debug, Default, PartialEq, Eq, salsa::SalsaValue)]
99struct RedTree<'db> {
100    nodes: Vec<SyntaxNode<'db>>,
101    ranges: UnorderedHashMap<SyntaxNodeData<'db>, std::ops::Range<u32>>,
102}
103
104impl<'db> cairo_lang_debug::DebugWithDb<'db> for SyntaxNodeData<'db> {
105    type Db = dyn Database;
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
107        f.debug_struct("SyntaxNode")
108            .field("green", &self.green(db).debug(db))
109            .field("offset_in_parent", &self.offset_in_parent(db))
110            .field("id", &self.id(db).debug(db))
111            .finish()
112    }
113}
114
115/// SyntaxNode. Untyped view of the syntax tree. Adds parent() and offset() capabilities.
116///
117/// This is a public wrapper around a private tracked struct. Construction only happens through
118/// tracked functions to ensure uniqueness of SyntaxNodes.
119/// The canonical tree of a file comes from `db.file_syntax(file_id)` (created via
120/// [`SyntaxNode::new_canonical_root`]); standalone trees use [`SyntaxNode::new_detached_root`].
121#[derive(Clone, Copy, salsa::SalsaValue, HeapSize)]
122pub struct SyntaxNode<'a> {
123    data: SyntaxNodeData<'a>,
124    /// The data of the outermost enclosing expression block, if any - the root of the arena
125    /// chunk this node belongs to. Expression code is consumed densely (body semantics visits
126    /// every node), so it is materialized as one arena per body; everything else (module and item
127    /// structure, which plugin expansion samples shallowly) stays lazily materialized per node.
128    /// Fully derived from `data`, like the other caches below.
129    arena_root: Option<SyntaxNodeData<'a>>,
130    /// Cached parent data to avoid database lookups. None for root nodes.
131    parent: Option<SyntaxNodeData<'a>>,
132    /// Cached kind to avoid database lookups.
133    kind: SyntaxKind,
134    /// Cached parent kind to avoid database lookups. None for root nodes.
135    parent_kind: Option<SyntaxKind>,
136}
137
138/// Equality and hashing consider only `data`: the other fields are caches fully derived from it
139/// at every construction site, and `data` is embedded (via parents and stable pointers) in the
140/// identity of practically every semantic object, so this is on very hot paths.
141impl PartialEq for SyntaxNode<'_> {
142    fn eq(&self, other: &Self) -> bool {
143        self.data == other.data
144    }
145}
146impl Eq for SyntaxNode<'_> {}
147impl Hash for SyntaxNode<'_> {
148    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
149        self.data.hash(state);
150    }
151}
152
153impl<'db> std::fmt::Debug for SyntaxNode<'db> {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        write!(f, "SyntaxNode({:x})", self.data.as_id().index())
156    }
157}
158
159impl<'db> cairo_lang_debug::DebugWithDb<'db> for SyntaxNode<'db> {
160    type Db = dyn Database;
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
162        self.data.fmt(f, db)
163    }
164}
165
166impl<'db> SyntaxNode<'db> {
167    /// Reconstructs the node view from its tracked data - every other field of [`SyntaxNode`] is
168    /// a cache fully derived from it.
169    fn from_data(db: &'db dyn Database, data: SyntaxNodeData<'db>) -> Self {
170        let (parent, parent_kind, inherited_arena_root) = match data.id(db) {
171            SyntaxNodeId::Child { parent, .. } => {
172                (Some(parent.data), Some(parent.kind), parent.arena_root)
173            }
174            SyntaxNodeId::Root(_) => (None, None, None),
175        };
176        let kind = data.green(db).long(db).kind;
177        let arena_root = inherited_arena_root.or((kind == SyntaxKind::ExprBlock).then_some(data));
178        SyntaxNode { data, arena_root, parent, kind, parent_kind }
179    }
180
181    /// Gets the absolute offset of this syntax node from the beginning of the file.
182    pub fn offset(self, db: &'db dyn Database) -> TextOffset {
183        absolute_offset(db, self.data)
184    }
185
186    /// Gets the parent syntax node, if any.
187    pub fn parent(self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
188        match self.data.id(db) {
189            SyntaxNodeId::Child { parent, .. } => Some(*parent),
190            SyntaxNodeId::Root(_) => None,
191        }
192    }
193
194    /// Gets the grandparent syntax node, if any.
195    /// This uses a cached parent when available, avoiding some database lookups.
196    pub fn grandparent(self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
197        self.parent?.parent(db)
198    }
199
200    /// Checks if this syntax node is the root of the syntax tree.
201    pub fn is_root(self) -> bool {
202        self.parent.is_none()
203    }
204
205    /// Gets the stable pointer for this syntax node.
206    pub fn stable_ptr(self, _db: &'db dyn Database) -> SyntaxStablePtrId<'db> {
207        SyntaxStablePtrId(self)
208    }
209
210    pub fn raw_id(&self, db: &'db dyn Database) -> &'db SyntaxNodeId<'db> {
211        self.data.id(db)
212    }
213
214    /// Gets the key fields of this syntax node. These define the unique identifier of the node.
215    pub fn key_fields(self, db: &'db dyn Database) -> &'db [GreenId<'db>] {
216        match self.data.id(db) {
217            SyntaxNodeId::Child { key_fields, .. } => key_fields,
218            SyntaxNodeId::Root(_) => &[],
219        }
220    }
221
222    /// Returns the file id of the file containing this node.
223    pub fn file_id(&self, db: &'db dyn Database) -> FileId<'db> {
224        // Use cached parent if available to avoid database lookup
225        if let Some(parent_data) = self.parent {
226            return match parent_data.id(db) {
227                SyntaxNodeId::Child { parent, .. } => parent.file_id(db),
228                SyntaxNodeId::Root(file_id) => *file_id,
229            };
230        }
231        match self.data.id(db) {
232            SyntaxNodeId::Root(file_id) => *file_id,
233            SyntaxNodeId::Child { .. } => {
234                unreachable!("Parent already checked and found to not exist.")
235            }
236        }
237    }
238
239    /// Returns the `n`th parent of this syntax node.
240    /// n = 0: returns itself.
241    /// n = 1: return the parent.
242    /// n = 2: return the grand parent.
243    /// And so on...
244    /// Assumes that the `n`th parent exists. Panics otherwise.
245    pub fn nth_parent<'r: 'db>(&self, db: &'r dyn Database, mut n: usize) -> SyntaxNode<'db> {
246        let mut result = Some(*self);
247        while let Some(p) = result
248            && n > 1
249        {
250            result = p.grandparent(db);
251            n -= 2;
252        }
253        if let Some(p) = result
254            && n == 1
255        {
256            result = p.parent(db);
257        }
258        result.unwrap_or_else(|| {
259            panic!(
260                "N'th parent did not exist. File {} Offset {:?}",
261                self.file_id(db).long(db).full_path(db),
262                self.offset(db)
263            )
264        })
265    }
266}
267
268/// Creates a new syntax node. `offset_in_parent` must be relative to the parent (absolute for a
269/// root) — see `SyntaxNodeData::offset_in_parent`.
270pub fn new_syntax_node<'db>(
271    db: &'db dyn Database,
272    green: GreenId<'db>,
273    offset_in_parent: TextOffset,
274    id: SyntaxNodeId<'db>,
275    kind: SyntaxKind,
276) -> SyntaxNode<'db> {
277    let parent = match &id {
278        SyntaxNodeId::Child { parent, .. } => Some(*parent),
279        SyntaxNodeId::Root(_) => None,
280    };
281    let data = SyntaxNodeData::new(db, green, offset_in_parent, id);
282    let arena_root = parent
283        .and_then(|parent| parent.arena_root)
284        .or((kind == SyntaxKind::ExprBlock).then_some(data));
285    SyntaxNode {
286        data,
287        arena_root,
288        parent: parent.map(|parent| parent.data),
289        kind,
290        parent_kind: parent.map(|parent| parent.kind),
291    }
292}
293
294/// Green-keyed query backing the *detached* root constructors. Keyed by `(file_id, green,
295/// offset)`, it deduplicates roots built from the same green tree (so repeated detached
296/// construction returns one node) and provides the tracked-function context salsa needs to mint a
297/// root's tracked struct. Because the key includes `green`, a detached root's id changes whenever
298/// the content does — fine for transient trees, but never use it for the canonical file tree
299/// (see [`SyntaxNode::new_canonical_root`]).
300#[salsa::tracked(returns(copy))]
301fn new_detached_root_node<'db>(
302    db: &'db dyn Database,
303    file_id: FileId<'db>,
304    green: GreenId<'db>,
305    offset: TextOffset,
306) -> SyntaxNode<'db> {
307    let kind = green.long(db).kind;
308    new_syntax_node(db, green, offset, SyntaxNodeId::Root(file_id), kind)
309}
310
311// Construction methods
312impl<'a> SyntaxNode<'a> {
313    /// Creates **the canonical root** for a file's syntax tree. Must be called only from the
314    /// per-file parse query (`file_syntax_data`): the root's tracked struct is then seeded by that
315    /// *file-keyed* query, so reparsing changed content reuses the same node ids instead of
316    /// minting fresh ones. That id stability is what lets downstream early cutoff fire across
317    /// edits. Every other consumer must obtain this tree via `db.file_syntax(file_id)`, not by
318    /// constructing a root directly.
319    pub fn new_canonical_root(
320        db: &'a dyn Database,
321        file_id: FileId<'a>,
322        green: GreenId<'a>,
323    ) -> Self {
324        let kind = green.long(db).kind;
325        new_syntax_node(db, green, TextOffset::START, SyntaxNodeId::Root(file_id), kind)
326    }
327
328    /// Creates a **detached** root: a standalone tree built directly from `green`, not unified with
329    /// the file's canonical tree. For transient/standalone parses (proc-macro token streams,
330    /// snippet formatting, tests) that have no per-file parse query to seed from. Its node ids are
331    /// green-keyed, so they are *not* stable across content changes — do not use it to back a file
332    /// that an editor mutates; use `db.file_syntax(file_id)` for that.
333    pub fn new_detached_root(
334        db: &'a dyn Database,
335        file_id: FileId<'a>,
336        green: GreenId<'a>,
337    ) -> Self {
338        new_detached_root_node(db, file_id, green, TextOffset::START)
339    }
340
341    /// A [`Self::new_detached_root`] with a custom initial offset, for embedding a standalone
342    /// subtree (e.g. a macro expansion) at its position in the parent file.
343    pub fn new_detached_root_with_offset(
344        db: &'a dyn Database,
345        file_id: FileId<'a>,
346        green: GreenId<'a>,
347        initial_offset: Option<TextOffset>,
348    ) -> Self {
349        new_detached_root_node(db, file_id, green, initial_offset.unwrap_or_default())
350    }
351
352    // Basic accessors
353
354    /// Gets the width of this syntax node.
355    pub fn width(&self, db: &dyn Database) -> TextWidth {
356        self.green_node(db).width(db)
357    }
358
359    /// Gets the syntax kind of this node.
360    pub fn kind(&self, _db: &dyn Database) -> SyntaxKind {
361        self.kind
362    }
363
364    /// Gets the span of this syntax node.
365    pub fn span(&self, db: &dyn Database) -> TextSpan {
366        TextSpan::new_with_width(self.offset(db), self.width(db))
367    }
368
369    /// Returns the text of the token if this node is a token.
370    pub fn text(&self, db: &'a dyn Database) -> Option<SmolStrId<'a>> {
371        match &self.green_node(db).details {
372            green::GreenNodeDetails::Token(text) => Some(*text),
373            green::GreenNodeDetails::Node { .. } => None,
374        }
375    }
376
377    /// Returns the green node of the syntax node.
378    pub fn green_node(&self, db: &'a dyn Database) -> &'a GreenNode<'a> {
379        self.data.green(db).long(db)
380    }
381
382    /// Returns whether the subtree rooted at this node contains a node of a missing kind, i.e.
383    /// whether it had a parser error.
384    pub fn contains_missing(&self, db: &'a dyn Database) -> bool {
385        /// Whether the green subtree rooted at `green_id` contains a node of a missing kind.
386        /// Walks the green tree directly - materializing the red tree just for this check would
387        /// create (and memoize) a `SyntaxNode` per descendant. Memoized per green id, so shared
388        /// subtrees are walked once.
389        #[salsa::tracked(returns(copy))]
390        fn query<'db>(db: &'db dyn Database, green_id: GreenId<'db>) -> bool {
391            let node = green_id.long(db);
392            node.kind.is_missing() || node.children().iter().any(|child| query(db, *child))
393        }
394        query(db, *self.data.green(db))
395    }
396
397    /// Returns the span of the syntax node without trivia.
398    pub fn span_without_trivia(&self, db: &dyn Database) -> TextSpan {
399        let green_node = self.green_node(db);
400        let (leading, trailing) = both_trivia_width(db, green_node);
401        let offset = self.offset(db);
402        let start = offset.add_width(leading);
403        let end = offset.add_width(green_node.width(db)).sub_width(trailing);
404        TextSpan::new(start, end)
405    }
406
407    /// Gets the inner token from a terminal SyntaxNode. If the given node is not a terminal,
408    /// returns None.
409    pub fn get_terminal_token(&'a self, db: &'a dyn Database) -> Option<SyntaxNode<'a>> {
410        let green_node = self.green_node(db);
411        require(green_node.kind.is_terminal())?;
412        // At this point we know we should have a second child which is the token.
413        self.get_children(db).get(1).copied()
414    }
415
416    // Children and tree navigation
417
418    /// Gets the children syntax nodes of the current node.
419    pub fn get_children(&self, db: &'a dyn Database) -> &'a [SyntaxNode<'a>] {
420        /// The children of every non-terminal node in the expression subtree rooted at `root`
421        /// (an outermost expression block), materialized eagerly in one query. A single memo per
422        /// body replaces a full salsa query (key lookup, sync-table claim, memo insert,
423        /// dependency edge) per node, which dominated compilation profiles. Terminal subtrees
424        /// (token + trivia, ~4/5 of all nodes) are rarely visited, so they stay lazy - served by
425        /// the per-node [`node_children`] fallback.
426        #[salsa::tracked(returns(ref))]
427        fn red_tree<'db>(db: &'db dyn Database, root: SyntaxNodeData<'db>) -> RedTree<'db> {
428            let root_node = SyntaxNode::from_data(db, root);
429            let mut res = RedTree::default();
430            // DFS is done so subtrees would occupy contiguous regions and tree-shaped consumers
431            // stay local.
432            let mut stack = vec![root_node];
433            while let Some(node) = stack.pop() {
434                if node.kind.is_terminal() {
435                    continue;
436                }
437                let start = res.nodes.len();
438                node.collect_children_into(db, &mut res.nodes);
439                let end = res.nodes.len();
440                res.ranges.insert(node.data, start as u32..end as u32);
441                stack.extend(res.nodes[start..end].iter().rev().copied());
442            }
443            res
444        }
445        /// Lazy per-node fallback for nodes the arena does not cover (terminals and their
446        /// descendants). Keyed on the tracked [`SyntaxNodeData`] struct, so the memo is attached
447        /// to it; the wrapper's other fields are caches derived from `data`.
448        #[salsa::tracked(returns(ref))]
449        fn node_children<'db>(
450            db: &'db dyn Database,
451            data: SyntaxNodeData<'db>,
452        ) -> Vec<SyntaxNode<'db>> {
453            let node = SyntaxNode::from_data(db, data);
454            let mut res = Vec::with_capacity(node.green_node(db).children().len());
455            node.collect_children_into(db, &mut res);
456            res
457        }
458        match self.arena_root {
459            Some(arena_root) => {
460                let tree = red_tree(db, arena_root);
461                match tree.ranges.get(&self.data) {
462                    Some(range) => &tree.nodes[range.start as usize..range.end as usize],
463                    None => node_children(db, self.data).as_slice(),
464                }
465            }
466            None => node_children(db, self.data).as_slice(),
467        }
468    }
469
470    /// Appends this node's children to `res`.
471    fn collect_children_into(&self, db: &'a dyn Database, res: &mut Vec<SyntaxNode<'a>>) {
472        // Children offsets are relative to this node, so seeding from `TextOffset::START` (rather
473        // than `self.offset(db)`) keeps `get_children` a pure function of the green tree.
474        let mut offset = TextOffset::START;
475        let self_green = self.green_node(db);
476        let children = self_green.children();
477        let mut key_map = Vec::<(_, usize)>::with_capacity(children.len());
478        for green_id in children {
479            let green = green_id.long(db);
480            let width = green.width(db);
481            let kind = green.kind;
482            let rng = key_fields::key_fields_range(kind);
483            let key_fields: &'a [GreenId<'a>] = &green.children()[rng];
484            let index = key_map
485                .iter_mut()
486                .find(|(k, _v)| *k == (kind, key_fields))
487                .map(|(_k, v)| {
488                    let index = *v;
489                    *v += 1;
490                    index
491                })
492                .unwrap_or_else(|| {
493                    key_map.push(((kind, key_fields), 1));
494                    0
495                });
496            // Create the SyntaxNode view for the child.
497            res.push(new_syntax_node(
498                db,
499                *green_id,
500                offset,
501                SyntaxNodeId::Child {
502                    parent: *self,
503                    kind,
504                    index,
505                    key_fields: Box::from(key_fields),
506                },
507                kind,
508            ));
509
510            offset = offset.add_width(width);
511        }
512    }
513
514    // Text and span utilities
515
516    /// Returns the start of the span of the syntax node without trivia.
517    pub fn span_start_without_trivia(&self, db: &dyn Database) -> TextOffset {
518        let green_node = self.green_node(db);
519        let leading = leading_trivia_width(db, green_node);
520        self.offset(db).add_width(leading)
521    }
522
523    /// Returns the end of the span of the syntax node without trivia.
524    pub fn span_end_without_trivia(&self, db: &dyn Database) -> TextOffset {
525        let green_node = self.green_node(db);
526        let trailing = trailing_trivia_width(db, green_node);
527        self.offset(db).add_width(green_node.width(db)).sub_width(trailing)
528    }
529
530    /// Looks up a syntax node using an offset.
531    pub fn lookup_offset(&self, db: &'a dyn Database, offset: TextOffset) -> SyntaxNode<'a> {
532        for child in self.get_children(db).iter() {
533            if child.offset(db).add_width(child.width(db)) > offset {
534                return child.lookup_offset(db, offset);
535            }
536        }
537        *self
538    }
539
540    /// Looks up a syntax node using a position.
541    pub fn lookup_position(&self, db: &'a dyn Database, position: TextPosition) -> SyntaxNode<'a> {
542        match position.offset_in_file(db, self.stable_ptr(db).file_id(db)) {
543            Some(offset) => self.lookup_offset(db, offset),
544            None => *self,
545        }
546    }
547
548    /// Returns all the text under the syntax node.
549    pub fn get_text(&self, db: &'a dyn Database) -> &'a str {
550        // A `None` return from reading the file content is only expected in the case of an IO
551        // error. Since a SyntaxNode exists and is being processed, we should have already
552        // successfully accessed this file earlier, therefore it should never fail.
553        let file_content =
554            db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
555
556        self.span(db).take(file_content)
557    }
558
559    /// Returns all the text under the syntax node.
560    /// It traverses all the syntax tree of the node, but ignores functions and modules.
561    /// We ignore those, because if there's some inner functions or modules, we don't want to get
562    /// raw text of them. Comments inside them refer themselves directly, not this SyntaxNode.
563    pub fn get_text_without_inner_commentable_children(&self, db: &dyn Database) -> String {
564        let mut buffer = String::new();
565
566        match &self.green_node(db).details {
567            green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
568            green::GreenNodeDetails::Node { .. } => {
569                for child in self.get_children(db).iter() {
570                    let kind = child.kind(db);
571
572                    // Checks all the items that the inner comment can be bubbled to (implementation
573                    // function is also a FunctionWithBody).
574                    if !matches!(
575                        kind,
576                        SyntaxKind::FunctionWithBody
577                            | SyntaxKind::ItemModule
578                            | SyntaxKind::TraitItemFunction
579                    ) {
580                        buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
581                            child, db,
582                        ));
583                    }
584                }
585            }
586        }
587        buffer
588    }
589
590    /// Returns all the text of the item without comments trivia.
591    /// It traverses all the syntax tree of the node.
592    pub fn get_text_without_all_comment_trivia(&self, db: &dyn Database) -> String {
593        let mut buffer = String::new();
594
595        match &self.green_node(db).details {
596            green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
597            green::GreenNodeDetails::Node { .. } => {
598                for child in self.get_children(db).iter() {
599                    if let Some(trivia) = ast::Trivia::cast(db, *child) {
600                        trivia.elements(db).for_each(|element| {
601                            if !matches!(
602                                element,
603                                ast::Trivium::SingleLineComment(_)
604                                    | ast::Trivium::SingleLineDocComment(_)
605                                    | ast::Trivium::SingleLineInnerComment(_)
606                            ) {
607                                buffer.push_str(
608                                    &element
609                                        .as_syntax_node()
610                                        .get_text_without_all_comment_trivia(db),
611                                );
612                            }
613                        });
614                    } else {
615                        buffer
616                            .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
617                    }
618                }
619            }
620        }
621        buffer
622    }
623
624    /// Returns all the text under the syntax node, without the outmost trivia (the leading trivia
625    /// of the first token and the trailing trivia of the last token).
626    ///
627    /// Note that this traverses the syntax tree, and generates a new string, so use responsibly.
628    pub fn get_text_without_trivia(self, db: &'a dyn Database) -> SmolStrId<'a> {
629        let file_content =
630            db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
631        SmolStrId::from(db, self.span_without_trivia(db).take(file_content))
632    }
633
634    /// Returns the text under the syntax node, according to the given span.
635    ///
636    /// `span` is assumed to be contained within the span of self.
637    ///
638    /// Note that this traverses the syntax tree, and generates a new string, so use responsibly.
639    pub fn get_text_of_span(self, db: &'a dyn Database, span: TextSpan) -> &'a str {
640        assert!(self.span(db).contains(span));
641        let file_content =
642            db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
643        span.take(file_content)
644    }
645
646    // Tree traversal iterators
647
648    /// Traverse the subtree rooted at the current node (including the current node) in preorder.
649    ///
650    /// This is a shortcut for [`Self::preorder`] paired with filtering for [`WalkEvent::Enter`]
651    /// events only.
652    pub fn descendants(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
653        self.preorder(db).filter_map(|event| match event {
654            WalkEvent::Enter(node) => Some(node),
655            WalkEvent::Leave(_) => None,
656        })
657    }
658
659    /// Traverse the subtree rooted at the current node (including the current node) in preorder,
660    /// excluding tokens.
661    pub fn preorder(&self, db: &'a dyn Database) -> Preorder<'a> {
662        Preorder::new(*self, db)
663    }
664
665    /// Gets all the leaves of the SyntaxTree, where the self node is the root of a tree.
666    pub fn tokens(&self, db: &'a dyn Database) -> impl Iterator<Item = Self> + 'a {
667        self.preorder(db).filter_map(|event| match event {
668            WalkEvent::Enter(node) if node.kind(db).is_terminal() => Some(node),
669            _ => None,
670        })
671    }
672
673    /// Mirror of [`TypedSyntaxNode::cast`].
674    pub fn cast<T: TypedSyntaxNode<'a>>(self, db: &'a dyn Database) -> Option<T> {
675        T::cast(db, self)
676    }
677
678    // Ancestor queries
679
680    /// Creates an iterator that yields ancestors of this syntax node.
681    pub fn ancestors(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
682        // We aren't reusing `ancestors_with_self` here to avoid cloning this node.
683        std::iter::successors(self.parent(db), |n| n.parent(db))
684    }
685
686    /// Creates an iterator that yields this syntax node and walks up its ancestors.
687    pub fn ancestors_with_self(
688        &self,
689        db: &'a dyn Database,
690    ) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
691        std::iter::successors(Some(*self), |n| n.parent(db))
692    }
693
694    /// Checks whether this syntax node is strictly above the given syntax node in the syntax tree.
695    pub fn is_ancestor(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
696        node.ancestors(db).any(|n| n == *self)
697    }
698
699    /// Checks whether this syntax node is strictly under the given syntax node in the syntax tree.
700    pub fn is_descendant(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
701        node.is_ancestor(db, self)
702    }
703
704    /// Checks whether this syntax node is or is above the given syntax node in the syntax tree.
705    pub fn is_ancestor_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
706        node.ancestors_with_self(db).any(|n| n == *self)
707    }
708
709    /// Checks whether this syntax node is or is under the given syntax node in the syntax tree.
710    pub fn is_descendant_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
711        node.is_ancestor_or_self(db, self)
712    }
713
714    // Specific ancestor/parent queries
715
716    /// Finds the first ancestor of a given kind.
717    pub fn ancestor_of_kind(
718        &self,
719        db: &'a dyn Database,
720        kind: SyntaxKind,
721    ) -> Option<SyntaxNode<'a>> {
722        self.ancestors(db).find(|node| node.kind(db) == kind)
723    }
724
725    /// Finds the first ancestor of a given kind and returns it in typed form.
726    pub fn ancestor_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
727        self.ancestors(db).find_map(|node| T::cast(db, node))
728    }
729
730    /// Finds the parent of a given kind.
731    pub fn parent_of_kind(&self, db: &'a dyn Database, kind: SyntaxKind) -> Option<SyntaxNode<'a>> {
732        self.parent(db).filter(|node| node.kind(db) == kind)
733    }
734
735    /// Finds the parent of a given kind and returns it in typed form.
736    pub fn parent_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
737        self.parent(db).and_then(|node| T::cast(db, node))
738    }
739
740    /// Finds the first parent of one of the kinds.
741    pub fn ancestor_of_kinds(
742        &self,
743        db: &'a dyn Database,
744        kinds: &[SyntaxKind],
745    ) -> Option<SyntaxNode<'a>> {
746        self.ancestors(db).find(|node| kinds.contains(&node.kind(db)))
747    }
748
749    /// Gets the kind of the given node's parent if it exists.
750    pub fn parent_kind(&self, _db: &dyn Database) -> Option<SyntaxKind> {
751        self.parent_kind
752    }
753
754    /// Gets the kind of the given node's grandparent if it exists.
755    pub fn grandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
756        self.parent(db)?.parent_kind(db)
757    }
758
759    /// Gets the kind of the given node's grandgrandparent if it exists.
760    pub fn grandgrandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
761        self.grandparent(db)?.parent_kind(db)
762    }
763}
764
765/// Trait for the typed view of the syntax tree. All the internal node implementations are under
766/// the ast module.
767pub trait TypedSyntaxNode<'a>: Sized {
768    /// The relevant SyntaxKind. None for enums.
769    const OPTIONAL_KIND: Option<SyntaxKind>;
770    type StablePtr: TypedStablePtr<'a>;
771    type Green;
772    fn missing(db: &'a dyn Database) -> Self::Green;
773    fn from_syntax_node(db: &'a dyn Database, node: SyntaxNode<'a>) -> Self;
774    fn cast(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self>;
775    fn as_syntax_node(&self) -> SyntaxNode<'a>;
776    fn stable_ptr(&self, db: &'a dyn Database) -> Self::StablePtr;
777}
778
779pub trait Token<'a>: TypedSyntaxNode<'a> {
780    fn new_green(db: &'a dyn Database, text: SmolStrId<'a>) -> Self::Green;
781    fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
782}
783
784pub trait Terminal<'a>: TypedSyntaxNode<'a> {
785    const KIND: SyntaxKind;
786    type TokenType: Token<'a>;
787    fn new_green(
788        db: &'a dyn Database,
789        leading_trivia: TriviaGreen<'a>,
790        token: <<Self as Terminal<'a>>::TokenType as TypedSyntaxNode<'a>>::Green,
791        trailing_trivia: TriviaGreen<'a>,
792    ) -> <Self as TypedSyntaxNode<'a>>::Green;
793    /// Returns the text of the token of this terminal (excluding the trivia).
794    fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
795    /// Casts a syntax node to this terminal type's token and then walks up to return the terminal.
796    fn cast_token(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self> {
797        if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
798            Some(Self::from_syntax_node(db, node.parent(db)?))
799        } else {
800            None
801        }
802    }
803}
804
805/// Trait for stable pointers to syntax nodes.
806pub trait TypedStablePtr<'a> {
807    type SyntaxNode: TypedSyntaxNode<'a>;
808    /// Returns the syntax node pointed to by this stable pointer.
809    fn lookup(&self, db: &'a dyn Database) -> Self::SyntaxNode;
810    /// Returns the untyped stable pointer.
811    fn untyped(self) -> SyntaxStablePtrId<'a>;
812}
813
814/// Returns the width of the leading trivia of the given green node.
815fn leading_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
816    match &green.details {
817        green::GreenNodeDetails::Token(_) => TextWidth::default(),
818        green::GreenNodeDetails::Node { children, width } => {
819            if *width == TextWidth::default() {
820                return TextWidth::default();
821            }
822            if green.kind.is_terminal() {
823                return children[0].long(db).width(db);
824            }
825            let non_empty = find_non_empty_child(db, &mut children.iter())
826                .expect("Parent width non-empty - one of the children should be non-empty");
827            leading_trivia_width(db, non_empty)
828        }
829    }
830}
831
832/// Returns the width of the trailing trivia of the given green node.
833fn trailing_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
834    match &green.details {
835        green::GreenNodeDetails::Token(_) => TextWidth::default(),
836        green::GreenNodeDetails::Node { children, width } => {
837            if *width == TextWidth::default() {
838                return TextWidth::default();
839            }
840            if green.kind.is_terminal() {
841                return children[2].long(db).width(db);
842            }
843            let non_empty = find_non_empty_child(db, &mut children.iter().rev())
844                .expect("Parent width non-empty - one of the children should be non-empty");
845            trailing_trivia_width(db, non_empty)
846        }
847    }
848}
849
850/// Returns the width of the leading and trailing trivia of the given green node.
851fn both_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> (TextWidth, TextWidth) {
852    match &green.details {
853        green::GreenNodeDetails::Token(_) => (TextWidth::default(), TextWidth::default()),
854        green::GreenNodeDetails::Node { children, width } => {
855            if *width == TextWidth::default() {
856                return (TextWidth::default(), TextWidth::default());
857            }
858            if green.kind.is_terminal() {
859                return (children[0].long(db).width(db), children[2].long(db).width(db));
860            }
861            let mut iter = children.iter();
862            let first_non_empty = find_non_empty_child(db, &mut iter)
863                .expect("Parent width non-empty - one of the children should be non-empty");
864            if let Some(last_non_empty) = find_non_empty_child(db, &mut iter.rev()) {
865                (
866                    leading_trivia_width(db, first_non_empty),
867                    trailing_trivia_width(db, last_non_empty),
868                )
869            } else {
870                both_trivia_width(db, first_non_empty)
871            }
872        }
873    }
874}
875
876/// Finds the first non-empty child in the given iterator.
877fn find_non_empty_child<'a>(
878    db: &'a dyn Database,
879    child_iter: &mut impl Iterator<Item = &'a GreenId<'a>>,
880) -> Option<&'a GreenNode<'a>> {
881    child_iter.find_map(|child| {
882        let child = child.long(db);
883        (child.width(db) != TextWidth::default()).then_some(child)
884    })
885}