-
-
Notifications
You must be signed in to change notification settings - Fork 204
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #920 from v-korshun/no-noop-setup-on-error-in-before
Add new rule `no-noop-setup-on-error-in-before`
- Loading branch information
Showing
5 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# no-noop-setup-on-error-in-before | ||
|
||
:wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. | ||
|
||
Disallows use of no-op `setupOnerror` in `before`/`beforeEach` since it could mask errors or rejections in tests unintentionally | ||
|
||
## Rule Details | ||
|
||
This rule aims to avoid single no-op `setupOnerror` for all tests in the module. In certain situations(maybe the majority of the test cases throw an error), the author of the test might resort to the definition of single no-op `setupOnerror` in `before`/`beforeEach`. This might make sense at the time of writing the tests, but modules tend to grow and no-op error handler would swallow any promise rejection or error that otherwise would be caught by test. | ||
|
||
## Examples | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
|
||
module('foo', function (hooks) { | ||
hooks.beforeEach(function () { | ||
setupOnerror(() => {}); | ||
}); | ||
}); | ||
``` | ||
|
||
```js | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
|
||
module('foo', function (hooks) { | ||
hooks.before(function () { | ||
setupOnerror(() => {}); | ||
}); | ||
}); | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module, test } from 'qunit'; | ||
|
||
module('foo', function (hooks) { | ||
test('something', function () { | ||
setupOnerror((error) => { | ||
assert.equal(error.message, 'test', 'Should have message'); | ||
}); | ||
}); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
'use strict'; | ||
|
||
const { getImportIdentifier } = require('../utils/import'); | ||
const types = require('../utils/types'); | ||
|
||
//------------------------------------------------------------------------------ | ||
// General rule - Disallows no-op `setupOnError` in `before` or `beforeEach`. | ||
//------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'disallows using no-op setupOnerror in `before` or `beforeEach`', | ||
category: 'Testing', | ||
recommended: false, | ||
url: | ||
'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-noop-setup-on-error-in-before.md', | ||
}, | ||
fixable: 'code', | ||
schema: [], | ||
messages: { | ||
main: 'Using no-op setupOnerror in `before` or `beforeEach` is not allowed', | ||
}, | ||
}, | ||
|
||
create(context) { | ||
let importedSetupOnerrorName, importedModuleName; | ||
let isInModule = false; | ||
let isInBeforeEachHook = false; | ||
let isInBeforeHook = false; | ||
let hooksName; | ||
const sourceCode = context.getSourceCode(); | ||
|
||
function reportErrorForNodeIfInBefore(node) { | ||
const isInBeforeOrBeforeEach = | ||
(isInBeforeEachHook || isInBeforeHook) && | ||
types.isIdentifier(node.callee) && | ||
node.callee.name === importedSetupOnerrorName; | ||
|
||
const callback = node.arguments[0]; | ||
const isFunction = | ||
types.isArrowFunctionExpression(callback) || | ||
types.isFunctionDeclaration(callback) || | ||
types.isFunctionExpression(callback); | ||
|
||
const isNoop = | ||
callback && | ||
isFunction && | ||
callback.body && | ||
callback.body.type === 'BlockStatement' && | ||
callback.body.body.length === 0; | ||
|
||
if (isInBeforeOrBeforeEach && isNoop) { | ||
context.report({ | ||
node, | ||
messageId: 'main', | ||
*fix(fixer) { | ||
yield fixer.remove(node); | ||
const semicolon = sourceCode.getTokenAfter(node); | ||
if (semicolon && semicolon.value === ';') { | ||
yield fixer.remove(semicolon); | ||
} | ||
}, | ||
}); | ||
} | ||
} | ||
|
||
return { | ||
ImportDeclaration(node) { | ||
if (node.source.value === '@ember/test-helpers') { | ||
importedSetupOnerrorName = | ||
importedSetupOnerrorName || | ||
getImportIdentifier(node, '@ember/test-helpers', 'setupOnerror'); | ||
} | ||
if (node.source.value === 'qunit') { | ||
importedModuleName = importedModuleName || getImportIdentifier(node, 'qunit', 'module'); | ||
} | ||
}, | ||
|
||
CallExpression(node) { | ||
if (types.isIdentifier(node.callee) && node.callee.name === importedModuleName) { | ||
isInModule = true; | ||
if (node.arguments.length > 1 && node.arguments[1]) { | ||
const moduleCallback = node.arguments[1]; | ||
hooksName = | ||
moduleCallback.params && | ||
moduleCallback.params.length > 0 && | ||
types.isIdentifier(moduleCallback.params[0]) && | ||
moduleCallback.params[0].name; | ||
} | ||
} | ||
if (types.isIdentifier(node.callee) && node.callee.name === importedSetupOnerrorName) { | ||
reportErrorForNodeIfInBefore(node); | ||
} | ||
}, | ||
|
||
'CallExpression:exit'(node) { | ||
if (types.isIdentifier(node.callee) && node.callee.name === importedModuleName) { | ||
isInModule = false; | ||
hooksName = undefined; | ||
} | ||
}, | ||
|
||
// potentially entering a `beforeEach` hook | ||
'CallExpression[callee.property.name="beforeEach"]'(node) { | ||
if ( | ||
isInModule && | ||
hooksName && | ||
types.isIdentifier(node.callee.object) && | ||
node.callee.object.name === hooksName | ||
) { | ||
isInBeforeEachHook = true; | ||
} | ||
}, | ||
|
||
// potentially exiting a `beforeEach` hook | ||
'CallExpression[callee.property.name="beforeEach"]:exit'() { | ||
if (isInBeforeEachHook) { | ||
isInBeforeEachHook = false; | ||
hooksName = undefined; | ||
} | ||
}, | ||
|
||
// potentially entering a `before` hook | ||
'CallExpression[callee.property.name="before"]'(node) { | ||
if ( | ||
isInModule && | ||
hooksName && | ||
types.isIdentifier(node.callee.object) && | ||
node.callee.object.name === hooksName | ||
) { | ||
isInBeforeHook = true; | ||
} | ||
}, | ||
|
||
// potentially exiting a `before` hook | ||
'CallExpression[callee.property.name="before"]:exit'() { | ||
if (isInBeforeHook) { | ||
isInBeforeHook = false; | ||
hooksName = undefined; | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
|
||
const rule = require('../../../lib/rules/no-noop-setup-on-error-in-before'); | ||
const RuleTester = require('eslint').RuleTester; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Tests | ||
//------------------------------------------------------------------------------ | ||
|
||
const ruleTester = new RuleTester({ | ||
parserOptions: { | ||
ecmaVersion: 6, | ||
sourceType: 'module', | ||
}, | ||
}); | ||
|
||
ruleTester.run('no-noop-setup-on-error-in-before', rule, { | ||
valid: [ | ||
` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module, test } from 'qunit'; | ||
module('foo', function(hooks) { | ||
test('something', function() { | ||
setupOnerror((error) => { | ||
assert.equal(error.message, 'test', 'Should have message'); | ||
}); | ||
}) | ||
}); | ||
`, | ||
` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
module('foo', function(hooks) { | ||
hooks.beforeEach(function() { | ||
setupOnerror((error) => { | ||
assert.equal(error.message, 'test', 'Should have message'); | ||
}); | ||
}); | ||
}); | ||
`, | ||
` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module, test } from 'qunit'; | ||
module('foo', function(hooks) { | ||
test('something', function() { | ||
setupOnerror(() => { | ||
}); | ||
}) | ||
}); | ||
`, | ||
` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module, test } from 'qunit'; | ||
import moduleBody from 'somewhere'; | ||
module('foo', moduleBody()); | ||
`, | ||
` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
module('foo', function() { | ||
}); | ||
`, | ||
], | ||
invalid: [ | ||
{ | ||
code: ` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module as moduleVariable } from 'qunit'; | ||
moduleVariable('foo', function(hooks) { | ||
hooks.beforeEach(function() { | ||
setupOnerror(() => {}); | ||
}); | ||
}); | ||
`, | ||
output: ` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module as moduleVariable } from 'qunit'; | ||
moduleVariable('foo', function(hooks) { | ||
hooks.beforeEach(function() { | ||
}); | ||
}); | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'main', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: ` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
module('foo', function(hooks) { | ||
hooks.beforeEach(function() { | ||
setupOnerror(function(){}); | ||
}); | ||
}); | ||
`, | ||
output: ` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
module('foo', function(hooks) { | ||
hooks.beforeEach(function() { | ||
}); | ||
}); | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'main', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: ` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
module('foo', function(hooks) { | ||
hooks.beforeEach(function() { | ||
setupOnerror(function noop(){}); | ||
}); | ||
}); | ||
`, | ||
output: ` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
module('foo', function(hooks) { | ||
hooks.beforeEach(function() { | ||
}); | ||
}); | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'main', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: ` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
module('foo', function(hooks) { | ||
hooks.before(function() { | ||
setupOnerror(() => {}); | ||
}); | ||
}); | ||
`, | ||
output: ` | ||
import { setupOnerror } from '@ember/test-helpers'; | ||
import { module } from 'qunit'; | ||
module('foo', function(hooks) { | ||
hooks.before(function() { | ||
}); | ||
}); | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'main', | ||
type: 'CallExpression', | ||
}, | ||
], | ||
}, | ||
], | ||
}); |