ast_grep_core/match_tree/
strictness.rs

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
use crate::matcher::PatternNode;
use crate::meta_var::MetaVariable;
use crate::{Doc, Node};
use std::iter::Peekable;
use std::str::FromStr;

#[derive(Clone)]
pub enum MatchStrictness {
  Cst,       // all nodes are matched
  Smart,     // all nodes except source trivial nodes are matched.
  Ast,       // only ast nodes are matched
  Relaxed,   // ast-nodes excluding comments are matched
  Signature, // ast-nodes excluding comments, without text
}

pub(crate) enum MatchOneNode {
  MatchedBoth,
  SkipBoth,
  SkipGoal,
  SkipCandidate,
  NoMatch,
}

fn skip_comment_or_unnamed(n: &Node<impl Doc>) -> bool {
  if !n.is_named() {
    return true;
  }
  let kind = n.kind();
  kind.contains("comment")
}

impl MatchStrictness {
  pub(crate) fn match_terminal<D: Doc>(
    &self,
    is_named: bool,
    text: &str,
    kind: u16,
    candidate: &Node<D>,
  ) -> MatchOneNode {
    use MatchStrictness as M;
    let k = candidate.kind_id();
    // work around ast-grep/ast-grep#1419 and tree-sitter/tree-sitter-typescript#306
    // tree-sitter-typescript has wrong span of unnamed node so text would not match
    // just compare kind for unnamed node
    if k == kind && (!is_named || text == candidate.text()) {
      return MatchOneNode::MatchedBoth;
    }
    let (skip_goal, skip_candidate) = match self {
      M::Cst => (false, false),
      M::Smart => (false, !candidate.is_named()),
      M::Ast => (!is_named, !candidate.is_named()),
      M::Relaxed => (!is_named, skip_comment_or_unnamed(candidate)),
      M::Signature => {
        if k == kind {
          return MatchOneNode::MatchedBoth;
        }
        (!is_named, skip_comment_or_unnamed(candidate))
      }
    };
    match (skip_goal, skip_candidate) {
      (true, true) => MatchOneNode::SkipBoth,
      (true, false) => MatchOneNode::SkipGoal,
      (false, true) => MatchOneNode::SkipCandidate,
      (false, false) => MatchOneNode::NoMatch,
    }
  }

  // TODO: this is a method for working around trailing nodes after pattern is matched
  pub(crate) fn should_skip_trailing<D: Doc>(&self, candidate: &Node<D>) -> bool {
    use MatchStrictness as M;
    match self {
      M::Cst => false,
      M::Smart => true,
      M::Ast => false,
      M::Relaxed => skip_comment_or_unnamed(candidate),
      M::Signature => skip_comment_or_unnamed(candidate),
    }
  }

  pub(crate) fn should_skip_goal<'p>(
    &self,
    goal_children: &mut Peekable<impl Iterator<Item = &'p PatternNode>>,
  ) -> bool {
    use MatchStrictness as M;
    while let Some(pattern) = goal_children.peek() {
      let skipped = match self {
        M::Cst => false,
        M::Smart => match pattern {
          PatternNode::MetaVar { meta_var } => match meta_var {
            MetaVariable::Multiple => true,
            MetaVariable::MultiCapture(_) => true,
            MetaVariable::Dropped(_) => false,
            MetaVariable::Capture(..) => false,
          },
          PatternNode::Terminal { .. } => false,
          PatternNode::Internal { .. } => false,
        },
        M::Ast | M::Relaxed | M::Signature => match pattern {
          PatternNode::MetaVar { meta_var } => match meta_var {
            MetaVariable::Multiple => true,
            MetaVariable::MultiCapture(_) => true,
            MetaVariable::Dropped(named) => !named,
            MetaVariable::Capture(_, named) => !named,
          },
          PatternNode::Terminal { is_named, .. } => !is_named,
          PatternNode::Internal { .. } => false,
        },
      };
      if !skipped {
        return false;
      }
      goal_children.next();
    }
    true
  }
}

impl FromStr for MatchStrictness {
  type Err = &'static str;
  fn from_str(s: &str) -> Result<Self, Self::Err> {
    match s {
      "cst" => Ok(MatchStrictness::Cst),
      "smart" => Ok(MatchStrictness::Smart),
      "ast" => Ok(MatchStrictness::Ast),
      "relaxed" => Ok(MatchStrictness::Relaxed),
      "signature" => Ok(MatchStrictness::Signature),
      _ => Err("invalid strictness, valid options are: cst, smart, ast, relaxed, signature"),
    }
  }
}