Skip to content

Commit ddd9f9f

Browse files
committed
fix(linter/forward-ref-uses-ref): dont suggest removing wrapper in invalid positions (#15388)
fixes #15384
1 parent dac2a9c commit ddd9f9f

File tree

1 file changed

+62
-31
lines changed

1 file changed

+62
-31
lines changed

crates/oxc_linter/src/rules/react/forward_ref_uses_ref.rs

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use oxc_diagnostics::OxcDiagnostic;
66
use oxc_macros::declare_oxc_lint;
77
use oxc_span::Span;
88

9-
use crate::{AstNode, context::LintContext, fixer::RuleFixer, rule::Rule};
9+
use crate::{
10+
AstNode, ast_util::outermost_paren_parent, context::LintContext, fixer::RuleFixer, rule::Rule,
11+
};
1012

1113
fn forward_ref_uses_ref_diagnostic(span: Span) -> OxcDiagnostic {
1214
OxcDiagnostic::warn("Components wrapped with `forwardRef` must have a `ref` parameter")
@@ -78,13 +80,14 @@ impl Rule for ForwardRefUsesRef {
7880
return; // SpreadElement like forwardRef(...x)
7981
};
8082

81-
check_forward_ref_inner(first_arg_as_exp, call_expr, ctx);
83+
check_forward_ref_inner(first_arg_as_exp, call_expr, node, ctx);
8284
}
8385
}
8486

8587
fn check_forward_ref_inner<'a>(
8688
exp: &Expression,
8789
call_expr: &CallExpression,
90+
node: &AstNode<'a>,
8891
ctx: &LintContext<'a>,
8992
) {
9093
let (params, span) = match exp {
@@ -96,30 +99,47 @@ fn check_forward_ref_inner<'a>(
9699
return;
97100
}
98101

99-
ctx.diagnostics_with_multiple_fixes(
100-
forward_ref_uses_ref_diagnostic(span),
101-
(FixKind::Suggestion, |fixer: RuleFixer<'_, 'a>| {
102-
fixer.replace_with(call_expr, exp).with_message("remove `forwardRef` wrapper")
103-
}),
104-
(FixKind::Suggestion, |fixer: RuleFixer<'_, 'a>| {
105-
let fixed = ctx.source_range(params.span);
106-
// remove the trailing `)`, `` and `,` if they exist
107-
let fixed = fixed.strip_suffix(')').unwrap_or(fixed).trim_end();
108-
let mut fixed = fixed.strip_suffix(',').unwrap_or(fixed).to_string();
109-
110-
if !fixed.starts_with('(') {
111-
fixed.insert(0, '(');
112-
}
113-
fixed.push_str(", ref)");
114-
115-
fixer.replace(params.span, fixed).with_message("add `ref` parameter")
116-
}),
117-
);
102+
let can_remove_forward_ref = match exp {
103+
Expression::FunctionExpression(f) if f.id.is_none() => !matches!(
104+
outermost_paren_parent(node, ctx.semantic()).map(AstNode::kind),
105+
Some(AstKind::ExpressionStatement(_))
106+
),
107+
_ => true,
108+
};
109+
110+
let add_ref_parameter_fix = |fixer: RuleFixer<'_, 'a>| {
111+
let fixed = ctx.source_range(params.span);
112+
// remove the trailing `)` and `,` if they exist
113+
let fixed = fixed.strip_suffix(')').unwrap_or(fixed).trim_end();
114+
let mut fixed = fixed.strip_suffix(',').unwrap_or(fixed).to_string();
115+
116+
if !fixed.starts_with('(') {
117+
fixed.insert(0, '(');
118+
}
119+
fixed.push_str(", ref)");
120+
121+
fixer.replace(params.span, fixed).with_message("add `ref` parameter")
122+
};
123+
124+
if can_remove_forward_ref {
125+
ctx.diagnostics_with_multiple_fixes(
126+
forward_ref_uses_ref_diagnostic(span),
127+
(FixKind::Suggestion, |fixer: RuleFixer<'_, 'a>| {
128+
fixer.replace_with(call_expr, exp).with_message("remove `forwardRef` wrapper")
129+
}),
130+
(FixKind::Suggestion, add_ref_parameter_fix),
131+
);
132+
} else {
133+
ctx.diagnostic_with_suggestion(
134+
forward_ref_uses_ref_diagnostic(span),
135+
add_ref_parameter_fix,
136+
);
137+
}
118138
}
119139

120140
#[test]
121141
fn test() {
122-
use crate::tester::Tester;
142+
use crate::tester::{ExpectFixTestCase, Tester};
123143

124144
let pass = vec![
125145
"
@@ -212,16 +232,27 @@ fn test() {
212232
",
213233
];
214234

215-
let fix = vec![
216-
("forwardRef((a) => {})", ("(a) => {}", "forwardRef((a, ref) => {})")),
217-
("forwardRef(a => {})", ("a => {}", "forwardRef((a, ref) => {})")),
218-
("forwardRef(function (a) {})", ("function (a) {}", "forwardRef(function (a, ref) {})")),
219-
("forwardRef(function(a,) {})", ("function(a,) {}", "forwardRef(function(a, ref) {})")),
220-
("forwardRef(function(a, ) {})", ("function(a, ) {}", "forwardRef(function(a, ref) {})")),
235+
let fix: Vec<ExpectFixTestCase> = vec![
236+
// Arrow functions - two fixes (remove wrapper or add ref)
237+
("forwardRef((a) => {})", ("(a) => {}", "forwardRef((a, ref) => {})")).into(),
238+
("forwardRef(a => {})", ("a => {}", "forwardRef((a, ref) => {})")).into(),
239+
// Named function expressions - two fixes
221240
(
222-
"React.forwardRef(function(a) {})",
223-
("function(a) {}", "React.forwardRef(function(a, ref) {})"),
224-
),
241+
"forwardRef(function Component(a) {})",
242+
("function Component(a) {}", "forwardRef(function Component(a, ref) {})"),
243+
)
244+
.into(),
245+
// Anonymous functions in expression context - two fixes
246+
(
247+
"const x = forwardRef(function(a) {})",
248+
("const x = function(a) {}", "const x = forwardRef(function(a, ref) {})"),
249+
)
250+
.into(),
251+
// Anonymous functions in statement context - one fix (add ref only)
252+
("forwardRef(function (a) {})", "forwardRef(function (a, ref) {})").into(),
253+
("forwardRef(function(a,) {})", "forwardRef(function(a, ref) {})").into(),
254+
("forwardRef(function(a, ) {})", "forwardRef(function(a, ref) {})").into(),
255+
("React.forwardRef(function(a) {})", "React.forwardRef(function(a, ref) {})").into(),
225256
];
226257

227258
Tester::new(ForwardRefUsesRef::NAME, ForwardRefUsesRef::PLUGIN, pass, fail)

0 commit comments

Comments
 (0)