1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use super::Matcher;
use crate::meta_var::MetaVarEnv;
use crate::replacer::Replacer;
use crate::source::Edit;
use crate::{Doc, Node};

use std::borrow::Borrow;
use std::ops::Deref;

/// Represents the matched node with populated MetaVarEnv.
/// It derefs to the Node so you can use it as a Node.
/// To access the underlying MetaVarEnv, call `get_env` method.
#[derive(Clone)]
pub struct NodeMatch<'tree, D: Doc>(Node<'tree, D>, MetaVarEnv<'tree, D>);

impl<'tree, D: Doc> NodeMatch<'tree, D> {
  pub fn new(node: Node<'tree, D>, env: MetaVarEnv<'tree, D>) -> Self {
    Self(node, env)
  }

  pub fn get_node(&self) -> &Node<'tree, D> {
    &self.0
  }

  /// Returns the populated MetaVarEnv for this match.
  pub fn get_env(&self) -> &MetaVarEnv<'tree, D> {
    &self.1
  }
  pub fn get_env_mut(&mut self) -> &mut MetaVarEnv<'tree, D> {
    &mut self.1
  }
  /// # Safety
  /// should only called for readopting nodes
  pub(crate) unsafe fn get_node_mut(&mut self) -> &mut Node<'tree, D> {
    &mut self.0
  }
}

impl<'tree, D: Doc> NodeMatch<'tree, D> {
  pub fn replace_by<R: Replacer<D>>(&self, replacer: R) -> Edit<D::Source> {
    let range = self.range();
    let position = range.start;
    let deleted_length = range.len();
    let inserted_text = replacer.generate_replacement(self);
    Edit {
      position,
      deleted_length,
      inserted_text,
    }
  }

  #[doc(hidden)]
  pub fn make_edit<M, R>(&self, matcher: &M, replacer: &R) -> Edit<D::Source>
  where
    M: Matcher<D::Lang>,
    R: Replacer<D>,
  {
    let range = replacer.get_replaced_range(self, matcher);
    let inserted_text = replacer.generate_replacement(self);
    Edit {
      position: range.start,
      deleted_length: range.len(),
      inserted_text,
    }
  }
}

impl<'tree, D: Doc> From<Node<'tree, D>> for NodeMatch<'tree, D> {
  fn from(node: Node<'tree, D>) -> Self {
    Self(node, MetaVarEnv::new())
  }
}

/// NodeMatch is an immutable view to Node
impl<'tree, D: Doc> From<NodeMatch<'tree, D>> for Node<'tree, D> {
  fn from(node_match: NodeMatch<'tree, D>) -> Self {
    node_match.0
  }
}

/// NodeMatch is an immutable view to Node
impl<'tree, D: Doc> Deref for NodeMatch<'tree, D> {
  type Target = Node<'tree, D>;
  fn deref(&self) -> &Self::Target {
    &self.0
  }
}

/// NodeMatch is an immutable view to Node
impl<'tree, D: Doc> Borrow<Node<'tree, D>> for NodeMatch<'tree, D> {
  fn borrow(&self) -> &Node<'tree, D> {
    &self.0
  }
}

#[cfg(test)]
mod test {
  use super::*;
  use crate::language::Tsx;
  use crate::{Language, StrDoc};

  fn use_node<L: Language>(n: &Node<StrDoc<L>>) -> String {
    n.text().to_string()
  }

  fn borrow_node<'a, D, B>(b: B) -> String
  where
    D: Doc + 'static,
    B: Borrow<Node<'a, D>>,
  {
    b.borrow().text().to_string()
  }

  #[test]
  fn test_node_match_as_node() {
    let root = Tsx.ast_grep("var a = 1");
    let node = root.root();
    let src = node.text().to_string();
    let nm = NodeMatch::from(node);
    let ret = use_node(&*nm);
    assert_eq!(ret, src);
    assert_eq!(use_node(&*nm), borrow_node(nm));
  }

  #[test]
  fn test_node_env() {
    let root = Tsx.ast_grep("var a = 1");
    let find = root.root().find("var $A = 1").expect("should find");
    let env = find.get_env();
    let node = env.get_match("A").expect("should find");
    assert_eq!(node.text(), "a");
  }

  #[test]
  fn test_replace_by() {
    let root = Tsx.ast_grep("var a = 1");
    let find = root.root().find("var $A = 1").expect("should find");
    let fixed = find.replace_by("var b = $A");
    assert_eq!(fixed.position, 0);
    assert_eq!(fixed.deleted_length, 9);
    assert_eq!(fixed.inserted_text, "var b = a".as_bytes());
  }
}