Skip to content

Commit 2a90f87

Browse files
authored
Merge pull request #920 from v-korshun/no-noop-setup-on-error-in-before
Add new rule `no-noop-setup-on-error-in-before`
2 parents 8be94f9 + 3da13a0 commit 2a90f87

File tree

5 files changed

+383
-0
lines changed

5 files changed

+383
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
183183
| :white_check_mark: | [no-ember-testing-in-module-scope](./docs/rules/no-ember-testing-in-module-scope.md) | disallow use of `Ember.testing` in module scope |
184184
| | [no-invalid-test-waiters](./docs/rules/no-invalid-test-waiters.md) | disallow incorrect usage of test waiter APIs |
185185
| :white_check_mark: | [no-legacy-test-waiters](./docs/rules/no-legacy-test-waiters.md) | disallow the use of the legacy test waiter APIs |
186+
| :wrench: | [no-noop-setup-on-error-in-before](./docs/rules/no-noop-setup-on-error-in-before.md) | disallows using no-op setupOnerror in `before` or `beforeEach` |
186187
| :white_check_mark: | [no-pause-test](./docs/rules/no-pause-test.md) | disallow usage of the `pauseTest` helper in tests |
187188
| | [no-replace-test-comments](./docs/rules/no-replace-test-comments.md) | disallow 'Replace this with your real tests' comments in test files |
188189
| :white_check_mark: | [no-restricted-resolver-tests](./docs/rules/no-restricted-resolver-tests.md) | disallow the use of patterns that use the restricted resolver in tests |
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# no-noop-setup-on-error-in-before
2+
3+
: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.
4+
5+
Disallows use of no-op `setupOnerror` in `before`/`beforeEach` since it could mask errors or rejections in tests unintentionally
6+
7+
## Rule Details
8+
9+
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.
10+
11+
## Examples
12+
13+
Examples of **incorrect** code for this rule:
14+
15+
```js
16+
import { setupOnerror } from '@ember/test-helpers';
17+
import { module } from 'qunit';
18+
19+
module('foo', function (hooks) {
20+
hooks.beforeEach(function () {
21+
setupOnerror(() => {});
22+
});
23+
});
24+
```
25+
26+
```js
27+
import { setupOnerror } from '@ember/test-helpers';
28+
import { module } from 'qunit';
29+
30+
module('foo', function (hooks) {
31+
hooks.before(function () {
32+
setupOnerror(() => {});
33+
});
34+
});
35+
```
36+
37+
Examples of **correct** code for this rule:
38+
39+
```js
40+
import { setupOnerror } from '@ember/test-helpers';
41+
import { module, test } from 'qunit';
42+
43+
module('foo', function (hooks) {
44+
test('something', function () {
45+
setupOnerror((error) => {
46+
assert.equal(error.message, 'test', 'Should have message');
47+
});
48+
});
49+
});
50+
```

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ module.exports = {
4242
'no-legacy-test-waiters': require('./rules/no-legacy-test-waiters'),
4343
'no-mixins': require('./rules/no-mixins'),
4444
'no-new-mixins': require('./rules/no-new-mixins'),
45+
'no-noop-setup-on-error-in-before': require('./rules/no-noop-setup-on-error-in-before'),
4546
'no-observers': require('./rules/no-observers'),
4647
'no-old-shims': require('./rules/no-old-shims'),
4748
'no-on-calls-in-components': require('./rules/no-on-calls-in-components'),
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
'use strict';
2+
3+
const { getImportIdentifier } = require('../utils/import');
4+
const types = require('../utils/types');
5+
6+
//------------------------------------------------------------------------------
7+
// General rule - Disallows no-op `setupOnError` in `before` or `beforeEach`.
8+
//------------------------------------------------------------------------------
9+
10+
module.exports = {
11+
meta: {
12+
type: 'problem',
13+
docs: {
14+
description: 'disallows using no-op setupOnerror in `before` or `beforeEach`',
15+
category: 'Testing',
16+
recommended: false,
17+
url:
18+
'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-noop-setup-on-error-in-before.md',
19+
},
20+
fixable: 'code',
21+
schema: [],
22+
messages: {
23+
main: 'Using no-op setupOnerror in `before` or `beforeEach` is not allowed',
24+
},
25+
},
26+
27+
create(context) {
28+
let importedSetupOnerrorName, importedModuleName;
29+
let isInModule = false;
30+
let isInBeforeEachHook = false;
31+
let isInBeforeHook = false;
32+
let hooksName;
33+
const sourceCode = context.getSourceCode();
34+
35+
function reportErrorForNodeIfInBefore(node) {
36+
const isInBeforeOrBeforeEach =
37+
(isInBeforeEachHook || isInBeforeHook) &&
38+
types.isIdentifier(node.callee) &&
39+
node.callee.name === importedSetupOnerrorName;
40+
41+
const callback = node.arguments[0];
42+
const isFunction =
43+
types.isArrowFunctionExpression(callback) ||
44+
types.isFunctionDeclaration(callback) ||
45+
types.isFunctionExpression(callback);
46+
47+
const isNoop =
48+
callback &&
49+
isFunction &&
50+
callback.body &&
51+
callback.body.type === 'BlockStatement' &&
52+
callback.body.body.length === 0;
53+
54+
if (isInBeforeOrBeforeEach && isNoop) {
55+
context.report({
56+
node,
57+
messageId: 'main',
58+
*fix(fixer) {
59+
yield fixer.remove(node);
60+
const semicolon = sourceCode.getTokenAfter(node);
61+
if (semicolon && semicolon.value === ';') {
62+
yield fixer.remove(semicolon);
63+
}
64+
},
65+
});
66+
}
67+
}
68+
69+
return {
70+
ImportDeclaration(node) {
71+
if (node.source.value === '@ember/test-helpers') {
72+
importedSetupOnerrorName =
73+
importedSetupOnerrorName ||
74+
getImportIdentifier(node, '@ember/test-helpers', 'setupOnerror');
75+
}
76+
if (node.source.value === 'qunit') {
77+
importedModuleName = importedModuleName || getImportIdentifier(node, 'qunit', 'module');
78+
}
79+
},
80+
81+
CallExpression(node) {
82+
if (types.isIdentifier(node.callee) && node.callee.name === importedModuleName) {
83+
isInModule = true;
84+
if (node.arguments.length > 1 && node.arguments[1]) {
85+
const moduleCallback = node.arguments[1];
86+
hooksName =
87+
moduleCallback.params &&
88+
moduleCallback.params.length > 0 &&
89+
types.isIdentifier(moduleCallback.params[0]) &&
90+
moduleCallback.params[0].name;
91+
}
92+
}
93+
if (types.isIdentifier(node.callee) && node.callee.name === importedSetupOnerrorName) {
94+
reportErrorForNodeIfInBefore(node);
95+
}
96+
},
97+
98+
'CallExpression:exit'(node) {
99+
if (types.isIdentifier(node.callee) && node.callee.name === importedModuleName) {
100+
isInModule = false;
101+
hooksName = undefined;
102+
}
103+
},
104+
105+
// potentially entering a `beforeEach` hook
106+
'CallExpression[callee.property.name="beforeEach"]'(node) {
107+
if (
108+
isInModule &&
109+
hooksName &&
110+
types.isIdentifier(node.callee.object) &&
111+
node.callee.object.name === hooksName
112+
) {
113+
isInBeforeEachHook = true;
114+
}
115+
},
116+
117+
// potentially exiting a `beforeEach` hook
118+
'CallExpression[callee.property.name="beforeEach"]:exit'() {
119+
if (isInBeforeEachHook) {
120+
isInBeforeEachHook = false;
121+
hooksName = undefined;
122+
}
123+
},
124+
125+
// potentially entering a `before` hook
126+
'CallExpression[callee.property.name="before"]'(node) {
127+
if (
128+
isInModule &&
129+
hooksName &&
130+
types.isIdentifier(node.callee.object) &&
131+
node.callee.object.name === hooksName
132+
) {
133+
isInBeforeHook = true;
134+
}
135+
},
136+
137+
// potentially exiting a `before` hook
138+
'CallExpression[callee.property.name="before"]:exit'() {
139+
if (isInBeforeHook) {
140+
isInBeforeHook = false;
141+
hooksName = undefined;
142+
}
143+
},
144+
};
145+
},
146+
};
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//------------------------------------------------------------------------------
2+
// Requirements
3+
//------------------------------------------------------------------------------
4+
5+
const rule = require('../../../lib/rules/no-noop-setup-on-error-in-before');
6+
const RuleTester = require('eslint').RuleTester;
7+
8+
//------------------------------------------------------------------------------
9+
// Tests
10+
//------------------------------------------------------------------------------
11+
12+
const ruleTester = new RuleTester({
13+
parserOptions: {
14+
ecmaVersion: 6,
15+
sourceType: 'module',
16+
},
17+
});
18+
19+
ruleTester.run('no-noop-setup-on-error-in-before', rule, {
20+
valid: [
21+
`
22+
import { setupOnerror } from '@ember/test-helpers';
23+
import { module, test } from 'qunit';
24+
25+
module('foo', function(hooks) {
26+
test('something', function() {
27+
setupOnerror((error) => {
28+
assert.equal(error.message, 'test', 'Should have message');
29+
});
30+
})
31+
});
32+
`,
33+
`
34+
import { setupOnerror } from '@ember/test-helpers';
35+
import { module } from 'qunit';
36+
37+
module('foo', function(hooks) {
38+
hooks.beforeEach(function() {
39+
setupOnerror((error) => {
40+
assert.equal(error.message, 'test', 'Should have message');
41+
});
42+
});
43+
});
44+
`,
45+
`
46+
import { setupOnerror } from '@ember/test-helpers';
47+
import { module, test } from 'qunit';
48+
49+
module('foo', function(hooks) {
50+
test('something', function() {
51+
setupOnerror(() => {
52+
});
53+
})
54+
});
55+
`,
56+
`
57+
import { setupOnerror } from '@ember/test-helpers';
58+
import { module, test } from 'qunit';
59+
import moduleBody from 'somewhere';
60+
61+
module('foo', moduleBody());
62+
`,
63+
`
64+
import { setupOnerror } from '@ember/test-helpers';
65+
import { module } from 'qunit';
66+
67+
module('foo', function() {
68+
});
69+
`,
70+
],
71+
invalid: [
72+
{
73+
code: `
74+
import { setupOnerror } from '@ember/test-helpers';
75+
import { module as moduleVariable } from 'qunit';
76+
77+
moduleVariable('foo', function(hooks) {
78+
hooks.beforeEach(function() {
79+
setupOnerror(() => {});
80+
});
81+
});
82+
`,
83+
output: `
84+
import { setupOnerror } from '@ember/test-helpers';
85+
import { module as moduleVariable } from 'qunit';
86+
87+
moduleVariable('foo', function(hooks) {
88+
hooks.beforeEach(function() {
89+
90+
});
91+
});
92+
`,
93+
errors: [
94+
{
95+
messageId: 'main',
96+
type: 'CallExpression',
97+
},
98+
],
99+
},
100+
{
101+
code: `
102+
import { setupOnerror } from '@ember/test-helpers';
103+
import { module } from 'qunit';
104+
105+
module('foo', function(hooks) {
106+
hooks.beforeEach(function() {
107+
setupOnerror(function(){});
108+
});
109+
});
110+
`,
111+
output: `
112+
import { setupOnerror } from '@ember/test-helpers';
113+
import { module } from 'qunit';
114+
115+
module('foo', function(hooks) {
116+
hooks.beforeEach(function() {
117+
118+
});
119+
});
120+
`,
121+
errors: [
122+
{
123+
messageId: 'main',
124+
type: 'CallExpression',
125+
},
126+
],
127+
},
128+
{
129+
code: `
130+
import { setupOnerror } from '@ember/test-helpers';
131+
import { module } from 'qunit';
132+
133+
module('foo', function(hooks) {
134+
hooks.beforeEach(function() {
135+
setupOnerror(function noop(){});
136+
});
137+
});
138+
`,
139+
output: `
140+
import { setupOnerror } from '@ember/test-helpers';
141+
import { module } from 'qunit';
142+
143+
module('foo', function(hooks) {
144+
hooks.beforeEach(function() {
145+
146+
});
147+
});
148+
`,
149+
errors: [
150+
{
151+
messageId: 'main',
152+
type: 'CallExpression',
153+
},
154+
],
155+
},
156+
{
157+
code: `
158+
import { setupOnerror } from '@ember/test-helpers';
159+
import { module } from 'qunit';
160+
161+
module('foo', function(hooks) {
162+
hooks.before(function() {
163+
setupOnerror(() => {});
164+
});
165+
});
166+
`,
167+
output: `
168+
import { setupOnerror } from '@ember/test-helpers';
169+
import { module } from 'qunit';
170+
171+
module('foo', function(hooks) {
172+
hooks.before(function() {
173+
174+
});
175+
});
176+
`,
177+
errors: [
178+
{
179+
messageId: 'main',
180+
type: 'CallExpression',
181+
},
182+
],
183+
},
184+
],
185+
});

0 commit comments

Comments
 (0)