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_utils::{Intern, define_short_id, require};
7use salsa::Database;
8use vector_map::VecMap;
9
10use self::ast::TriviaGreen;
11use self::green::GreenNode;
12use self::ids::{GreenId, SyntaxStablePtrId};
13use self::kind::SyntaxKind;
14use self::stable_ptr::SyntaxStablePtr;
15use crate::node::db::SyntaxGroup;
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<'a> {
39 green: GreenId<'a>,
40 offset: TextOffset,
43 parent: Option<SyntaxNode<'a>>,
44 stable_ptr: SyntaxStablePtrId<'a>,
45}
46define_short_id!(SyntaxNode, SyntaxNodeLongId<'db>);
47impl<'a> SyntaxNode<'a> {
48 pub fn new_root(db: &'a dyn Database, file_id: FileId<'a>, green: GreenId<'a>) -> Self {
49 Self::new_with_inner(
50 db,
51 green,
52 TextOffset::START,
53 None,
54 SyntaxStablePtr::Root(file_id, green).intern(db),
55 )
56 }
57
58 pub fn new_root_with_offset(
59 db: &'a dyn Database,
60 file_id: FileId<'a>,
61 green: GreenId<'a>,
62 initial_offset: Option<TextOffset>,
63 ) -> Self {
64 Self::new_with_inner(
65 db,
66 green,
67 initial_offset.unwrap_or_default(),
68 None,
69 SyntaxStablePtr::Root(file_id, green).intern(db),
70 )
71 }
72
73 pub fn new_with_inner(
74 db: &'a dyn Database,
75 green: GreenId<'a>,
76 offset: TextOffset,
77 parent: Option<SyntaxNode<'a>>,
78 stable_ptr: SyntaxStablePtrId<'a>,
79 ) -> Self {
80 SyntaxNodeLongId { green, offset, parent, stable_ptr }.intern(db)
81 }
82
83 pub fn offset(&self, db: &dyn Database) -> TextOffset {
84 self.long(db).offset
85 }
86 pub fn width(&self, db: &dyn Database) -> TextWidth {
87 self.green_node(db).width(db)
88 }
89 pub fn kind(&self, db: &dyn Database) -> SyntaxKind {
90 self.green_node(db).kind
91 }
92 pub fn span(&self, db: &dyn Database) -> TextSpan {
93 TextSpan::new_with_width(self.offset(db), self.width(db))
94 }
95 pub fn text(&self, db: &'a dyn Database) -> Option<SmolStrId<'a>> {
97 match &self.green_node(db).details {
98 green::GreenNodeDetails::Token(text) => Some(*text),
99 green::GreenNodeDetails::Node { .. } => None,
100 }
101 }
102
103 pub fn green_node(&self, db: &'a dyn Database) -> &'a GreenNode<'a> {
105 self.long(db).green.long(db)
106 }
107
108 pub fn span_without_trivia(&self, db: &dyn Database) -> TextSpan {
110 let node = self.long(db);
111 let green_node = node.green.long(db);
112 let (leading, trailing) = both_trivia_width(db, green_node);
113 let start = node.offset.add_width(leading);
114 let end = node.offset.add_width(green_node.width(db)).sub_width(trailing);
115 TextSpan::new(start, end)
116 }
117 pub fn parent(&self, db: &'a dyn Database) -> Option<SyntaxNode<'a>> {
118 self.long(db).parent
119 }
120 pub fn stable_ptr(&self, db: &'a dyn Database) -> SyntaxStablePtrId<'a> {
121 self.long(db).stable_ptr
122 }
123
124 pub fn get_terminal_token(&'a self, db: &'a dyn Database) -> Option<SyntaxNode<'a>> {
127 let green_node = self.green_node(db);
128 require(green_node.kind.is_terminal())?;
129 self.get_children(db).get(1).copied()
131 }
132
133 pub fn get_children(&self, db: &'a dyn Database) -> &'a [SyntaxNode<'a>] {
135 db.get_children(*self)
136 }
137
138 fn get_children_impl(&self, db: &'a dyn Database) -> Vec<SyntaxNode<'a>> {
140 let self_long_id = self.long(db);
141 let mut offset = self_long_id.offset;
142 let self_green = self_long_id.green.long(db);
143 let children = self_green.children();
144 let mut res: Vec<SyntaxNode<'_>> = Vec::with_capacity(children.len());
145 let mut key_map = VecMap::<_, usize>::new();
146 for green_id in children {
147 let green = green_id.long(db);
148 let width = green.width(db);
149 let kind = green.kind;
150 let rng = key_fields::key_fields_range(kind);
151 let key_fields: &'a [GreenId<'a>] = &green.children()[rng];
152 let key_count = key_map.entry((kind, key_fields)).or_insert(0);
153 let stable_ptr = SyntaxStablePtr::Child {
154 parent: self_long_id.stable_ptr,
155 kind,
156 key_fields: key_fields.into(),
157 index: *key_count,
158 }
159 .intern(db);
160 *key_count += 1;
161 res.push(
163 SyntaxNodeLongId { green: *green_id, offset, parent: Some(*self), stable_ptr }
164 .intern(db),
165 );
166
167 offset = offset.add_width(width);
168 }
169 res
170 }
171
172 pub fn span_start_without_trivia(&self, db: &dyn Database) -> TextOffset {
174 let node = self.long(db);
175 let green_node = node.green.long(db);
176 let leading = leading_trivia_width(db, green_node);
177 node.offset.add_width(leading)
178 }
179
180 pub fn span_end_without_trivia(&self, db: &dyn Database) -> TextOffset {
182 let node = self.long(db);
183 let green_node = node.green.long(db);
184 let trailing = trailing_trivia_width(db, green_node);
185 node.offset.add_width(green_node.width(db)).sub_width(trailing)
186 }
187
188 pub fn lookup_offset(&self, db: &'a dyn Database, offset: TextOffset) -> SyntaxNode<'a> {
190 for child in self.get_children(db).iter() {
191 if child.offset(db).add_width(child.width(db)) > offset {
192 return child.lookup_offset(db, offset);
193 }
194 }
195 *self
196 }
197
198 pub fn lookup_position(&self, db: &'a dyn Database, position: TextPosition) -> SyntaxNode<'a> {
200 match position.offset_in_file(db, self.stable_ptr(db).file_id(db)) {
201 Some(offset) => self.lookup_offset(db, offset),
202 None => *self,
203 }
204 }
205
206 pub fn get_text(&self, db: &'a dyn Database) -> &'a str {
208 let file_content =
212 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
213
214 self.span(db).take(file_content)
215 }
216
217 pub fn get_text_without_inner_commentable_children(&self, db: &dyn Database) -> String {
222 let mut buffer = String::new();
223
224 match &self.green_node(db).details {
225 green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
226 green::GreenNodeDetails::Node { .. } => {
227 for child in self.get_children(db).iter() {
228 let kind = child.kind(db);
229
230 if !matches!(
233 kind,
234 SyntaxKind::FunctionWithBody
235 | SyntaxKind::ItemModule
236 | SyntaxKind::TraitItemFunction
237 ) {
238 buffer.push_str(&SyntaxNode::get_text_without_inner_commentable_children(
239 child, db,
240 ));
241 }
242 }
243 }
244 }
245 buffer
246 }
247
248 pub fn get_text_without_all_comment_trivia(&self, db: &dyn Database) -> String {
251 let mut buffer = String::new();
252
253 match &self.green_node(db).details {
254 green::GreenNodeDetails::Token(text) => buffer.push_str(text.long(db)),
255 green::GreenNodeDetails::Node { .. } => {
256 for child in self.get_children(db).iter() {
257 if let Some(trivia) = ast::Trivia::cast(db, *child) {
258 trivia.elements(db).for_each(|element| {
259 if !matches!(
260 element,
261 ast::Trivium::SingleLineComment(_)
262 | ast::Trivium::SingleLineDocComment(_)
263 | ast::Trivium::SingleLineInnerComment(_)
264 ) {
265 buffer.push_str(
266 &element
267 .as_syntax_node()
268 .get_text_without_all_comment_trivia(db),
269 );
270 }
271 });
272 } else {
273 buffer
274 .push_str(&SyntaxNode::get_text_without_all_comment_trivia(child, db));
275 }
276 }
277 }
278 }
279 buffer
280 }
281
282 pub fn get_text_without_trivia(self, db: &'a dyn Database) -> SmolStrId<'a> {
287 let file_content =
288 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
289 SmolStrId::from(db, self.span_without_trivia(db).take(file_content))
290 }
291
292 pub fn get_text_of_span(self, db: &'a dyn Database, span: TextSpan) -> &'a str {
298 assert!(self.span(db).contains(span));
299 let file_content =
300 db.file_content(self.stable_ptr(db).file_id(db)).expect("Failed to read file content");
301 span.take(file_content)
302 }
303
304 pub fn descendants(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
309 self.preorder(db).filter_map(|event| match event {
310 WalkEvent::Enter(node) => Some(node),
311 WalkEvent::Leave(_) => None,
312 })
313 }
314
315 pub fn preorder(&self, db: &'a dyn Database) -> Preorder<'a> {
318 Preorder::new(*self, db)
319 }
320
321 pub fn tokens(&self, db: &'a dyn Database) -> impl Iterator<Item = Self> + 'a {
323 self.preorder(db).filter_map(|event| match event {
324 WalkEvent::Enter(node) if node.green_node(db).kind.is_terminal() => Some(node),
325 _ => None,
326 })
327 }
328
329 pub fn cast<T: TypedSyntaxNode<'a>>(self, db: &'a dyn Database) -> Option<T> {
331 T::cast(db, self)
332 }
333
334 pub fn ancestors(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
336 std::iter::successors(self.parent(db), |n| n.parent(db))
338 }
339
340 pub fn ancestors_with_self(
342 &self,
343 db: &'a dyn Database,
344 ) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
345 std::iter::successors(Some(*self), |n| n.parent(db))
346 }
347
348 pub fn is_ancestor(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
350 node.ancestors(db).any(|n| n == *self)
351 }
352
353 pub fn is_descendant(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
355 node.is_ancestor(db, self)
356 }
357
358 pub fn is_ancestor_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
360 node.ancestors_with_self(db).any(|n| n == *self)
361 }
362
363 pub fn is_descendant_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
365 node.is_ancestor_or_self(db, self)
366 }
367
368 pub fn ancestor_of_kind(
370 &self,
371 db: &'a dyn Database,
372 kind: SyntaxKind,
373 ) -> Option<SyntaxNode<'a>> {
374 self.ancestors(db).find(|node| node.kind(db) == kind)
375 }
376
377 pub fn ancestor_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
379 self.ancestors(db).find_map(|node| T::cast(db, node))
380 }
381
382 pub fn parent_of_kind(&self, db: &'a dyn Database, kind: SyntaxKind) -> Option<SyntaxNode<'a>> {
384 self.parent(db).filter(|node| node.kind(db) == kind)
385 }
386
387 pub fn parent_of_type<T: TypedSyntaxNode<'a>>(&self, db: &'a dyn Database) -> Option<T> {
389 self.parent(db).and_then(|node| T::cast(db, node))
390 }
391
392 pub fn ancestor_of_kinds(
394 &self,
395 db: &'a dyn Database,
396 kinds: &[SyntaxKind],
397 ) -> Option<SyntaxNode<'a>> {
398 self.ancestors(db).find(|node| kinds.contains(&node.kind(db)))
399 }
400
401 pub fn parent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
403 Some(self.parent(db)?.kind(db))
404 }
405
406 pub fn grandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
408 Some(self.parent(db)?.parent(db)?.kind(db))
409 }
410
411 pub fn grandgrandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
413 Some(self.parent(db)?.parent(db)?.parent(db)?.kind(db))
414 }
415}
416
417pub trait TypedSyntaxNode<'a>: Sized {
420 const OPTIONAL_KIND: Option<SyntaxKind>;
422 type StablePtr: TypedStablePtr<'a>;
423 type Green;
424 fn missing(db: &'a dyn Database) -> Self::Green;
425 fn from_syntax_node(db: &'a dyn Database, node: SyntaxNode<'a>) -> Self;
426 fn cast(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self>;
427 fn as_syntax_node(&self) -> SyntaxNode<'a>;
428 fn stable_ptr(&self, db: &'a dyn Database) -> Self::StablePtr;
429}
430
431pub trait Token<'a>: TypedSyntaxNode<'a> {
432 fn new_green(db: &'a dyn Database, text: SmolStrId<'a>) -> Self::Green;
433 fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
434}
435
436pub trait Terminal<'a>: TypedSyntaxNode<'a> {
437 const KIND: SyntaxKind;
438 type TokenType: Token<'a>;
439 fn new_green(
440 db: &'a dyn Database,
441 leading_trivia: TriviaGreen<'a>,
442 token: <<Self as Terminal<'a>>::TokenType as TypedSyntaxNode<'a>>::Green,
443 trailing_trivia: TriviaGreen<'a>,
444 ) -> <Self as TypedSyntaxNode<'a>>::Green;
445 fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
447 fn cast_token(db: &'a dyn Database, node: SyntaxNode<'a>) -> Option<Self> {
449 if node.kind(db) == Self::TokenType::OPTIONAL_KIND? {
450 Some(Self::from_syntax_node(db, node.parent(db)?))
451 } else {
452 None
453 }
454 }
455}
456
457pub trait TypedStablePtr<'a> {
459 type SyntaxNode: TypedSyntaxNode<'a>;
460 fn lookup(&self, db: &'a dyn Database) -> Self::SyntaxNode;
462 fn untyped(self) -> SyntaxStablePtrId<'a>;
464}
465
466fn leading_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
468 match &green.details {
469 green::GreenNodeDetails::Token(_) => TextWidth::default(),
470 green::GreenNodeDetails::Node { children, width } => {
471 if *width == TextWidth::default() {
472 return TextWidth::default();
473 }
474 if green.kind.is_terminal() {
475 return children[0].long(db).width(db);
476 }
477 let non_empty = find_non_empty_child(db, &mut children.iter())
478 .expect("Parent width non-empty - one of the children should be non-empty");
479 leading_trivia_width(db, non_empty)
480 }
481 }
482}
483
484fn trailing_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> TextWidth {
486 match &green.details {
487 green::GreenNodeDetails::Token(_) => TextWidth::default(),
488 green::GreenNodeDetails::Node { children, width } => {
489 if *width == TextWidth::default() {
490 return TextWidth::default();
491 }
492 if green.kind.is_terminal() {
493 return children[2].long(db).width(db);
494 }
495 let non_empty = find_non_empty_child(db, &mut children.iter().rev())
496 .expect("Parent width non-empty - one of the children should be non-empty");
497 trailing_trivia_width(db, non_empty)
498 }
499 }
500}
501
502fn both_trivia_width<'a>(db: &'a dyn Database, green: &GreenNode<'a>) -> (TextWidth, TextWidth) {
504 match &green.details {
505 green::GreenNodeDetails::Token(_) => (TextWidth::default(), TextWidth::default()),
506 green::GreenNodeDetails::Node { children, width } => {
507 if *width == TextWidth::default() {
508 return (TextWidth::default(), TextWidth::default());
509 }
510 if green.kind.is_terminal() {
511 return (children[0].long(db).width(db), children[2].long(db).width(db));
512 }
513 let mut iter = children.iter();
514 let first_non_empty = find_non_empty_child(db, &mut iter)
515 .expect("Parent width non-empty - one of the children should be non-empty");
516 if let Some(last_non_empty) = find_non_empty_child(db, &mut iter.rev()) {
517 (
518 leading_trivia_width(db, first_non_empty),
519 trailing_trivia_width(db, last_non_empty),
520 )
521 } else {
522 both_trivia_width(db, first_non_empty)
523 }
524 }
525 }
526}
527
528fn find_non_empty_child<'a>(
530 db: &'a dyn Database,
531 child_iter: &mut impl Iterator<Item = &'a GreenId<'a>>,
532) -> Option<&'a GreenNode<'a>> {
533 child_iter.find_map(|child| {
534 let child = child.long(db);
535 (child.width(db) != TextWidth::default()).then_some(child)
536 })
537}