Skip to main content

ast_grep_core/
node.rs

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