2020-01-03 08:32:40 +00:00
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Copyright ( c ) Microsoft Corporation . All rights reserved .
* Licensed under the MIT License . See License . txt in the project root for license information .
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * /
// FORKED FROM https://github.com/eslint/eslint/blob/b23ad0d789a909baf8d7c41a35bc53df932eaf30/lib/rules/no-unused-expressions.js
// and added support for `OptionalCallExpression`, see https://github.com/facebook/create-react-app/issues/8107 and https://github.com/eslint/eslint/issues/12642
/ * *
* @fileoverview Flag expressions in statement position that do not side effect
* @author Michael Ficarra
* /
2020-09-24 15:10:39 +00:00
import * as eslint from 'eslint' ;
import { TSESTree } from '@typescript-eslint/experimental-utils' ;
import * as ESTree from 'estree' ;
2020-01-03 08:32:40 +00:00
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module .exports = {
meta : {
type : 'suggestion' ,
docs : {
description : 'disallow unused expressions' ,
category : 'Best Practices' ,
recommended : false ,
url : 'https://eslint.org/docs/rules/no-unused-expressions'
} ,
schema : [
{
type : 'object' ,
properties : {
allowShortCircuit : {
type : 'boolean' ,
default : false
} ,
allowTernary : {
type : 'boolean' ,
default : false
} ,
allowTaggedTemplates : {
type : 'boolean' ,
default : false
}
} ,
additionalProperties : false
}
]
} ,
2020-09-24 15:10:39 +00:00
create ( context : eslint.Rule.RuleContext ) {
2020-01-03 08:32:40 +00:00
const config = context . options [ 0 ] || { } ,
allowShortCircuit = config . allowShortCircuit || false ,
allowTernary = config . allowTernary || false ,
allowTaggedTemplates = config . allowTaggedTemplates || false ;
// eslint-disable-next-line jsdoc/require-description
2020-09-24 15:10:39 +00:00
/ * *
* @param node any node
* @returns whether the given node structurally represents a directive
* /
function looksLikeDirective ( node : TSESTree.Node ) : boolean {
2020-01-03 08:32:40 +00:00
return node . type === 'ExpressionStatement' &&
node . expression . type === 'Literal' && typeof node . expression . value === 'string' ;
}
// eslint-disable-next-line jsdoc/require-description
2020-09-24 15:10:39 +00:00
/ * *
* @param predicate ( [ a ] - > Boolean ) the function used to make the determination
* @param list the input list
* @returns the leading sequence of members in the given list that pass the given predicate
* /
function takeWhile < T > ( predicate : ( item : T ) = > boolean , list : T [ ] ) : T [ ] {
2020-01-03 08:32:40 +00:00
for ( let i = 0 ; i < list . length ; ++ i ) {
if ( ! predicate ( list [ i ] ) ) {
return list . slice ( 0 , i ) ;
}
}
return list . slice ( ) ;
}
// eslint-disable-next-line jsdoc/require-description
2020-09-24 15:10:39 +00:00
/ * *
* @param node a Program or BlockStatement node
* @returns the leading sequence of directive nodes in the given node ' s body
* /
function directives ( node : TSESTree.Program | TSESTree . BlockStatement ) : TSESTree . Node [ ] {
2020-01-03 08:32:40 +00:00
return takeWhile ( looksLikeDirective , node . body ) ;
}
// eslint-disable-next-line jsdoc/require-description
2020-09-24 15:10:39 +00:00
/ * *
* @param node any node
* @param ancestors the given node ' s ancestors
* @returns whether the given node is considered a directive in its current position
* /
function isDirective ( node : TSESTree.Node , ancestors : TSESTree.Node [ ] ) : boolean {
2020-01-03 08:32:40 +00:00
const parent = ancestors [ ancestors . length - 1 ] ,
grandparent = ancestors [ ancestors . length - 2 ] ;
return ( parent . type === 'Program' || parent . type === 'BlockStatement' &&
( / F u n c t i o n / u . t e s t ( g r a n d p a r e n t . t y p e ) ) ) & &
directives ( parent ) . indexOf ( node ) >= 0 ;
}
2020-09-24 15:10:39 +00:00
/ * *
* Determines whether or not a given node is a valid expression . Recurses on short circuit eval and ternary nodes if enabled by flags .
* @param node any node
* @returns whether the given node is a valid expression
* /
function isValidExpression ( node : TSESTree.Node ) : boolean {
2020-01-03 08:32:40 +00:00
if ( allowTernary ) {
// Recursive check for ternary and logical expressions
if ( node . type === 'ConditionalExpression' ) {
return isValidExpression ( node . consequent ) && isValidExpression ( node . alternate ) ;
}
}
if ( allowShortCircuit ) {
if ( node . type === 'LogicalExpression' ) {
return isValidExpression ( node . right ) ;
}
}
if ( allowTaggedTemplates && node . type === 'TaggedTemplateExpression' ) {
return true ;
}
2022-01-27 00:29:14 +00:00
if ( node . type === 'ExpressionStatement' ) {
return isValidExpression ( node . expression ) ;
}
return / ^ ( ? : A s s i g n m e n t | O p t i o n a l C a l l | C a l l | N e w | U p d a t e | Y i e l d | A w a i t | C h a i n ) E x p r e s s i o n $ / u . t e s t ( n o d e . t y p e ) | |
2020-01-03 08:32:40 +00:00
( node . type === 'UnaryExpression' && [ 'delete' , 'void' ] . indexOf ( node . operator ) >= 0 ) ;
}
return {
2020-09-24 15:10:39 +00:00
ExpressionStatement ( node : TSESTree.ExpressionStatement ) {
if ( ! isValidExpression ( node . expression ) && ! isDirective ( node , < TSESTree.Node [ ] > context . getAncestors ( ) ) ) {
2022-01-27 00:29:14 +00:00
context . report ( { node : < ESTree.Node > node , message : ` Expected an assignment or function call and instead saw an expression. ${ node . expression } ` } ) ;
2020-01-03 08:32:40 +00:00
}
}
} ;
}
} ;