@@ -15,46 +15,20 @@ use oxc_ast::{
15
15
} ,
16
16
AstKind ,
17
17
} ;
18
- use oxc_semantic:: { AstNode , SymbolId } ;
18
+ use oxc_semantic:: { AstNode , AstNodeId } ;
19
19
use oxc_span:: { GetSpan , Span } ;
20
20
use oxc_syntax:: operator:: { LogicalOperator , UnaryOperator } ;
21
- use rustc_hash:: FxHashSet ;
22
- use std:: { cell:: Cell , cell:: RefCell } ;
23
21
24
22
use crate :: {
25
23
ast_util:: { get_declaration_of_variable, get_symbol_id_of_variable} ,
26
24
utils:: {
27
25
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 ,
30
29
} ,
31
- LintContext ,
32
30
} ;
33
31
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
-
58
32
pub trait ListenerMap {
59
33
fn report_effects ( & self , _options : & NodeListenerOptions ) { }
60
34
fn report_effects_when_assigned ( & self , _options : & NodeListenerOptions ) { }
@@ -276,21 +250,28 @@ impl<'a> ListenerMap for AstNode<'a> {
276
250
class. report_effects_when_called ( options) ;
277
251
}
278
252
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
+ ) ;
282
259
}
283
260
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
+ ) ;
288
267
}
289
268
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
+ ) ;
294
275
}
295
276
_ => { }
296
277
}
@@ -324,6 +305,24 @@ impl<'a> ListenerMap for AstNode<'a> {
324
305
}
325
306
}
326
307
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
+
327
326
impl < ' a > ListenerMap for Declaration < ' a > {
328
327
fn report_effects ( & self , options : & NodeListenerOptions ) {
329
328
match self {
@@ -1053,11 +1052,7 @@ impl<'a> ListenerMap for CallExpression<'a> {
1053
1052
let ctx = options. ctx ;
1054
1053
if let Expression :: Identifier ( ident) = & self . callee {
1055
1054
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) {
1061
1056
return ;
1062
1057
}
1063
1058
options. ctx . diagnostic ( super :: call_return_value ( self . span ) ) ;
@@ -1120,7 +1115,7 @@ impl<'a> ListenerMap for IdentifierReference<'a> {
1120
1115
}
1121
1116
1122
1117
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) {
1124
1119
return ;
1125
1120
}
1126
1121
@@ -1249,31 +1244,43 @@ impl<'a> ListenerMap for StaticMemberExpression<'a> {
1249
1244
fn report_effects_when_called ( & self , options : & NodeListenerOptions ) {
1250
1245
self . report_effects ( options) ;
1251
1246
1252
- let mut node = & self . object ;
1247
+ let mut root_member_expr = & self . object ;
1253
1248
loop {
1254
- match node {
1249
+ match root_member_expr {
1255
1250
Expression :: ComputedMemberExpression ( expr) => {
1256
- node = & expr. object ;
1251
+ root_member_expr = & expr. object ;
1257
1252
}
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 ,
1260
1255
_ => {
1261
1256
break ;
1262
1257
}
1263
1258
}
1264
1259
}
1265
1260
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 ( ) ) ) ;
1268
1263
return ;
1269
1264
} ;
1270
1265
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 ;
1276
1281
}
1282
+
1283
+ options. ctx . diagnostic ( super :: call_member ( self . span ) ) ;
1277
1284
}
1278
1285
fn report_effects_when_assigned ( & self , options : & NodeListenerOptions ) {
1279
1286
self . report_effects ( options) ;
0 commit comments