ast_grep_core/
node.rs

1use crate::matcher::{Matcher, MatcherExt, NodeMatch};
2use crate::replacer::Replacer;
3use crate::source::{Content, Edit as E, SgNode};
4use crate::Doc;
5use crate::Language;
6
7type Edit<D> = E<<D as Doc>::Source>;
8
9use std::borrow::Cow;
10
11/// Represents a position in the source code.
12/// The line and column are zero-based, character offsets.
13/// It is different from tree-sitter's position which is zero-based `byte` offsets.
14/// Note, accessing `column` is O(n) operation.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct Position {
17  /// zero-based line offset. Text encoding does not matter.
18  line: usize,
19  /// zero-based BYTE offset instead of character offset
20  byte_column: usize,
21  /// byte offset of this position
22  byte_offset: usize,
23}
24
25impl Position {
26  pub fn new(line: usize, byte_column: usize, byte_offset: usize) -> Self {
27    Self {
28      line,
29      byte_column,
30      byte_offset,
31    }
32  }
33  pub fn line(&self) -> usize {
34    self.line
35  }
36  /// TODO: return unicode character offset
37  pub fn column<D: Doc>(&self, node: &Node<'_, D>) -> usize {
38    let source = node.get_doc().get_source();
39    source.get_char_column(self.byte_column, self.byte_offset)
40  }
41  pub fn byte_point(&self) -> (u32, u32) {
42    (self.line as u32, self.byte_column as u32)
43  }
44}
45
46/// Represents [`tree_sitter::Tree`] and owns source string
47/// Note: Root is generic against [`Language`](crate::language::Language)
48#[derive(Clone)]
49pub struct Root<D: Doc> {
50  pub(crate) doc: D,
51}
52
53impl<D: Doc> Root<D> {
54  pub fn doc(doc: D) -> Self {
55    Self { doc }
56  }
57
58  pub fn lang(&self) -> &D::Lang {
59    self.doc.get_lang()
60  }
61  /// The root node represents the entire source
62  pub fn root(&self) -> Node<D> {
63    Node {
64      inner: self.doc.root_node(),
65      root: self,
66    }
67  }
68
69  // extract non generic implementation to reduce code size
70  pub fn edit(&mut self, edit: Edit<D>) -> Result<&mut Self, String> {
71    self.doc.do_edit(&edit)?;
72    Ok(self)
73  }
74
75  pub fn replace<M: Matcher, R: Replacer<D>>(
76    &mut self,
77    pattern: M,
78    replacer: R,
79  ) -> Result<bool, String> {
80    let root = self.root();
81    if let Some(edit) = root.replace(pattern, replacer) {
82      drop(root); // rust cannot auto drop root if D is not specified
83      self.edit(edit)?;
84      Ok(true)
85    } else {
86      Ok(false)
87    }
88  }
89
90  /// Adopt the tree_sitter as the descendant of the root and return the wrapped sg Node.
91  /// It assumes `inner` is the under the root and will panic at dev build if wrong node is used.
92  pub fn adopt<'r>(&'r self, inner: D::Node<'r>) -> Node<'r, D> {
93    debug_assert!(self.check_lineage(&inner));
94    Node { inner, root: self }
95  }
96
97  fn check_lineage(&self, inner: &D::Node<'_>) -> bool {
98    let mut node = inner.clone();
99    while let Some(n) = node.parent() {
100      node = n;
101    }
102    node.node_id() == self.doc.root_node().node_id()
103  }
104
105  /// P.S. I am your father.
106  #[doc(hidden)]
107  pub unsafe fn readopt<'a: 'b, 'b>(&'a self, node: &mut Node<'b, D>) {
108    debug_assert!(self.check_lineage(&node.inner));
109    node.root = self;
110  }
111}
112
113// why we need one more content? https://github.com/ast-grep/ast-grep/issues/1951
114/// 'r represents root lifetime
115#[derive(Clone)]
116pub struct Node<'r, D: Doc> {
117  pub(crate) inner: D::Node<'r>,
118  pub(crate) root: &'r Root<D>,
119}
120pub type KindId = u16;
121
122/// APIs for Node inspection
123impl<'r, D: Doc> Node<'r, D> {
124  pub fn get_doc(&self) -> &'r D {
125    &self.root.doc
126  }
127  pub fn node_id(&self) -> usize {
128    self.inner.node_id()
129  }
130  pub fn is_leaf(&self) -> bool {
131    self.inner.is_leaf()
132  }
133  /// if has no named children.
134  /// N.B. it is different from is_named && is_leaf
135  // see https://github.com/ast-grep/ast-grep/issues/276
136  pub fn is_named_leaf(&self) -> bool {
137    self.inner.is_named_leaf()
138  }
139  pub fn is_error(&self) -> bool {
140    self.inner.is_error()
141  }
142  pub fn kind(&self) -> Cow<str> {
143    self.inner.kind()
144  }
145  pub fn kind_id(&self) -> KindId {
146    self.inner.kind_id()
147  }
148
149  pub fn is_named(&self) -> bool {
150    self.inner.is_named()
151  }
152  pub fn is_missing(&self) -> bool {
153    self.inner.is_missing()
154  }
155
156  /// byte offsets of start and end.
157  pub fn range(&self) -> std::ops::Range<usize> {
158    self.inner.range()
159  }
160
161  /// Nodes' start position in terms of zero-based rows and columns.
162  pub fn start_pos(&self) -> Position {
163    self.inner.start_pos()
164  }
165
166  /// Nodes' end position in terms of rows and columns.
167  pub fn end_pos(&self) -> Position {
168    self.inner.end_pos()
169  }
170
171  pub fn text(&self) -> Cow<'r, str> {
172    self.root.doc.get_node_text(&self.inner)
173  }
174
175  pub fn lang(&self) -> &'r D::Lang {
176    self.root.lang()
177  }
178
179  /// the underlying tree-sitter Node
180  pub fn get_inner_node(&self) -> D::Node<'r> {
181    self.inner.clone()
182  }
183
184  pub fn root(&self) -> &'r Root<D> {
185    self.root
186  }
187}
188
189/**
190 * Corresponds to inside/has/precedes/follows
191 */
192impl<D: Doc> Node<'_, D> {
193  pub fn matches<M: Matcher>(&self, m: M) -> bool {
194    m.match_node(self.clone()).is_some()
195  }
196
197  pub fn inside<M: Matcher>(&self, m: M) -> bool {
198    self.ancestors().find_map(|n| m.match_node(n)).is_some()
199  }
200
201  pub fn has<M: Matcher>(&self, m: M) -> bool {
202    self.dfs().skip(1).find_map(|n| m.match_node(n)).is_some()
203  }
204
205  pub fn precedes<M: Matcher>(&self, m: M) -> bool {
206    self.next_all().find_map(|n| m.match_node(n)).is_some()
207  }
208
209  pub fn follows<M: Matcher>(&self, m: M) -> bool {
210    self.prev_all().find_map(|n| m.match_node(n)).is_some()
211  }
212}
213
214/// tree traversal API
215impl<'r, D: Doc> Node<'r, D> {
216  #[must_use]
217  pub fn parent(&self) -> Option<Self> {
218    let inner = self.inner.parent()?;
219    Some(Node {
220      inner,
221      root: self.root,
222    })
223  }
224
225  pub fn children(&self) -> impl ExactSizeIterator<Item = Node<'r, D>> + '_ {
226    self.inner.children().map(|inner| Node {
227      inner,
228      root: self.root,
229    })
230  }
231
232  #[must_use]
233  pub fn child(&self, nth: usize) -> Option<Self> {
234    let inner = self.inner.child(nth)?;
235    Some(Node {
236      inner,
237      root: self.root,
238    })
239  }
240
241  pub fn field(&self, name: &str) -> Option<Self> {
242    let inner = self.inner.field(name)?;
243    Some(Node {
244      inner,
245      root: self.root,
246    })
247  }
248
249  pub fn child_by_field_id(&self, field_id: u16) -> Option<Self> {
250    let inner = self.inner.child_by_field_id(field_id)?;
251    Some(Node {
252      inner,
253      root: self.root,
254    })
255  }
256
257  pub fn field_children(&self, name: &str) -> impl Iterator<Item = Node<'r, D>> + '_ {
258    let field_id = self.lang().field_to_id(name);
259    self.inner.field_children(field_id).map(|inner| Node {
260      inner,
261      root: self.root,
262    })
263  }
264
265  /// Returns all ancestors nodes of `self`.
266  /// Using cursor is overkill here because adjust cursor is too expensive.
267  pub fn ancestors(&self) -> impl Iterator<Item = Node<'r, D>> + '_ {
268    let root = self.root.doc.root_node();
269    self.inner.ancestors(root).map(|inner| Node {
270      inner,
271      root: self.root,
272    })
273  }
274  #[must_use]
275  pub fn next(&self) -> Option<Self> {
276    let inner = self.inner.next()?;
277    Some(Node {
278      inner,
279      root: self.root,
280    })
281  }
282
283  /// Returns all sibling nodes next to `self`.
284  // NOTE: Need go to parent first, then move to current node by byte offset.
285  // This is because tree_sitter cursor is scoped to the starting node.
286  // See https://github.com/tree-sitter/tree-sitter/issues/567
287  pub fn next_all(&self) -> impl Iterator<Item = Node<'r, D>> + '_ {
288    self.inner.next_all().map(|inner| Node {
289      inner,
290      root: self.root,
291    })
292  }
293
294  #[must_use]
295  pub fn prev(&self) -> Option<Node<'r, D>> {
296    let inner = self.inner.prev()?;
297    Some(Node {
298      inner,
299      root: self.root,
300    })
301  }
302
303  pub fn prev_all(&self) -> impl Iterator<Item = Node<'r, D>> + '_ {
304    self.inner.prev_all().map(|inner| Node {
305      inner,
306      root: self.root,
307    })
308  }
309
310  pub fn dfs<'s>(&'s self) -> impl Iterator<Item = Node<'r, D>> + 's {
311    self.inner.dfs().map(|inner| Node {
312      inner,
313      root: self.root,
314    })
315  }
316
317  #[must_use]
318  pub fn find<M: Matcher>(&self, pat: M) -> Option<NodeMatch<'r, D>> {
319    pat.find_node(self.clone())
320  }
321
322  pub fn find_all<'s, M: Matcher + 's>(
323    &'s self,
324    pat: M,
325  ) -> impl Iterator<Item = NodeMatch<'r, D>> + 's {
326    let kinds = pat.potential_kinds();
327    self.dfs().filter_map(move |cand| {
328      if let Some(k) = &kinds {
329        if !k.contains(cand.kind_id().into()) {
330          return None;
331        }
332      }
333      pat.match_node(cand)
334    })
335  }
336}
337
338/// Tree manipulation API
339impl<D: Doc> Node<'_, D> {
340  pub fn replace<M: Matcher, R: Replacer<D>>(&self, matcher: M, replacer: R) -> Option<Edit<D>> {
341    let matched = matcher.find_node(self.clone())?;
342    let edit = matched.make_edit(&matcher, &replacer);
343    Some(edit)
344  }
345
346  pub fn after(&self) -> Edit<D> {
347    todo!()
348  }
349  pub fn before(&self) -> Edit<D> {
350    todo!()
351  }
352  pub fn append(&self) -> Edit<D> {
353    todo!()
354  }
355  pub fn prepend(&self) -> Edit<D> {
356    todo!()
357  }
358
359  /// Empty children. Remove all child node
360  pub fn empty(&self) -> Option<Edit<D>> {
361    let mut children = self.children().peekable();
362    let start = children.peek()?.range().start;
363    let end = children.last()?.range().end;
364    Some(Edit::<D> {
365      position: start,
366      deleted_length: end - start,
367      inserted_text: Vec::new(),
368    })
369  }
370
371  /// Remove the node itself
372  pub fn remove(&self) -> Edit<D> {
373    let range = self.range();
374    Edit::<D> {
375      position: range.start,
376      deleted_length: range.end - range.start,
377      inserted_text: Vec::new(),
378    }
379  }
380}
381
382#[cfg(test)]
383mod test {
384  use crate::language::{Language, Tsx};
385  use crate::tree_sitter::LanguageExt;
386  #[test]
387  fn test_is_leaf() {
388    let root = Tsx.ast_grep("let a = 123");
389    let node = root.root();
390    assert!(!node.is_leaf());
391  }
392
393  #[test]
394  fn test_children() {
395    let root = Tsx.ast_grep("let a = 123");
396    let node = root.root();
397    let children: Vec<_> = node.children().collect();
398    assert_eq!(children.len(), 1);
399    let texts: Vec<_> = children[0]
400      .children()
401      .map(|c| c.text().to_string())
402      .collect();
403    assert_eq!(texts, vec!["let", "a = 123"]);
404  }
405  #[test]
406  fn test_empty() {
407    let root = Tsx.ast_grep("let a = 123");
408    let node = root.root();
409    let edit = node.empty().unwrap();
410    assert_eq!(edit.inserted_text.len(), 0);
411    assert_eq!(edit.deleted_length, 11);
412    assert_eq!(edit.position, 0);
413  }
414
415  #[test]
416  fn test_field_children() {
417    let root = Tsx.ast_grep("let a = 123");
418    let node = root.root().find("let a = $A").unwrap();
419    let children: Vec<_> = node.field_children("kind").collect();
420    assert_eq!(children.len(), 1);
421    assert_eq!(children[0].text(), "let");
422  }
423
424  const MULTI_LINE: &str = "
425if (a) {
426  test(1)
427} else {
428  x
429}
430";
431
432  #[test]
433  fn test_display_context() {
434    // src, matcher, lead, trail
435    let cases = [
436      ["i()", "i()", "", ""],
437      ["i()", "i", "", "()"],
438      [MULTI_LINE, "test", "  ", "(1)"],
439    ];
440    // display context should not panic
441    for [src, matcher, lead, trail] in cases {
442      let root = Tsx.ast_grep(src);
443      let node = root.root().find(matcher).expect("should match");
444      let display = node.display_context(0, 0);
445      assert_eq!(display.leading, lead);
446      assert_eq!(display.trailing, trail);
447    }
448  }
449
450  #[test]
451  fn test_multi_line_context() {
452    let cases = [
453      ["i()", "i()", "", ""],
454      [MULTI_LINE, "test", "if (a) {\n  ", "(1)\n} else {"],
455    ];
456    // display context should not panic
457    for [src, matcher, lead, trail] in cases {
458      let root = Tsx.ast_grep(src);
459      let node = root.root().find(matcher).expect("should match");
460      let display = node.display_context(1, 1);
461      assert_eq!(display.leading, lead);
462      assert_eq!(display.trailing, trail);
463    }
464  }
465
466  #[test]
467  fn test_replace_all_nested() {
468    let root = Tsx.ast_grep("Some(Some(1))");
469    let node = root.root();
470    let edits = node.replace_all("Some($A)", "$A");
471    assert_eq!(edits.len(), 1);
472    assert_eq!(edits[0].inserted_text, "Some(1)".as_bytes());
473  }
474
475  #[test]
476  fn test_replace_all_multiple_sorted() {
477    let root = Tsx.ast_grep("Some(Some(1)); Some(2)");
478    let node = root.root();
479    let edits = node.replace_all("Some($A)", "$A");
480    // edits must be sorted by position
481    assert_eq!(edits.len(), 2);
482    assert_eq!(edits[0].inserted_text, "Some(1)".as_bytes());
483    assert_eq!(edits[1].inserted_text, "2".as_bytes());
484  }
485
486  #[test]
487  fn test_inside() {
488    let root = Tsx.ast_grep("Some(Some(1)); Some(2)");
489    let root = root.root();
490    let node = root.find("Some(1)").expect("should exist");
491    assert!(node.inside("Some($A)"));
492  }
493  #[test]
494  fn test_has() {
495    let root = Tsx.ast_grep("Some(Some(1)); Some(2)");
496    let root = root.root();
497    let node = root.find("Some($A)").expect("should exist");
498    assert!(node.has("Some(1)"));
499  }
500  #[test]
501  fn precedes() {
502    let root = Tsx.ast_grep("Some(Some(1)); Some(2);");
503    let root = root.root();
504    let node = root.find("Some($A);").expect("should exist");
505    assert!(node.precedes("Some(2);"));
506  }
507  #[test]
508  fn follows() {
509    let root = Tsx.ast_grep("Some(Some(1)); Some(2);");
510    let root = root.root();
511    let node = root.find("Some(2);").expect("should exist");
512    assert!(node.follows("Some(Some(1));"));
513  }
514
515  #[test]
516  fn test_field() {
517    let root = Tsx.ast_grep("class A{}");
518    let root = root.root();
519    let node = root.find("class $C {}").expect("should exist");
520    assert!(node.field("name").is_some());
521    assert!(node.field("none").is_none());
522  }
523  #[test]
524  fn test_child_by_field_id() {
525    let root = Tsx.ast_grep("class A{}");
526    let root = root.root();
527    let node = root.find("class $C {}").expect("should exist");
528    let id = Tsx.field_to_id("name").unwrap();
529    assert!(node.child_by_field_id(id).is_some());
530    assert!(node.child_by_field_id(id + 1).is_none());
531  }
532
533  #[test]
534  fn test_remove() {
535    let root = Tsx.ast_grep("Some(Some(1)); Some(2);");
536    let root = root.root();
537    let node = root.find("Some(2);").expect("should exist");
538    let edit = node.remove();
539    assert_eq!(edit.position, 15);
540    assert_eq!(edit.deleted_length, 8);
541  }
542
543  #[test]
544  fn test_ascii_pos() {
545    let root = Tsx.ast_grep("a");
546    let root = root.root();
547    let node = root.find("$A").expect("should exist");
548    assert_eq!(node.start_pos().line(), 0);
549    assert_eq!(node.start_pos().column(&*node), 0);
550    assert_eq!(node.end_pos().line(), 0);
551    assert_eq!(node.end_pos().column(&*node), 1);
552  }
553
554  #[test]
555  fn test_unicode_pos() {
556    let root = Tsx.ast_grep("🦀");
557    let root = root.root();
558    let node = root.find("$A").expect("should exist");
559    assert_eq!(node.start_pos().line(), 0);
560    assert_eq!(node.start_pos().column(&*node), 0);
561    assert_eq!(node.end_pos().line(), 0);
562    assert_eq!(node.end_pos().column(&*node), 1);
563    let root = Tsx.ast_grep("\n  🦀🦀");
564    let root = root.root();
565    let node = root.find("$A").expect("should exist");
566    assert_eq!(node.start_pos().line(), 1);
567    assert_eq!(node.start_pos().column(&*node), 2);
568    assert_eq!(node.end_pos().line(), 1);
569    assert_eq!(node.end_pos().column(&*node), 4);
570  }
571}