Skip to content

Commit 6b39654

Browse files
authored
feat(linter/tree-shaking): support options (#3504)
1 parent 568c9c5 commit 6b39654

File tree

4 files changed

+353
-66
lines changed

4 files changed

+353
-66
lines changed

crates/oxc_linter/src/rules/tree_shaking/no_side_effects_in_initialization/listener_map.rs

+66-59
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,20 @@ use oxc_ast::{
1515
},
1616
AstKind,
1717
};
18-
use oxc_semantic::{AstNode, SymbolId};
18+
use oxc_semantic::{AstNode, AstNodeId};
1919
use oxc_span::{GetSpan, Span};
2020
use oxc_syntax::operator::{LogicalOperator, UnaryOperator};
21-
use rustc_hash::FxHashSet;
22-
use std::{cell::Cell, cell::RefCell};
2321

2422
use crate::{
2523
ast_util::{get_declaration_of_variable, get_symbol_id_of_variable},
2624
utils::{
2725
calculate_binary_operation, calculate_logical_operation, calculate_unary_operation,
28-
get_write_expr, has_comment_about_side_effect_check, has_pure_notation, is_pure_function,
29-
no_effects, FunctionName, Value,
26+
get_write_expr, has_comment_about_side_effect_check, has_pure_notation,
27+
is_function_side_effect_free, is_local_variable_a_whitelisted_module, is_pure_function,
28+
no_effects, FunctionName, NodeListenerOptions, Value,
3029
},
31-
LintContext,
3230
};
3331

34-
pub struct NodeListenerOptions<'a, 'b> {
35-
checked_mutated_nodes: RefCell<FxHashSet<SymbolId>>,
36-
ctx: &'b LintContext<'a>,
37-
has_valid_this: Cell<bool>,
38-
called_with_new: Cell<bool>,
39-
}
40-
41-
impl<'a, 'b> NodeListenerOptions<'a, 'b> {
42-
fn insert_mutated_node(&self, symbol_id: SymbolId) -> bool {
43-
self.checked_mutated_nodes.borrow_mut().insert(symbol_id)
44-
}
45-
}
46-
47-
impl<'a, 'b> NodeListenerOptions<'a, 'b> {
48-
pub fn new(ctx: &'b LintContext<'a>) -> Self {
49-
Self {
50-
checked_mutated_nodes: RefCell::new(FxHashSet::default()),
51-
ctx,
52-
has_valid_this: Cell::new(false),
53-
called_with_new: Cell::new(false),
54-
}
55-
}
56-
}
57-
5832
pub trait ListenerMap {
5933
fn report_effects(&self, _options: &NodeListenerOptions) {}
6034
fn report_effects_when_assigned(&self, _options: &NodeListenerOptions) {}
@@ -276,21 +250,28 @@ impl<'a> ListenerMap for AstNode<'a> {
276250
class.report_effects_when_called(options);
277251
}
278252
AstKind::ImportDefaultSpecifier(specifier) => {
279-
if !has_comment_about_side_effect_check(specifier.span, options.ctx) {
280-
options.ctx.diagnostic(super::call_import(specifier.span));
281-
}
253+
report_on_imported_call(
254+
specifier.local.span,
255+
&specifier.local.name,
256+
self.id(),
257+
options,
258+
);
282259
}
283260
AstKind::ImportSpecifier(specifier) => {
284-
let span = specifier.local.span;
285-
if !has_comment_about_side_effect_check(span, options.ctx) {
286-
options.ctx.diagnostic(super::call_import(span));
287-
}
261+
report_on_imported_call(
262+
specifier.local.span,
263+
&specifier.local.name,
264+
self.id(),
265+
options,
266+
);
288267
}
289268
AstKind::ImportNamespaceSpecifier(specifier) => {
290-
let span = specifier.local.span;
291-
if !has_comment_about_side_effect_check(span, options.ctx) {
292-
options.ctx.diagnostic(super::call_import(span));
293-
}
269+
report_on_imported_call(
270+
specifier.local.span,
271+
&specifier.local.name,
272+
self.id(),
273+
options,
274+
);
294275
}
295276
_ => {}
296277
}
@@ -324,6 +305,24 @@ impl<'a> ListenerMap for AstNode<'a> {
324305
}
325306
}
326307

