use ast_grep_core::{meta_var::MetaVarEnv, Doc, Node};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct SerializablePosition {
pub line: usize,
pub column: Option<usize>,
}
#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct SerializableRange {
pub start: SerializablePosition,
pub end: SerializablePosition,
}
use std::borrow::Cow;
use bit_set::BitSet;
use thiserror::Error;
use super::Matcher;
#[derive(Debug, Error)]
pub enum RangeMatcherError {
#[error("The start position must be before the end position.")]
InvalidRange,
}
pub struct RangeMatcher {
start: SerializablePosition,
end: SerializablePosition,
}
impl RangeMatcher {
pub fn new(start_pos: SerializablePosition, end_pos: SerializablePosition) -> Self {
Self {
start: start_pos,
end: end_pos,
}
}
pub fn try_new(
start_pos: SerializablePosition,
end_pos: SerializablePosition,
) -> Result<RangeMatcher, RangeMatcherError> {
if start_pos.line > end_pos.line
|| (start_pos.line == end_pos.line && start_pos.column > end_pos.column)
{
return Err(RangeMatcherError::InvalidRange);
}
let range = Self::new(start_pos, end_pos);
Ok(range)
}
}
impl Matcher for RangeMatcher {
fn match_node_with_env<'tree, D: Doc>(
&self,
node: Node<'tree, D>,
_env: &mut Cow<MetaVarEnv<'tree, D>>,
) -> Option<Node<'tree, D>> {
let node_start_pos = node.start_pos();
let node_end_pos = node.end_pos();
if self.start.line != node_start_pos.line() || self.end.line != node_end_pos.line() {
return None;
}
if let Some(start_col) = self.start.column {
if start_col != node_start_pos.column(&node) {
return None;
}
}
if let Some(end_col) = self.end.column {
if end_col != node_end_pos.column(&node) {
return None;
}
}
Some(node)
}
fn potential_kinds(&self) -> Option<BitSet> {
None
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::test::TypeScript as TS;
use ast_grep_core::matcher::MatcherExt;
use ast_grep_core::tree_sitter::LanguageExt;
#[test]
fn test_invalid_range() {
let range = RangeMatcher::try_new(
SerializablePosition {
line: 0,
column: Some(10),
},
SerializablePosition {
line: 0,
column: Some(5),
},
);
assert!(range.is_err());
}
#[test]
fn test_range_match() {
let cand = TS::Tsx.ast_grep("class A { a = 123 }");
let cand = cand.root();
let pattern = RangeMatcher::new(
SerializablePosition {
line: 0,
column: Some(10),
},
SerializablePosition {
line: 0,
column: Some(17),
},
);
assert!(pattern.find_node(cand).is_some());
}
#[test]
fn test_range_non_match() {
let cand = TS::Tsx.ast_grep("class A { a = 123 }");
let cand = cand.root();
let pattern = RangeMatcher::new(
SerializablePosition {
line: 0,
column: Some(10),
},
SerializablePosition {
line: 0,
column: Some(15),
},
);
assert!(pattern.find_node(cand).is_none(),);
}
#[test]
fn test_multiline_range() {
let cand = TS::Tsx
.ast_grep("class A { \n b = () => { \n const c = 1 \n const d = 3 \n return c + d \n } }");
let cand = cand.root();
let pattern = RangeMatcher::new(
SerializablePosition {
line: 1,
column: Some(1),
},
SerializablePosition {
line: 5,
column: Some(2),
},
);
assert!(pattern.find_node(cand).is_some());
}
#[test]
fn test_unicode_range() {
let cand = TS::Tsx.ast_grep("let a = '🦄'");
let cand = cand.root();
let pattern = RangeMatcher::new(
SerializablePosition {
line: 0,
column: Some(8),
},
SerializablePosition {
line: 0,
column: Some(11),
},
);
let node = pattern.find_node(cand);
assert!(node.is_some());
assert_eq!(node.expect("should exist").text(), "'🦄'");
}
#[test]
fn test_range_with_none_start_column() {
let cand = TS::Tsx.ast_grep("class A { a = 123 }");
let cand = cand.root();
let pattern = RangeMatcher::new(
SerializablePosition {
line: 0,
column: None,
},
SerializablePosition {
line: 0,
column: Some(17),
},
);
assert!(pattern.find_node(cand).is_some());
}
#[test]
fn test_range_with_none_end_column() {
let cand = TS::Tsx.ast_grep("class A { a = 123 }");
let cand = cand.root();
let pattern = RangeMatcher::new(
SerializablePosition {
line: 0,
column: Some(10),
},
SerializablePosition {
line: 0,
column: None,
},
);
assert!(pattern.find_node(cand.clone()).is_some());
let pattern = RangeMatcher::new(
SerializablePosition {
line: 0,
column: None,
},
SerializablePosition {
line: 0,
column: None,
},
);
assert!(pattern.find_node(cand).is_some());
}
#[test]
fn test_range_with_none_columns_multiline() {
let cand = TS::Tsx
.ast_grep("class A { \n b = () => { \n const c = 1 \n const d = 3 \n return c + d \n } }");
let cand = cand.root();
let pattern = RangeMatcher::new(
SerializablePosition {
line: 1,
column: None,
},
SerializablePosition {
line: 5,
column: None,
},
);
assert!(pattern.find_node(cand).is_some());
}
#[test]
fn test_try_new_invalid_range_with_none_columns() {
let range = RangeMatcher::try_new(
SerializablePosition {
line: 5,
column: None,
},
SerializablePosition {
line: 3,
column: None,
},
);
assert!(range.is_err());
}
}