diff --git a/eslint/babel-eslint-plugin/README.md b/eslint/babel-eslint-plugin/README.md index 3571f1923c..70f6f833a5 100644 --- a/eslint/babel-eslint-plugin/README.md +++ b/eslint/babel-eslint-plugin/README.md @@ -32,7 +32,8 @@ original ones as well!). "babel/no-invalid-this": 1, "babel/object-curly-spacing": 1, "babel/quotes": 1, - "babel/semi": 1 + "babel/semi": 1, + "babel/no-unused-expressions": 1 } } ``` @@ -47,6 +48,7 @@ Each rule corresponds to a core `eslint` rule, and has the same options. - `babel/object-curly-spacing`: doesn't complain about `export x from "mod";` or `export * as x from "mod";` (🛠) - `babel/quotes`: doesn't complain about JSX fragment shorthand syntax (`<>foo;`) - `babel/semi`: doesn't fail when using `for await (let something of {})`. Includes class properties (🛠) +- `babel/no-unused-expressions`: doesn't fail when using `do` expressions #### Deprecated diff --git a/eslint/babel-eslint-plugin/index.js b/eslint/babel-eslint-plugin/index.js index 98fcdc1376..39962b9f75 100644 --- a/eslint/babel-eslint-plugin/index.js +++ b/eslint/babel-eslint-plugin/index.js @@ -14,6 +14,7 @@ module.exports = { 'object-shorthand': require('./rules/object-shorthand'), 'quotes': require('./rules/quotes'), 'semi': require('./rules/semi'), + 'no-unused-expressions': require('./rules/no-unused-expressions'), }, rulesConfig: { 'generator-star-spacing': 0, @@ -28,5 +29,6 @@ module.exports = { 'no-invalid-this': 0, 'quotes': 0, 'semi': 0, + 'no-unused-expressions': 0, } }; diff --git a/eslint/babel-eslint-plugin/rules/no-unused-expressions.js b/eslint/babel-eslint-plugin/rules/no-unused-expressions.js new file mode 100644 index 0000000000..94628cb32a --- /dev/null +++ b/eslint/babel-eslint-plugin/rules/no-unused-expressions.js @@ -0,0 +1,49 @@ +"use strict"; + +const ruleComposer = require('eslint-rule-composer'); +const eslint = require('eslint'); +const rule = new eslint.Linter().getRules().get('no-unused-expressions'); + +/** + * @param {ASTNode} node - any node + * @returns {boolean} whether the given node is either an IfStatement or an + * ExpressionStatement and is the last node in the body of a BlockStatement + */ +function isFinalStatementInBlockStatement(node) { + const parent = node.parent; + return /^(?:If|Expression)Statement$/.test(node.type) && + parent.type === 'BlockStatement' && + parent.body[parent.body.length - 1] === node; +} + +/** + * @param {ASTNode} node - any node + * @returns {boolean} whether the given node represents an unbroken chain of + * tail ExpressionStatements and IfStatements within a DoExpression + */ +function isInDoStatement(node) { + if (!node) return false; + + if (node.type === 'DoExpression') return true; + + // this is an `else if` + if ( + node.type === 'IfStatement' && + node.parent && + node.parent.type === 'IfStatement' + ) { + return isInDoStatement(node.parent); + } + + if (isFinalStatementInBlockStatement(node)) { + return isInDoStatement(node.parent.parent); + } + + return false; +} + +module.exports = ruleComposer.filterReports( + rule, + (problem, metadata) => !isInDoStatement(problem.node) +); + diff --git a/eslint/babel-eslint-plugin/tests/rules/no-unused-expressions.js b/eslint/babel-eslint-plugin/tests/rules/no-unused-expressions.js new file mode 100644 index 0000000000..a8156ec174 --- /dev/null +++ b/eslint/babel-eslint-plugin/tests/rules/no-unused-expressions.js @@ -0,0 +1,143 @@ +/** + * @fileoverview Tests for no-unused-expressions rule. + * @author Michael Ficarra + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../rules/no-unused-expressions"), + RuleTester = require("../RuleTester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester(); + +ruleTester.run("no-unused-expressions", rule, { + valid: [ + // Original test cases. + "function f(){}", + "a = b", + "new a", + "{}", + "f(); g()", + "i++", + "a()", + { code: "a && a()", options: [{ allowShortCircuit: true }] }, + { code: "a() || (b = c)", options: [{ allowShortCircuit: true }] }, + { code: "a ? b() : c()", options: [{ allowTernary: true }] }, + { code: "a ? b() || (c = d) : e()", options: [{ allowShortCircuit: true, allowTernary: true }] }, + "delete foo.bar", + "void new C", + "\"use strict\";", + "\"directive one\"; \"directive two\"; f();", + "function foo() {\"use strict\"; return true; }", + { code: "var foo = () => {\"use strict\"; return true; }", parserOptions: { ecmaVersion: 6 } }, + "function foo() {\"directive one\"; \"directive two\"; f(); }", + "function foo() { var foo = \"use strict\"; return true; }", + { + code: "function* foo(){ yield 0; }", + parserOptions: { ecmaVersion: 6 } + }, + { + code: "async function foo() { await 5; }", + parserOptions: { ecmaVersion: 8 } + }, + { + code: "async function foo() { await foo.bar; }", + parserOptions: { ecmaVersion: 8 } + }, + { + code: "async function foo() { bar && await baz; }", + options: [{ allowShortCircuit: true }], + parserOptions: { ecmaVersion: 8 } + }, + { + code: "async function foo() { foo ? await bar : await baz; }", + options: [{ allowTernary: true }], + parserOptions: { ecmaVersion: 8 } + }, + { + code: "tag`tagged template literal`", + options: [{ allowTaggedTemplates: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "shouldNotBeAffectedByAllowTemplateTagsOption()", + options: [{ allowTaggedTemplates: true }], + parserOptions: { ecmaVersion: 6 } + }, + + // Babel-specific test cases. + "let a = do { if (foo) { foo.bar } }", + "let a = do { foo }", + "let a = do { let b = 2; foo; }", + "let a = do { (foo + 1) }", + "let a = do { if (foo) { if (foo.bar) { foo.bar } } }", + "let a = do { if (foo) { if (foo.bar) { foo.bar } else if (foo.baz) { foo.baz } } }", + + ], + invalid: [ + { code: "0", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "f(), 0", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "{0}", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "[]", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a && b();", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a() || false", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a || (b = c)", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a ? b() || (c = d) : e", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { + code: "`untagged template literal`", + parserOptions: { ecmaVersion: 6 }, + errors: ["Expected an assignment or function call and instead saw an expression."] + }, + { + code: "tag`tagged template literal`", + parserOptions: { ecmaVersion: 6 }, + errors: ["Expected an assignment or function call and instead saw an expression."] + }, + { code: "a && b()", options: [{ allowTernary: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a ? b() : c()", options: [{ allowShortCircuit: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a || b", options: [{ allowShortCircuit: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a() && b", options: [{ allowShortCircuit: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a ? b : 0", options: [{ allowTernary: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "a ? b : c()", options: [{ allowTernary: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "foo.bar;", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "!a", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "+a", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "\"directive one\"; f(); \"directive two\";", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "function foo() {\"directive one\"; f(); \"directive two\"; }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "if (0) { \"not a directive\"; f(); }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "function foo() { var foo = true; \"use strict\"; }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "var foo = () => { var foo = true; \"use strict\"; }", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { + code: "`untagged template literal`", + options: [{ allowTaggedTemplates: true }], + parserOptions: { ecmaVersion: 6 }, + errors: ["Expected an assignment or function call and instead saw an expression."] + }, + { + code: "`untagged template literal`", + options: [{ allowTaggedTemplates: false }], + parserOptions: { ecmaVersion: 6 }, + errors: ["Expected an assignment or function call and instead saw an expression."] + }, + { + code: "tag`tagged template literal`", + options: [{ allowTaggedTemplates: false }], + parserOptions: { ecmaVersion: 6 }, + errors: ["Expected an assignment or function call and instead saw an expression."] + }, + + // Babel-specific test cases. + { code: "let a = do { foo; let b = 2; }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + { code: "let a = do { if (foo) { foo.bar } else { a; bar.foo } }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }, + + ] +});