fix(linter/unicorn/prefer-default-parameters): report violation for ??= and ||= (#26607)

## Summary

`unicorn/prefer-default-parameters` already reports `value = value ??
false` and `value = value || false`, but skipped the equivalent logical
assignment forms.

This treats `value ??= false` and `value ||= false` the same way,
matching `eslint-plugin-unicorn`.

Fixes #26596

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
This commit is contained in:
Bartok
2026-09-13 23:39:26 -04:00
committed by GitHub
parent 2bed25a993
commit aaff7583a6
2 changed files with 178 additions and 16 deletions
@@ -70,9 +70,6 @@ impl Rule for PreferDefaultParameters {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
match node.kind() {
AstKind::AssignmentExpression(assign_expr) => {
if assign_expr.operator != AssignmentOperator::Assign {
return;
}
if let AssignmentTarget::AssignmentTargetIdentifier(left_ident) = &assign_expr.left
{
let statement_span = ctx
@@ -81,15 +78,31 @@ impl Rule for PreferDefaultParameters {
.kind()
.as_expression_statement()
.map(|stmt| stmt.span);
check_expression(
ctx,
node,
&left_ident.name,
&assign_expr.right,
true,
assign_expr.span,
statement_span,
);
match assign_expr.operator {
AssignmentOperator::Assign => {
check_expression(
ctx,
node,
&left_ident.name,
&assign_expr.right,
true,
assign_expr.span,
statement_span,
);
}
AssignmentOperator::LogicalOr | AssignmentOperator::LogicalNullish => {
check_logical_assignment(
ctx,
node,
&left_ident.name,
left_ident.span,
&assign_expr.right,
assign_expr.span,
statement_span,
);
}
_ => {}
}
}
}
AstKind::VariableDeclaration(var_decl) => {
@@ -117,6 +130,32 @@ impl Rule for PreferDefaultParameters {
}
}
fn check_logical_assignment<'a>(
ctx: &LintContext<'a>,
node: &AstNode<'a>,
left_name: &str,
left_span: Span,
right: &Expression<'a>,
stmt_span: Span,
statement_span: Option<Span>,
) {
if !right.get_inner_expression().is_literal() {
return;
}
check_parameter_default(
ctx,
node,
left_name,
left_name,
ctx.source_range(right.span()),
true,
stmt_span,
statement_span,
Some(left_span),
true,
);
}
fn check_expression<'a>(
ctx: &LintContext<'a>,
node: &AstNode<'a>,
@@ -139,7 +178,6 @@ fn check_expression<'a>(
};
let param_name = param_ident.name.as_str();
let default_value_text = ctx.source_range(logical_expr.right.span());
if !logical_expr.right.get_inner_expression().is_literal() {
return;
}
@@ -148,6 +186,32 @@ fn check_expression<'a>(
return;
}
check_parameter_default(
ctx,
node,
param_name,
left_name,
ctx.source_range(logical_expr.right.span()),
is_assignment,
stmt_span,
statement_span,
Some(param_ident.span),
false,
);
}
fn check_parameter_default<'a>(
ctx: &LintContext<'a>,
node: &AstNode<'a>,
param_name: &str,
left_name: &str,
default_value_text: &str,
is_assignment: bool,
stmt_span: Span,
statement_span: Option<Span>,
param_ident_span: Option<Span>,
logical_assignment: bool,
) {
let Some((function_id, function_body_id)) = find_enclosing_function(ctx, node) else {
return;
};
@@ -185,11 +249,18 @@ fn check_expression<'a>(
return;
}
if is_assignment {
if !check_no_extra_references_assignment(ctx, param_ident.span, param) {
let Some(read_span) = param_ident_span else {
return;
};
if logical_assignment {
if !check_logical_assignment_is_first_reference(ctx, read_span, param) {
return;
}
} else if !check_no_extra_references(ctx, param_ident.span, param) {
} else if is_assignment {
if !check_no_extra_references_assignment(ctx, read_span, param) {
return;
}
} else if !check_no_extra_references(ctx, read_span, param) {
return;
}
@@ -439,6 +510,23 @@ fn check_no_extra_references_assignment<'a>(
writes == 1 && has_matching_read
}
fn check_logical_assignment_is_first_reference<'a>(
ctx: &LintContext<'a>,
param_ident_span: Span,
param: &FormalParameter<'a>,
) -> bool {
let BindingPattern::BindingIdentifier(binding_ident) = &param.pattern else {
return false;
};
let symbol_id = binding_ident.symbol_id();
let Some(reference_id) = ctx.scoping().get_resolved_reference_ids(symbol_id).first() else {
return false;
};
ctx.semantic().reference_span(ctx.scoping().get_reference(*reference_id)) == param_ident_span
}
#[test]
fn test() {
use crate::tester::Tester;
@@ -608,6 +696,11 @@ fn test() {
value = value ?? '';
}
};",
r"function example(value) {
const before = value;
value ||= false;
return before;
}",
];
let fail = vec![
@@ -729,6 +822,19 @@ fn test() {
r"function abc(foo) {
const bar = function() {};
foo = foo || 123;
}",
r"function example(value) {
value ??= false;
return value;
}",
r"function example(value) {
value ||= false;
return value;
}",
r"function example(value) {
value ||= false;
value = true;
return value;
}",
];
@@ -991,6 +1097,35 @@ bar(); baz();
}",
r"function abc(foo = 123) {
const bar = function() {};
}",
),
(
r"function example(value) {
value ??= false;
return value;
}",
r"function example(value = false) {
return value;
}",
),
(
r"function example(value) {
value ||= false;
return value;
}",
r"function example(value = false) {
return value;
}",
),
(
r"function example(value) {
value ||= false;
value = true;
return value;
}",
r"function example(value = false) {
value = true;
return value;
}",
),
(
@@ -283,3 +283,30 @@ source: crates/oxc_linter/src/tester.rs
4 │ }
╰────
help: Replace the reassignment with a default parameter.
⚠ unicorn(prefer-default-parameters): Prefer default parameters over reassignment for 'value'.
╭─[prefer_default_parameters.tsx:2:5]
1 │ function example(value) {
2 │ value ??= false;
· ───────────────
3 │ return value;
╰────
help: Replace the reassignment with a default parameter.
⚠ unicorn(prefer-default-parameters): Prefer default parameters over reassignment for 'value'.
╭─[prefer_default_parameters.tsx:2:5]
1 │ function example(value) {
2 │ value ||= false;
· ───────────────
3 │ return value;
╰────
help: Replace the reassignment with a default parameter.
⚠ unicorn(prefer-default-parameters): Prefer default parameters over reassignment for 'value'.
╭─[prefer_default_parameters.tsx:2:5]
1 │ function example(value) {
2 │ value ||= false;
· ───────────────
3 │ value = true;
╰────
help: Replace the reassignment with a default parameter.