-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Sindre Sorhus <[email protected]>
- Loading branch information
1 parent
330af0e
commit c165ac2
Showing
5 changed files
with
204 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Prefer using `t.regex()` to test regular expressions | ||
|
||
The AVA [`t.regex()` assertion](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#regexcontents-regex-message) can test a string against a regular expression. | ||
|
||
This rule will enforce the use of `t.regex()` instead of manually using `RegExp#test()`, which will make your code look clearer and produce better failure output. | ||
|
||
This rule is fixable. It will replace the use of `RegExp#test()`, `String#match()`, or `String#search()` with `t.regex()`. | ||
|
||
|
||
## Fail | ||
|
||
```js | ||
import test from 'ava'; | ||
|
||
test('main', t => { | ||
t.true(/\w+/.test('foo')); | ||
}); | ||
``` | ||
|
||
```js | ||
import test from 'ava'; | ||
|
||
test('main', t => { | ||
t.truthy('foo'.match(/\w+/)); | ||
}); | ||
``` | ||
|
||
|
||
## Pass | ||
|
||
```js | ||
import test from 'ava'; | ||
|
||
test('main', async t => { | ||
t.regex('foo', /\w+/); | ||
}); | ||
``` |
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
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,98 @@ | ||
'use strict'; | ||
const {visitIf} = require('enhance-visitors'); | ||
const createAvaRule = require('../create-ava-rule'); | ||
const util = require('../util'); | ||
|
||
const create = context => { | ||
const ava = createAvaRule(); | ||
|
||
const booleanTests = [ | ||
'true', | ||
'false', | ||
'truthy', | ||
'falsy' | ||
]; | ||
|
||
const findReference = name => { | ||
const reference = context.getScope().references.find(reference => reference.identifier.name === name); | ||
const definitions = reference.resolved.defs; | ||
return definitions[definitions.length - 1].node; | ||
}; | ||
|
||
return ava.merge({ | ||
CallExpression: visitIf([ | ||
ava.isInTestFile, | ||
ava.isInTestNode | ||
])(node => { | ||
// Call a boolean assertion, for example, `t.true`, `t.false`, … | ||
const isBooleanAssertion = node.callee.type === 'MemberExpression' && | ||
booleanTests.includes(node.callee.property.name) && | ||
util.getNameOfRootNodeObject(node.callee) === 't'; | ||
|
||
if (!isBooleanAssertion) { | ||
return; | ||
} | ||
|
||
const firstArg = node.arguments[0]; | ||
|
||
// First argument is a call expression | ||
const isFunctionCall = firstArg.type === 'CallExpression'; | ||
if (!isFunctionCall) { | ||
return; | ||
} | ||
|
||
const {name} = firstArg.callee.property; | ||
let lookup = {}; | ||
let variable = {}; | ||
|
||
if (name === 'test') { | ||
// `lookup.test(variable)` | ||
lookup = firstArg.callee.object; | ||
variable = firstArg.arguments[0]; | ||
} else if (['search', 'match'].includes(name)) { | ||
// `variable.match(lookup)` | ||
lookup = firstArg.arguments[0]; | ||
variable = firstArg.callee.object; | ||
} | ||
|
||
let isRegExp = lookup.regex; | ||
|
||
// It's not a regexp but an identifier | ||
if (!isRegExp && lookup.type === 'Identifier') { | ||
const reference = findReference(lookup.name); | ||
isRegExp = reference.init.regex; | ||
} | ||
|
||
if (!isRegExp) { | ||
return; | ||
} | ||
|
||
const assertion = ['true', 'truthy'].includes(node.callee.property.name) ? 'regex' : 'notRegex'; | ||
|
||
const fix = fixer => { | ||
const source = context.getSourceCode(); | ||
return [ | ||
fixer.replaceText(node.callee.property, assertion), | ||
fixer.replaceText(firstArg, `${source.getText(variable)}, ${source.getText(lookup)}`) | ||
]; | ||
}; | ||
|
||
context.report({ | ||
node, | ||
message: `Prefer using the \`t.${assertion}()\` assertion.`, | ||
fix | ||
}); | ||
}) | ||
}); | ||
}; | ||
|
||
module.exports = { | ||
create, | ||
meta: { | ||
docs: { | ||
url: util.getDocsUrl(__filename) | ||
}, | ||
fixable: 'code', | ||
type: 'suggestion' | ||
} | ||
}; |
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,66 @@ | ||
import test from 'ava'; | ||
import avaRuleTester from 'eslint-ava-rule-tester'; | ||
import rule from '../rules/prefer-t-regex'; | ||
|
||
const ruleTester = avaRuleTester(test, { | ||
env: { | ||
es6: true | ||
} | ||
}); | ||
|
||
const errors = assertion => [{ | ||
ruleId: 'prefer-t-regex', | ||
message: `Prefer using the \`t.${assertion}()\` assertion.` | ||
}]; | ||
const header = 'const test = require(\'ava\');\n'; | ||
|
||
ruleTester.run('prefer-t-regex', rule, { | ||
valid: [ | ||
header + 'test(t => t.regex("foo", /\\d+/));', | ||
header + 'test(t => t.regex(foo(), /\\d+/));', | ||
header + 'test(t => t.is(/\\d+/.test("foo")), true);', | ||
header + 'test(t => t.true(1 === 1));', | ||
header + 'test(t => t.true(foo.bar()));', | ||
header + 'const a = /\\d+/;\ntest(t => t.truthy(a));', | ||
header + 'const a = "not a regexp";\ntest(t => t.true(a.test("foo")));', | ||
// Shouldn't be triggered since it's not a test file | ||
'test(t => t.true(/\\d+/.test("foo")));' | ||
], | ||
invalid: [ | ||
{ | ||
code: header + 'test(t => t.true(/\\d+/.test("foo")));', | ||
output: header + 'test(t => t.regex("foo", /\\d+/));', | ||
errors: errors('regex') | ||
}, | ||
{ | ||
code: header + 'test(t => t.false(foo.search(/\\d+/)));', | ||
output: header + 'test(t => t.notRegex(foo, /\\d+/));', | ||
errors: errors('notRegex') | ||
}, | ||
{ | ||
code: header + 'const regexp = /\\d+/;\ntest(t => t.true(foo.search(regexp)));', | ||
output: header + 'const regexp = /\\d+/;\ntest(t => t.regex(foo, regexp));', | ||
errors: errors('regex') | ||
}, | ||
{ | ||
code: header + 'test(t => t.truthy(foo.match(/\\d+/)));', | ||
output: header + 'test(t => t.regex(foo, /\\d+/));', | ||
errors: errors('regex') | ||
}, | ||
{ | ||
code: header + 'test(t => t.false(/\\d+/.test("foo")));', | ||
output: header + 'test(t => t.notRegex("foo", /\\d+/));', | ||
errors: errors('notRegex') | ||
}, | ||
{ | ||
code: header + 'test(t => t.true(/\\d+/.test(foo())));', | ||
output: header + 'test(t => t.regex(foo(), /\\d+/));', | ||
errors: errors('regex') | ||
}, | ||
{ | ||
code: header + 'const reg = /\\d+/;\ntest(t => t.true(reg.test(foo.bar())));', | ||
output: header + 'const reg = /\\d+/;\ntest(t => t.regex(foo.bar(), reg));', | ||
errors: errors('regex') | ||
} | ||
] | ||
}); |