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#[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: SyntaxNode<'db>,
43 kind: SyntaxKind,
46 index: usize,
48 key_fields: Box<[GreenId<'db>]>,
51 },
52}
53
54#[salsa::tracked]
57#[derive(Debug, HeapSize)]
58struct SyntaxNodeData<'a> {
59 #[tracked]
60 green: GreenId<'a>,
61 #[tracked]
65 #[returns(copy)]
66 offset_in_parent: TextOffset,
67 #[returns(ref)]
69 id: SyntaxNodeId<'a>,
70}
71
72impl<'db> SyntaxNodeData<'db> {
73 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#[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#[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#[derive(Clone, Copy, salsa::SalsaValue, HeapSize)]
122pub struct SyntaxNode<'a> {
123 data: SyntaxNodeData<'a>,
124 arena_root: Option<SyntaxNodeData<'a>>,
130 parent: Option<SyntaxNodeData<'a>>,
132 kind: SyntaxKind,
134 parent_kind: Option<SyntaxKind>,
136}
137
138impl 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 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 pub fn offset(self, db: &'db dyn Database) -> TextOffset {
183 absolute_offset(db, self.data)
184 }
185
186 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 pub fn grandparent(self, db: &'db dyn Database) -> Option<SyntaxNode<'db>> {
197 self.parent?.parent(db)
198 }
199
200 pub fn is_root(self) -> bool {
202 self.parent.is_none()
203 }
204
205 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 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 pub fn file_id(&self, db: &'db dyn Database) -> FileId<'db> {
224 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 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
268pub 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#[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
311impl<'a> SyntaxNode<'a> {
313 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 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 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 pub fn width(&self, db: &dyn Database) -> TextWidth {
356 self.green_node(db).width(db)
357 }
358
359 pub fn kind(&self, _db: &dyn Database) -> SyntaxKind {
361 self.kind
362 }
363
364 pub fn span(&self, db: &dyn Database) -> TextSpan {
366 TextSpan::new_with_width(self.offset(db), self.width(db))
367 }
368
369 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 pub fn green_node(&self, db: &'a dyn Database) -> &'a GreenNode<'a> {
379 self.data.green(db).long(db)
380 }
381
382 pub fn contains_missing(&self, db: &'a dyn Database) -> bool {
385 #[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 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 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 self.get_children(db).get(1).copied()
414 }
415
416 pub fn get_children(&self, db: &'a dyn Database) -> &'a [SyntaxNode<'a>] {
420 #[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 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 #[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 fn collect_children_into(&self, db: &'a dyn Database, res: &mut Vec<SyntaxNode<'a>>) {
472 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 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 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 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 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 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 pub fn get_text(&self, db: &'a dyn Database) -> &'a str {
550 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 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 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 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 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 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 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 pub fn preorder(&self, db: &'a dyn Database) -> Preorder<'a> {
662 Preorder::new(*self, db)
663 }
664
665 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 pub fn cast<T: TypedSyntaxNode<'a>>(self, db: &'a dyn Database) -> Option<T> {
675 T::cast(db, self)
676 }
677
678 pub fn ancestors(&self, db: &'a dyn Database) -> impl Iterator<Item = SyntaxNode<'a>> + 'a {
682 std::iter::successors(self.parent(db), |n| n.parent(db))
684 }
685
686 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 pub fn is_ancestor(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
696 node.ancestors(db).any(|n| n == *self)
697 }
698
699 pub fn is_descendant(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
701 node.is_ancestor(db, self)
702 }
703
704 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 pub fn is_descendant_or_self(&self, db: &dyn Database, node: &SyntaxNode<'_>) -> bool {
711 node.is_ancestor_or_self(db, self)
712 }
713
714 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 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 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 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 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 pub fn parent_kind(&self, _db: &dyn Database) -> Option<SyntaxKind> {
751 self.parent_kind
752 }
753
754 pub fn grandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
756 self.parent(db)?.parent_kind(db)
757 }
758
759 pub fn grandgrandparent_kind(&self, db: &dyn Database) -> Option<SyntaxKind> {
761 self.grandparent(db)?.parent_kind(db)
762 }
763}
764
765pub trait TypedSyntaxNode<'a>: Sized {
768 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 fn text(&self, db: &'a dyn Database) -> SmolStrId<'a>;
795 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
805pub trait TypedStablePtr<'a> {
807 type SyntaxNode: TypedSyntaxNode<'a>;
808 fn lookup(&self, db: &'a dyn Database) -> Self::SyntaxNode;
810 fn untyped(self) -> SyntaxStablePtrId<'a>;
812}
813
814fn 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
832fn 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
850fn 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
876fn 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}