-
-
Notifications
You must be signed in to change notification settings - Fork 474
/
allowed.rs
301 lines (271 loc) · 11.6 KB
/
allowed.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
//! This module checks if an unused variable is allowed. Note that this does not
//! consider variables ignored by name pattern, but by where they are declared.
#[allow(clippy::wildcard_imports)]
use oxc_ast::{ast::*, AstKind};
use oxc_semantic::{AstNode, AstNodeId, Semantic};
use oxc_span::GetSpan;
use crate::rules::eslint::no_unused_vars::binding_pattern::{BindingContext, HasAnyUsedBinding};
use super::{options::ArgsOption, NoUnusedVars, Symbol};
impl<'s, 'a> Symbol<'s, 'a> {
/// Returns `true` if this function is use.
///
/// Checks for these cases
/// 1. passed as a callback to another [`CallExpression`] or [`NewExpression`]
/// 2. invoked as an IIFE
/// 3. Returned from another function
/// 4. Used as an attribute in a JSX element
#[inline]
pub fn is_function_or_class_declaration_used(&self) -> bool {
#[cfg(debug_assertions)]
{
let kind = self.declaration().kind();
assert!(kind.is_function_like() || matches!(kind, AstKind::Class(_)));
}
for parent in self.iter_parents() {
match parent.kind() {
AstKind::MemberExpression(_) | AstKind::ParenthesizedExpression(_) => {
continue;
}
// Returned from another function. Definitely won't be the same
// function because we're walking up from its declaration
AstKind::ReturnStatement(_)
// <Component onClick={function onClick(e) { }} />
| AstKind::JSXExpressionContainer(_)
// Function declaration is passed as an argument to another function.
| AstKind::CallExpression(_) | AstKind::Argument(_)
// e.g. `const x = { foo: function foo() {} }`
| AstKind::ObjectProperty(_)
// e.g. var foo = function bar() { }
// we don't want to check for violations on `bar`, just `foo`
| AstKind::VariableDeclarator(_)
// new (class CustomRenderer{})
// new (function() {})
| AstKind::NewExpression(_)
=> {
return true;
}
// !function() {}; is an IIFE
AstKind::UnaryExpression(expr) => return expr.operator.is_not(),
// function is used as a value for an assignment
// e.g. Array.prototype.sort ||= function sort(a, b) { }
AstKind::AssignmentExpression(assignment) if assignment.right.span().contains_inclusive(self.span()) => {
return self != &assignment.left;
}
AstKind::ExpressionStatement(_) => {
// implicit return in arrow function expression
let Some(AstKind::FunctionBody(body)) = self.nodes().parent_kind(parent.id()) else {
return false;
};
return body.span.contains_inclusive(self.span()) && body.statements.len() == 1 && !self.get_snippet(body.span).starts_with('{')
}
_ => {
parent.kind().debug_name();
return false;
}
}
}
false
}
fn is_declared_in_for_of_loop(&self) -> bool {
for parent in self.iter_parents() {
match parent.kind() {
AstKind::ParenthesizedExpression(_)
| AstKind::VariableDeclaration(_)
| AstKind::BindingIdentifier(_)
| AstKind::SimpleAssignmentTarget(_)
| AstKind::AssignmentTarget(_) => continue,
AstKind::ForInStatement(ForInStatement { body, .. })
| AstKind::ForOfStatement(ForOfStatement { body, .. }) => match body {
Statement::ReturnStatement(_) => return true,
Statement::BlockStatement(b) => {
return b
.body
.first()
.is_some_and(|s| matches!(s, Statement::ReturnStatement(_)))
}
_ => return false,
},
_ => return false,
}
}
false
}
pub fn is_in_declared_module(&self) -> bool {
let scopes = self.scopes();
let nodes = self.nodes();
scopes.ancestors(self.scope_id())
.map(|scope_id| scopes.get_node_id(scope_id))
.map(|node_id| nodes.get_node(node_id))
.any(|node| matches!(node.kind(), AstKind::TSModuleDeclaration(namespace) if is_ambient_namespace(namespace)))
}
}
#[inline]
fn is_ambient_namespace(namespace: &TSModuleDeclaration) -> bool {
namespace.declare || namespace.kind.is_global()
}
impl NoUnusedVars {
#[allow(clippy::unused_self)]
pub(super) fn is_allowed_class_or_function(&self, symbol: &Symbol<'_, '_>) -> bool {
symbol.is_function_or_class_declaration_used()
// || symbol.is_function_or_class_assigned_to_same_name_variable()
}
#[allow(clippy::unused_self)]
pub(super) fn is_allowed_ts_namespace<'a>(
&self,
symbol: &Symbol<'_, 'a>,
namespace: &TSModuleDeclaration<'a>,
) -> bool {
if is_ambient_namespace(namespace) {
return true;
}
symbol.is_in_declared_module()
}
/// Returns `true` if this unused variable declaration should be allowed
/// (i.e. not reported)
pub(super) fn is_allowed_variable_declaration<'a>(
&self,
symbol: &Symbol<'_, 'a>,
decl: &VariableDeclarator<'a>,
) -> bool {
if decl.kind.is_var() && self.vars.is_local() && symbol.is_root() {
return true;
}
// allow unused iterators, since they're required for valid syntax
if symbol.is_declared_in_for_of_loop() {
return true;
}
false
}
#[allow(clippy::unused_self)]
pub(super) fn is_allowed_type_parameter(
&self,
symbol: &Symbol<'_, '_>,
declaration_id: AstNodeId,
) -> bool {
matches!(symbol.nodes().parent_kind(declaration_id), Some(AstKind::TSMappedType(_)))
}
/// Returns `true` if this unused parameter should be allowed (i.e. not
/// reported)
pub(super) fn is_allowed_argument<'a>(
&self,
semantic: &Semantic<'a>,
symbol: &Symbol<'_, 'a>,
param: &FormalParameter<'a>,
) -> bool {
// early short-circuit when no argument checking should be performed
if self.args.is_none() {
return true;
}
// find FormalParameters. Should be the next parent of param, but this
// is safer.
let Some((params, params_id)) = symbol.iter_parents().find_map(|p| {
if let AstKind::FormalParameters(params) = p.kind() {
Some((params, p.id()))
} else {
None
}
}) else {
debug_assert!(false, "FormalParameter should always have a parent FormalParameters");
return false;
};
if Self::is_allowed_param_because_of_method(semantic, param, params_id) {
return true;
}
// Parameters are always checked. Must be done after above checks,
// because in those cases a parameter is required. However, even if
// `args` is `all`, it may be ignored using `ignoreRestSiblings` or `destructuredArrayIgnorePattern`.
if self.args.is_all() {
return false;
}
debug_assert_eq!(self.args, ArgsOption::AfterUsed);
// from eslint rule documentation:
// after-used - unused positional arguments that occur before the last
// used argument will not be checked, but all named arguments and all
// positional arguments after the last used argument will be checked.
// unused non-positional arguments are never allowed
if param.pattern.kind.is_destructuring_pattern() {
return false;
}
// find the index of the parameter in the parameters list. We want to
// check all parameters after this one for usages.
let position =
params.items.iter().enumerate().find(|(_, p)| p.span == param.span).map(|(i, _)| i);
debug_assert!(
position.is_some(),
"could not find FormalParameter in a FormalParameters node that is its parent."
);
let Some(position) = position else {
return false;
};
// This is the last parameter, so need to check for usages on following parameters
if position == params.items.len() - 1 {
return false;
}
let ctx = BindingContext { options: self, semantic };
params
.items
.iter()
.skip(position + 1)
// has_modifier() to handle:
// constructor(unused: number, public property: string) {}
// no need to check if param is in a constructor, because if it's
// not that's a parse error.
.any(|p| p.has_modifier() || p.pattern.has_any_used_binding(ctx))
}
/// `params_id` is the [`AstNodeId`] to a [`AstKind::FormalParameters`] node.
///
/// The following allowed conditions are handled:
/// 1. setter parameters - removing them causes a syntax error.
/// 2. TS constructor property definitions - they declare class members.
fn is_allowed_param_because_of_method<'a>(
semantic: &Semantic<'a>,
param: &FormalParameter<'a>,
params_id: AstNodeId,
) -> bool {
let mut parents_iter = semantic.nodes().iter_parents(params_id).skip(1).map(AstNode::kind);
// in function declarations, the parent immediately before the
// FormalParameters is a TSDeclareBlock
let Some(parent) = parents_iter.next() else {
return false;
};
if matches!(parent, AstKind::Function(f) if f.r#type == FunctionType::TSDeclareFunction) {
return true;
}
// for non-overloads, the next parent will be the function
let Some(maybe_method_or_fn) = parents_iter.next() else {
return false;
};
match maybe_method_or_fn {
// arguments inside setters are allowed. Without them, the program
// has invalid syntax
AstKind::MethodDefinition(MethodDefinition {
kind: MethodDefinitionKind::Set, ..
})
| AstKind::ObjectProperty(ObjectProperty { kind: PropertyKind::Set, .. }) => true,
// Allow unused parameters in function overloads
AstKind::Function(f)
if f.body.is_none() || f.r#type == FunctionType::TSDeclareFunction =>
{
true
}
// Allow unused parameters in method overloads and overrides
AstKind::MethodDefinition(method)
if method.value.r#type == FunctionType::TSEmptyBodyFunctionExpression
|| method.r#override =>
{
true
}
// constructor property definitions are allowed because they declare
// class members
// e.g. `class Foo { constructor(public a) {} }`
AstKind::MethodDefinition(method) if method.kind.is_constructor() => {
param.has_modifier()
}
// parameters in abstract methods will never be directly used b/c
// abstract methods have no bodies. However, since this establishes
// an API contract and gets used by subclasses, it is allowed.
AstKind::MethodDefinition(method) if method.r#type.is_abstract() => true,
_ => false,
}
}
}