use diagnostics::LisetteDiagnostic;
use syntax::ast::{Expression, Pattern};
use syntax::program::{CallKind, DotAccessKind};
use crate::facts::Facts;
pub fn check_redundant_closure(
expression: &Expression,
facts: &Facts,
diagnostics: &mut Vec<LisetteDiagnostic>,
) {
let Expression::Lambda {
params, body, span, ..
} = expression
else {
return;
};
let Expression::Call {
expression: callee,
args,
spread,
type_args,
call_kind,
..
} = lambda_body(body)
else {
return;
};
if !matches!(call_kind, Some(CallKind::Regular))
|| spread.is_some()
|| !type_args.is_empty()
|| args.len() != params.len()
{
return;
}
let mut param_names = Vec::with_capacity(params.len());
for (param, arg) in params.iter().zip(args) {
let Pattern::Identifier { identifier, .. } = ¶m.pattern else {
return;
};
let Expression::Identifier { value, .. } = arg.unwrap_parens() else {
return;
};
if identifier.as_str() != value.as_str() {
return;
}
param_names.push(identifier.as_str());
}
let Some(callee_name) = hoistable_callee(callee.unwrap_parens(), ¶m_names, facts) else {
return;
};
diagnostics.push(diagnostics::lint::redundant_closure(span, &callee_name));
}
fn lambda_body(body: &Expression) -> &Expression {
match body.unwrap_parens() {
Expression::Block { items, .. } if items.len() == 1 => items[0].unwrap_parens(),
other => other,
}
}
fn hoistable_callee(callee: &Expression, params: &[&str], facts: &Facts) -> Option<String> {
if callee.get_type().get_param_mutability().iter().any(|m| *m) {
return None;
}
match callee {
Expression::Identifier {
value, binding_id, ..
} => {
if params.contains(&value.as_str()) {
return None;
}
if let Some(id) = binding_id {
match facts.bindings.get(id) {
Some(binding) if !binding.kind.is_mutable() => {}
_ => return None,
}
}
Some(value.to_string())
}
Expression::DotAccess {
expression: base,
member,
dot_access_kind: Some(DotAccessKind::ModuleMember),
..
} => {
let Expression::Identifier { value: base, .. } = base.unwrap_parens() else {
return None;
};
Some(format!("{base}.{member}"))
}
_ => None,
}
}