1
- use oxc_ast:: AstKind ;
1
+ use oxc_ast:: { AstKind , ast :: Expression } ;
2
2
use oxc_diagnostics:: OxcDiagnostic ;
3
3
use oxc_macros:: declare_oxc_lint;
4
4
use oxc_span:: { GetSpan , Span } ;
5
+ use serde_json:: Value ;
5
6
6
7
use crate :: { AstNode , ast_util:: IsConstant , context:: LintContext , rule:: Rule } ;
7
8
@@ -11,9 +12,38 @@ fn no_constant_condition_diagnostic(span: Span) -> OxcDiagnostic {
11
12
. with_label ( span)
12
13
}
13
14
15
+ #[ derive( Debug , Default , Clone , PartialEq ) ]
16
+ enum CheckLoops {
17
+ All ,
18
+ #[ default]
19
+ AllExceptWhileTrue ,
20
+ None ,
21
+ }
22
+
23
+ impl CheckLoops {
24
+ fn from ( value : & Value ) -> Option < Self > {
25
+ match value {
26
+ Value :: String ( str) => match str. as_str ( ) {
27
+ "all" => Some ( Self :: All ) ,
28
+ "allExceptWhileTrue" => Some ( Self :: AllExceptWhileTrue ) ,
29
+ "none" => Some ( Self :: None ) ,
30
+ _ => None ,
31
+ } ,
32
+ Value :: Bool ( bool) => {
33
+ if * bool {
34
+ Some ( Self :: All )
35
+ } else {
36
+ Some ( Self :: None )
37
+ }
38
+ }
39
+ _ => None ,
40
+ }
41
+ }
42
+ }
43
+
14
44
#[ derive( Debug , Default , Clone ) ]
15
45
pub struct NoConstantCondition {
16
- _check_loops : bool ,
46
+ check_loops : CheckLoops ,
17
47
}
18
48
19
49
declare_oxc_lint ! (
@@ -31,73 +61,108 @@ declare_oxc_lint!(
31
61
/// - `if`, `for`, `while`, or `do...while` statement
32
62
/// - `?`: ternary expression
33
63
///
34
- ///
35
- /// ### Example
64
+ /// ### Examples
36
65
///
37
66
/// Examples of **incorrect** code for this rule:
38
67
/// ```js
39
68
/// if (false) {
40
- /// doSomethingUnfinished();
69
+ /// doSomethingUnfinished();
41
70
/// }
42
71
///
43
72
/// if (new Boolean(x)) {
44
- /// doSomethingAlways();
73
+ /// doSomethingAlways();
45
74
/// }
46
75
/// if (x ||= true) {
47
- /// doSomethingAlways();
76
+ /// doSomethingAlways();
48
77
/// }
49
78
///
50
79
/// do {
51
- /// doSomethingForever();
80
+ /// doSomethingForever();
52
81
/// } while (x = -1);
53
82
/// ```
54
83
///
55
84
/// Examples of **correct** code for this rule:
56
85
/// ```js
57
86
/// if (x === 0) {
58
- /// doSomething();
87
+ /// doSomething();
59
88
/// }
60
89
///
61
90
/// while (typeof x === "undefined") {
62
- /// doSomething();
91
+ /// doSomething();
63
92
/// }
64
93
/// ```
94
+ ///
95
+ /// ### Options
96
+ ///
97
+ /// #### checkLoops
98
+ ///
99
+ /// `{ type: "all" | "allExceptWhileTrue" | "none" | boolean, default: "allExceptWhileTrue" }`
100
+ ///
101
+ /// - `"all"` or `true` disallows constant expressions in loops
102
+ /// - `"allExceptWhileTrue"` disallows constant expressions in loops except while loops with expression `true`
103
+ /// - `"none"` or `false` allows constant expressions in loops
65
104
NoConstantCondition ,
66
105
eslint,
67
106
correctness
68
107
) ;
69
108
70
109
impl Rule for NoConstantCondition {
71
- fn from_configuration ( value : serde_json :: Value ) -> Self {
110
+ fn from_configuration ( value : Value ) -> Self {
72
111
let obj = value. get ( 0 ) ;
73
112
74
113
Self {
75
- _check_loops : obj
114
+ check_loops : obj
76
115
. and_then ( |v| v. get ( "checkLoops" ) )
77
- . and_then ( serde_json :: Value :: as_bool )
116
+ . and_then ( CheckLoops :: from )
78
117
. unwrap_or_default ( ) ,
79
118
}
80
119
}
81
120
82
121
fn run < ' a > ( & self , node : & AstNode < ' a > , ctx : & LintContext < ' a > ) {
83
122
match node. kind ( ) {
84
- AstKind :: IfStatement ( if_stmt) => {
85
- if if_stmt. test . is_constant ( true , ctx) {
86
- ctx. diagnostic ( no_constant_condition_diagnostic ( if_stmt. test . span ( ) ) ) ;
87
- }
123
+ AstKind :: IfStatement ( if_stmt) => check ( ctx, & if_stmt. test ) ,
10000
124
+ AstKind :: ConditionalExpression ( condition_expr) => check ( ctx, & condition_expr. test ) ,
125
+ AstKind :: WhileStatement ( while_stmt) => self . check_loop ( ctx, & while_stmt. test , true ) ,
126
+ AstKind :: DoWhileStatement ( do_while_stmt) => {
127
+ self . check_loop ( ctx, & do_while_stmt. test , false ) ;
88
128
}
89
- AstKind :: ConditionalExpression ( condition_expr) => {
90
- if condition_expr. test . is_constant ( true , ctx) {
91
- ctx. diagnostic ( no_constant_condition_diagnostic ( condition_expr. test . span ( ) ) ) ;
92
- }
129
+ AstKind :: ForStatement ( for_stmt) => {
130
+ let Some ( test) = & for_stmt. test else {
131
+ return ;
132
+ } ;
133
+
134
+ self . check_loop ( ctx, test, false ) ;
93
135
}
94
136
_ => { }
95
137
}
96
138
}
97
139
}
98
140
141
+ impl NoConstantCondition {
142
+ fn check_loop < ' a > ( & self , ctx : & LintContext < ' a > , test : & ' a Expression < ' _ > , is_while : bool ) {
143
+ match self . check_loops {
144
+ CheckLoops :: None => return ,
145
+ CheckLoops :: AllExceptWhileTrue if is_while => match test {
146
+ Expression :: BooleanLiteral ( bool) if bool. value => return ,
147
+ _ => { }
148
+ } ,
149
+ _ => { }
150
+ }
151
+
152
+ check ( ctx, test) ;
153
+ }
154
+ }
155
+
156
+ fn check < ' a > ( ctx : & LintContext < ' a > , test : & ' a Expression < ' _ > ) {
157
+ if test. is_constant ( true , ctx) {
158
+ ctx. diagnostic ( no_constant_condition_diagnostic ( test. span ( ) ) ) ;
159
+ }
160
+ }
161
+
99
162
#[ test]
100
163
fn test ( ) {
164
+ use serde_json:: json;
165
+
101
166
use crate :: tester:: Tester ;
102
167
103
168
let pass = vec ! [
@@ -220,28 +285,36 @@ fn test() {
220
285
// ("const undefined = 'lol'; if (undefined) {}", None),
221
286
// ("function foo(Boolean) { if (Boolean(1)) {} }", None),
222
287
// ("const Boolean = () => {}; if (Boolean(1)) {}", None),
223
- // "if (Boolean()) {}",
224
- // "if (undefined) {}",
288
+ // ( "if (Boolean()) {}", None) ,
289
+ // ( "if (undefined) {}", None) ,
225
290
( "q > 0 ? 1 : 2;" , None ) ,
226
291
( "`${a}` === a ? 1 : 2" , None ) ,
227
292
( "`foo${a}` === a ? 1 : 2" , None ) ,
228
293
( "tag`a` === a ? 1 : 2" , None ) ,
229
294
( "tag`${a}` === a ? 1 : 2" , None ) ,
230
- //TODO
231
- // ("while(~!a);", None),
232
- // ("while(a = b);", None),
233
- // ("while(`${a}`);", None),
234
- // ("for(;x < 10;);", None),
235
- // ("for(;;);", None),
236
- // ("for(;`${a}`;);", None),
237
- // ("do{ }while(x)", None),
238
- // ("while(x += 3) {}", None),
239
- // ("while(tag`a`) {}", None),
240
- // ("while(tag`${a}`) {}", None),
241
- // ("while(`\\\n${a}`) {}", None),
242
- // ("while(true);", Some(json!([{"checkLoops":false}]))),
243
- // ("for(;true;);", Some(json!([{"checkLoops":false}]))),
244
- // ("do{}while(true)", Some(json!([{"checkLoops":false}]))),
295
+ ( "while(~!a);" , None ) ,
296
+ ( "while(a = b);" , None ) ,
297
+ ( "while(`${a}`);" , None ) ,
298
+ ( "for(;x < 10;);" , None ) ,
299
+ ( "for(;;);" , None ) ,
300
+ ( "for(;`${a}`;);" , None ) ,
301
+ ( "do{ }while(x)" , None ) ,
302
+ ( "while(x += 3) {}" , None ) ,
303
+ ( "while(tag`a`) {}" , None ) ,
304
+ ( "while(tag`${a}`) {}" , None ) ,
305
+ ( "while(`\\ \n ${a}`) {}" , None ) ,
306
+ ( "while(true);" , Some ( json!( [ { "checkLoops" : false } ] ) ) ) ,
307
+ ( "for(;true;);" , Some ( json!( [ { "checkLoops" : false } ] ) ) ) ,
308
+ ( "do{}while(true)" , Some ( json!( [ { "checkLoops" : "none" } ] ) ) ) ,
309
+ ( "while(true);" , Some ( json!( [ { "checkLoops" : "none" } ] ) ) ) ,
310
+ ( "for(;true;);" , Some ( json!( [ { "checkLoops" : "none" } ] ) ) ) ,
311
+ ( "do{ }while(x);" , Some ( json!( [ { "checkLoops" : "all" } ] ) ) ) ,
312
+ ( "while(true);" , Some ( json!( [ { "checkLoops" : "allExceptWhileTrue" } ] ) ) ) ,
313
+ ( "while(true);" , None ) ,
314
+ ( "while(a == b);" , Some ( json!( [ { "checkLoops" : "all" } ] ) ) ) ,
315
+ ( "for (let x = 0; x <= 10; x++) {};" , Some ( json!( [ { "checkLoops" : "all" } ] ) ) ) ,
316
+ ( "do{}while(true)" , Some ( json!( [ { "checkLoops" : false } ] ) ) ) ,
317
+ // TODO
245
318
// ("function* foo(){while(true){yield 'foo';}}", None),
246
319
// ("function* foo(){for(;true;){yield 'foo';}}", None),
247
320
// ("function* foo(){do{yield 'foo';}while(true)}", None),
@@ -254,7 +327,6 @@ fn test() {
254
327
] ;
255
328
256
329
let fail = vec ! [
257
- ( "if(-2);" , None ) ,
258
330
( "if(-2);" , None ) ,
259
331
( "if(true);" , None ) ,
260
332
( "if(1);" , None ) ,
@@ -306,11 +378,13 @@ fn test() {
306
378
( "if((a &&= null) && b);" , None ) ,
307
379
( "if(false || (a &&= false));" , None ) ,
308
380
( "if((a &&= false) || false);" , None ) ,
381
+ ( "while(x = 1);" , None ) ,
309
382
( "if(typeof x){}" , None ) ,
310
383
( "if(typeof 'abc' === 'string'){}" , None ) ,
311
384
( "if(a = typeof b){}" , None ) ,
312
385
( "if(a, typeof b){}" , None ) ,
313
386
( "if(typeof 'a' == 'string' || typeof 'b' == 'string'){}" , None ) ,
387
+ ( "while(typeof x){}" , None ) ,
314
388
( "if(1 || void x);" , None ) ,
315
389
( "if(void x);" , None ) ,
316
390
( "if(y = void x);" , None ) ,
@@ -370,39 +444,46 @@ fn test() {
370
444
( "`` ? 1 : 2;" , None ) ,
371
445
( "`foo` ? 1 : 2;" , None ) ,
372
446
( "`foo${bar}` ? 1 : 2;" , None ) ,
447
+ ( "for(;true;);" , None ) ,
448
+ ( "for(;``;);" , None ) ,
449
+ ( "for(;`foo`;);" , None ) ,
450
+ ( "for(;`foo${bar}`;);" , None ) ,
451
+ ( "do{}while(true)" , None ) ,
452
+ ( "do{}while('1')" , None ) ,
453
+ ( "do{}while(0)" , None ) ,
454
+ ( "do{}while(t = -2)" , None ) ,
455
+ ( "do{}while(``)" , None ) ,
456
+ ( "do{}while(`foo`)" , None ) ,
457
+ ( "do{}while(`foo${bar}`)" , None ) ,
458
+ ( "while([]);" , None ) ,
459
+ ( "while(~!0);" , None ) ,
460
+ ( "while(x = 1);" , Some ( json!( [ { "checkLoops" : "all" } ] ) ) ) ,
461
+ ( "while(function(){});" , None ) ,
462
+ ( "while(true);" , Some ( json!( [ { "checkLoops" : "all" } ] ) ) ) ,
463
+ ( "while(1);" , None ) ,
464
+ ( "while(() => {});" , None ) ,
465
+ ( "while(`foo`);" , None ) ,
466
+ ( "while(``);" , None ) ,
467
+ ( "while(`${'foo'}`);" , None ) ,
468
+ ( "while(`${'foo' + 'bar'}`);" , None ) ,
469
+ ( "do{ }while(x = 1)" , Some ( json!( [ { "checkLoops" : "all" } ] ) ) ) ,
470
+ ( "for (;true;) {};" , Some ( json!( [ { "checkLoops" : "all" } ] ) ) ) ,
373
471
// TODO
374
- // ("for(;true;);", None),
375
- // ("for(;``;);", None),
376
- // ("for(;`foo`;);", None),
377
- // ("for(;`foo${bar}`;);", None),
378
- // ("do{}while(true)", None),
379
- // ("do{}while('1')", None),
380
- // ("do{}while(0)", None),
381
- // ("do{}while(t = -2)", None),
382
- // ("do{}while(``)", None),
383
- // ("do{}while(`foo`)", None),
384
- // ("do{}while(`foo${bar}`)", None),
385
- // ("while([]);", None),
386
- // ("while(~!0);", None),
387
- // ("while(x = 1);", None),
388
- // ("while(function(){});", None),
389
- // ("while(true);", None),
390
- // ("while(1);", None),
391
- // ("while(() => {});", None),
392
- // ("while(`foo`);", None),
393
- // ("while(``);", None),
394
- // ("while(`${'foo'}`);", None),
395
- // ("while(`${'foo' + 'bar'}`);", None),
396
- // ("function* foo(){while(true){} yield 'foo';}", None),
397
- // ("function* foo(){while(true){if (true) {yield 'foo';}}}", None),
398
- // ("function* foo(){while(true){yield 'foo';} while(true) {}}", None),
399
- // ("var a = function* foo(){while(true){} yield 'foo';}", None),
400
- // ("while (true) { function* foo() {yield;}}", None),
472
+ // ("function* foo(){while(true){} yield 'foo';}", Some(json!([{ "checkLoops": "all" }]))),
473
+ // ("function* foo(){while(true){} yield 'foo';}", Some(json!([{ "checkLoops": true }]))),
474
+ // ("function* foo(){while(true){if (true) {yield 'foo';}}}",Some(json!([{ "checkLoops": "all" }])),),
475
+ // ("function* foo(){while(true){if (true) {yield 'foo';}}}",Some(json!([{ "checkLoops": true }])),),
476
+ // ("function* foo(){while(true){yield 'foo';} while(true) {}}", Some(json!([{ "checkLoops": "all" }])),),
477
+ // ("function* foo(){while(true){yield 'foo';} while(true) {}}",Some(json!([{ "checkLoops": true }])),),
478
+ // ("var a = function* foo(){while(true){} yield 'foo';}",Some(json!([{ "checkLoops": "all" }])),),
479
+ // ("var a = function* foo(){while(true){} yield 'foo';}",Some(json!([{ "checkLoops": true }])),),
480
+ // ("while (true) { function* foo() {yield;}}", Some(json!([{ "checkLoops": "all" }]))),
481
+ // ("while (true) { function* foo() {yield;}}", Some(json!([{ "checkLoops": true }]))),
401
482
// ("function* foo(){if (true) {yield 'foo';}}", None),
402
483
// ("function* foo() {for (let foo = yield; true;) {}}", None),
403
484
// ("function* foo() {for (foo = yield; true;) {}}", None),
404
- // ("function foo() {while (true) {function* bar() {while (true) {yield;}}}}", None ),
405
- // ("function foo() {while (true) {const bar = function*() {while (true) {yield;}}}}", None ),
485
+ // ("function foo() {while (true) {function* bar() {while (true) {yield;}}}}",Some(json!([{ "checkLoops": "all" }])), ),
486
+ // ("function foo() {while (true) {const bar = function*() {while (true) {yield;}}}}",Some(json!([{ "checkLoops": "all" }])), ),
406
487
// ("function* foo() { for (let foo = 1 + 2 + 3 + (yield); true; baz) {}}", None),
407
488
] ;
408
489
0 commit comments