ast_grep_core/
lib.rs

1/*!
2This module contains the core library for ast-grep.
3
4It provides APIs for parsing, traversing, searching and replacing tree-sitter nodes.
5Usually you will only need ast-grep CLI instead of this crate.
6But if you want to use ast-grep as a library, this is the right place.
7*/
8
9pub mod language;
10pub mod matcher;
11pub mod meta_var;
12pub mod ops;
13pub mod replacer;
14pub mod source;
15pub mod traversal;
16
17#[doc(hidden)]
18pub mod pinned;
19
20mod match_tree;
21mod node;
22
23pub use language::Language;
24pub use match_tree::MatchStrictness;
25pub use matcher::{Matcher, NodeMatch, Pattern, PatternError};
26pub use node::{Node, Position};
27pub use source::{Doc, StrDoc};
28
29#[doc(hidden)]
30pub use node::DisplayContext;
31
32use replacer::Replacer;
33
34use node::Root;
35use source::{Edit, TSParseError};
36
37#[derive(Clone)]
38pub struct AstGrep<D: Doc> {
39  #[doc(hidden)]
40  pub inner: Root<D>,
41}
42impl<D: Doc> AstGrep<D> {
43  pub fn root(&self) -> Node<D> {
44    self.inner.root()
45  }
46
47  pub fn edit(&mut self, edit: Edit<D::Source>) -> Result<&mut Self, TSParseError> {
48    self.inner.do_edit(edit)?;
49    Ok(self)
50  }
51
52  pub fn replace<M: Matcher<D::Lang>, R: Replacer<D>>(
53    &mut self,
54    pattern: M,
55    replacer: R,
56  ) -> Result<bool, TSParseError> {
57    if let Some(edit) = self.root().replace(pattern, replacer) {
58      self.edit(edit)?;
59      Ok(true)
60    } else {
61      Ok(false)
62    }
63  }
64
65  pub fn lang(&self) -> &D::Lang {
66    self.inner.lang()
67  }
68
69  /// Use this method to avoid expensive string encoding overhead
70  /// TODO: add more documents on what is happening
71  pub fn doc(d: D) -> Self {
72    Self {
73      inner: Root::doc(d),
74    }
75  }
76}
77
78impl<L: Language> AstGrep<StrDoc<L>> {
79  pub fn new<S: AsRef<str>>(src: S, lang: L) -> Self {
80    Self {
81      inner: Root::new(src.as_ref(), lang),
82    }
83  }
84
85  /*
86  pub fn customized<C: Content>(content: C, lang: L) -> Result<Self, TSParseError> {
87    Ok(Self {
88      inner: Root::customized(content, lang)?,
89    })
90  }
91  */
92  pub fn source(&self) -> &str {
93    self.inner.doc.get_source().as_str()
94  }
95
96  pub fn generate(self) -> String {
97    self.inner.doc.src
98  }
99}
100
101#[cfg(test)]
102mod test {
103  use super::*;
104  use language::Tsx;
105  use ops::Op;
106
107  pub type Result = std::result::Result<(), TSParseError>;
108
109  #[test]
110  fn test_replace() -> Result {
111    let mut ast_grep = Tsx.ast_grep("var a = 1; let b = 2;");
112    ast_grep.replace("var $A = $B", "let $A = $B")?;
113    let source = ast_grep.generate();
114    assert_eq!(source, "let a = 1; let b = 2;"); // note the semicolon
115    Ok(())
116  }
117
118  #[test]
119  fn test_replace_by_rule() -> Result {
120    let rule = Op::either("let a = 123").or("let b = 456");
121    let mut ast_grep = Tsx.ast_grep("let a = 123");
122    let replaced = ast_grep.replace(rule, "console.log('it works!')")?;
123    assert!(replaced);
124    let source = ast_grep.generate();
125    assert_eq!(source, "console.log('it works!')");
126    Ok(())
127  }
128
129  #[test]
130  fn test_replace_unnamed_node() -> Result {
131    // ++ and -- is unnamed node in tree-sitter javascript
132    let mut ast_grep = Tsx.ast_grep("c++");
133    ast_grep.replace("$A++", "$A--")?;
134    let source = ast_grep.generate();
135    assert_eq!(source, "c--");
136    Ok(())
137  }
138
139  #[test]
140  fn test_replace_trivia() -> Result {
141    let mut ast_grep = Tsx.ast_grep("var a = 1 /*haha*/;");
142    ast_grep.replace("var $A = $B", "let $A = $B")?;
143    let source = ast_grep.generate();
144    assert_eq!(source, "let a = 1 /*haha*/;"); // semicolon
145
146    let mut ast_grep = Tsx.ast_grep("var a = 1; /*haha*/");
147    ast_grep.replace("var $A = $B", "let $A = $B")?;
148    let source = ast_grep.generate();
149    assert_eq!(source, "let a = 1; /*haha*/");
150    Ok(())
151  }
152
153  #[test]
154  fn test_replace_trivia_with_skipped() -> Result {
155    let mut ast_grep = Tsx.ast_grep("return foo(1, 2,) /*haha*/;");
156    ast_grep.replace("return foo($A, $B)", "return bar($A, $B)")?;
157    let source = ast_grep.generate();
158    assert_eq!(source, "return bar(1, 2) /*haha*/;"); // semicolon
159    Ok(())
160  }
161}