308+
fn report_on_imported_call(
309+
span: Span,
310+
name: &str,
311+
node_id: AstNodeId,
312+
options: &NodeListenerOptions,
313+
) {
314+
if has_comment_about_side_effect_check(span, options.ctx) {
315+
return;
316+
}
317+
let Some(AstKind::ImportDeclaration(decl)) = options.ctx.nodes().parent_kind(node_id) else {
318+
return;
319+
};
320+
if is_function_side_effect_free(name, &decl.source.value, options) {
321+
return;
322+
}
323+
options.ctx.diagnostic(super::call_import(span));
324+
}
325+
327326
impl<'a> ListenerMap for Declaration<'a> {
328327
fn report_effects(&self, options: &NodeListenerOptions) {
329328
match self {
@@ -1053,11 +1052,7 @@ impl<'a> ListenerMap for CallExpression<'a> {
10531052
let ctx = options.ctx;
10541053
if let Expression::Identifier(ident) = &self.callee {
10551054
if let Some(node) = get_declaration_of_variable(ident, ctx) {
1056-
let Some(parent) = ctx.nodes().parent_kind(node.id()) else {
1057-
return;
1058-
};
1059-
// TODO: `isLocalVariableAWhitelistedModule`
1060-
if matches!(parent, AstKind::ImportDeclaration(_)) {
1055+
if is_local_variable_a_whitelisted_module(node, ident.name.as_str(), options) {
10611056
return;
10621057
}
10631058
options.ctx.diagnostic(super::call_return_value(self.span));
@@ -1120,7 +1115,7 @@ impl<'a> ListenerMap for IdentifierReference<'a> {
11201115
}
11211116

11221117
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
1123-
if is_pure_function(&FunctionName::Identifier(self), options.ctx) {
1118+
if is_pure_function(&FunctionName::Identifier(self), options) {
11241119
return;
11251120
}
11261121

@@ -1249,31 +1244,43 @@ impl<'a> ListenerMap for StaticMemberExpression<'a> {
12491244
fn report_effects_when_called(&self, options: &NodeListenerOptions) {
12501245
self.report_effects(options);
12511246

1252-
let mut node = &self.object;
1247+
let mut root_member_expr = &self.object;
12531248
loop {
1254-
match node {
1249+
match root_member_expr {
12551250
Expression::ComputedMemberExpression(expr) => {
1256-
node = &expr.object;
1251+
root_member_expr = &expr.object;
12571252
}
1258-
Expression::StaticMemberExpression(expr) => node = &expr.object,
1259-
Expression::PrivateInExpression(expr) => node = &expr.right,
1253+
Expression::StaticMemberExpression(expr) => root_member_expr = &expr.object,
1254+
Expression::PrivateInExpression(expr) => root_member_expr = &expr.right,
12601255
_ => {
12611256
break;
12621257
}
12631258
}
12641259
}
12651260

1266-
let Expression::Identifier(ident) = node else {
1267-
options.ctx.diagnostic(super::call_member(node.span()));
1261+
let Expression::Identifier(ident) = root_member_expr else {
1262+
options.ctx.diagnostic(super::call_member(root_member_expr.span()));
12681263
return;
12691264
};
12701265

1271-
if get_declaration_of_variable(ident, options.ctx)
1272-
.is_some_and(|_| !has_pure_notation(self.span, options.ctx))
1273-
|| !is_pure_function(&FunctionName::StaticMemberExpr(self), options.ctx)
1274-
{
1275-
options.ctx.diagnostic(super::call_member(self.span));
1266+
let Some(node) = get_declaration_of_variable(ident, options.ctx) else {
1267+
// If the variable is not declared, it is a global variable.
1268+
// `ext.x()`
1269+
if !is_pure_function(&FunctionName::StaticMemberExpr(self), options) {
1270+
options.ctx.diagnostic(super::call_member(self.span));
1271+
}
1272+
return;
1273+
};
1274+
1275+
if is_local_variable_a_whitelisted_module(node, &ident.name, options) {
1276+
return;
1277+
};
1278+
1279+
if has_pure_notation(self.span, options.ctx) {
1280+
return;
12761281
}
1282+
1283+
options.ctx.diagnostic(super::call_member(self.span));
12771284
}
12781285
fn report_effects_when_assigned(&self, options: &NodeListenerOptions) {
12791286
self.report_effects(options);

0 commit comments

Comments
 (0)