1
- use oxc_ast:: { ast:: MemberExpression , AstKind } ;
1
+ use oxc_ast:: {
2
+ ast:: { Argument , CallExpression , MemberExpression } ,
3
+ AstKind ,
4
+ } ;
2
5
use oxc_diagnostics:: OxcDiagnostic ;
3
6
use oxc_macros:: declare_oxc_lint;
4
7
use oxc_span:: { GetSpan , Span } ;
5
8
6
- use crate :: { ast_util:: outermost_paren_parent, context:: LintContext , rule:: Rule , AstNode } ;
9
+ use crate :: {
10
+ ast_util:: outermost_paren_parent,
11
+ context:: LintContext ,
12
+ fixer:: { Fix , RuleFixer } ,
13
+ rule:: Rule ,
14
+ AstNode ,
15
+ } ;
7
16
8
17
fn resolve ( span0 : Span , x1 : & str ) -> OxcDiagnostic {
9
18
OxcDiagnostic :: warn ( format ! ( "eslint-plugin-unicorn(no-useless-promise-resolve-reject): Prefer `{x1} value` over `{x1} Promise.resolve(value)`." ) )
@@ -75,7 +84,9 @@ impl Rule for NoUselessPromiseResolveReject {
75
84
}
76
85
}
77
86
78
- let Some ( ( is_async, function_node) ) = get_function_like_node ( node, ctx) else {
87
+ let Some ( ( is_async, function_node, is_in_try_statement) ) =
88
+ get_function_like_node ( node, ctx)
89
+ else {
79
90
return ;
80
91
} ;
81
92
@@ -85,16 +96,36 @@ impl Rule for NoUselessPromiseResolveReject {
85
96
86
97
match static_member_expr. property . name . as_str ( ) {
87
98
"resolve" => {
88
- ctx. diagnostic ( resolve (
89
- node. kind ( ) . span ( ) ,
90
- if is_yield { "yield" } else { "return" } ,
91
- ) ) ;
99
+ ctx. diagnostic_with_fix (
100
+ resolve ( node. kind ( ) . span ( ) , if is_yield { "yield" } else { "return" } ) ,
101
+ |fixer| {
102
+ generate_fix (
103
+ call_expr,
104
+ false ,
105
+ is_yield,
106
+ is_in_try_statement,
107
+ fixer,
108
+ ctx,
109
+ node,
110
+ )
111
+ } ,
112
+ ) ;
92
113
}
93
114
"reject" => {
94
- ctx. diagnostic ( reject (
95
- node. kind ( ) . span ( ) ,
96
- if is_yield { "yield" } else { "return" } ,
97
- ) ) ;
115
+ ctx. diagnostic_with_fix (
116
+ reject ( node. kind ( ) . span ( ) , if is_yield { "yield" } else { "return" } ) ,
117
+ |fixer| {
118
+ generate_fix (
119
+ call_expr,
120
+ true ,
121
+ is_yield,
122
+ is_in_try_statement,
123
+ fixer,
124
+ ctx,
125
+ node,
126
+ )
127
+ } ,
128
+ ) ;
98
129
}
99
130
_ => unreachable ! ( ) ,
100
131
}
@@ -104,23 +135,29 @@ impl Rule for NoUselessPromiseResolveReject {
104
135
fn get_function_like_node < ' a , ' b > (
105
136
node : & ' a AstNode < ' b > ,
106
137
ctx : & ' a LintContext < ' b > ,
107
- ) -> Option < ( bool , & ' a AstNode < ' b > ) > {
138
+ ) -> Option < ( bool , & ' a AstNode < ' b > , bool ) > {
108
139
let mut parent = node;
140
+ let mut is_in_try_statement = false ;
109
141
110
142
let fnx = loop {
111
143
if let Some ( grand_parent) = ctx. nodes ( ) . parent_node ( parent. id ( ) ) {
112
144
parent = grand_parent;
113
145
if parent. kind ( ) . is_function_like ( ) {
114
146
break parent;
115
147
}
148
+ if matches ! ( parent. kind( ) , AstKind :: TryStatement ( _) ) {
149
+ is_in_try_statement = true ;
150
+ }
116
151
} else {
117
152
return None ;
118
153
}
119
154
} ;
120
155
121
156
match fnx. kind ( ) {
122
- AstKind :: ArrowFunctionExpression ( arrow_expr) => Some ( ( arrow_expr. r#async , parent) ) ,
123
- AstKind :: Function ( func) => Some ( ( func. r#async , parent) ) ,
157
+ AstKind :: ArrowFunctionExpression ( arrow_expr) => {
158
+ Some ( ( arrow_expr. r#async , parent, is_in_try_statement) )
159
+ }
160
+ AstKind :: Function ( func) => Some ( ( func. r#async , parent, is_in_try_statement) ) ,
124
161
_ => None ,
125
162
}
126
163
}
@@ -163,6 +200,133 @@ fn is_promise_callback<'a, 'b>(node: &'a AstNode<'b>, ctx: &'a LintContext<'b>)
163
200
false
164
201
}
165
202
203
+ fn match_arrow_function_body < ' a > ( ctx : & LintContext < ' a > , parent : & AstNode < ' a > ) -> bool {
204
+ match ctx. nodes ( ) . parent_node ( parent. id ( ) ) {
205
+ Some ( arrow_function_body) => match arrow_function_body. kind ( ) {
206
+ AstKind :: FunctionBody ( _) => match ctx. nodes ( ) . parent_node ( arrow_function_body. id ( ) ) {
207
+ Some ( arrow_function) => {
208
+ matches ! ( arrow_function. kind( ) , AstKind :: ArrowFunctionExpression ( _) )
209
+ }
210
+ None => false ,
211
+ } ,
212
+ _ => false ,
213
+ } ,
214
+ None => false ,
215
+ }
216
+ }
217
+
218
+ fn generate_fix < ' a > (
219
+ call_expr : & CallExpression ,
220
+ is_reject : bool ,
221
+ is_yield : bool ,
222
+ is_in_try_statement : bool ,
223
+ fixer : RuleFixer < ' _ , ' a > ,
224
+ ctx : & LintContext < ' a > ,
225
+ node : & AstNode < ' a > ,
226
+ ) -> Fix < ' a > {
227
+ if call_expr. arguments . len ( ) > 1 {
228
+ return fixer. noop ( ) ;
229
+ }
230
+
231
+ let arg_text = if call_expr. arguments . is_empty ( ) {
232
+ & String :: new ( )
233
+ } else {
234
+ let arg = & call_expr. arguments [ 0 ] ;
235
+ if arg. is_spread ( ) {
236
+ return fixer. noop ( ) ;
237
+ }
238
+ fixer. source_range ( arg. span ( ) )
239
+ } ;
240
+
241
+ if is_reject {
242
+ if is_in_try_statement {
243
+ return fixer. noop ( ) ;
244
+ }
245
+ if is_yield {
246
+ if let Some ( parent) = ctx. nodes ( ) . parent_node ( node. id ( ) ) {
247
+ if let Some ( grand_parent) = ctx. nodes ( ) . parent_node ( parent. id ( ) ) {
248
+ if !matches ! (
249
+ grand_parent. kind( ) ,
250
+ AstKind :: ExpressionStatement ( _) | AstKind :: ParenthesizedExpression ( _)
251
+ ) {
252
+ return fixer. noop ( ) ;
253
+ }
254
+ } ;
255
+ } ;
256
+ } ;
257
+ }
258
+
259
+ let node = get_parenthesized_node ( node, ctx) ;
260
+
261
+ let Some ( parent) = ctx. nodes ( ) . parent_node ( node. id ( ) ) else {
262
+ return fixer. noop ( ) ;
263
+ } ;
264
+
265
+ let is_arrow_function_body = match parent. kind ( ) {
266
+ AstKind :: ExpressionStatement ( _) => match_arrow_function_body ( ctx, parent) ,
267
+ _ => false ,
268
+ } ;
269
+
270
+ let mut replace_range = if is_reject { parent. kind ( ) . span ( ) } else { call_expr. span ( ) } ;
271
+ let replacement_text = if is_reject {
272
+ let mut text =
273
+ if arg_text. is_empty ( ) { "undefined" . to_string ( ) } else { arg_text. to_string ( ) } ;
274
+ text = format ! ( "throw {text}" ) ;
275
+
276
+ if is_yield {
277
+ replace_range = get_parenthesized_node ( parent, ctx) . kind ( ) . span ( ) ;
278
+ text
279
+ } else {
280
+ text = format ! ( "{text};" ) ;
281
+ // `=> Promise.reject(error)` -> `=> { throw error; }`
282
+ if is_arrow_function_body {
283
+ replace_range = get_parenthesized_node ( parent, ctx) . kind ( ) . span ( ) ;
284
+ text = format ! ( "{{ {text} }}" ) ;
285
+ }
286
+ text
287
+ }
288
+ } else {
289
+ let mut text = arg_text. to_string ( ) ;
290
+ if text. is_empty ( ) {
291
+ // `=> Promise.resolve()` -> `=> {}`
292
+ if is_arrow_function_body {
293
+ text = "{}" . to_string ( ) ;
294
+ text
295
+ } else {
296
+ if matches ! ( parent. kind( ) , AstKind :: ReturnStatement ( _) ) {
297
+ replace_range. start = parent. kind ( ) . span ( ) . start + 6 ;
298
+ }
299
+ if is_yield {
300
+ replace_range. start = parent. kind ( ) . span ( ) . start + 5 ;
301
+ }
302
+ text
303
+ }
304
+ } else {
305
+ if matches ! ( & call_expr. arguments[ 0 ] , Argument :: ObjectExpression ( _) ) {
306
+ text = format ! ( "({text})" ) ;
307
+ }
308
+ text
309
+ }
310
+ } ;
311
+
312
+ fixer. replace ( replace_range, replacement_text)
313
+ }
314
+
315
+ fn get_parenthesized_node < ' a , ' b > (
316
+ node : & ' a AstNode < ' b > ,
317
+ ctx : & ' a LintContext < ' b > ,
318
+ ) -> & ' a AstNode < ' b > {
319
+ let mut node = node;
320
+ while let Some ( parent_node) = ctx. nodes ( ) . parent_node ( node. id ( ) ) {
321
+ if let AstKind :: ParenthesizedExpression ( _) = parent_node. kind ( ) {
322
+ node = parent_node;
323
+ } else {
324
+ break ;
325
+ }
326
+ }
327
+ node
328
+ }
329
+
166
330
#[ test]
167
331
fn test ( ) {
168
332
use crate :: tester:: Tester ;
@@ -459,5 +623,405 @@ fn test() {
459
623
r"promise.then(() => {}, async () => { return Promise.reject(bar); })" ,
460
624
] ;
461
625
462
- Tester :: new ( NoUselessPromiseResolveReject :: NAME , pass, fail) . test_and_snapshot ( ) ;
626
+ let fix = vec ! [
627
+ (
628
+ r"
629
+ const main = async foo => {
630
+ if (foo > 4) {
631
+ return Promise.reject(new Error('🤪'));
632
+ }
633
+
634
+ return Promise.resolve(result);
635
+ };
636
+ " ,
637
+ r"
638
+ const main = async foo => {
639
+ if (foo > 4) {
640
+ throw new Error('🤪');
641
+ }
642
+
643
+ return result;
644
+ };
645
+ " ,
646
+ None ,
647
+ ) ,
648
+ // Async function returning Promise.resolve
649
+ ( "async () => Promise.resolve(bar);" , "async () => bar;" , None ) ,
650
+ (
651
+ r"
652
+ async () => {
653
+ return Promise.resolve(bar);
654
+ };
655
+ " ,
656
+ r"
657
+ async () => {
658
+ return bar;
659
+ };
660
+ " ,
661
+ None ,
662
+ ) ,
663
+ (
664
+ r"
665
+ async function foo() {
666
+ return Promise.resolve(bar);
667
+ }
668
+ " ,
669
+ r"
670
+ async function foo() {
671
+ return bar;
672
+ }
673
+ " ,
674
+ None ,
675
+ ) ,
676
+ (
677
+ r"
678
+ (async function() {
679
+ return Promise.resolve(bar);
680
+ });
681
+ " ,
682
+ r"
683
+ (async function() {
684
+ return bar;
685
+ });
686
+ " ,
687
+ None ,
688
+ ) ,
689
+ (
690
+ r"
691
+ async function * foo() {
692
+ return Promise.resolve(bar);
693
+ }
694
+ " ,
695
+ r"
696
+ async function * foo() {
697
+ return bar;
698
+ }
699
+ " ,
700
+ None ,
701
+ ) ,
702
+ (
703
+ r"
704
+ (async function*() {
705
+ return Promise.resolve(bar);
706
+ });
707
+ " ,
708
+ r"
709
+ (async function*() {
710
+ return bar;
711
+ });
712
+ " ,
713
+ None ,
714
+ ) ,
715
+ // Async function returning Promise.reject
716
+ ( "async () => Promise.reject(bar);" , "async () => { throw bar; };" , None ) ,
717
+ (
718
+ r"
719
+ async () => {
720
+ return Promise.reject(bar);
721
+ };
722
+ " ,
723
+ r"
724
+ async () => {
725
+ throw bar;
726
+ };
727
+ " ,
728
+ None ,
729
+ ) ,
730
+ (
731
+ r"
732
+ async function foo() {
733
+ return Promise.reject(bar);
734
+ }
735
+ " ,
736
+ r"
737
+ async function foo() {
738
+ throw bar;
739
+ }
740
+ " ,
741
+ None ,
742
+ ) ,
743
+ (
744
+ r"
745
+ (async function() {
746
+ return Promise.reject(bar);
747
+ });
748
+ " ,
749
+ r"
750
+ (async function() {
751
+ throw bar;
752
+ });
753
+ " ,
754
+ None ,
755
+ ) ,
756
+ (
757
+ r"
758
+ async function * foo() {
759
+ return Promise.reject(bar);
760
+ }
761
+ " ,
762
+ r"
763
+ async function * foo() {
764
+ throw bar;
765
+ }
766
+ " ,
767
+ None ,
768
+ ) ,
769
+ (
770
+ r"
771
+ (async function*() {
772
+ return Promise.reject(bar);
773
+ });
774
+ " ,
775
+ r"
776
+ (async function*() {
777
+ throw bar;
778
+ });
779
+ " ,
780
+ None ,
781
+ ) ,
782
+ // Async generator yielding Promise.resolve
783
+ (
784
+ r"
785
+ async function * foo() {
786
+ yield Promise.resolve(bar);
787
+ }
788
+ " ,
789
+ r"
790
+ async function * foo() {
791
+ yield bar;
792
+ }
793
+ " ,
794
+ None ,
795
+ ) ,
796
+ (
797
+ r"
798
+ (async function * () {
799
+ yield Promise.resolve(bar);
800
+ });
801
+ " ,
802
+ r"
803
+ (async function * () {
804
+ yield bar;
805
+ });
806
+ " ,
807
+ None ,
808
+ ) ,
809
+ // Async generator yielding Promise.reject
810
+ (
811
+ r"
812
+ async function * foo() {
813
+ yield Promise.reject(bar);
814
+ }
815
+ " ,
816
+ r"
817
+ async function * foo() {
818
+ throw bar;
819
+ }
820
+ " ,
821
+ None ,
822
+ ) ,
823
+ (
824
+ r"
825
+ (async function * () {
826
+ yield Promise.reject(bar);
827
+ });
828
+ " ,
829
+ r"
830
+ (async function * () {
831
+ throw bar;
832
+ });
833
+ " ,
834
+ None ,
835
+ ) ,
836
+ // No arguments
837
+ ( r"async () => Promise.resolve();" , r"async () => {};" , None ) ,
838
+ (
839
+ r"
840
+ async function foo() {
841
+ return Promise.resolve();
842
+ }
843
+ " ,
844
+ r"
845
+ async function foo() {
846
+ return;
847
+ }
848
+ " ,
849
+ None ,
850
+ ) ,
851
+ ( r"async () => Promise.reject();" , r"async () => { throw undefined; };" , None ) ,
852
+ (
853
+ r"
854
+ async function foo() {
855
+ return Promise.reject();
856
+ }
857
+ " ,
858
+ r"
859
+ async function foo() {
860
+ throw undefined;
861
+ }
862
+ " ,
863
+ None ,
864
+ ) ,
865
+ (
866
+ r"
867
+ async function * foo() {
868
+ yield Promise.resolve();
869
+ }
870
+ " ,
871
+ r"
872
+ async function * foo() {
873
+ yield;
874
+ }
875
+ " ,
876
+ None ,
877
+ ) ,
878
+ // Sequence expressions
879
+ (
880
+ r"
881
+ async function * foo() {
882
+ yield Promise.resolve((bar, baz));
883
+ }
884
+ " ,
885
+ r"
886
+ async function * foo() {
887
+ yield (bar, baz);
888
+ }
889
+ " ,
890
+ None ,
891
+ ) ,
892
+ ( r"async () => Promise.resolve((bar, baz))" , r"async () => (bar, baz)" , None ) ,
893
+ // Arrow function returning an object
894
+ ( r"async () => Promise.resolve({})" , r"async () => ({})" , None ) ,
895
+ // Try statements
896
+ (
897
+ r"
898
+ async function foo() {
899
+ try {
900
+ return Promise.resolve(1);
901
+ } catch {}
902
+ }
903
+ " ,
904
+ r"
905
+ async function foo() {
906
+ try {
907
+ return 1;
908
+ } catch {}
909
+ }
910
+ " ,
911
+ None ,
912
+ ) ,
913
+ // Try statements reject case: don't change
914
+ (
915
+ r"
916
+ async function foo() {
917
+ try {
918
+ return Promise.reject(1);
919
+ } catch {}
920
+ }
921
+ " ,
922
+ r"
923
+ async function foo() {
924
+ try {
925
+ return Promise.reject(1);
926
+ } catch {}
927
+ }
928
+ " ,
929
+ None ,
930
+ ) ,
931
+ // Spread arguments can not modify the original array
932
+ ( r"async () => Promise.resolve(...bar);" , r"async () => Promise.resolve(...bar);" , None ) ,
933
+ ( r"async () => Promise.reject(...bar);" , r"async () => Promise.reject(...bar);" , None ) ,
934
+ // Yield not in an ExpressionStatement
935
+ (
936
+ r"
937
+ async function * foo() {
938
+ const baz = yield Promise.resolve(bar);
939
+ }
940
+ " ,
941
+ r"
942
+ async function * foo() {
943
+ const baz = yield bar;
944
+ }
945
+ " ,
946
+ None ,
947
+ ) ,
948
+ // Yield reject not in an ExpressionStatement not modify
949
+ (
950
+ r"
951
+ async function * foo() {
952
+ const baz = yield Promise.reject(bar);
953
+ }
954
+ " ,
955
+ r"
956
+ async function * foo() {
957
+ const baz = yield Promise.reject(bar);
958
+ }
959
+ " ,
960
+ None ,
961
+ ) ,
962
+ // Parenthesized Promise.resolve/reject
963
+ ( r"async () => (Promise.resolve(bar));" , r"async () => (bar);" , None ) ,
964
+ ( r"async () => (Promise.reject(bar));" , r"async () => { throw bar; };" , None ) ,
965
+ ( r"async () => ((Promise.reject(bar)));" , r"async () => { throw bar; };" , None ) ,
966
+ (
967
+ r"
968
+ async function * foo() {
969
+ (yield Promise.reject(bar));
970
+ }
971
+ " ,
972
+ r"
973
+ async function * foo() {
974
+ throw bar;
975
+ }
976
+ " ,
977
+ None ,
978
+ ) ,
979
+ (
980
+ r"
981
+ async function * foo() {
982
+ ((yield Promise.reject(bar)));
983
+ }
984
+ " ,
985
+ r"
986
+ async function * foo() {
987
+ throw bar;
988
+ }
989
+ " ,
990
+ None ,
991
+ ) ,
992
+ // Promise#then/catch/finally callbacks returning Promise.resolve/reject
993
+ (
994
+ r"promise.then(() => {}, () => Promise.resolve(bar))" ,
995
+ r"promise.then(() => {}, () => bar)" ,
996
+ None ,
997
+ ) ,
998
+ (
999
+ r"promise.then(() => Promise.resolve(bar), () => Promise.resolve(baz))" ,
1000
+ r"promise.then(() => bar, () => baz)" ,
1001
+ None ,
1002
+ ) ,
1003
+ (
1004
+ r"promise.then(() => {}, () => { return Promise.resolve(bar); })" ,
1005
+ r"promise.then(() => {}, () => { return bar; })" ,
1006
+ None ,
1007
+ ) ,
1008
+ (
1009
+ r"promise.then(() => {}, async () => Promise.reject(bar))" ,
1010
+ r"promise.then(() => {}, async () => { throw bar; })" ,
1011
+ None ,
1012
+ ) ,
1013
+ (
1014
+ r"promise.then(() => {}, async () => { return Promise.reject(bar); })" ,
1015
+ r"promise.then(() => {}, async () => { throw bar; })" ,
1016
+ None ,
1017
+ ) ,
1018
+ (
1019
+ r"promise.then(() => {}, async () => { return Promise.reject(bar); })" ,
1020
+ r"promise.then(() => {}, async () => { throw bar; })" ,
1021
+ None ,
1022
+ ) ,
1023
+ ] ;
1024
+ Tester :: new ( NoUselessPromiseResolveReject :: NAME , pass, fail)
1025
+ . expect_fix ( fix)
1026
+ . test_and_snapshot ( ) ;
463
1027
}
0 commit comments