Skip to content

Commit 914ef72

Browse files
committed
refactor: fix failed test
1 parent 35e0f92 commit 914ef72

File tree

2 files changed

+318
-105
lines changed

2 files changed

+318
-105
lines changed

crates/oxc_linter/src/rules/unicorn/prefer_default_parameters.rs

Lines changed: 80 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -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
8167
fn 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

132174
fn 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

195225
fn 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>(
291305
impl 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

Comments
 (0)