use crate::{binding::Binding, scope::Scope, usage};
use rnix::{
types::{TokenWrapper, TypedNode},
NixLanguage,
};
use rowan::api::SyntaxNode;
use std::{collections::{HashMap, HashSet}, fmt};
#[derive(Debug, Clone)]
pub struct DeadCode {
pub scope: Scope,
pub binding: Binding,
}
impl fmt::Display for DeadCode {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "Unused {}: {}", self.scope, self.binding.name.as_str())
}
}
#[derive(Debug, Clone)]
pub struct Settings {
pub no_lambda_arg: bool,
pub no_lambda_pattern_names: bool,
pub no_underscore: bool,
}
impl Settings {
pub fn find_dead_code(&self, node: &SyntaxNode<NixLanguage>) -> Vec<DeadCode> {
let mut dead = HashSet::new();
let mut results = HashMap::new();
let mut prev_results_len = 1;
while prev_results_len != results.len() {
prev_results_len = results.len();
self.scan(node, &mut dead, &mut results);
}
let mut results = results.into_values().collect::<Vec<_>>();
results.sort_unstable_by_key(|result| result.binding.name.node().text_range().start());
results
}
fn scan(
&self,
node: &SyntaxNode<NixLanguage>,
dead: &mut HashSet<SyntaxNode<NixLanguage>>,
results: &mut HashMap<SyntaxNode<NixLanguage>, DeadCode>,
) {
if let Some(scope) = Scope::new(node) {
if !(self.no_lambda_arg && scope.is_lambda_arg()) {
for binding in scope.bindings() {
if binding.is_mortal()
&& !(self.no_underscore && binding.name.as_str().starts_with('_'))
&& !(self.no_lambda_pattern_names && scope.is_lambda_pattern_name(&binding.name))
&& !scope.bodies().any(|body| {
body != binding.body_node &&
dead.get(&body).is_none() &&
usage::find(&binding.name, &body)
}) {
dead.insert(binding.body_node.clone());
results.insert(
binding.decl_node.clone(),
DeadCode {
scope: scope.clone(),
binding,
},
);
}
}
}
}
for child in node.children() {
self.scan(&child, dead, results);
}
}
}