From ee9f100a2f9a874c2b361482742686eeaa9bdac7 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Wed, 10 Jun 2020 05:01:04 +0900 Subject: [PATCH] fix(eslint-plugin): [no-unused-expressions] handle ternary and short-circuit options (#2194) --- .../src/rules/no-unused-expressions.ts | 38 +++++++++++++++---- .../tests/rules/no-unused-expressions.test.ts | 36 ++++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 16 ++++---- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unused-expressions.ts b/packages/eslint-plugin/src/rules/no-unused-expressions.ts index 151b8611c24..692b484f45d 100644 --- a/packages/eslint-plugin/src/rules/no-unused-expressions.ts +++ b/packages/eslint-plugin/src/rules/no-unused-expressions.ts @@ -1,4 +1,7 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-unused-expressions'; import * as util from '../util'; @@ -18,17 +21,36 @@ export default util.createRule({ schema: baseRule.meta.schema, messages: baseRule.meta.messages, }, - defaultOptions: [], - create(context) { + defaultOptions: [ + { + allowShortCircuit: false, + allowTernary: false, + allowTaggedTemplates: false, + }, + ], + create(context, options) { const rules = baseRule.create(context); + const { allowShortCircuit = false, allowTernary = false } = options[0]; + + function isValidExpression(node: TSESTree.Node): boolean { + if (allowShortCircuit && node.type === AST_NODE_TYPES.LogicalExpression) { + return isValidExpression(node.right); + } + if (allowTernary && node.type === AST_NODE_TYPES.ConditionalExpression) { + return ( + isValidExpression(node.alternate) && + isValidExpression(node.consequent) + ); + } + return ( + node.type === AST_NODE_TYPES.OptionalCallExpression || + node.type === AST_NODE_TYPES.ImportExpression + ); + } return { ExpressionStatement(node): void { - if ( - node.directive || - node.expression.type === AST_NODE_TYPES.OptionalCallExpression || - node.expression.type === AST_NODE_TYPES.ImportExpression - ) { + if (node.directive || isValidExpression(node.expression)) { return; } diff --git a/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts b/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts index d1b782348d8..55264e918e7 100644 --- a/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts @@ -71,6 +71,18 @@ ruleTester.run('no-unused-expressions', rule, { ` import('./foo').then(() => {}); `, + { + code: 'foo && foo?.();', + options: [{ allowShortCircuit: true }], + }, + { + code: "foo && import('./foo');", + options: [{ allowShortCircuit: true }], + }, + { + code: "foo ? import('./foo') : import('./bar');", + options: [{ allowTernary: true }], + }, ], invalid: [ { @@ -259,5 +271,29 @@ function foo() { }, ]), }, + { + code: 'foo && foo?.bar;', + options: [{ allowShortCircuit: true }], + errors: error([ + { + line: 1, + endLine: 1, + column: 1, + endColumn: 17, + }, + ]), + }, + { + code: 'foo ? foo?.bar : bar.baz;', + options: [{ allowTernary: true }], + errors: error([ + { + line: 1, + endLine: 1, + column: 1, + endColumn: 26, + }, + ]), + }, ], }); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index c27e620399e..31a752878b8 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -411,15 +411,13 @@ declare module 'eslint/lib/rules/no-unused-expressions' { const rule: TSESLint.RuleModule< 'expected', - ( - | 'all' - | 'local' - | { - allowShortCircuit?: boolean; - allowTernary?: boolean; - allowTaggedTemplates?: boolean; - } - )[], + [ + { + allowShortCircuit?: boolean; + allowTernary?: boolean; + allowTaggedTemplates?: boolean; + }, + ], { ExpressionStatement(node: TSESTree.ExpressionStatement): void; }