From b829dbcc38859f302283103b3c9a4684d94c0eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 5 Jul 2019 01:04:10 -0400 Subject: [PATCH] Add `no-inline-assertions` rule (#262) Co-authored-by: Sindre Sorhus --- docs/rules/no-inline-assertions.md | 25 ++++++++++++++++ index.js | 1 + readme.md | 2 ++ rules/no-inline-assertions.js | 46 +++++++++++++++++++++++++++++ test/no-inline-assertions.js | 47 ++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 docs/rules/no-inline-assertions.md create mode 100644 rules/no-inline-assertions.js create mode 100644 test/no-inline-assertions.js diff --git a/docs/rules/no-inline-assertions.md b/docs/rules/no-inline-assertions.md new file mode 100644 index 00000000..2b092107 --- /dev/null +++ b/docs/rules/no-inline-assertions.md @@ -0,0 +1,25 @@ +# Ensure assertions are not called from inline arrow functions + +The test implementation should not purely consist of an inline assertion as assertions do not return a value and having them inline also makes the tests less readable. + +This rule is fixable. It will wrap the assertion in braces `{}`. It will not do any whitespace or style changes. + + +## Fail + +```js +import test from 'ava'; + +test('foo', t => t.true(fn())); +``` + + +## Pass + +```js +import test from 'ava'; + +test('foo', t => { + t.true(fn()); +}); +``` diff --git a/index.js b/index.js index 2cd3aec1..12fcddf6 100644 --- a/index.js +++ b/index.js @@ -28,6 +28,7 @@ module.exports = { 'ava/no-identical-title': 'error', 'ava/no-ignored-test-files': 'error', 'ava/no-import-test-files': 'error', + 'ava/no-inline-assertions': 'error', 'ava/no-invalid-end': 'error', 'ava/no-nested-tests': 'error', 'ava/no-only-test': 'error', diff --git a/readme.md b/readme.md index 24813d19..8e40eac8 100644 --- a/readme.md +++ b/readme.md @@ -46,6 +46,7 @@ Configure it in `package.json`. "ava/no-identical-title": "error", "ava/no-ignored-test-files": "error", "ava/no-import-test-files": "error", + "ava/no-inline-assertions": "error", "ava/no-invalid-end": "error", "ava/no-nested-tests": "error", "ava/no-only-test": "error", @@ -83,6 +84,7 @@ The rules will only activate in test files. - [no-identical-title](docs/rules/no-identical-title.md) - Ensure no tests have the same title. - [no-ignored-test-files](docs/rules/no-ignored-test-files.md) - Ensure no tests are written in ignored files. - [no-import-test-files](docs/rules/no-import-test-files.md) - Ensure no test files are imported anywhere. +- [no-inline-assertions](docs/rules/no-inline-assertions.md) - Ensure assertions are not called from inline arrow functions. *(fixable)* - [no-invalid-end](docs/rules/no-invalid-end.md) - Ensure `t.end()` is only called inside `test.cb()`. - [no-nested-tests](docs/rules/no-nested-tests.md) - Ensure no tests are nested. - [no-only-test](docs/rules/no-only-test.md) - Ensure no `test.only()` are present. *(fixable)* diff --git a/rules/no-inline-assertions.js b/rules/no-inline-assertions.js new file mode 100644 index 00000000..d1b313b6 --- /dev/null +++ b/rules/no-inline-assertions.js @@ -0,0 +1,46 @@ +'use strict'; +const {visitIf} = require('enhance-visitors'); +const createAvaRule = require('../create-ava-rule'); +const util = require('../util'); + +const create = context => { + const ava = createAvaRule(); + + return ava.merge({ + CallExpression: visitIf([ + ava.isInTestFile, + ava.isTestNode + ])(node => { + const functionArgIndex = node.arguments.length - 1; + if (functionArgIndex > 1) { + return; + } + + const functionArg = node.arguments[functionArgIndex]; + + if (!util.isFunctionExpression(functionArg)) { + return; + } + + const {body} = functionArg; + if (body.type === 'CallExpression') { + context.report({ + node, + message: 'The test implementation should not be an inline arrow function.', + fix: fixer => [fixer.insertTextBefore(body, '{'), fixer.insertTextAfter(body, '}')] + }); + } + }) + }); +}; + +module.exports = { + create, + meta: { + docs: { + url: util.getDocsUrl(__filename) + }, + type: 'suggestion', + fixable: 'code' + } +}; diff --git a/test/no-inline-assertions.js b/test/no-inline-assertions.js new file mode 100644 index 00000000..834aacfc --- /dev/null +++ b/test/no-inline-assertions.js @@ -0,0 +1,47 @@ +import test from 'ava'; +import avaRuleTester from 'eslint-ava-rule-tester'; +import rule from '../rules/no-inline-assertions'; + +const ruleTester = avaRuleTester(test, { + env: { + es6: true + } +}); + +const errors = [{ruleId: 'no-inline-assertions'}]; +const header = 'const test = require(\'ava\');\n'; + +ruleTester.run('no-todo-test', rule, { + valid: [ + // Shouldn't be triggered as the test implementation is not an inline arrow function + header + 'test("my test name", t => {\n t.true(fn()); \n});', + header + 'test("my test name", function (t) { foo(); });', + // Shouldn't be triggered since test body is empty + header + 'test("my test name", () => {});', + header + 'test("my test name", t => {});', + // Shouldn't be triggered since test body is ill-defined + header + 'test("my test name", t => "foo");', + // Shouldn't be triggered since it's not a test file + 'test.todo("my test name");', + // Shouldn't be triggered since the signature is incorrect + header + 'test.todo("my test name", "bar");', + header + 'test.todo("my test name", undefined, t => {})' + ], + invalid: [ + { + code: header + 'test("my test name", t => t.skip());', + errors, + output: header + 'test("my test name", t => {t.skip()});' + }, + { + code: header + 'test("my test name", t => t.true(fn()));', + errors, + output: header + 'test("my test name", t => {t.true(fn())});' + }, + { + code: header + 'test("my test name", t => \n t.true(fn()));', + errors, + output: header + 'test("my test name", t => \n {t.true(fn())});' + } + ] +});