use ast_grep_core::{Doc, Node};
use super::syntax::{replace, source_of};
use super::RawEdit;
fn binds_variables<D: Doc>(pattern: &Node<'_, D>) -> bool {
pattern.dfs().any(|node| match &*node.kind() {
"captured_pattern"
| "mut_pattern"
| "ref_pattern"
| "shorthand_field_identifier"
| "macro_invocation" => true,
"identifier" => {
let is_path = node.parent().is_some_and(|p| {
matches!(&*p.kind(), "scoped_identifier" | "scoped_type_identifier")
|| p.field("type").is_some_and(|t| t.range() == node.range())
});
!is_path
&& node
.text()
.starts_with(|c: char| c.is_ascii_lowercase() || c == '_')
}
_ => false,
})
}
fn mergeable_pattern<'r, D: Doc>(arm: &Node<'r, D>) -> Option<Node<'r, D>> {
if arm.kind() != "match_arm" || arm.text().starts_with('#') {
return None;
}
let pattern = arm.field("pattern")?;
let is_wildcard = pattern.text().split('|').any(|alt| alt.trim() == "_");
if is_wildcard || pattern.field("condition").is_some() || binds_variables(&pattern) {
return None;
}
Some(pattern)
}
fn normalized(text: &str) -> String {
text.split_whitespace().collect::<Vec<_>>().join(" ")
}
pub(super) fn fix<D: Doc>(first: &Node<'_, D>) -> Option<Vec<RawEdit>> {
let second = first.next()?;
let first_pattern = mergeable_pattern(first)?;
mergeable_pattern(&second)?;
let body = |arm: &Node<'_, D>| arm.field("value").map(|v| normalized(&v.text()));
if body(first)? != body(&second)? {
return None;
}
let src = source_of(first);
let (start, end) = (first.range().start, second.range().start);
if !src[first.range().end..end].trim().is_empty() {
return None;
}
Some(vec![replace(
start,
end,
format!("{} | ", first_pattern.text()),
)])
}