Skip to content

Commit 2ce89d9

Browse files
Add rule to remove unused variables (#172)
1 parent f193982 commit 2ce89d9

33 files changed

+677
-117
lines changed

.github/workflows/test.yml

-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ jobs:
4444
run: |
4545
luarocks install luafilesystem
4646
luarocks install busted
47-
luarocks install luacheck
4847
4948
- name: Run end-to-end tests
5049
run: lua ./scripts/test-commands.lua

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
* add rule to remove unused variables (`remove_unused_variable`). Fix issue with `rename_variables` where `self` variables and some cases of variable shadowing were not correctly renamed ([#172](https://github.com/seaofvoices/darklua/pull/172))
6+
57
## 0.12.1
68

79
* fix `append_text_comment` rule to support multiline comments ([#167](https://github.com/seaofvoices/darklua/pull/167))

scripts/install-lua.sh

-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,3 @@ export PATH=$PATH:$PWD/lua_install/bin
66

77
luarocks install luafilesystem
88
luarocks install busted
9-
luarocks install luacheck

site/content/rules/append_text_comment.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ parameters:
99
type: string
1010
description: A path to a file to be used as the comment content (required if `text` is not defined)
1111
- name: location
12-
default: "start"
12+
default: start
1313
type: '"start" or "end"'
1414
description: The location where to add the comment
1515
examples:

site/content/rules/remove_assertions.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ parameters:
55
- name: preserve_arguments_side_effects
66
type: boolean
77
description: Defines how darklua handle arguments passed to the function. If true, darklua will inspect each argument and preserve any potential side effects. When false, darklua will not perform any verification and simply erase any arguments passed.
8-
default: true
8+
default: "true"
99
examples:
1010
- content: assert(condition, 'condition is incorrect!')
1111
---

site/content/rules/remove_debug_profiling.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ parameters:
55
- name: preserve_arguments_side_effects
66
type: boolean
77
description: Defines how darklua handle arguments passed to the functions. If true, darklua will inspect each argument and preserve any potential side effects. When false, darklua will not perform any verification and simply erase any arguments passed.
8-
default: true
8+
default: "true"
99
examples:
1010
- content: |
1111
debug.profilebegin('function name')

site/content/rules/remove_empty_do.md

+2
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ examples:
1212
end
1313
return {}
1414
---
15+
16+
This simple rule removes all empty do blocks found.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
description: Removes unused variable declarations
3+
added_in: "unreleased"
4+
parameters: []
5+
examples:
6+
- content: "local var"
7+
- content: |
8+
local var1 = true
9+
local var2 = var1
10+
- content: "local var = call()"
11+
- content: "local function fn() print('unused') end"
12+
- content: |
13+
local a, b, c = 1, 2, 3
14+
return a
15+
---
16+
17+
This rule removes unused variables from code. It also removes unused local function definitions.

site/gatsby-node.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ exports.createSchemaCustomization = ({ actions }) => {
217217
}
218218
219219
type RuleParameter {
220-
name: String
221-
type: String
220+
name: String!
221+
type: String!
222222
default: String
223223
description: String
224224
added_in: String

src/nodes/block.rs

+10
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,21 @@ impl Block {
136136
self.last_statement.is_none() && self.statements.is_empty()
137137
}
138138

139+
#[inline]
140+
pub fn statements_len(&self) -> usize {
141+
self.statements.len()
142+
}
143+
139144
#[inline]
140145
pub fn iter_statements(&self) -> impl Iterator<Item = &Statement> {
141146
self.statements.iter()
142147
}
143148

149+
#[inline]
150+
pub fn reverse_iter_statements(&self) -> impl Iterator<Item = &Statement> {
151+
self.statements.iter().rev()
152+
}
153+
144154
#[inline]
145155
pub fn get_last_statement(&self) -> Option<&LastStatement> {
146156
self.last_statement.as_ref()

src/nodes/statements/function.rs

+5
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ impl FunctionName {
114114
self.method.as_ref()
115115
}
116116

117+
#[inline]
118+
pub fn has_method(&self) -> bool {
119+
self.method.is_some()
120+
}
121+
117122
#[inline]
118123
pub fn get_name(&self) -> &Identifier {
119124
&self.name

src/process/processors/find_identifier.rs

+14-7
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use crate::{nodes::Identifier, process::NodeProcessor};
1111
/// # use darklua_core::nodes::Expression;
1212
/// # use darklua_core::process::processors::FindVariables;
1313
/// # use darklua_core::process::{DefaultVisitor, NodeProcessor, NodeVisitor};
14-
/// let variables = vec!["foo".to_owned()];
15-
/// let mut find_foo: FindVariables = variables.iter().collect();
14+
/// let variables = vec!["foo"];
15+
/// let mut find_foo: FindVariables = variables.into_iter().collect();
1616
///
1717
/// let mut foo_expression = Expression::identifier("foo");
1818
/// DefaultVisitor::visit_expression(&mut foo_expression, &mut find_foo);
@@ -25,27 +25,34 @@ use crate::{nodes::Identifier, process::NodeProcessor};
2525
/// # use darklua_core::nodes::Expression;
2626
/// # use darklua_core::process::processors::FindVariables;
2727
/// # use darklua_core::process::{DefaultVisitor, NodeProcessor, NodeVisitor};
28-
/// # let variables = vec!["foo".to_owned()];
29-
/// # let mut find_foo: FindVariables = variables.iter().collect();
28+
/// # let variables = vec!["foo"];
29+
/// # let mut find_foo: FindVariables = variables.into_iter().collect();
3030
/// let mut bar_expression = Expression::identifier("bar");
3131
/// DefaultVisitor::visit_expression(&mut bar_expression, &mut find_foo);
3232
///
3333
/// assert!(!find_foo.has_found_usage());
3434
/// ```
3535
pub struct FindVariables<'a> {
36-
variables: Vec<&'a String>,
36+
variables: Vec<&'a str>,
3737
usage_found: bool,
3838
}
3939

4040
impl<'a> FindVariables<'a> {
41+
pub fn new(variable: &'a str) -> Self {
42+
Self {
43+
variables: vec![variable],
44+
usage_found: false,
45+
}
46+
}
47+
4148
#[inline]
4249
pub fn has_found_usage(&self) -> bool {
4350
self.usage_found
4451
}
4552
}
4653

47-
impl<'a> FromIterator<&'a String> for FindVariables<'a> {
48-
fn from_iter<T: IntoIterator<Item = &'a String>>(iter: T) -> Self {
54+
impl<'a> FromIterator<&'a str> for FindVariables<'a> {
55+
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
4956
Self {
5057
variables: iter.into_iter().collect(),
5158
usage_found: false,

src/process/processors/find_usage.rs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::ops;
2+
3+
use crate::{
4+
nodes::Identifier,
5+
process::{IdentifierTracker, NodeProcessor},
6+
};
7+
8+
/// A processor to find usage of a given variable.
9+
pub(crate) struct FindUsage<'a> {
10+
variable: &'a str,
11+
usage_found: bool,
12+
identifier_tracker: IdentifierTracker,
13+
}
14+
15+
impl<'a> ops::Deref for FindUsage<'a> {
16+
type Target = IdentifierTracker;
17+
18+
fn deref(&self) -> &Self::Target {
19+
&self.identifier_tracker
20+
}
21+
}
22+
23+
impl<'a> ops::DerefMut for FindUsage<'a> {
24+
fn deref_mut(&mut self) -> &mut Self::Target {
25+
&mut self.identifier_tracker
26+
}
27+
}
28+
29+
impl<'a> FindUsage<'a> {
30+
pub fn new(variable: &'a str) -> Self {
31+
Self {
32+
variable,
33+
usage_found: false,
34+
identifier_tracker: Default::default(),
35+
}
36+
}
37+
38+
#[inline]
39+
pub fn has_found_usage(&self) -> bool {
40+
self.usage_found
41+
}
42+
}
43+
44+
impl<'a> NodeProcessor for FindUsage<'a> {
45+
fn process_variable_expression(&mut self, variable: &mut Identifier) {
46+
if !self.usage_found && variable.get_name() == self.variable {
47+
self.usage_found = !self.is_identifier_used(self.variable);
48+
}
49+
}
50+
}

src/process/processors/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! A collection of utility processors that can be used when creating rules.
22
33
mod find_identifier;
4+
mod find_usage;
45

56
pub use find_identifier::*;
7+
pub(crate) use find_usage::*;

src/process/scope_visitor.rs

+78-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub trait Scope {
1818
/// Called when entering a function block (with each parameters of the function), with the
1919
/// identifiers from a generic for statement or the identifier from a numeric for loop.
2020
fn insert(&mut self, identifier: &mut String);
21+
/// Called when entering a function defined with a method
22+
fn insert_self(&mut self);
2123
/// Called when a new local variable is initialized.
2224
fn insert_local(&mut self, identifier: &mut String, value: Option<&mut Expression>);
2325
/// Called when a new local function is initialized.
@@ -36,13 +38,7 @@ impl ScopeVisitor {
3638
.for_each(|statement| Self::visit_statement(statement, scope));
3739

3840
if let Some(last_statement) = block.mutate_last_statement() {
39-
scope.process_last_statement(last_statement);
40-
41-
if let LastStatement::Return(expressions) = last_statement {
42-
expressions
43-
.iter_mut_expressions()
44-
.for_each(|expression| Self::visit_expression(expression, scope));
45-
};
41+
Self::visit_last_statement(last_statement, scope);
4642
};
4743
}
4844
}
@@ -61,6 +57,13 @@ impl<T: NodeProcessor + Scope> NodeVisitor<T> for ScopeVisitor {
6157
.iter_mut_values()
6258
.for_each(|value| Self::visit_expression(value, scope));
6359

60+
for r#type in statement
61+
.iter_mut_variables()
62+
.filter_map(TypedIdentifier::mutate_type)
63+
{
64+
Self::visit_type(r#type, scope);
65+
}
66+
6467
statement.for_each_assignment(|variable, expression| {
6568
scope.insert_local(variable.mutate_name(), expression)
6669
});
@@ -69,6 +72,21 @@ impl<T: NodeProcessor + Scope> NodeVisitor<T> for ScopeVisitor {
6972
fn visit_function_expression(function: &mut FunctionExpression, scope: &mut T) {
7073
scope.process_function_expression(function);
7174

75+
for r#type in function
76+
.iter_mut_parameters()
77+
.filter_map(TypedIdentifier::mutate_type)
78+
{
79+
Self::visit_type(r#type, scope);
80+
}
81+
82+
if let Some(variadic_type) = function.mutate_variadic_type() {
83+
Self::visit_function_variadic_type(variadic_type, scope);
84+
}
85+
86+
if let Some(return_type) = function.mutate_return_type() {
87+
Self::visit_function_return_type(return_type, scope);
88+
}
89+
7290
scope.push();
7391
function
7492
.mutate_parameters()
@@ -83,7 +101,25 @@ impl<T: NodeProcessor + Scope> NodeVisitor<T> for ScopeVisitor {
83101
scope.process_function_statement(statement);
84102
scope.process_variable_expression(statement.mutate_function_name().mutate_identifier());
85103

104+
for r#type in statement
105+
.iter_mut_parameters()
106+
.filter_map(TypedIdentifier::mutate_type)
107+
{
108+
Self::visit_type(r#type, scope);
109+
}
110+
111+
if let Some(variadic_type) = statement.mutate_variadic_type() {
112+
Self::visit_function_variadic_type(variadic_type, scope);
113+
}
114+
115+
if let Some(return_type) = statement.mutate_return_type() {
116+
Self::visit_function_return_type(return_type, scope);
117+
}
118+
86119
scope.push();
120+
if statement.get_name().has_method() {
121+
scope.insert_self();
122+
}
87123
statement
88124
.mutate_parameters()
89125
.iter_mut()
@@ -98,6 +134,21 @@ impl<T: NodeProcessor + Scope> NodeVisitor<T> for ScopeVisitor {
98134

99135
scope.insert_local_function(statement);
100136

137+
for r#type in statement
138+
.iter_mut_parameters()
139+
.filter_map(TypedIdentifier::mutate_type)
140+
{
141+
Self::visit_type(r#type, scope);
142+
}
143+
144+
if let Some(variadic_type) = statement.mutate_variadic_type() {
145+
Self::visit_function_variadic_type(variadic_type, scope);
146+
}
147+
148+
if let Some(return_type) = statement.mutate_return_type() {
149+
Self::visit_function_return_type(return_type, scope);
150+
}
151+
101152
scope.push();
102153
statement
103154
.mutate_parameters()
@@ -119,6 +170,13 @@ impl<T: NodeProcessor + Scope> NodeVisitor<T> for ScopeVisitor {
119170
.iter_mut_identifiers()
120171
.for_each(|identifier| scope.insert(identifier.mutate_name()));
121172

173+
for r#type in statement
174+
.iter_mut_identifiers()
175+
.filter_map(TypedIdentifier::mutate_type)
176+
{
177+
Self::visit_type(r#type, scope);
178+
}
179+
122180
Self::visit_block(statement.mutate_block(), scope);
123181
}
124182

@@ -132,6 +190,10 @@ impl<T: NodeProcessor + Scope> NodeVisitor<T> for ScopeVisitor {
132190
Self::visit_expression(step, scope);
133191
};
134192

193+
if let Some(r#type) = statement.mutate_identifier().mutate_type() {
194+
Self::visit_type(r#type, scope);
195+
}
196+
135197
scope.push();
136198
scope.insert(statement.mutate_identifier().mutate_name());
137199

@@ -220,6 +282,10 @@ impl Scope for IdentifierTracker {
220282
self.insert_identifier(identifier);
221283
}
222284

285+
fn insert_self(&mut self) {
286+
self.insert_identifier("self");
287+
}
288+
223289
fn insert_local(&mut self, identifier: &mut String, _value: Option<&mut Expression>) {
224290
self.insert_identifier(identifier);
225291
}
@@ -250,6 +316,11 @@ where
250316
self.deref_mut().insert(identifier);
251317
}
252318

319+
#[inline]
320+
fn insert_self(&mut self) {
321+
self.deref_mut().insert_self();
322+
}
323+
253324
#[inline]
254325
fn insert_local(&mut self, identifier: &mut String, value: Option<&mut Expression>) {
255326
self.deref_mut().insert_local(identifier, value)

0 commit comments

Comments
 (0)