mirror of
https://github.com/astro/deadnix.git
synced 2024-11-24 09:12:50 +03:00
fix, simplify, refactor
This commit is contained in:
parent
1874a16ad9
commit
62e1b35cd7
@ -7,20 +7,21 @@ use rowan::api::SyntaxNode;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Binding {
|
||||
pub name: Ident,
|
||||
pub node: SyntaxNode<NixLanguage>,
|
||||
pub body_node: SyntaxNode<NixLanguage>,
|
||||
pub decl_node: SyntaxNode<NixLanguage>,
|
||||
mortal: bool,
|
||||
}
|
||||
|
||||
impl PartialEq for Binding {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.node == other.node && self.name.as_str() == other.name.as_str()
|
||||
self.decl_node == other.decl_node && self.name.as_str() == other.name.as_str()
|
||||
}
|
||||
}
|
||||
impl Eq for Binding {}
|
||||
|
||||
impl Binding {
|
||||
pub fn new(name: Ident, node: SyntaxNode<NixLanguage>, mortal: bool) -> Self {
|
||||
Binding { name, node, mortal }
|
||||
pub fn new(name: Ident, body_node: SyntaxNode<NixLanguage>, decl_node: SyntaxNode<NixLanguage>, mortal: bool) -> Self {
|
||||
Binding { name, body_node, decl_node, mortal }
|
||||
}
|
||||
|
||||
pub fn is_mortal(&self) -> bool {
|
||||
|
@ -53,21 +53,20 @@ impl Settings {
|
||||
if let Some(scope) = Scope::new(node) {
|
||||
if !(self.no_lambda_arg && scope.is_lambda_arg()) {
|
||||
for binding in scope.bindings() {
|
||||
let name_node = binding.name.node();
|
||||
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| {
|
||||
// exclude this binding's own node
|
||||
body != binding.node &&
|
||||
body != binding.body_node &&
|
||||
// excluding already unused results
|
||||
dead.get(&body).is_none() &&
|
||||
// find if used anywhere
|
||||
usage::find(&binding.name, &body)
|
||||
}) {
|
||||
dead.insert(binding.node.clone());
|
||||
dead.insert(binding.body_node.clone());
|
||||
results.insert(
|
||||
binding.name.node().clone(),
|
||||
binding.decl_node.clone(),
|
||||
DeadCode {
|
||||
scope: scope.clone(),
|
||||
binding,
|
||||
|
145
src/edit.rs
145
src/edit.rs
@ -4,7 +4,6 @@ use rnix::{
|
||||
NixLanguage, SyntaxKind,
|
||||
};
|
||||
use rowan::api::SyntaxNode;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Edit {
|
||||
@ -32,15 +31,10 @@ fn apply_edits<'a>(src: &str, edits: impl Iterator<Item = &'a Edit>) -> String {
|
||||
/// assumes `node` to be presorted
|
||||
pub fn edit_dead_code(
|
||||
original: &str,
|
||||
node: &SyntaxNode<NixLanguage>,
|
||||
dead: impl Iterator<Item = DeadCode>,
|
||||
) -> String {
|
||||
let mut dead = dead
|
||||
.map(|result| (result.binding.node.clone(), result))
|
||||
.collect::<HashMap<_, _>>();
|
||||
let mut edits = Vec::with_capacity(dead.len());
|
||||
scan(node, &mut dead, &mut edits);
|
||||
|
||||
let mut edits = dead.filter_map(dead_to_edit)
|
||||
.collect::<Vec<_>>();
|
||||
edits.sort_unstable_by(|e1, e2| {
|
||||
if e1.start == e2.start {
|
||||
e1.end.cmp(&e2.end)
|
||||
@ -62,91 +56,78 @@ pub fn edit_dead_code(
|
||||
}
|
||||
}
|
||||
|
||||
fn scan(
|
||||
node: &SyntaxNode<NixLanguage>,
|
||||
dead: &mut HashMap<SyntaxNode<NixLanguage>, DeadCode>,
|
||||
edits: &mut Vec<Edit>,
|
||||
) {
|
||||
if let Some(dead_code) = dead.remove(node) {
|
||||
let range = dead_code.binding.node.text_range();
|
||||
let mut start = usize::from(range.start());
|
||||
let mut end = usize::from(range.end());
|
||||
let mut replace_node = node.clone();
|
||||
let mut replacement = None;
|
||||
match dead_code.scope {
|
||||
Scope::LambdaPattern(pattern, _) => {
|
||||
if pattern.at().map_or(false, |at| at.node() == node) {
|
||||
if let Some(pattern_bind_node) = pattern.node().children().find(|child|
|
||||
child.kind() == SyntaxKind::NODE_PAT_BIND
|
||||
) {
|
||||
// `dead @ { ... }`, `{ ... } @ dead` forms
|
||||
let pattern_bind_range = pattern_bind_node.text_range();
|
||||
start = usize::from(pattern_bind_range.start());
|
||||
end = usize::from(pattern_bind_range.end());
|
||||
// also remove trailing whitespace for this form
|
||||
if let Some(next) = pattern_bind_node.next_sibling_or_token() {
|
||||
if next.kind() == SyntaxKind::TOKEN_WHITESPACE {
|
||||
end = usize::from(next.text_range().end());
|
||||
}
|
||||
fn dead_to_edit(
|
||||
dead_code: DeadCode,
|
||||
) -> Option<Edit> {
|
||||
let range = dead_code.binding.decl_node.text_range();
|
||||
let mut start = usize::from(range.start());
|
||||
let mut end = usize::from(range.end());
|
||||
let mut replace_node = dead_code.binding.decl_node.clone();
|
||||
let mut replacement = None;
|
||||
match dead_code.scope {
|
||||
Scope::LambdaPattern(pattern, _) => {
|
||||
if pattern.at().map_or(false, |at| *at.node() == dead_code.binding.decl_node) {
|
||||
if let Some(pattern_bind_node) = pattern.node().children().find(|child|
|
||||
child.kind() == SyntaxKind::NODE_PAT_BIND
|
||||
) {
|
||||
// `dead @ { ... }`, `{ ... } @ dead` forms
|
||||
let pattern_bind_range = pattern_bind_node.text_range();
|
||||
start = usize::from(pattern_bind_range.start());
|
||||
end = usize::from(pattern_bind_range.end());
|
||||
// also remove trailing whitespace for this form
|
||||
if let Some(next) = pattern_bind_node.next_sibling_or_token() {
|
||||
if next.kind() == SyntaxKind::TOKEN_WHITESPACE {
|
||||
end = usize::from(next.text_range().end());
|
||||
}
|
||||
replacement = Some("".to_string());
|
||||
replace_node = pattern_bind_node;
|
||||
}
|
||||
} else if let Some(next) = node.next_sibling_or_token() {
|
||||
// { dead, ... } form
|
||||
if next.kind() == SyntaxKind::TOKEN_COMMA {
|
||||
end = usize::from(next.text_range().end());
|
||||
replacement = Some("".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scope::LambdaArg(name, _) => {
|
||||
replacement = Some(format!("_{}", name.as_str()));
|
||||
}
|
||||
|
||||
Scope::LetIn(let_in) => {
|
||||
if let_in.entries().any(|entry| entry.node() == node) {
|
||||
replacement = Some("".to_string());
|
||||
} else if let Some(inherit) =
|
||||
let_in.inherits().find(|inherit| inherit.node() == node)
|
||||
{
|
||||
if let Some(ident) = inherit
|
||||
.idents()
|
||||
.find(|ident| ident.as_str() == dead_code.binding.name.as_str())
|
||||
{
|
||||
let range = ident.node().text_range();
|
||||
start = usize::from(range.start());
|
||||
end = usize::from(range.end());
|
||||
replace_node = ident.node().clone();
|
||||
replacement = Some("".to_string());
|
||||
}
|
||||
replace_node = pattern_bind_node;
|
||||
}
|
||||
} else if let Some(next) = dead_code.binding.decl_node.next_sibling_or_token() {
|
||||
// { dead, ... } form
|
||||
if next.kind() == SyntaxKind::TOKEN_COMMA {
|
||||
end = usize::from(next.text_range().end());
|
||||
replacement = Some("".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
Scope::RecAttrSet(_) => {}
|
||||
}
|
||||
|
||||
if let Some(replacement) = replacement {
|
||||
// remove whitespace before node
|
||||
if let Some(prev) = replace_node.prev_sibling_or_token() {
|
||||
if prev.kind() == SyntaxKind::TOKEN_WHITESPACE {
|
||||
start = usize::from(prev.text_range().start());
|
||||
}
|
||||
Scope::LambdaArg(name, _) => {
|
||||
replacement = Some(format!("_{}", name.as_str()));
|
||||
}
|
||||
|
||||
Scope::LetIn(let_in) => {
|
||||
if let_in.entries().any(|entry| *entry.node() == dead_code.binding.decl_node) {
|
||||
replacement = Some("".to_string());
|
||||
} else if let Some(ident) = let_in.inherits().flat_map(|inherit| {
|
||||
inherit.idents()
|
||||
.filter(|ident| *ident.node() == dead_code.binding.decl_node)
|
||||
}).next() {
|
||||
let range = ident.node().text_range();
|
||||
start = usize::from(range.start());
|
||||
end = usize::from(range.end());
|
||||
replace_node = ident.node().clone();
|
||||
replacement = Some("".to_string());
|
||||
}
|
||||
|
||||
edits.push(Edit {
|
||||
start,
|
||||
end,
|
||||
replacement,
|
||||
});
|
||||
}
|
||||
|
||||
Scope::RecAttrSet(_) => {}
|
||||
}
|
||||
|
||||
// recurse through the AST
|
||||
for node in node.children() {
|
||||
scan(&node, dead, edits);
|
||||
}
|
||||
replacement.map(|replacement| {
|
||||
// remove whitespace before node
|
||||
if let Some(prev) = replace_node.prev_sibling_or_token() {
|
||||
if prev.kind() == SyntaxKind::TOKEN_WHITESPACE {
|
||||
start = usize::from(prev.text_range().start());
|
||||
}
|
||||
}
|
||||
|
||||
Edit {
|
||||
start,
|
||||
end,
|
||||
replacement,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_empty_scopes(node: &SyntaxNode<NixLanguage>, edits: &mut Vec<Edit>) {
|
||||
|
@ -12,7 +12,7 @@ fn run(content: &str) -> String {
|
||||
no_underscore: false,
|
||||
}
|
||||
.find_dead_code(&ast.node());
|
||||
crate::edit::edit_dead_code(content, &ast.node(), results.into_iter())
|
||||
crate::edit::edit_dead_code(content, results.into_iter())
|
||||
}
|
||||
|
||||
macro_rules! no_edits {
|
||||
@ -67,6 +67,12 @@ fn let_inherit_in_dead_only() {
|
||||
assert_eq!(results, "alive");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_inherit_multi_in_dead_only() {
|
||||
let results = run("let inherit dead1 dead2 dead3; in alive");
|
||||
assert_eq!(results, "alive");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_inherit_from_in_alive() {
|
||||
no_edits!("let inherit (x) alive; in alive");
|
||||
@ -90,6 +96,12 @@ fn let_inherit_from_in_dead_only() {
|
||||
assert_eq!(results, "alive");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_inherit_from_multi_in_dead_only() {
|
||||
let results = run("let inherit (grave) dead1 dead2 dead3; in alive");
|
||||
assert_eq!(results, "alive");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda_arg_alive() {
|
||||
no_edits!("alive: alive");
|
||||
|
@ -118,7 +118,7 @@ fn main() {
|
||||
crate::report::Report::new(file.to_string(), &content, results.clone()).print();
|
||||
}
|
||||
if edit {
|
||||
let new_ast = crate::edit::edit_dead_code(&content, &ast.node(), results.into_iter());
|
||||
let new_ast = crate::edit::edit_dead_code(&content, results.into_iter());
|
||||
fs::write(file, new_ast).expect("fs::write");
|
||||
}
|
||||
}
|
||||
|
22
src/scope.rs
22
src/scope.rs
@ -89,33 +89,36 @@ impl Scope {
|
||||
.at()
|
||||
.map(|name| {
|
||||
let binding_node = name.node().clone();
|
||||
Binding::new(name, binding_node, true)
|
||||
Binding::new(name, binding_node.clone(), binding_node, true)
|
||||
})
|
||||
.into_iter()
|
||||
.chain(pattern.entries().map(move |entry| {
|
||||
let name = entry.name().expect("entry.name");
|
||||
Binding::new(name, entry.node().clone(), mortal)
|
||||
Binding::new(name, entry.node().clone(), entry.node().clone(), mortal)
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
Scope::LambdaArg(name, _) => {
|
||||
let mortal = !name.as_str().starts_with('_');
|
||||
Box::new(Some(Binding::new(name.clone(), name.node().clone(), mortal)).into_iter())
|
||||
Box::new(Some(Binding::new(name.clone(), name.node().clone(), name.node().clone(), mortal)).into_iter())
|
||||
}
|
||||
|
||||
Scope::LetIn(let_in) => Box::new(
|
||||
let_in
|
||||
.inherits()
|
||||
.flat_map(|inherit| {
|
||||
let binding_node = if let Some(from) = inherit.from() {
|
||||
let body_node = if let Some(from) = inherit.from() {
|
||||
from.node().clone()
|
||||
} else {
|
||||
inherit.node().clone()
|
||||
};
|
||||
inherit
|
||||
.idents()
|
||||
.map(move |name| Binding::new(name, binding_node.clone(), true))
|
||||
.map(move |name| {
|
||||
let name_node = name.node().clone();
|
||||
Binding::new(name, body_node.clone(), name_node, true)
|
||||
})
|
||||
})
|
||||
.chain(let_in.entries().map(|entry| {
|
||||
let key = entry
|
||||
@ -125,7 +128,7 @@ impl Scope {
|
||||
.next()
|
||||
.expect("key.path.next");
|
||||
let name = Ident::cast(key).expect("Ident::cast");
|
||||
Binding::new(name, entry.node().clone(), true)
|
||||
Binding::new(name, entry.node().clone(), entry.node().clone(), true)
|
||||
})),
|
||||
),
|
||||
|
||||
@ -136,7 +139,10 @@ impl Scope {
|
||||
let binding_node = inherit.node().clone();
|
||||
inherit
|
||||
.idents()
|
||||
.map(move |name| Binding::new(name, binding_node.clone(), false))
|
||||
.map(move |name| {
|
||||
let name_node = name.node().clone();
|
||||
Binding::new(name, binding_node.clone(), name_node, false)
|
||||
})
|
||||
})
|
||||
.chain(attr_set.entries().filter_map(|entry| {
|
||||
let key = entry
|
||||
@ -147,7 +153,7 @@ impl Scope {
|
||||
.expect("key.path.next");
|
||||
if key.kind() == SyntaxKind::NODE_IDENT {
|
||||
let name = Ident::cast(key).expect("Ident::cast");
|
||||
Some(Binding::new(name, entry.node().clone(), false))
|
||||
Some(Binding::new(name, entry.node().clone(), entry.node().clone(), false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user