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: 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 self.start.column != node_start_pos.column(&node)
|| self.end.column != 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: 10,
},
SerializablePosition { line: 0, column: 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: 10,
},
SerializablePosition {
line: 0,
column: 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: 10,
},
SerializablePosition {
line: 0,
column: 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: 1 },
SerializablePosition { line: 5, column: 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: 8 },
SerializablePosition {
line: 0,
column: 11,
},
);
let node = pattern.find_node(cand);
assert!(node.is_some());
assert_eq!(node.expect("should exist").text(), "'🦄'");
}
}