1
1
use std:: ops:: Deref ;
2
2
3
- use oxc_ast:: { AstKind , ast:: Expression } ;
3
+ use oxc_ast:: {
4
+ AstKind ,
5
+ ast:: { CallExpression , Expression } ,
6
+ } ;
4
7
use oxc_diagnostics:: OxcDiagnostic ;
5
8
use oxc_macros:: declare_oxc_lint;
6
9
use oxc_span:: Span ;
7
10
8
- use crate :: { AstNode , ast_util:: is_method_call, context:: LintContext , rule:: Rule } ;
11
+ use crate :: {
12
+ AstNode ,
13
+ ast_util:: is_method_call,
14
+ context:: LintContext ,
15
+ fixer:: { RuleFix , RuleFixer } ,
16
+ rule:: Rule ,
17
+ } ;
9
18
10
19
fn prefer_structured_clone_diagnostic ( span : Span ) -> OxcDiagnostic {
11
20
OxcDiagnostic :: warn ( "Use `structuredClone(…)` to create a deep clone." )
@@ -54,7 +63,7 @@ declare_oxc_lint!(
54
63
PreferStructuredClone ,
55
64
unicorn,
56
65
style,
57
- pending ,
66
+ suggestion ,
58
67
) ;
59
68
60
69
impl Rule for PreferStructuredClone {
@@ -73,7 +82,7 @@ impl Rule for PreferStructuredClone {
73
82
}
74
83
75
84
fn run < ' a > ( & self , node : & AstNode < ' a > , ctx : & LintContext < ' a > ) {
76
- let AstKind :: CallExpression ( call_expr) : oxc_ast :: AstKind < ' a > = node. kind ( ) else {
85
+ let AstKind :: CallExpression ( call_expr) = node. kind ( ) else {
77
86
return ;
78
87
} ;
79
88
@@ -85,7 +94,6 @@ impl Rule for PreferStructuredClone {
85
94
return ;
86
95
}
87
96
88
- // `JSON.parse(JSON.stringify(…))
89
97
if is_method_call ( call_expr, Some ( & [ "JSON" ] ) , Some ( & [ "parse" ] ) , Some ( 1 ) , Some ( 1 ) ) {
90
98
let Some ( first_argument) = call_expr. arguments [ 0 ] . as_expression ( ) else {
91
99
return ;
@@ -110,29 +118,49 @@ impl Rule for PreferStructuredClone {
110
118
return ;
111
119
}
112
120
113
- if inner_call_expr. arguments [ 0 ] . is_spread ( ) {
121
+ let Some ( first_argument ) = inner_call_expr. arguments [ 0 ] . as_expression ( ) else {
114
122
return ;
115
- }
123
+ } ;
116
124
117
- let span = Span :: new ( call_expr. span . start , inner_call_expr. span . end ) ;
118
- ctx. diagnostic ( prefer_structured_clone_diagnostic ( span) ) ;
119
- } else if !call_expr. arguments [ 0 ] . is_spread ( ) {
125
+ ctx. diagnostic_with_suggestion (
126
+ prefer_structured_clone_diagnostic ( call_expr. span ) ,
127
+ |fixer| replace_with_structured_clone ( fixer, call_expr, first_argument) ,
128
+ ) ;
129
+ } else if let Some ( first_argument) = call_expr. arguments [ 0 ] . as_expression ( ) {
120
130
for function in & self . allowed_functions {
121
131
if let Some ( ( object, method) ) = function. split_once ( '.' ) {
122
132
if is_method_call ( call_expr, Some ( & [ object] ) , Some ( & [ method] ) , None , None ) {
123
- ctx. diagnostic ( prefer_structured_clone_diagnostic ( call_expr. span ) ) ;
133
+ ctx. diagnostic_with_suggestion (
134
+ prefer_structured_clone_diagnostic ( call_expr. span ) ,
135
+ |fixer| replace_with_structured_clone ( fixer, call_expr, first_argument) ,
136
+ ) ;
124
137
}
125
138
} else if is_method_call ( call_expr, None , Some ( & [ function] ) , None , None )
126
139
|| is_method_call ( call_expr, Some ( & [ function] ) , None , None , None )
127
140
|| call_expr. callee . is_specific_id ( function)
128
141
{
129
- ctx. diagnostic ( prefer_structured_clone_diagnostic ( call_expr. span ) ) ;
142
+ ctx. diagnostic_with_suggestion (
143
+ prefer_structured_clone_diagnostic ( call_expr. span ) ,
144
+ |fixer| replace_with_structured_clone ( fixer, call_expr, first_argument) ,
145
+ ) ;
130
146
}
131
147
}
132
148
}
133
149
}
134
150
}
135
151
152
+ fn replace_with_structured_clone < ' a > (
153
+ fixer : RuleFixer < ' _ , ' a > ,
154
+ call_expr : & CallExpression < ' _ > ,
155
+ first_argument : & Expression < ' _ > ,
156
+ ) -> RuleFix < ' a > {
157
+ let mut codegen = fixer. codegen ( ) ;
158
+ codegen. print_str ( "structuredClone(" ) ;
159
+ codegen. print_expression ( first_argument) ;
160
+ codegen. print_str ( ")" ) ;
161
+ fixer. replace ( call_expr. span , codegen)
162
+ }
163
+
136
164
#[ test]
137
165
fn test ( ) {
138
166
use crate :: tester:: Tester ;
@@ -174,6 +202,7 @@ fn test() {
174
202
( "JSON.parse( ((JSON.stringify)) (foo))" , None ) ,
175
203
( "(( JSON.parse)) (JSON.stringify(foo))" , None ) ,
176
204
( "JSON.parse(JSON.stringify( ((foo)) ))" , None ) ,
205
+ ( "JSON.parse(JSON.stringify( ((foo.bar['hello'])) ))" , None ) ,
177
206
(
178
207
"
179
208
function foo() {
@@ -185,7 +214,7 @@ fn test() {
185
214
),
186
215
);
187
216
}
188
- " ,
217
+ " ,
189
218
None ,
190
219
) ,
191
220
( "_.cloneDeep(foo)" , None ) ,
@@ -198,6 +227,55 @@ fn test() {
198
227
( "my.cloneDeep(foo,)" , Some ( serde_json:: json!( [ { "functions" : [ "my.cloneDeep" ] } ] ) ) ) ,
199
228
] ;
200
229
230
+ let fix = vec ! [
231
+ ( "JSON.parse((JSON.stringify((foo))))" , "structuredClone(foo)" , None ) ,
232
+ ( "JSON.parse(JSON.stringify(foo))" , "structuredClone(foo)" , None ) ,
233
+ ( "JSON.parse(JSON.stringify(foo),)" , "structuredClone(foo)" , None ) ,
234
+ ( "JSON.parse(JSON.stringify(foo,))" , "structuredClone(foo)" , None ) ,
235
+ ( "JSON.parse(JSON.stringify(foo,),)" , "structuredClone(foo)" , None ) ,
236
+ ( "JSON.parse( ((JSON.stringify)) (foo))" , "structuredClone(foo)" , None ) ,
237
+ ( "(( JSON.parse)) (JSON.stringify(foo))" , "structuredClone(foo)" , None ) ,
238
+ ( "JSON.parse(JSON.stringify( ((foo)) ))" , "structuredClone(foo)" , None ) ,
239
+ (
240
+ "JSON.parse(JSON.stringify( ((foo.bar['hello'])) ))" ,
241
+ "structuredClone(foo.bar['hello'])" ,
242
+ None ,
243
+ ) ,
244
+ (
245
+ "
246
+ function foo() {
247
+ return JSON
248
+ .parse(
249
+ JSON.
250
+ stringify(
251
+ bar,
252
+ ),
253
+ );
254
+ }
255
+ " ,
256
+ "
257
+ function foo() {
258
+ return structuredClone(bar);
259
+ }
260
+ " ,
261
+ None ,
262
+ ) ,
263
+ ( "_.cloneDeep(foo)" , "structuredClone(foo)" , None ) ,
264
+ ( "lodash.cloneDeep(foo)" , "structuredClone(foo)" , None ) ,
265
+ ( "lodash.cloneDeep(foo,)" , "structuredClone(foo)" , None ) ,
266
+ (
267
+ "myCustomDeepCloneFunction(foo,)" ,
268
+ "structuredClone(foo)" ,
269
+ Some ( serde_json:: json!( [ { "functions" : [ "myCustomDeepCloneFunction" ] } ] ) ) ,
270
+ ) ,
271
+ (
272
+ "my.cloneDeep(foo,)" ,
273
+ "structuredClone(foo)" ,
274
+ Some ( serde_json:: json!( [ { "functions" : [ "my.cloneDeep" ] } ] ) ) ,
275
+ ) ,
276
+ ] ;
277
+
201
278
Tester :: new ( PreferStructuredClone :: NAME , PreferStructuredClone :: PLUGIN , pass, fail)
279
+ . expect_fix ( fix)
202
280
. test_and_snapshot ( ) ;
203
281
}
0 commit comments