@@ -6,7 +6,9 @@ use oxc_diagnostics::OxcDiagnostic;
66use oxc_macros:: declare_oxc_lint;
77use 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
1113fn 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
8587fn 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]
121141fn 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