@@ -143,7 +143,10 @@ export default createRule({
143
143
node : TSESTree . Node ,
144
144
expectedObjectNode : TSESTree . Node ,
145
145
) : boolean {
146
- if ( node . type === AST_NODE_TYPES . MemberExpression ) {
146
+ if (
147
+ node . type === AST_NODE_TYPES . MemberExpression ||
148
+ node . type === AST_NODE_TYPES . OptionalMemberExpression
149
+ ) {
147
150
return (
148
151
getPropertyName ( node , globalScope ) === 'length' &&
149
152
isSameTokens ( node . object , expectedObjectNode )
@@ -191,7 +194,7 @@ export default createRule({
191
194
* @param node The member expression node to get.
192
195
*/
193
196
function getPropertyRange (
194
- node : TSESTree . MemberExpression ,
197
+ node : TSESTree . MemberExpression | TSESTree . OptionalMemberExpression ,
195
198
) : [ number , number ] {
196
199
const dotOrOpenBracket = sourceCode . getTokenAfter (
197
200
node . object ,
@@ -269,26 +272,30 @@ export default createRule({
269
272
* @param fixer The rule fixer.
270
273
* @param node The node which was reported.
271
274
* @param kind The kind of the report.
272
- * @param negative The flag to fix to negative condition.
275
+ * @param isNegative The flag to fix to negative condition.
273
276
*/
274
277
function * fixWithRightOperand (
275
278
fixer : TSESLint . RuleFixer ,
276
279
node : TSESTree . BinaryExpression ,
277
280
kind : 'start' | 'end' ,
278
- negative : boolean ,
281
+ isNegative : boolean ,
282
+ isOptional : boolean ,
279
283
) : IterableIterator < TSESLint . RuleFix > {
280
284
// left is CallExpression or MemberExpression.
281
- const leftNode = ( node . left . type === AST_NODE_TYPES . CallExpression
285
+ const leftNode = ( node . left . type === AST_NODE_TYPES . CallExpression ||
286
+ node . left . type === AST_NODE_TYPES . OptionalCallExpression
282
287
? node . left . callee
283
- : node . left ) as TSESTree . MemberExpression ;
288
+ : node . left ) as
289
+ | TSESTree . MemberExpression
290
+ | TSESTree . OptionalMemberExpression ;
284
291
const propertyRange = getPropertyRange ( leftNode ) ;
285
292
286
- if ( negative ) {
293
+ if ( isNegative ) {
287
294
yield fixer . insertTextBefore ( node , '!' ) ;
288
295
}
289
296
yield fixer . replaceTextRange (
290
297
[ propertyRange [ 0 ] , node . right . range [ 0 ] ] ,
291
- `. ${ kind } sWith(` ,
298
+ `${ isOptional ? '?.' : '.' } ${ kind } sWith(` ,
292
299
) ;
293
300
yield fixer . replaceTextRange ( [ node . right . range [ 1 ] , node . range [ 1 ] ] , ')' ) ;
294
301
}
@@ -306,16 +313,21 @@ export default createRule({
306
313
node : TSESTree . BinaryExpression ,
307
314
kind : 'start' | 'end' ,
308
315
negative : boolean ,
316
+ isOptional : boolean ,
309
317
) : IterableIterator < TSESLint . RuleFix > {
310
- const callNode = node . left as TSESTree . CallExpression ;
311
- const calleeNode = callNode . callee as TSESTree . MemberExpression ;
318
+ const callNode = node . left as
319
+ | TSESTree . CallExpression
320
+ | TSESTree . OptionalCallExpression ;
321
+ const calleeNode = callNode . callee as
322
+ | TSESTree . MemberExpression
323
+ | TSESTree . OptionalMemberExpression ;
312
324
313
325
if ( negative ) {
314
326
yield fixer . insertTextBefore ( node , '!' ) ;
315
327
}
316
328
yield fixer . replaceTextRange (
317
329
getPropertyRange ( calleeNode ) ,
318
- `. ${ kind } sWith` ,
330
+ `${ isOptional ? '?.' : '.' } ${ kind } sWith` ,
319
331
) ;
320
332
yield fixer . removeRange ( [ callNode . range [ 1 ] , node . range [ 1 ] ] ) ;
321
333
}
@@ -325,13 +337,18 @@ export default createRule({
325
337
// foo.charAt(0) === "a"
326
338
// foo[foo.length - 1] === "a"
327
339
// foo.charAt(foo.length - 1) === "a"
328
- [ String ( [
329
- 'BinaryExpression > MemberExpression.left[computed=true]' ,
330
- 'BinaryExpression > CallExpression.left > MemberExpression.callee[property.name="charAt"][computed=false]' ,
331
- ] ) ] ( node : TSESTree . MemberExpression ) : void {
340
+ [ [
341
+ 'BinaryExpression > :matches(MemberExpression, OptionalMemberExpression).left[computed=true]' ,
342
+ 'BinaryExpression > :matches(CallExpression, OptionalCallExpression).left > :matches(MemberExpression, OptionalMemberExpression).callee[property.name="charAt"][computed=false]' ,
343
+ ] . join ( ', ' ) ] (
344
+ node : TSESTree . MemberExpression | TSESTree . OptionalMemberExpression ,
345
+ ) : void {
332
346
let parentNode = node . parent ! ;
333
347
let indexNode : TSESTree . Node | null = null ;
334
- if ( parentNode . type === AST_NODE_TYPES . CallExpression ) {
348
+ if (
349
+ parentNode . type === AST_NODE_TYPES . CallExpression ||
350
+ parentNode . type === AST_NODE_TYPES . OptionalCallExpression
351
+ ) {
335
352
if ( parentNode . arguments . length === 1 ) {
336
353
indexNode = parentNode . arguments [ 0 ] ;
337
354
}
@@ -368,16 +385,19 @@ export default createRule({
368
385
eqNode ,
369
386
isStartsWith ? 'start' : 'end' ,
370
387
eqNode . operator . startsWith ( '!' ) ,
388
+ node . optional ,
371
389
) ;
372
390
} ,
373
391
} ) ;
374
392
} ,
375
393
376
394
// foo.indexOf('bar') === 0
377
- 'BinaryExpression > CallExpression.left > MemberExpression.callee[property.name="indexOf"][computed=false]' (
378
- node : TSESTree . MemberExpression ,
395
+ 'BinaryExpression > :matches( CallExpression, OptionalCallExpression) .left > :matches( MemberExpression, OptionalMemberExpression) .callee[property.name="indexOf"][computed=false]' (
396
+ node : TSESTree . MemberExpression | TSESTree . OptionalMemberExpression ,
379
397
) : void {
380
- const callNode = node . parent ! as TSESTree . CallExpression ;
398
+ const callNode = node . parent as
399
+ | TSESTree . CallExpression
400
+ | TSESTree . OptionalCallExpression ;
381
401
const parentNode = callNode . parent ! ;
382
402
383
403
if (
@@ -399,17 +419,20 @@ export default createRule({
399
419
parentNode ,
400
420
'start' ,
401
421
parentNode . operator . startsWith ( '!' ) ,
422
+ node . optional ,
402
423
) ;
403
424
} ,
404
425
} ) ;
405
426
} ,
406
427
407
428
// foo.lastIndexOf('bar') === foo.length - 3
408
429
// foo.lastIndexOf(bar) === foo.length - bar.length
409
- 'BinaryExpression > CallExpression.left > MemberExpression.callee[property.name="lastIndexOf"][computed=false]' (
410
- node : TSESTree . MemberExpression ,
430
+ 'BinaryExpression > :matches( CallExpression, OptionalCallExpression) .left > :matches( MemberExpression, OptionalMemberExpression) .callee[property.name="lastIndexOf"][computed=false]' (
431
+ node : TSESTree . MemberExpression | TSESTree . OptionalMemberExpression ,
411
432
) : void {
412
- const callNode = node . parent ! as TSESTree . CallExpression ;
433
+ const callNode = node . parent ! as
434
+ | TSESTree . CallExpression
435
+ | TSESTree . OptionalCallExpression ;
413
436
const parentNode = callNode . parent ! ;
414
437
415
438
if (
@@ -434,17 +457,20 @@ export default createRule({
434
457
parentNode ,
435
458
'end' ,
436
459
parentNode . operator . startsWith ( '!' ) ,
460
+ node . optional ,
437
461
) ;
438
462
} ,
439
463
} ) ;
440
464
} ,
441
465
442
466
// foo.match(/^bar/) === null
443
467
// foo.match(/bar$/) === null
444
- 'BinaryExpression > CallExpression.left > MemberExpression.callee[property.name="match"][computed=false]' (
445
- node : TSESTree . MemberExpression ,
468
+ 'BinaryExpression > :matches( CallExpression, OptionalCallExpression) .left > :matches( MemberExpression, OptionalMemberExpression) .callee[property.name="match"][computed=false]' (
469
+ node : TSESTree . MemberExpression | TSESTree . OptionalMemberExpression ,
446
470
) : void {
447
- const callNode = node . parent as TSESTree . CallExpression ;
471
+ const callNode = node . parent as
472
+ | TSESTree . CallExpression
473
+ | TSESTree . OptionalCallExpression ;
448
474
const parentNode = callNode . parent as TSESTree . BinaryExpression ;
449
475
if (
450
476
! isEqualityComparison ( parentNode ) ||
@@ -472,7 +498,9 @@ export default createRule({
472
498
}
473
499
yield fixer . replaceTextRange (
474
500
getPropertyRange ( node ) ,
475
- `.${ isStartsWith ? 'start' : 'end' } sWith` ,
501
+ `${ node . optional ? '?.' : '.' } ${
502
+ isStartsWith ? 'start' : 'end'
503
+ } sWith`,
476
504
) ;
477
505
yield fixer . replaceText (
478
506
callNode . arguments [ 0 ] ,
@@ -489,11 +517,15 @@ export default createRule({
489
517
// foo.substring(0, 3) === 'bar'
490
518
// foo.substring(foo.length - 3) === 'bar'
491
519
// foo.substring(foo.length - 3, foo.length) === 'bar'
492
- [ String ( [
493
- 'CallExpression > MemberExpression.callee[property.name=slice][computed=false]' ,
494
- 'CallExpression > MemberExpression.callee[property.name=substring][computed=false]' ,
495
- ] ) ] ( node : TSESTree . MemberExpression ) : void {
496
- const callNode = node . parent ! as TSESTree . CallExpression ;
520
+ [ [
521
+ ':matches(CallExpression, OptionalCallExpression) > :matches(MemberExpression, OptionalMemberExpression).callee[property.name="slice"][computed=false]' ,
522
+ ':matches(CallExpression, OptionalCallExpression) > :matches(MemberExpression, OptionalMemberExpression).callee[property.name="substring"][computed=false]' ,
523
+ ] . join ( ', ' ) ] (
524
+ node : TSESTree . MemberExpression | TSESTree . OptionalMemberExpression ,
525
+ ) : void {
526
+ const callNode = node . parent ! as
527
+ | TSESTree . CallExpression
528
+ | TSESTree . OptionalCallExpression ;
497
529
const parentNode = callNode . parent ! ;
498
530
if (
499
531
! isEqualityComparison ( parentNode ) ||
@@ -555,17 +587,20 @@ export default createRule({
555
587
parentNode ,
556
588
isStartsWith ? 'start' : 'end' ,
557
589
parentNode . operator . startsWith ( '!' ) ,
590
+ node . optional ,
558
591
) ;
559
592
} ,
560
593
} ) ;
561
594
} ,
562
595
563
596
// /^bar/.test(foo)
564
597
// /bar$/.test(foo)
565
- 'CallExpression > MemberExpression.callee[property.name="test"][computed=false]' (
566
- node : TSESTree . MemberExpression ,
598
+ ':matches( CallExpression, OptionalCallExpression) > :matches( MemberExpression, OptionalMemberExpression) .callee[property.name="test"][computed=false]' (
599
+ node : TSESTree . MemberExpression | TSESTree . OptionalMemberExpression ,
567
600
) : void {
568
- const callNode = node . parent as TSESTree . CallExpression ;
601
+ const callNode = node . parent as
602
+ | TSESTree . CallExpression
603
+ | TSESTree . OptionalCallExpression ;
569
604
const parsed =
570
605
callNode . arguments . length === 1 ? parseRegExp ( node . object ) : null ;
571
606
if ( parsed == null ) {
@@ -585,7 +620,9 @@ export default createRule({
585
620
argNode . type !== AST_NODE_TYPES . TemplateLiteral &&
586
621
argNode . type !== AST_NODE_TYPES . Identifier &&
587
622
argNode . type !== AST_NODE_TYPES . MemberExpression &&
588
- argNode . type !== AST_NODE_TYPES . CallExpression ;
623
+ argNode . type !== AST_NODE_TYPES . OptionalMemberExpression &&
624
+ argNode . type !== AST_NODE_TYPES . CallExpression &&
625
+ argNode . type !== AST_NODE_TYPES . OptionalCallExpression ;
589
626
590
627
yield fixer . removeRange ( [ callNode . range [ 0 ] , argNode . range [ 0 ] ] ) ;
591
628
if ( needsParen ) {
@@ -594,7 +631,11 @@ export default createRule({
594
631
}
595
632
yield fixer . insertTextAfter (
596
633
argNode ,
597
- `.${ methodName } (${ JSON . stringify ( text ) } ` ,
634
+ `${
635
+ callNode . type === AST_NODE_TYPES . OptionalCallExpression
636
+ ? '?.'
637
+ : '.'
638
+ } ${ methodName } (${ JSON . stringify ( text ) } `,
598
639
) ;
599
640
} ,
600
641
} ) ;
0 commit comments