cairo_lang_syntax/node/
mod.rs

1use core::hash::Hash;
2use std::fmt::Display;
3use std::sync::Arc;
4
5use cairo_lang_filesystem::ids::FileId;
6use cairo_lang_filesystem::span::{TextOffset, TextPosition, TextSpan, TextWidth};
7use cairo_lang_utils::{Intern, LookupIntern, require};
8use smol_str::SmolStr;
9
10use self::ast::TriviaGreen;
11use self::db::SyntaxGroup;
12use self::green::GreenNode;
13use self::ids::{GreenId, SyntaxStablePtrId};
14use self::kind::SyntaxKind;
15use self::stable_ptr::SyntaxStablePtr;
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/// SyntaxNode. Untyped view of the syntax tree. Adds parent() and offset() capabilities.
36#[derive(Clone, Debug, Hash, PartialEq, Eq)]
37pub struct SyntaxNode(Arc<SyntaxNodeInner>);
38#[derive(Clone, Debug, Hash, PartialEq, Eq)]
39struct SyntaxNodeInner {
40    green: GreenId,
41    /// Number of characters from the beginning of the file to the start of the span of this
42    /// syntax subtree.
43    offset: TextOffset,
44    parent: Option<SyntaxNode>,
45    stable_ptr: SyntaxStablePtrId,
46}
47impl SyntaxNode {
48    pub fn new_root(db: &dyn SyntaxGroup, file_id: FileId, green: GreenId) -> Self {
49        let inner = SyntaxNodeInner {
50            green,
51            offset: TextOffset::START,
52            parent: None,
53            stable_ptr: SyntaxStablePtr::Root(file_id, green).intern(db),
54        };
55        Self(Arc::new(inner))
56    }
57
58    pub fn new_root_with_offset(
59        db: &dyn SyntaxGroup,
60        file_id: FileId,
61        green: GreenId,
62        initial_offset: Option<TextOffset>,
63    ) -> Self {
64        let inner = SyntaxNodeInner {
65            green,
66            offset: initial_offset.unwrap_or_default(),
67            parent: None,
68            stable_ptr: SyntaxStablePtr::Root(file_id, green).intern(db),
69        };
70        Self(Arc::new(inner))
71    }
72
73    pub fn offset(&self) -> TextOffset {
74        self.0.offset
75    }
76    pub fn width(&self, db: &dyn SyntaxGroup) -> TextWidth {
77        self.green_node(db).width()
78    }
79    pub fn kind(&self, db: &dyn SyntaxGroup) -> SyntaxKind {
80        self.green_node(db).kind
81    }
82    pub fn span(&self, db: &dyn SyntaxGroup) -> TextSpan {
83        let start = self.offset();
84        let end = start.add_width(self.width(db));
85        TextSpan { start, end }
86    }
87    /// Returns the text of the token if this node is a token.
88    pub fn text(&self, db: &dyn SyntaxGroup) -> Option<SmolStr> {
89        match &self.green_node(db).details {
90            green::GreenNodeDetails::Token(text) => Some(text.clone()),
91            green::GreenNodeDetails::Node { .. } => None,
92        }
93    }
94    pub fn green_node(&self, db: &dyn SyntaxGroup) -> Arc<GreenNode> {
95        self.0.green.lookup_intern(db)
96    }
97    pub fn span_without_trivia(&self, db: &dyn SyntaxGroup) -> TextSpan {
98        let start = self.span_start_without_trivia(db);
99        let end = self.span_end_without_trivia(db);
100        TextSpan { start, end }
101    }
102    pub fn parent(&self) -> Option<SyntaxNode> {
103        self.0.parent.as_ref().cloned()
104    }
105    pub fn stable_ptr(&self) -> SyntaxStablePtrId {
106        self.0.stable_ptr
107    }
108
109    /// Gets the inner token from a terminal SyntaxNode. If the given node is not a terminal,
110    /// returns None.
111    pub fn get_terminal_token(&self, db: &dyn SyntaxGroup) -> Option<SyntaxNode> {
112        let green_node = self.green_node(db);
113        require(green_node.kind.is_terminal())?;
114        // At this point we know we should have a second child which is the token.
115        let token_node = db.get_children(self.clone())[1].clone();
116        Some(token_node)
117    }
118
119    pub fn span_start_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
120        let green_node = self.green_node(db);
121        match green_node.details {
122            green::GreenNodeDetails::Node { .. } => {
123                if let Some(token_node) = self.get_terminal_token(db) {
124                    return token_node.offset();
125                }
126                let children = db.get_children(self.clone());
127                if let Some(child) =
128                    children.iter().find(|child| child.width(db) != TextWidth::default())
129                {
130                    child.span_start_without_trivia(db)
131                } else {
132                    self.offset()
133                }
134            }
135            green::GreenNodeDetails::Token(_) => self.offset(),
136        }
137    }
138    pub fn span_end_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
139        let green_node = self.green_node(db);
140        match green_node.details {
141            green::GreenNodeDetails::Node { .. } => {
142                if let Some(token_node) = self.get_terminal_token(db) {
143                    return token_node.span(db).end;
144                }
145                let children = &mut db.get_children(self.clone());
146                if let Some(child) = children
147                    .iter()
148                    .filter(|child| child.width(db) != TextWidth::default())
149                    .next_back()
150                {
151                    child.span_end_without_trivia(db)
152                } else {
153                    self.span(db).end
154                }
155            }
156            green::GreenNodeDetails::Token(_) => self.span(db).end,
157        }
158    }
159
160    /// Lookups a syntax node using an offset.
161    pub fn lookup_offset(&self, db: &dyn SyntaxGroup, offset: TextOffset) -> SyntaxNode {
162        for child in db.get_children(self.clone()).iter() {
163            if child.offset().add_width(child.width(db)) > offset {
164                return child.lookup_offset(db, offset);
165            }
166        }
167        self.clone()
168    }
169
170    /// Lookups a syntax node using a position.
171    pub fn lookup_position(&self, db: &dyn SyntaxGroup, position: TextPosition) -> SyntaxNode {
172        match position.offset_in_file(db.upcast(), self.stable_ptr().file_id(db)) {
173            Some(offset) => self.lookup_offset(db, offset),
174            None => self.clone(),
175        }
176    }
177
178    /// Returns all the text under the syntax node.
179    /// Note that this traverses the syntax tree, and generates a new string, so use responsibly.
180    pub fn get_text(&self, db: &dyn SyntaxGroup) -> String {
181        format!("{}", NodeTextFormatter { node: self, db })
182    }
183
184    /// Returns all the text under the syntax node.
185    /// It traverses all the syntax tree of the node, but ignores functions and modules.
186    /// We ignore those, because if there's some inner functions or modules, we don't want to get
187    /// raw text of them. Comments inside them refer themselves directly, not this SyntaxNode.
188    pub fn get_text_without_inner_commentable_children(&self, db: &dyn SyntaxGroup) -> String {
189        let mut buffer = String::new();
190
191        match &self.green_node(db).as_ref().details {
192            green::GreenNodeDetails::Token(text) => buffer.push_str(text),
193            green::GreenNodeDetails::Node { .. } => {
194                for child in db.get_children(self.clone()).iter() {
195                    let kind = child.kind(db);
196
197                    // Checks all the items that the inner comment can be bubbled to (implementation
198                    // function is also a FunctionWithBody).
199                    if !matches!(
200                        kind,
201                        SyntaxKind::FunctionWithBody
202                            | SyntaxKind::ItemModule
203                            | SyntaxKind::TraitItemFunction
204                    ) {
205                        buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
206                            child, db,
207                        ));
208                    }
209                }
210            }
211        }
212        buffer
213    }
214
215    /// Returns all the text of the item without comments trivia.
216    /// It traverses all the syntax tree of the node.
217    pub fn get_text_without_all_comment_trivia(&self, db: &dyn SyntaxGroup) -> String {
218        let mut buffer = String::new();
219
220        match &self.green_node(db).as_ref().details {
221            green::GreenNodeDetails::Token(text) => buffer.push_str(text),
222            green::GreenNodeDetails::Node { .. } => {
223                for child in db.get_children(self.clone()).iter() {
224                    if let Some(trivia) = ast::Trivia::cast(db, child.clone()) {
225                        trivia.elements(db).iter().for_each(|element| {
226                            if !matches!(
227                                element,
228                                ast::Trivium::SingleLineComment(_)
229                                    | ast::Trivium::SingleLineDocComment(_)
230                                    | ast::Trivium::SingleLineInnerComment(_)
231                            ) {
232                                buffer.push_str(
233                                    &element
234                                        .as_syntax_node()
235                                        .get_text_without_all_comment_trivia(db),
236                                );
237                            }
238                        });
239                    } else {
240                        buffer
241                            .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
242                    }
243                }
244            }
245        }
246        buffer
247    }
248
249    /// Returns all the text under the syntax node, without the outmost trivia (the leading trivia
250    /// of the first token and the trailing trivia of the last token).
251    ///
252    /// Note that this traverses the syntax tree, and generates a new string, so use responsibly.
253    pub fn get_text_without_trivia(self, db: &dyn SyntaxGroup) -> String {
254        let trimmed_span = self.span_without_trivia(db);
255
256        self.get_text_of_span(db, trimmed_span)
257    }
258
259    /// Returns the text under the syntax node, according to the given span.
260    ///
261    /// `span` is assumed to be contained within the span of self.
262    ///
263    /// Note that this traverses the syntax tree, and generates a new string, so use responsibly.
264    pub fn get_text_of_span(self, db: &dyn SyntaxGroup, span: TextSpan) -> String {
265        let orig_span = self.span(db);
266        assert!(orig_span.contains(span));
267        let full_text = self.get_text(db);
268
269        let span_in_span = TextSpan {
270            start: (span.start - orig_span.start).as_offset(),
271            end: (span.end - orig_span.start).as_offset(),
272        };
273        span_in_span.take(&full_text).to_string()
274    }
275
276    /// Traverse the subtree rooted at the current node (including the current node) in preorder.
277    ///
278    /// This is a shortcut for [`Self::preorder`] paired with filtering for [`WalkEvent::Enter`]
279    /// events only.
280    pub fn descendants<'db>(
281        &self,
282        db: &'db dyn SyntaxGroup,
283    ) -> impl Iterator<Item = SyntaxNode> + 'db {
284        self.preorder(db).filter_map(|event| match event {
285            WalkEvent::Enter(node) => Some(node),
286            WalkEvent::Leave(_) => None,
287        })
288    }
289
290    /// Traverse the subtree rooted at the current node (including the current node) in preorder,
291    /// excluding tokens.
292    pub fn preorder<'db>(&self, db: &'db dyn SyntaxGroup) -> Preorder<'db> {
293        Preorder::new(self.clone(), db)
294    }
295
296    /// Gets all the leaves of the SyntaxTree, where the self node is the root of a tree.
297    pub fn tokens<'a>(&'a self, db: &'a dyn SyntaxGroup) -> impl Iterator<Item = Self> + 'a {
298        self.preorder(db).filter_map(|event| match event {
299            WalkEvent::Enter(node) if node.green_node(db).kind.is_terminal() => Some(node),
300            _ => None,
301        })
302    }
303
304    /// Mirror of [`TypedSyntaxNode::cast`].
305    pub fn cast<T: TypedSyntaxNode>(self, db: &dyn SyntaxGroup) -> Option<T> {
306        T::cast(db, self)
307    }
308
309    /// Creates an iterator that yields ancestors of this syntax node.
310    pub fn ancestors(&self) -> impl Iterator<Item = SyntaxNode> {
311        // We aren't reusing `ancestors_with_self` here to avoid cloning this node.
312        std::iter::successors(self.parent(), SyntaxNode::parent)
313    }
314
315    /// Creates an iterator that yields this syntax node and walks up its ancestors.
316    pub fn ancestors_with_self(&self) -> impl Iterator<Item = SyntaxNode> {
317        std::iter::successors(Some(self.clone()), SyntaxNode::parent)
318    }
319
320    /// Checks whether this syntax node is strictly above the given syntax node in the syntax tree.
321    pub fn is_ancestor(&self, node: &SyntaxNode) -> bool {
322        node.ancestors().any(|n| n == *self)
323    }
324
325    /// Checks whether this syntax node is strictly under the given syntax node in the syntax tree.
326    pub fn is_descendant(&self, node: &SyntaxNode) -> bool {
327        node.is_ancestor(self)
328    }
329
330    /// Checks whether this syntax node is or is above the given syntax node in the syntax tree.
331    pub fn is_ancestor_or_self(&self, node: &SyntaxNode) -> bool {
332        node.ancestors_with_self().any(|n| n == *self)
333    }
334
335    /// Checks whether this syntax node is or is under the given syntax node in the syntax tree.
336    pub fn is_descendant_or_self(&self, node: &SyntaxNode) -> bool {
337        node.is_ancestor_or_self(self)
338    }
339
340    /// Finds the first ancestor of a given kind.
341    pub fn ancestor_of_kind(&self, db: &dyn SyntaxGroup, kind: SyntaxKind) -> Option<SyntaxNode> {
342        self.ancestors().find(|node| node.kind(db) == kind)
343    }
344
345    /// Finds the first ancestor of a given kind and returns it in typed form.
346    pub fn ancestor_of_type<T: TypedSyntaxNode>(&self, db: &dyn SyntaxGroup) -> Option<T> {
347        self.ancestors().find_map(|node| T::cast(db, node))
348    }
349
350    /// Finds the parent of a given kind.
351    pub fn parent_of_kind(&self, db: &dyn SyntaxGroup, kind: SyntaxKind) -> Option<SyntaxNode> {
352        self.parent().filter(|node| node.kind(db) == kind)
353    }
354
355    /// Finds the parent of a given kind and returns it in typed form.
356    pub fn parent_of_type<T: TypedSyntaxNode>(&self, db: &dyn SyntaxGroup) -> Option<T> {
357        self.parent().and_then(|node| T::cast(db, node))
358    }
359
360    /// Finds the first parent of one of the kinds.
361    pub fn ancestor_of_kinds(
362        &self,
363        db: &dyn SyntaxGroup,
364        kinds: &[SyntaxKind],
365    ) -> Option<SyntaxNode> {
366        self.ancestors().find(|node| kinds.contains(&node.kind(db)))
367    }
368
369    /// Gets the kind of the given node's parent if it exists.
370    pub fn parent_kind(&self, db: &dyn SyntaxGroup) -> Option<SyntaxKind> {
371        Some(self.parent()?.kind(db))
372    }
373
374    /// Gets the kind of the given node's grandparent if it exists.
375    pub fn grandparent_kind(&self, db: &dyn SyntaxGroup) -> Option<SyntaxKind> {
376        Some(self.parent()?.parent()?.kind(db))
377    }
378}
379
380/// Trait for the typed view of the syntax tree. All the internal node implementations are under
381/// the ast module.
382pub trait TypedSyntaxNode: Sized {
383    /// The relevant SyntaxKind. None for enums.
384    const OPTIONAL_KIND: Option<SyntaxKind>;
385    type StablePtr: TypedStablePtr;
386    type Green;
387    fn missing(db: &dyn SyntaxGroup) -> Self::Green;
388    fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self;
389    fn cast(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self>;
390    fn as_syntax_node(&self) -> SyntaxNode;
391    fn stable_ptr(&self) -> Self::StablePtr;
392}
393
394pub trait Token: TypedSyntaxNode {
395    fn new_green(db: &dyn SyntaxGroup, text: SmolStr) -> Self::Green;
396    fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
397}
398
399pub trait Terminal: TypedSyntaxNode {
400    const KIND: SyntaxKind;
401    type TokenType: Token;
402    fn new_green(
403        db: &dyn SyntaxGroup,
404        leading_trivia: TriviaGreen,
405        token: <<Self as Terminal>::TokenType as TypedSyntaxNode>::Green,
406        trailing_trivia: TriviaGreen,
407    ) -> <Self as TypedSyntaxNode>::Green;
408    /// Returns the text of the token of this terminal (excluding the trivia).
409    fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
410    /// Casts a syntax node to this terminal type's token and then walks up to return the terminal.
411    fn cast_token(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self> {
412        if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
413            Some(Self::from_syntax_node(db, node.parent()?))
414        } else {
415            None
416        }
417    }
418}
419
420/// Trait for stable pointers to syntax nodes.
421pub trait TypedStablePtr {
422    type SyntaxNode: TypedSyntaxNode;
423    /// Returns the syntax node pointed to by this stable pointer.
424    fn lookup(&self, db: &dyn SyntaxGroup) -> Self::SyntaxNode;
425    /// Returns the untyped stable pointer.
426    fn untyped(&self) -> SyntaxStablePtrId;
427}
428
429/// Wrapper for formatting the text of syntax nodes.
430pub struct NodeTextFormatter<'a> {
431    /// The node to format.
432    pub node: &'a SyntaxNode,
433    /// The syntax db.
434    pub db: &'a dyn SyntaxGroup,
435}
436impl Display for NodeTextFormatter<'_> {
437    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
438        match &self.node.green_node(self.db).as_ref().details {
439            green::GreenNodeDetails::Token(text) => write!(f, "{text}")?,
440            green::GreenNodeDetails::Node { .. } => {
441                for child in self.db.get_children(self.node.clone()).iter() {
442                    write!(f, "{}", NodeTextFormatter { node: child, db: self.db })?;
443                }
444            }
445        }
446        Ok(())
447    }
448}