use oxc_ast::ast::{ChainElement, Expression};
pub fn is_pure_function(callee: &Expression, pure_functions: &[String]) -> bool {
if pure_functions.is_empty() {
return false;
}
let Some(path_parts) = extract_callee_path(callee) else {
return false;
};
pure_functions.iter().any(|pure_fn| is_path_match(&path_parts, pure_fn))
}
fn extract_callee_path<'a>(callee: &'a Expression<'a>) -> Option<Vec<&'a str>> {
let mut path_parts: Vec<&str> = Vec::new();
let mut current = callee;
loop {
match current {
Expression::Identifier(ident) => {
path_parts.push(ident.name.as_str());
break;
}
Expression::StaticMemberExpression(member) => {
path_parts.push(member.property.name.as_str());
current = &member.object;
}
Expression::ComputedMemberExpression(member) => {
let Expression::StringLiteral(lit) = &member.expression else {
return None;
};
path_parts.push(lit.value.as_str());
current = &member.object;
}
Expression::CallExpression(call) => {
path_parts.clear();
current = &call.callee;
}
Expression::ChainExpression(chain) => match &chain.expression {
ChainElement::StaticMemberExpression(member) => {
path_parts.push(member.property.name.as_str());
current = &member.object;
}
ChainElement::ComputedMemberExpression(member) => {
let Expression::StringLiteral(lit) = &member.expression else {
return None;
};
path_parts.push(lit.value.as_str());
current = &member.object;
}
ChainElement::CallExpression(call) => {
path_parts.clear();
current = &call.callee;
}
ChainElement::TSNonNullExpression(ts) => {
current = &ts.expression;
}
ChainElement::PrivateFieldExpression(_) => {
return None;
}
},
Expression::ParenthesizedExpression(paren) => {
current = &paren.expression;
}
_ => {
return None;
}
}
}
Some(path_parts)
}
fn is_path_match(path_parts: &[&str], pure_fn: &str) -> bool {
let pure_parts_count = pure_fn.bytes().filter(|&b| b == b'.').count() + 1;
if pure_parts_count > path_parts.len() {
return false;
}
pure_fn.split('.').zip(path_parts.iter().rev()).all(|(a, b)| a == *b)
}