@@ -15,46 +15,20 @@ use oxc_ast::{
1515 } ,
1616 AstKind ,
1717} ;
18- use oxc_semantic:: { AstNode , SymbolId } ;
18+ use oxc_semantic:: { AstNode , AstNodeId } ;
1919use oxc_span:: { GetSpan , Span } ;
2020use oxc_syntax:: operator:: { LogicalOperator , UnaryOperator } ;
21- use rustc_hash:: FxHashSet ;
22- use std:: { cell:: Cell , cell:: RefCell } ;
2321
2422use 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-
5832pub 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+
327326impl < ' 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