1use core::hash::Hash;
2use std::sync::Arc;
3
4use cairo_lang_filesystem::ids::FileId;
5use cairo_lang_filesystem::span::{TextOffset, TextPosition, TextSpan, TextWidth};
6use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
7use cairo_lang_utils::{Intern, LookupIntern, define_short_id, 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#[derive(Clone, Debug, Hash, PartialEq, Eq)]
38pub struct SyntaxNodeLongId {
39 green: GreenId,
40 offset: TextOffset,
43 parent: Option<SyntaxNode>,
44 stable_ptr: SyntaxStablePtrId,
45}
46define_short_id!(
47 SyntaxNode,
48 SyntaxNodeLongId,
49 SyntaxGroup,
50 lookup_intern_syntax_node,
51 intern_syntax_node
52);
53impl SyntaxNode {
54 pub fn new_root(db: &dyn SyntaxGroup, file_id: FileId, green: GreenId) -> Self {
55 Self::new_with_inner(
56 db,
57 green,
58 TextOffset::START,
59 None,
60 SyntaxStablePtr::Root(file_id, green).intern(db),
61 )
62 }
63
64 pub fn new_root_with_offset(
65 db: &dyn SyntaxGroup,
66 file_id: FileId,
67 green: GreenId,
68 initial_offset: Option<TextOffset>,
69 ) -> Self {
70 Self::new_with_inner(
71 db,
72 green,
73 initial_offset.unwrap_or_default(),
74 None,
75 SyntaxStablePtr::Root(file_id, green).intern(db),
76 )
77 }
78
79 pub fn new_with_inner(
80 db: &dyn SyntaxGroup,
81 green: GreenId,
82 offset: TextOffset,
83 parent: Option<SyntaxNode>,
84 stable_ptr: SyntaxStablePtrId,
85 ) -> Self {
86 SyntaxNodeLongId { green, offset, parent, stable_ptr }.intern(db)
87 }
88
89 pub fn offset(&self, db: &dyn SyntaxGroup) -> TextOffset {
90 self.lookup_intern(db).offset
91 }
92 pub fn width(&self, db: &dyn SyntaxGroup) -> TextWidth {
93 self.green_node(db).width()
94 }
95 pub fn kind(&self, db: &dyn SyntaxGroup) -> SyntaxKind {
96 self.green_node(db).kind
97 }
98 pub fn span(&self, db: &dyn SyntaxGroup) -> TextSpan {
99 let start = self.offset(db);
100 let end = start.add_width(self.width(db));
101 TextSpan { start, end }
102 }
103 pub fn text(&self, db: &dyn SyntaxGroup) -> Option<SmolStr> {
105 match &self.green_node(db).details {
106 green::GreenNodeDetails::Token(text) => Some(text.clone()),
107 green::GreenNodeDetails::Node { .. } => None,
108 }
109 }
110
111 pub fn green_node(&self, db: &dyn SyntaxGroup) -> Arc<GreenNode> {
113 self.lookup_intern(db).green.lookup_intern(db)
114 }
115
116 pub fn span_without_trivia(&self, db: &dyn SyntaxGroup) -> TextSpan {
118 let node = self.lookup_intern(db);
119 let green_node = node.green.lookup_intern(db);
120 let (leading, trailing) = both_trivia_width(db, &green_node);
121 let start = node.offset.add_width(leading);
122 let end = node.offset.add_width(green_node.width()).sub_width(trailing);
123 TextSpan { start, end }
124 }
125 pub fn parent(&self, db: &dyn SyntaxGroup) -> Option<SyntaxNode> {
126 self.lookup_intern(db).parent.as_ref().cloned()
127 }
128 pub fn stable_ptr(&self, db: &dyn SyntaxGroup) -> SyntaxStablePtrId {
129 self.lookup_intern(db).stable_ptr
130 }
131
132 pub fn get_terminal_token(&self, db: &dyn SyntaxGroup) -> Option<SyntaxNode> {
135 let green_node = self.green_node(db);
136 require(green_node.kind.is_terminal())?;
137 self.get_children(db).get(1).copied()
139 }
140
141 pub fn get_children(&self, db: &dyn SyntaxGroup) -> Arc<[SyntaxNode]> {
143 db.get_children(*self)
144 }
145
146 fn get_children_impl(&self, db: &dyn SyntaxGroup) -> Vec<SyntaxNode> {
148 let self_long_id = self.lookup_intern(db);
149 let mut offset = self_long_id.offset;
150 let self_green = self_long_id.green.lookup_intern(db);
151 let children = self_green.children();
152 let mut res: Vec<SyntaxNode> = Vec::with_capacity(children.len());
153 let mut key_map = UnorderedHashMap::<_, usize>::default();
154 for green_id in children {
155 let green = green_id.lookup_intern(db);
156 let width = green.width();
157 let kind = green.kind;
158 let key_fields = key_fields::get_key_fields(kind, green.children());
159 let key_count = key_map.entry((kind, key_fields.clone())).or_default();
160 let stable_ptr = SyntaxStablePtr::Child {
161 parent: self_long_id.stable_ptr,
162 kind,
163 key_fields,
164 index: *key_count,
165 }
166 .intern(db);
167 *key_count += 1;
168 res.push(
170 SyntaxNodeLongId { green: *green_id, offset, parent: Some(*self), stable_ptr }
171 .intern(db),
172 );
173
174 offset = offset.add_width(width);
175 }
176 res
177 }
178
179 pub fn span_start_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
181 let node = self.lookup_intern(db);
182 let green_node = node.green.lookup_intern(db);
183 let leading = leading_trivia_width(db, &green_node);
184 node.offset.add_width(leading)
185 }
186
187 pub fn span_end_without_trivia(&self, db: &dyn SyntaxGroup) -> TextOffset {
189 let node = self.lookup_intern(db);
190 let green_node = node.green.lookup_intern(db);
191 let trailing = trailing_trivia_width(db, &green_node);
192 node.offset.add_width(green_node.width()).sub_width(trailing)
193 }
194
195 pub fn lookup_offset(&self, db: &dyn SyntaxGroup, offset: TextOffset) -> SyntaxNode {
197 for child in self.get_children(db).iter() {
198 if child.offset(db).add_width(child.width(db)) > offset {
199 return child.lookup_offset(db, offset);
200 }
201 }
202 *self
203 }
204
205 pub fn lookup_position(&self, db: &dyn SyntaxGroup, position: TextPosition) -> SyntaxNode {
207 match position.offset_in_file(db, self.stable_ptr(db).file_id(db)) {
208 Some(offset) => self.lookup_offset(db, offset),
209 None => *self,
210 }
211 }
212
213 pub fn get_text(&self, db: &dyn SyntaxGroup) -> String {
215 let file_content =
219 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
220
221 self.span(db).take(&file_content).to_string()
222 }
223
224 pub fn get_text_without_inner_commentable_children(&self, db: &dyn SyntaxGroup) -> String {
229 let mut buffer = String::new();
230
231 match &self.green_node(db).as_ref().details {
232 green::GreenNodeDetails::Token(text) => buffer.push_str(text),
233 green::GreenNodeDetails::Node { .. } => {
234 for child in self.get_children(db).iter() {
235 let kind = child.kind(db);
236
237 if !matches!(
240 kind,
241 SyntaxKind::FunctionWithBody
242 | SyntaxKind::ItemModule
243 | SyntaxKind::TraitItemFunction
244 ) {
245 buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
246 child, db,
247 ));
248 }
249 }
250 }
251 }
252 buffer
253 }
254
255 pub fn get_text_without_all_comment_trivia(&self, db: &dyn SyntaxGroup) -> String {
258 let mut buffer = String::new();
259
260 match &self.green_node(db).as_ref().details {
261 green::GreenNodeDetails::Token(text) => buffer.push_str(text),
262 green::GreenNodeDetails::Node { .. } => {
263 for child in self.get_children(db).iter() {
264 if let Some(trivia) = ast::Trivia::cast(db, *child) {
265 trivia.elements(db).for_each(|element| {
266 if !matches!(
267 element,
268 ast::Trivium::SingleLineComment(_)
269 | ast::Trivium::SingleLineDocComment(_)
270 | ast::Trivium::SingleLineInnerComment(_)
271 ) {
272 buffer.push_str(
273 &element
274 .as_syntax_node()
275 .get_text_without_all_comment_trivia(db),
276 );
277 }
278 });
279 } else {
280 buffer
281 .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
282 }
283 }
284 }
285 }
286 buffer
287 }
288
289 pub fn get_text_without_trivia(self, db: &dyn SyntaxGroup) -> String {
294 let file_content =
295 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
296 self.span_without_trivia(db).take(&file_content).to_string()
297 }
298
299 pub fn get_text_of_span(self, db: &dyn SyntaxGroup, span: TextSpan) -> String {
305 assert!(self.span(db).contains(span));
306 let file_content =
307 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
308 span.take(&file_content).to_string()
309 }
310
311 pub fn descendants<'db>(
316 &self,
317 db: &'db dyn SyntaxGroup,
318 ) -> impl Iterator<Item = SyntaxNode> + 'db {
319 self.preorder(db).filter_map(|event| match event {
320 WalkEvent::Enter(node) => Some(node),
321 WalkEvent::Leave(_) => None,
322 })
323 }
324
325 pub fn preorder<'db>(&self, db: &'db dyn SyntaxGroup) -> Preorder<'db> {
328 Preorder::new(*self, db)
329 }
330
331 pub fn tokens<'a>(&'a self, db: &'a dyn SyntaxGroup) -> impl Iterator<Item = Self> + 'a {
333 self.preorder(db).filter_map(|event| match event {
334 WalkEvent::Enter(node) if node.green_node(db).kind.is_terminal() => Some(node),
335 _ => None,
336 })
337 }
338
339 pub fn cast<T: TypedSyntaxNode>(self, db: &dyn SyntaxGroup) -> Option<T> {
341 T::cast(db, self)
342 }
343
344 pub fn ancestors<'a>(&self, db: &'a dyn SyntaxGroup) -> impl Iterator<Item = SyntaxNode> + 'a {
346 std::iter::successors(self.parent(db), |n| n.parent(db))
348 }
349
350 pub fn ancestors_with_self<'a>(
352 &self,
353 db: &'a dyn SyntaxGroup,
354 ) -> impl Iterator<Item = SyntaxNode> + 'a {
355 std::iter::successors(Some(*self), |n| n.parent(db))
356 }
357
358 pub fn is_ancestor(&self, db: &dyn SyntaxGroup, node: &SyntaxNode) -> bool {
360 node.ancestors(db).any(|n| n == *self)
361 }
362
363 pub fn is_descendant(&self, db: &dyn SyntaxGroup, node: &SyntaxNode) -> bool {
365 node.is_ancestor(db, self)
366 }
367
368 pub fn is_ancestor_or_self(&self, db: &dyn SyntaxGroup, node: &SyntaxNode) -> bool {
370 node.ancestors_with_self(db).any(|n| n == *self)
371 }
372
373 pub fn is_descendant_or_self(&self, db: &dyn SyntaxGroup, node: &SyntaxNode) -> bool {
375 node.is_ancestor_or_self(db, self)
376 }
377
378 pub fn ancestor_of_kind(&self, db: &dyn SyntaxGroup, kind: SyntaxKind) -> Option<SyntaxNode> {
380 self.ancestors(db).find(|node| node.kind(db) == kind)
381 }
382
383 pub fn ancestor_of_type<T: TypedSyntaxNode>(&self, db: &dyn SyntaxGroup) -> Option<T> {
385 self.ancestors(db).find_map(|node| T::cast(db, node))
386 }
387
388 pub fn parent_of_kind(&self, db: &dyn SyntaxGroup, kind: SyntaxKind) -> Option<SyntaxNode> {
390 self.parent(db).filter(|node| node.kind(db) == kind)
391 }
392
393 pub fn parent_of_type<T: TypedSyntaxNode>(&self, db: &dyn SyntaxGroup) -> Option<T> {
395 self.parent(db).and_then(|node| T::cast(db, node))
396 }
397
398 pub fn ancestor_of_kinds(
400 &self,
401 db: &dyn SyntaxGroup,
402 kinds: &[SyntaxKind],
403 ) -> Option<SyntaxNode> {
404 self.ancestors(db).find(|node| kinds.contains(&node.kind(db)))
405 }
406
407 pub fn parent_kind(&self, db: &dyn SyntaxGroup) -> Option<SyntaxKind> {
409 Some(self.parent(db)?.kind(db))
410 }
411
412 pub fn grandparent_kind(&self, db: &dyn SyntaxGroup) -> Option<SyntaxKind> {
414 Some(self.parent(db)?.parent(db)?.kind(db))
415 }
416
417 pub fn grandgrandparent_kind(&self, db: &dyn SyntaxGroup) -> Option<SyntaxKind> {
419 Some(self.parent(db)?.parent(db)?.parent(db)?.kind(db))
420 }
421}
422
423pub trait TypedSyntaxNode: Sized {
426 const OPTIONAL_KIND: Option<SyntaxKind>;
428 type StablePtr: TypedStablePtr;
429 type Green;
430 fn missing(db: &dyn SyntaxGroup) -> Self::Green;
431 fn from_syntax_node(db: &dyn SyntaxGroup, node: SyntaxNode) -> Self;
432 fn cast(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self>;
433 fn as_syntax_node(&self) -> SyntaxNode;
434 fn stable_ptr(&self, db: &dyn SyntaxGroup) -> Self::StablePtr;
435}
436
437pub trait Token: TypedSyntaxNode {
438 fn new_green(db: &dyn SyntaxGroup, text: SmolStr) -> Self::Green;
439 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
440}
441
442pub trait Terminal: TypedSyntaxNode {
443 const KIND: SyntaxKind;
444 type TokenType: Token;
445 fn new_green(
446 db: &dyn SyntaxGroup,
447 leading_trivia: TriviaGreen,
448 token: <<Self as Terminal>::TokenType as TypedSyntaxNode>::Green,
449 trailing_trivia: TriviaGreen,
450 ) -> <Self as TypedSyntaxNode>::Green;
451 fn text(&self, db: &dyn SyntaxGroup) -> SmolStr;
453 fn cast_token(db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<Self> {
455 if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
456 Some(Self::from_syntax_node(db, node.parent(db)?))
457 } else {
458 None
459 }
460 }
461}
462
463pub trait TypedStablePtr {
465 type SyntaxNode: TypedSyntaxNode;
466 fn lookup(&self, db: &dyn SyntaxGroup) -> Self::SyntaxNode;
468 fn untyped(&self) -> SyntaxStablePtrId;
470}
471
472fn leading_trivia_width(db: &dyn SyntaxGroup, green: &GreenNode) -> TextWidth {
474 match &green.details {
475 green::GreenNodeDetails::Token(_) => TextWidth::default(),
476 green::GreenNodeDetails::Node { children, width } => {
477 if *width == TextWidth::default() {
478 return TextWidth::default();
479 }
480 if green.kind.is_terminal() {
481 return children[0].lookup_intern(db).width();
482 }
483 let non_empty = find_non_empty_child(db, &mut children.iter())
484 .expect("Parent width non-empty - one of the children should be non-empty");
485 leading_trivia_width(db, &non_empty)
486 }
487 }
488}
489
490fn trailing_trivia_width(db: &dyn SyntaxGroup, green: &GreenNode) -> TextWidth {
492 match &green.details {
493 green::GreenNodeDetails::Token(_) => TextWidth::default(),
494 green::GreenNodeDetails::Node { children, width } => {
495 if *width == TextWidth::default() {
496 return TextWidth::default();
497 }
498 if green.kind.is_terminal() {
499 return children[2].lookup_intern(db).width();
500 }
501 let non_empty = find_non_empty_child(db, &mut children.iter().rev())
502 .expect("Parent width non-empty - one of the children should be non-empty");
503 trailing_trivia_width(db, &non_empty)
504 }
505 }
506}
507
508fn both_trivia_width(db: &dyn SyntaxGroup, green: &GreenNode) -> (TextWidth, TextWidth) {
510 match &green.details {
511 green::GreenNodeDetails::Token(_) => (TextWidth::default(), TextWidth::default()),
512 green::GreenNodeDetails::Node { children, width } => {
513 if *width == TextWidth::default() {
514 return (TextWidth::default(), TextWidth::default());
515 }
516 if green.kind.is_terminal() {
517 return (
518 children[0].lookup_intern(db).width(),
519 children[2].lookup_intern(db).width(),
520 );
521 }
522 let mut iter = children.iter();
523 let first_non_empty = find_non_empty_child(db, &mut iter)
524 .expect("Parent width non-empty - one of the children should be non-empty");
525 if let Some(last_non_empty) = find_non_empty_child(db, &mut iter.rev()) {
526 (
527 leading_trivia_width(db, &first_non_empty),
528 trailing_trivia_width(db, &last_non_empty),
529 )
530 } else {
531 both_trivia_width(db, &first_non_empty)
532 }
533 }
534 }
535}
536
537fn find_non_empty_child<'a>(
539 db: &dyn SyntaxGroup,
540 child_iter: &mut impl Iterator<Item = &'a GreenId>,
541) -> Option<Arc<GreenNode>> {
542 child_iter.find_map(|child| {
543 let child = child.lookup_intern(db);
544 (child.width() != TextWidth::default()).then_some(child)
545 })
546}