@@ -64,37 +64,20 @@ declare_oxc_lint!(
6464 pending,
6565) ;
6666
67- fn is_literal ( expr : & Expression ) -> bool {
68- matches ! (
69- expr. without_parentheses( ) ,
70- Expression :: StringLiteral ( _)
71- | Expression :: NumericLiteral ( _)
72- | Expression :: BooleanLiteral ( _)
73- | Expression :: NullLiteral ( _)
74- | Expression :: BigIntLiteral ( _)
75- | Expression :: RegExpLiteral ( _)
76- | Expression :: TemplateLiteral ( _)
77- )
78- }
79-
80- /// Find enclosing function and function body node IDs
8167fn find_enclosing_function < ' a > (
8268 ctx : & LintContext < ' a > ,
8369 node : & AstNode < ' a > ,
8470) -> Option < ( NodeId , NodeId ) > {
8571 let mut current = ctx. nodes ( ) . parent_node ( node. id ( ) ) ;
8672
87- // First, find the FunctionBody
8873 while !matches ! ( current. kind( ) , AstKind :: FunctionBody ( _) ) {
89- // Check if we've reached the root
9074 if matches ! ( current. kind( ) , AstKind :: Program ( _) ) {
9175 return None ;
9276 }
9377 current = ctx. nodes ( ) . parent_node ( current. id ( ) ) ;
9478 }
9579 let function_body_id = current. id ( ) ;
9680
97- // Then find the Function or ArrowFunctionExpression
9881 current = ctx. nodes ( ) . parent_node ( current. id ( ) ) ;
9982 if matches ! ( current. kind( ) , AstKind :: Function ( _) | AstKind :: ArrowFunctionExpression ( _) ) {
10083 Some ( ( current. id ( ) , function_body_id) )
@@ -110,31 +93,89 @@ fn get_param_name<'a>(param: &FormalParameter<'a>) -> Option<&'a str> {
11093 }
11194}
11295
113- fn is_first_statement < ' a > (
96+ fn has_no_side_effects_before < ' a > (
11497 ctx : & LintContext < ' a > ,
11598 node : & AstNode < ' a > ,
11699 function_body_id : NodeId ,
100+ param_name : & str ,
117101) -> bool {
118102 let function_body_node = ctx. nodes ( ) . get_node ( function_body_id) ;
119103 let AstKind :: FunctionBody ( body) = function_body_node. kind ( ) else {
120104 return false ;
121105 } ;
122106
123- // Get the first statement in the function body
124- let Some ( first_stmt) = body. statements . first ( ) else {
125- return false ;
126- } ;
107+ let node_span = node. kind ( ) . span ( ) ;
108+
109+ for stmt in & body. statements {
110+ if stmt. span ( ) == node_span {
111+ return true ;
112+ }
113+
114+ if !is_side_effect_free_statement ( stmt, param_name) {
115+ return false ;
116+ }
117+ }
118+
119+ false
120+ }
121+
122+ fn is_side_effect_free_statement ( stmt : & oxc_ast:: ast:: Statement , param_name : & str ) -> bool {
123+ use oxc_ast:: ast:: Statement ;
127124
128- // The node's span should match the first statement's span
129- first_stmt. span ( ) == node. kind ( ) . span ( )
125+ match stmt {
126+ Statement :: VariableDeclaration ( var_decl) => var_decl. declarations . iter ( ) . all ( |decl| {
127+ if let Some ( init) = & decl. init {
128+ is_side_effect_free_expression ( init, param_name)
129+ } else {
130+ true
131+ }
132+ } ) ,
133+ Statement :: ExpressionStatement ( expr_stmt) => {
134+ is_side_effect_free_expression ( & expr_stmt. expression , param_name)
135+ }
136+ Statement :: FunctionDeclaration ( _) => true ,
137+ _ => false ,
138+ }
139+ }
140+
141+ fn is_side_effect_free_expression ( expr : & Expression , param_name : & str ) -> bool {
142+ match expr. without_parentheses ( ) {
143+ Expression :: NumericLiteral ( _)
144+ | Expression :: StringLiteral ( _)
145+ | Expression :: BooleanLiteral ( _)
146+ | Expression :: NullLiteral ( _)
147+ | Expression :: BigIntLiteral ( _)
148+ | Expression :: RegExpLiteral ( _)
149+ | Expression :: TemplateLiteral ( _)
150+ | Expression :: FunctionExpression ( _)
151+ | Expression :: ArrowFunctionExpression ( _) => true ,
152+ Expression :: Identifier ( ident) => ident. name . as_str ( ) != param_name,
153+ Expression :: AssignmentExpression ( assign) => {
154+ let target_ok = match & assign. left {
155+ AssignmentTarget :: AssignmentTargetIdentifier ( ident) => {
156+ ident. name . as_str ( ) != param_name
157+ }
158+ _ => false ,
159+ } ;
160+ target_ok && is_side_effect_free_expression ( & assign. right , param_name)
161+ }
162+ Expression :: BinaryExpression ( bin) => {
163+ is_side_effect_free_expression ( & bin. left , param_name)
164+ && is_side_effect_free_expression ( & bin. right , param_name)
165+ }
166+ Expression :: UnaryExpression ( unary) => {
167+ !matches ! ( unary. operator, oxc_ast:: ast:: UnaryOperator :: Delete )
168+ && is_side_effect_free_expression ( & unary. argument , param_name)
169+ }
170+ _ => false ,
171+ }
130172}
131173
132174fn check_no_extra_references < ' a > (
133175 ctx : & LintContext < ' a > ,
134176 param_ident_span : Span ,
135177 param : & FormalParameter < ' a > ,
136178) -> bool {
137- // Get the symbol for the parameter
138179 let BindingPatternKind :: BindingIdentifier ( binding_ident) = & param. pattern . kind else {
139180 return false ;
140181 } ;
@@ -143,16 +184,12 @@ fn check_no_extra_references<'a>(
143184 return false ;
144185 } ;
145186
146- // Check how many times the parameter is referenced
147187 let references: Vec < _ > = ctx. scoping ( ) . get_resolved_references ( symbol_id) . collect ( ) ;
148188
149- // For `const bar = foo || 'bar'`, the parameter should only be referenced once
150- // (in the LogicalExpression)
151189 if references. len ( ) != 1 {
152190 return false ;
153191 }
154192
155- // The single reference should be the one in the LogicalExpression
156193 let reference = & references[ 0 ] ;
157194 ctx. semantic ( ) . reference_span ( reference) == param_ident_span
158195}
@@ -162,7 +199,6 @@ fn check_no_extra_references_assignment<'a>(
162199 param_ident_span : Span ,
163200 param : & FormalParameter < ' a > ,
164201) -> bool {
165- // Get the symbol for the parameter
166202 let BindingPatternKind :: BindingIdentifier ( binding_ident) = & param. pattern . kind else {
167203 return false ;
168204 } ;
@@ -171,25 +207,19 @@ fn check_no_extra_references_assignment<'a>(
171207 return false ;
172208 } ;
173209
174- // For assignment `foo = foo || 'bar'`, the parameter should have exactly 2 references:
175- // 1. The read in the LogicalExpression (foo || 'bar')
176- // 2. The write in the AssignmentExpression (foo = ...)
177- let references: Vec < _ > = ctx. scoping ( ) . get_resolved_references ( symbol_id) . collect ( ) ;
178-
179- if references. len ( ) != 2 {
180- return false ;
181- }
182-
183- // One should be a read (in logical expr), one should be a write (assignment target)
184- let reads: Vec < _ > = references. iter ( ) . filter ( |r| !r. is_write ( ) ) . collect ( ) ;
185- let write_count = references. iter ( ) . filter ( |r| r. is_write ( ) ) . count ( ) ;
186-
187- if reads. len ( ) != 1 || write_count != 1 {
188- return false ;
189- }
210+ let ( has_matching_read, writes) = ctx. scoping ( ) . get_resolved_references ( symbol_id) . fold (
211+ ( false , 0usize ) ,
212+ |( has_matching_read, writes) , r| {
213+ if r. is_write ( ) {
214+ ( has_matching_read, writes + 1 )
215+ } else {
216+ let is_matching = ctx. semantic ( ) . reference_span ( r) == param_ident_span;
217+ ( has_matching_read || is_matching, writes)
218+ }
219+ } ,
220+ ) ;
190221
191- // The read should be at the same span as param_ident
192- ctx. semantic ( ) . reference_span ( reads[ 0 ] ) == param_ident_span
222+ writes == 1 && has_matching_read
193223}
194224
195225fn check_expression < ' a > (
@@ -200,7 +230,6 @@ fn check_expression<'a>(
200230 is_assignment : bool ,
201231 stmt_span : Span ,
202232) {
203- // Right must be a LogicalExpression with || or ??
204233 let Expression :: LogicalExpression ( logical_expr) = right. without_parentheses ( ) else {
205234 return ;
206235 } ;
@@ -209,77 +238,62 @@ fn check_expression<'a>(
209238 return ;
210239 }
211240
212- // Left side of logical expression must be an identifier
213241 let Expression :: Identifier ( param_ident) = logical_expr. left . without_parentheses ( ) else {
214242 return ;
215243 } ;
216244
217245 let param_name = param_ident. name . as_str ( ) ;
218246
219- // Right side of logical expression must be a literal
220- if !is_literal ( & logical_expr. right ) {
247+ if !& logical_expr. right . get_inner_expression ( ) . is_literal ( ) {
221248 return ;
222249 }
223250
224- // For assignment (foo = foo || 'bar'), the left side must match the parameter
225251 if is_assignment && left_name != param_name {
226252 return ;
227253 }
228254
229- // Find the enclosing function
230255 let Some ( ( function_id, function_body_id) ) = find_enclosing_function ( ctx, node) else {
231256 return ;
232257 } ;
233258
234- // Get the function parameters
235259 let function_node = ctx. nodes ( ) . get_node ( function_id) ;
236260 let params = match function_node. kind ( ) {
237261 AstKind :: Function ( func) => & func. params ,
238262 AstKind :: ArrowFunctionExpression ( arrow) => & arrow. params ,
239263 _ => return ,
240264 } ;
241265
242- // Find the parameter that matches param_name
243266 let Some ( ( param_index, param) ) =
244267 params. items . iter ( ) . enumerate ( ) . find ( |( _, p) | get_param_name ( p) == Some ( param_name) )
245268 else {
246269 return ;
247270 } ;
248271
249- // Parameter must be the last parameter (no parameters after it)
250272 if param_index != params. items . len ( ) - 1 {
251273 return ;
252274 }
253275
254- // Parameter must not have a default value already
255276 if matches ! ( param. pattern. kind, BindingPatternKind :: AssignmentPattern ( _) ) {
256277 return ;
257278 }
258279
259- // If there's a rest parameter, the last item parameter is not actually last
260280 if params. rest . is_some ( ) {
261281 return ;
262282 }
263283
264- // Parameter must be a simple identifier (not destructuring or rest)
265284 if !matches ! ( param. pattern. kind, BindingPatternKind :: BindingIdentifier ( _) ) {
266285 return ;
267286 }
268287
269- // Check that this statement is the first in the function body
270- if !is_first_statement ( ctx, node, function_body_id) {
288+ if !has_no_side_effects_before ( ctx, node, function_body_id, param_name) {
271289 return ;
272290 }
273291
274- // Check references based on assignment type
275292 if is_assignment {
276- // For assignment, check that the parameter is not referenced before this assignment
277- // and not referenced elsewhere except in this assignment
278293 if !check_no_extra_references_assignment ( ctx, param_ident. span , param) {
279294 return ;
280295 }
281296 } else {
282- // For non-assignment (const bar = foo || 'bar'), check that bar is not used elsewhere with foo
283297 if !check_no_extra_references ( ctx, param_ident. span , param) {
284298 return ;
285299 }
@@ -291,7 +305,6 @@ fn check_expression<'a>(
291305impl Rule for PreferDefaultParameters {
292306 fn run < ' a > ( & self , node : & AstNode < ' a > , ctx : & LintContext < ' a > ) {
293307 match node. kind ( ) {
294- // Handle: foo = foo || 'bar'
295308 AstKind :: ExpressionStatement ( expr_stmt) => {
296309 if let Expression :: AssignmentExpression ( assign_expr) = & expr_stmt. expression {
297310 if assign_expr. operator != AssignmentOperator :: Assign {
@@ -311,7 +324,6 @@ impl Rule for PreferDefaultParameters {
311324 }
312325 }
313326 }
314- // Handle: const bar = foo || 'bar'
315327 AstKind :: VariableDeclaration ( var_decl) => {
316328 if var_decl. declarations . len ( ) != 1 {
317329 return ;
0 commit comments