Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add use-t-well rule #73

Merged
merged 1 commit into from
Apr 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/rules/use-t-well.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Prevent the incorrect use of `t`

Prevent the use of unknown assertion methods and the access to members other than the assertion methods and `context`, as well as some known misuses of `t`.


## Fail

```js
import test from 'ava';

test(t => {
t(value); // `t` is not a function
t.depEqual(value, [2]); // Unknown assertion method
t.contxt.foo = 100; // Unknown member `context`
t.foo = 1000; // Unknown member `foo`. Use `context.foo` instead
t.deepEqual.is(value, value); // Can't chain assertion methods
t.skip(); // Missing assertion method
t.deepEqual.skip.skip(); // Too many chained uses of `skip`
});
```


## Pass

```js
import test from 'ava';

test(t => {
t.deepEqual(value, [2]);
t.context.a = 100;
t.deepEqual.skip();
});
```
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = {
'prefer-power-assert': require('./rules/prefer-power-assert'),
'test-ended': require('./rules/test-ended'),
'test-title': require('./rules/test-title'),
'use-t-well': require('./rules/use-t-well'),
'use-t': require('./rules/use-t'),
'use-test': require('./rules/use-test')
},
Expand Down Expand Up @@ -43,6 +44,7 @@ module.exports = {
'ava/prefer-power-assert': 'off',
'ava/test-ended': 'error',
'ava/test-title': ['error', 'if-multiple'],
'ava/use-t-well': 'error',
'ava/use-t': 'error',
'ava/use-test': 'error'
}
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Configure it in `package.json`.
"ava/prefer-power-assert": "off",
"ava/test-ended": "error",
"ava/test-title": ["error", "if-multiple"],
"ava/use-t-well": "error",
"ava/use-t": "error",
"ava/use-test": "error"
}
Expand All @@ -69,6 +70,7 @@ The rules will only activate in test files.
- [prefer-power-assert](docs/rules/prefer-power-assert.md) - Allow only use of the asserts that have no [power-assert](https://github.com/power-assert-js/power-assert) alternative.
- [test-ended](docs/rules/test-ended.md) - Ensure callback tests are explicitly ended.
- [test-title](docs/rules/test-title.md) - Ensure tests have a title.
- [use-t-well](docs/rules/use-t-well.md) - Prevent the incorrect use of `t`.
- [use-t](docs/rules/use-t.md) - Ensure test functions use `t` as their parameter.
- [use-test](docs/rules/use-test.md) - Ensure that AVA is imported with `test` as the variable name.

Expand Down
96 changes: 96 additions & 0 deletions rules/use-t-well.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use strict';
var util = require('../util');
var createAvaRule = require('../create-ava-rule');

var methods = [
'end', 'pass', 'fail', 'truthy', 'falsy', 'true', 'false', 'is', 'not',
'deepEqual', 'notDeepEqual', 'throws', 'notThrows', 'regex', 'ifError'
];

function isMethod(name) {
return methods.indexOf(name) !== -1;
}

function getMembers(node) {
var name = node.property.name;
if (node.object.type === 'MemberExpression') {
return getMembers(node.object).concat(name);
}

return [name];
}

function isCallExpression(node) {
return node.parent.type === 'CallExpression' &&
node.parent.callee === node;
}

function getMemberStats(members) {
var initial = {
skip: [],
method: [],
other: []
};

return members.reduce(function (res, member) {
if (member === 'skip') {
res.skip.push(member);
} else if (isMethod(member)) {
res.method.push(member);
} else {
res.other.push(member);
}

return res;
}, initial);
}

module.exports = function (context) {
var ava = createAvaRule();

return ava.merge({
CallExpression: function (node) {
if (ava.isTestFile &&
ava.currentTestNode &&
node.callee.type !== 'MemberExpression' &&
node.callee.name === 't') {
context.report(node, '`t` is not a function');
}
},
MemberExpression: function (node) {
if (!ava.isTestFile ||
!ava.currentTestNode ||
node.parent.type === 'MemberExpression' ||
util.nameOfRootObject(node) !== 't') {
return;
}

var members = getMembers(node);
var stats = getMemberStats(members);

if (members[0] === 'context') {
// Anything is fine when of the form `t.context...`
if (members.length === 1 && isCallExpression(node)) {
// except `t.context()`
context.report(node, 'Unknown assertion method `context`');
}

return;
}

if (isCallExpression(node)) {
if (stats.other.length > 0) {
context.report(node, 'Unknown assertion method `' + stats.other[0] + '`');
} else if (stats.skip.length > 1) {
context.report(node, 'Too many chained uses of `skip`');
} else if (stats.method.length > 1) {
context.report(node, 'Can\'t chain assertion methods');
} else if (stats.method.length === 0) {
context.report(node, 'Missing assertion method');
}
} else if (stats.other.length > 0) {
context.report(node, 'Unknown member `' + stats.other[0] + '`. Use `context.' + stats.other[0] + '` instead');
}
}
});
};
129 changes: 129 additions & 0 deletions test/use-t-well.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import test from 'ava';
import {RuleTester} from 'eslint';
import rule from '../rules/use-t-well';

const ruleTester = new RuleTester({
env: {
es6: true
}
});

const ruleError = {ruleId: 'use-t-well'};
const header = `const test = require('ava');\n`;

function testCase(contents, prependHeader) {
const content = `test(t => { ${contents} });`;
if (prependHeader !== false) {
return header + content;
}

return content;
}

test(() => {
ruleTester.run('use-t-well', rule, {
valid: [
testCase('t;'),
testCase('fn(t);'),
testCase('t.end();'),
testCase('t.pass();'),
testCase('t.fail();'),
testCase('t.truthy(v);'),
testCase('t.falsy(v);'),
testCase('t.true(v);'),
testCase('t.false(v);'),
testCase('t.is(v);'),
testCase('t.not(v);'),
testCase('t.deepEqual(v, v);'),
testCase('t.notDeepEqual(v, v);'),
testCase('t.throws(fn);'),
testCase('t.notThrows(fn);'),
testCase('t.regex(v, /v/);'),
testCase('t.ifError(error);'),
testCase('t.deepEqual.skip(a, a);'),
testCase('t.skip.deepEqual(a, a);'),
testCase('t.context.a = 1;'),
testCase('t.context.foo.skip();'),
testCase('setImmediate(t.end);'),
testCase('t.deepEqual;'),
testCase('a.foo();'),
// shouldn't be triggered since it's not a test file
testCase('t.foo(a, a);', false),
testCase('t.foo;', false)
],
invalid: [
{
code: testCase('t();'),
errors: [
{...ruleError, message: '`t` is not a function'}
]
},
{
code: testCase('t.foo(a, a);'),
errors: [
{...ruleError, message: 'Unknown assertion method `foo`'}
]
},
{
code: testCase('t.depEqual(a, a);'),
errors: [
{...ruleError, message: 'Unknown assertion method `depEqual`'}
]
},
{
code: testCase('t.deepEqual.skp(a, a);'),
errors: [
{...ruleError, message: 'Unknown assertion method `skp`'}
]
},
{
code: testCase('t.skp.deepEqual(a, a);'),
errors: [
{...ruleError, message: 'Unknown assertion method `skp`'}
]
},
{
code: testCase('t.context();'),
errors: [
{...ruleError, message: 'Unknown assertion method `context`'}
]
},
{
code: testCase('t.a = 1;'),
errors: [
{...ruleError, message: 'Unknown member `a`. Use `context.a` instead'}
]
},
{
code: testCase('t.ctx.a = 1;'),
errors: [
{...ruleError, message: 'Unknown member `ctx`. Use `context.ctx` instead'}
]
},
{
code: testCase('t.deepEqu;'),
errors: [
{...ruleError, message: 'Unknown member `deepEqu`. Use `context.deepEqu` instead'}
]
},
{
code: testCase('t.deepEqual.is(a, a);'),
errors: [
{...ruleError, message: `Can't chain assertion methods`}
]
},
{
code: testCase('t.skip();'),
errors: [
{...ruleError, message: 'Missing assertion method'}
]
},
{
code: testCase('t.deepEqual.skip.skip(a, a);'),
errors: [
{...ruleError, message: 'Too many chained uses of `skip`'}
]
}
]
});
});