use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "PascalCase")]
pub enum NodeKind {
Expr,
Literal,
Path,
MethodCall,
FunctionCall,
MacroCall,
BinaryOp,
UnaryOp,
If,
Match,
Loop,
Block,
Closure,
Await,
Try,
Return,
Index,
Function,
Struct,
Enum,
Trait,
Impl,
Mod,
Use,
Const,
Static,
TypeAlias,
Field,
Variant,
Param,
Arg,
GenericArg,
Lifetime,
Attribute,
LetExpr,
Wildcard,
Unit,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum NameMatcher {
Exact(String),
Pattern(NamePattern),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct NamePattern {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub glob: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub regex: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub starts_with: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ends_with: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contains: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum PatternExpr {
Pattern(Box<CodePattern>),
Name(NameMatcher),
Literal(serde_json::Value),
Wildcard,
Capture(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ArmPattern {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pattern_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub body: Option<Box<CodePattern>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CodePattern {
pub node: NodeKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arm_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub arms: Option<Vec<ArmPattern>>,
#[serde(flatten)]
pub children: HashMap<String, PatternExpr>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capture: Option<String>,
#[serde(default)]
pub ellipsis: bool,
}
impl CodePattern {
pub fn new(node: NodeKind) -> Self {
Self {
node,
arm_count: None,
arms: None,
children: HashMap::new(),
capture: None,
ellipsis: false,
}
}
pub fn with_child(mut self, name: impl Into<String>, pattern: PatternExpr) -> Self {
self.children.insert(name.into(), pattern);
self
}
pub fn with_capture(mut self, var: impl Into<String>) -> Self {
self.capture = Some(var.into());
self
}
pub fn with_ellipsis(mut self) -> Self {
self.ellipsis = true;
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct BodyMatch {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contains: Option<Vec<CodePattern>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub not_contains: Option<Vec<CodePattern>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub all_of: Option<Vec<CodePattern>>,
}
impl BodyMatch {
pub fn new() -> Self {
Self::default()
}
pub fn contains(mut self, pattern: CodePattern) -> Self {
self.contains.get_or_insert_with(Vec::new).push(pattern);
self
}
pub fn not_contains(mut self, pattern: CodePattern) -> Self {
self.not_contains.get_or_insert_with(Vec::new).push(pattern);
self
}
pub fn all_of(mut self, pattern: CodePattern) -> Self {
self.all_of.get_or_insert_with(Vec::new).push(pattern);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_code_pattern_builder() {
let pattern = CodePattern::new(NodeKind::MethodCall)
.with_child(
"method",
PatternExpr::Name(NameMatcher::Exact("unwrap".into())),
)
.with_capture("$UNWRAP");
assert_eq!(pattern.node, NodeKind::MethodCall);
assert_eq!(pattern.capture, Some("$UNWRAP".to_string()));
assert!(pattern.children.contains_key("method"));
}
#[test]
fn test_body_match_builder() {
let body = BodyMatch::new()
.contains(CodePattern::new(NodeKind::MethodCall))
.not_contains(CodePattern::new(NodeKind::MacroCall));
assert!(body.contains.is_some());
assert!(body.not_contains.is_some());
assert!(body.all_of.is_none());
}
#[test]
fn test_serialize_deserialize() {
let pattern = CodePattern::new(NodeKind::MethodCall).with_child(
"method",
PatternExpr::Name(NameMatcher::Exact("unwrap".into())),
);
let json = serde_json::to_string(&pattern).unwrap();
let deserialized: CodePattern = serde_json::from_str(&json).unwrap();
assert_eq!(pattern.node, deserialized.node);
}
}