use ast_grep_core::{Doc, Node};
use super::syntax::{
add_derive, attached_prefix, is_comment, is_generic, remove_item, sibling_item, source_of,
Derive,
};
use super::RawEdit;
fn copies_field<D: Doc>(value: &Node<'_, D>, field: &str) -> bool {
let text = value.text();
let access = text.strip_suffix(".clone()").unwrap_or(&text);
access.strip_prefix("self.") == Some(field)
}
fn rebuilds_every_field<D: Doc>(expression: &Node<'_, D>, fields_list: &Node<'_, D>) -> bool {
match (&*expression.kind(), &*fields_list.kind()) {
("struct_expression", "field_declaration_list") => {
let Some(body) = expression.field("body") else {
return false;
};
let initializers: Vec<_> = body.named_children().filter(|c| !is_comment(c)).collect();
let declared = fields_list
.named_children()
.filter(|c| c.kind() == "field_declaration")
.count();
initializers.len() == declared
&& initializers.iter().all(|init| {
init.kind() == "field_initializer"
&& init.field("field").is_some_and(|name| {
init.field("value")
.is_some_and(|v| copies_field(&v, &name.text()))
})
})
}
("call_expression", "ordered_field_declaration_list") => {
let Some(arguments) = expression.field("arguments") else {
return false;
};
let arguments: Vec<_> = arguments.named_children().collect();
let declared = fields_list
.named_children()
.filter(|c| {
!is_comment(c)
&& !matches!(&*c.kind(), "attribute_item" | "visibility_modifier")
})
.count();
arguments.len() == declared
&& arguments
.iter()
.enumerate()
.all(|(i, arg)| copies_field(arg, &i.to_string()))
}
_ => false,
}
}
fn sole_expression<'r, D: Doc>(function: &Node<'r, D>) -> Option<Node<'r, D>> {
let body = function.field("body")?;
let mut children = body.named_children();
let expression = children.next()?;
children.next().is_none().then_some(expression)
}
fn is_doc<D: Doc>(node: &Node<'_, D>) -> bool {
let text = node.text();
text.starts_with("///") || text.starts_with("/**") || text.replace(' ', "").starts_with("#[doc")
}
pub(super) fn fix<D: Doc>(implementation: &Node<'_, D>) -> Option<Vec<RawEdit>> {
let src = source_of(implementation);
let documented = attached_prefix(implementation, &src).iter().any(is_doc)
|| implementation.dfs().any(|n| is_comment(&n) && is_doc(&n));
if documented || is_generic(implementation) {
return None;
}
let ty = implementation.field("type")?;
if ty.kind() != "type_identifier" {
return None;
}
let items = implementation.field("body")?;
let mut functions = items.named_children().filter(|c| !is_comment(c));
let clone_fn = functions.next()?;
if functions.next().is_some()
|| clone_fn.kind() != "function_item"
|| clone_fn.field("name")?.text() != "clone"
{
return None;
}
let structure = sibling_item(implementation, "struct_item", &ty.text())?;
if is_generic(&structure) {
return None;
}
let fields = structure.field("body")?;
if !rebuilds_every_field(&sole_expression(&clone_fn)?, &fields) {
return None;
}
let Derive::Add(derive) = add_derive(&structure, &src, "Clone", "Clone") else {
return None;
};
Some(vec![derive, remove_item(implementation, &src)])
}