This commit is contained in:
2022-07-21 03:28:35 +00:00
parent d7c883d6df
commit 51b34b0e1d
30103 changed files with 4152204 additions and 23 deletions
+14
View File
@@ -0,0 +1,14 @@
'use strict';
const containsIdentifier = require('./lib/contains-identifier');
module.exports = {
computeStaticExpression: require('./lib/compute-static-expression'),
containsIdentifier: containsIdentifier.containsIdentifier,
getPropertyName: require('./lib/get-property-name'),
getRequireSource: require('./lib/get-require-source'),
isFunctionExpression: require('./lib/is-function-expression'),
isPromise: require('./lib/is-promise'),
isStaticRequire: require('./lib/is-static-require'),
someContainIdentifier: containsIdentifier.someContainIdentifier
};
+123
View File
@@ -0,0 +1,123 @@
'use strict';
const zip = require('lodash.zip');
const toValue = value => ({value});
function computeTemplateLiteral(node) {
const expressions = node.expressions.map(computeStaticExpression);
if (expressions.some(expression => expression === undefined)) {
return undefined;
}
const quasi = node.quasis.map(quasis => quasis.value.cooked);
const value = zip(quasi, expressions.map(expr => expr.value))
.reduce((res, elts) => res.concat(elts))
.filter(Boolean)
.join('');
return toValue(value);
}
function computeBinaryExpression(operator, leftExpr, rightExpr) { // eslint-disable-line complexity
if (!leftExpr || !rightExpr) {
return undefined;
}
const left = leftExpr.value;
const right = rightExpr.value;
switch (operator) { // eslint-disable-line default-case
case '+': return toValue(left + right);
case '-': return toValue(left - right);
case '*': return toValue(left * right);
case '/': return toValue(left / right);
case '%': return toValue(left % right);
case '**': return toValue(Math.pow(left, right));
case '<<': return toValue(left << right);
case '>>': return toValue(left >> right);
case '>>>': return toValue(left >>> right);
case '&': return toValue(left & right);
case '|': return toValue(left | right);
case '^': return toValue(left | right);
case '&&': return toValue(left && right);
case '||': return toValue(left || right);
case '===': return toValue(left === right);
case '!==': return toValue(left !== right);
case '==': return toValue(left == right); // eslint-disable-line eqeqeq
case '!=': return toValue(left != right); // eslint-disable-line eqeqeq
case '<': return toValue(left < right);
case '>': return toValue(left > right);
case '<=': return toValue(left <= right);
case '>=': return toValue(left >= right);
}
}
function applyUnaryOperator(operator, expr) {
if (operator === 'void') {
return toValue(undefined);
}
if (!expr) {
return undefined;
}
const value = expr.value;
switch (operator) { // eslint-disable-line default-case
case '+': return toValue(+value); // eslint-disable-line no-implicit-coercion
case '-': return toValue(-value);
case '!': return toValue(!value);
case '~': return toValue(~value);
}
}
function computeConditionalExpression(test, consequent, alternate) {
if (!test) {
return undefined;
}
return test.value ? consequent : alternate;
}
function computeStaticExpression(node) {
if (!node) {
return undefined;
}
switch (node.type) {
case 'Identifier':
return node.name === 'undefined' ? toValue(undefined) : undefined;
case 'Literal':
return toValue(node.value);
case 'TemplateLiteral':
return computeTemplateLiteral(node);
case 'UnaryExpression':
return applyUnaryOperator(node.operator, computeStaticExpression(node.argument));
case 'BinaryExpression': {
return computeBinaryExpression(
node.operator,
computeStaticExpression(node.left),
computeStaticExpression(node.right)
);
}
case 'LogicalExpression': {
return computeBinaryExpression(
node.operator,
computeStaticExpression(node.left),
computeStaticExpression(node.right)
);
}
case 'ConditionalExpression':
return computeConditionalExpression(
computeStaticExpression(node.test),
computeStaticExpression(node.consequent),
computeStaticExpression(node.alternate)
);
default:
return undefined;
}
}
module.exports = computeStaticExpression;
+274
View File
@@ -0,0 +1,274 @@
'use strict';
function introduces(name, node) { // eslint-disable-line complexity
if (!node) {
return false;
}
switch (node.type) {
case 'Identifier':
return node.name === name;
case 'FunctionDeclaration':
return introduces(name, node.id) ||
someIntroduce(name, node.params);
case 'ArrowFunctionExpression':
return someIntroduce(name, node.params);
case 'FunctionExpression':
return someIntroduce(name, node.params);
case 'BlockStatement':
return someIntroduce(name, node.body);
case 'VariableDeclaration':
return someIntroduce(name, node.declarations);
case 'VariableDeclarator':
return introduces(name, node.id);
case 'ObjectPattern':
return someIntroduce(name, node.properties);
case 'ArrayPattern':
return someIntroduce(name, node.elements);
case 'Property':
return introduces(name, node.value);
case 'ExperimentalRestProperty':
return introduces(name, node.argument);
case 'ForStatement':
return introduces(name, node.init);
case 'ClassDeclaration':
return introduces(name, node.id);
case 'RestElement':
return introduces(name, node.argument);
case 'Program':
return someIntroduce(name, node.body);
case 'ImportDeclaration':
return someIntroduce(name, node.specifiers);
case 'ImportDefaultSpecifier':
return introduces(name, node.local);
case 'ImportSpecifier':
return introduces(name, node.local);
case 'ImportNamespaceSpecifier':
return introduces(name, node.local);
default:
return false;
}
}
function someIntroduce(name, array) {
return Array.isArray(array) && array.some(item => {
return introduces(name, item);
});
}
function containsIdentifier(name, node) { // eslint-disable-line complexity
if (!node) {
return false;
}
switch (node.type) {
// Primitives
case 'Identifier':
return node.name === name;
case 'Literal':
return false;
case 'ThisExpression':
return false;
// Objects / Arrays
case 'ArrayExpression':
return someContainIdentifier(name, node.elements);
case 'ObjectExpression':
return someContainIdentifier(name, node.properties);
case 'ExperimentalSpreadProperty':
return containsIdentifier(name, node.argument);
case 'Property':
return (node.computed && containsIdentifier(name, node.key)) ||
containsIdentifier(name, node.value);
// Expressions
case 'TemplateLiteral':
return someContainIdentifier(name, node.expressions);
case 'TaggedTemplateExpression':
return containsIdentifier(name, node.tag) || containsIdentifier(name, node.quasi);
case 'SequenceExpression':
return someContainIdentifier(name, node.expressions);
case 'CallExpression':
return containsIdentifier(name, node.callee) ||
someContainIdentifier(name, node.arguments);
case 'NewExpression':
return containsIdentifier(name, node.callee) ||
someContainIdentifier(name, node.arguments);
case 'MemberExpression':
if (node.computed === false) {
return containsIdentifier(name, node.object);
}
return containsIdentifier(name, node.property) ||
containsIdentifier(name, node.object);
case 'ConditionalExpression':
return containsIdentifier(name, node.test) ||
containsIdentifier(name, node.consequent) ||
containsIdentifier(name, node.alternate);
case 'BinaryExpression':
return containsIdentifier(name, node.left) ||
containsIdentifier(name, node.right);
case 'LogicalExpression':
return containsIdentifier(name, node.left) ||
containsIdentifier(name, node.right);
case 'AssignmentExpression':
return containsIdentifier(name, node.left) ||
containsIdentifier(name, node.right);
case 'UpdateExpression':
return containsIdentifier(name, node.argument);
case 'UnaryExpression':
return containsIdentifier(name, node.argument);
case 'YieldExpression':
return containsIdentifier(name, node.argument);
case 'AwaitExpression':
return containsIdentifier(name, node.argument);
case 'ArrowFunctionExpression':
if (node.params.some(param => param.type !== 'Identifier' && containsIdentifier(name, param))) {
return true;
}
return !introduces(name, node) && containsIdentifier(name, node.body);
case 'FunctionExpression':
if (node.params.some(param => param.type !== 'Identifier' && containsIdentifier(name, param))) {
return true;
}
return !introduces(name, node) && containsIdentifier(name, node.body);
case 'SpreadElement':
return containsIdentifier(name, node.argument);
// Statements / control flow
case 'ExpressionStatement':
return containsIdentifier(name, node.expression);
case 'ReturnStatement':
return containsIdentifier(name, node.argument);
case 'ThrowStatement':
return containsIdentifier(name, node.argument);
case 'IfStatement':
return containsIdentifier(name, node.test) ||
containsIdentifier(name, node.consequent) ||
containsIdentifier(name, node.alternate);
case 'BreakStatement':
return false;
case 'ContinueStatement':
return false;
case 'ForOfStatement':
return containsIdentifier(name, node.left) ||
containsIdentifier(name, node.right) ||
containsIdentifier(name, node.body);
case 'ForInStatement':
return containsIdentifier(name, node.left) ||
containsIdentifier(name, node.right) ||
containsIdentifier(name, node.body);
case 'ForStatement':
return !introduces(name, node) && (
containsIdentifier(name, node.init) ||
containsIdentifier(name, node.test) ||
containsIdentifier(name, node.update) ||
containsIdentifier(name, node.body)
);
case 'WhileStatement':
return containsIdentifier(name, node.test) ||
containsIdentifier(name, node.body);
case 'DoWhileStatement':
return containsIdentifier(name, node.test) ||
containsIdentifier(name, node.body);
case 'Program':
return !introduces(name, node) && someContainIdentifier(name, node.body);
case 'BlockStatement':
return !introduces(name, node) && someContainIdentifier(name, node.body);
case 'TryStatement':
return containsIdentifier(name, node.block) ||
containsIdentifier(name, node.handler) ||
containsIdentifier(name, node.finalizer);
case 'CatchClause':
return !introduces(name, node.param) && containsIdentifier(name, node.body);
case 'SwitchStatement':
return containsIdentifier(name, node.discriminant) || someContainIdentifier(name, node.cases);
case 'SwitchCase':
return containsIdentifier(name, node.test) || someContainIdentifier(name, node.consequent);
case 'LabeledStatement':
return containsIdentifier(name, node.body);
case 'DebuggerStatement':
return false;
case 'EmptyStatement':
return false;
// Assignment / Declaration
case 'AssignmentPattern':
return containsIdentifier(name, node.left) ||
containsIdentifier(name, node.right);
case 'VariableDeclarator':
if (node.id.type !== 'Identifier') {
return containsIdentifier(name, node.id) ||
containsIdentifier(name, node.init);
}
return containsIdentifier(name, node.init);
case 'ObjectPattern':
return node.properties.some(prop =>
prop.type === 'Property' && prop.value.type !== 'Identifier' && containsIdentifier(name, prop.value)
);
case 'FunctionDeclaration':
if (node.params.some(param => param.type !== 'Identifier' && containsIdentifier(name, param))) {
return true;
}
return !introduces(name, node) && containsIdentifier(name, node.body);
case 'ArrayPattern':
return node.elements.some(item => {
return item && item.type !== 'Identifier' && containsIdentifier(name, item);
});
case 'VariableDeclaration':
return someContainIdentifier(name, node.declarations);
case 'RestElement':
return false;
// Classes
case 'ClassDeclaration':
return !introduces(name, node) && (
containsIdentifier(name, node.superClass) ||
containsIdentifier(name, node.body)
);
case 'ClassExpression':
return containsIdentifier(name, node.superClass) ||
containsIdentifier(name, node.body);
case 'ClassBody':
return someContainIdentifier(name, node.body);
case 'MethodDefinition':
return containsIdentifier(name, node.value);
case 'Super':
return false;
// Import / export
case 'ImportDeclaration':
return false;
case 'ExportDefaultDeclaration':
return containsIdentifier(name, node.declaration);
case 'ExportNamedDeclaration':
return containsIdentifier(name, node.declaration);
// JSX
case 'JSXIdentifier':
return node.name === name;
case 'JSXElement':
return containsIdentifier(name, node.openingElement) ||
someContainIdentifier(name, node.children);
case 'JSXOpeningElement':
return containsIdentifier(name, node.name) ||
someContainIdentifier(name, node.attributes);
case 'JSXExpressionContainer':
return containsIdentifier(name, node.expression);
case 'JSXSpreadAttribute':
return containsIdentifier(name, node.argument);
case 'JSXAttribute':
return containsIdentifier(name, node.value);
default:
return false;
}
}
function someContainIdentifier(name, array) {
return Array.isArray(array) && array.some(item => {
return containsIdentifier(name, item);
});
}
module.exports = {
containsIdentifier,
someContainIdentifier
};
+18
View File
@@ -0,0 +1,18 @@
'use strict';
const computeStaticExpression = require('./compute-static-expression');
function getPropertyName(node) {
if (!node || node.type !== 'MemberExpression') {
return undefined;
}
if (node.property.type === 'Identifier' && node.computed === false) {
return node.property.name;
}
const expression = computeStaticExpression(node.property);
return expression && expression.value;
}
module.exports = getPropertyName;
+10
View File
@@ -0,0 +1,10 @@
'use strict';
const get = require('lodash.get');
const isStaticRequire = require('./is-static-require');
function getRequireSource(node) {
return isStaticRequire(node) ? get(node, 'arguments.0.value') : undefined;
}
module.exports = getRequireSource;
+13
View File
@@ -0,0 +1,13 @@
'use strict';
const functionExpressions = [
'FunctionExpression',
'ArrowFunctionExpression'
];
function isFunctionExpression(node) {
return Boolean(node) &&
functionExpressions.indexOf(node.type) !== -1;
}
module.exports = isFunctionExpression;
+37
View File
@@ -0,0 +1,37 @@
'use strict';
const getPropertyName = require('./get-property-name');
const prototypeMethods = ['then', 'catch'];
const knownNotMethods = ['promisify', 'promisifyAll', 'cancel', 'is'];
function containsThenOrCatch(node) {
return Boolean(node) &&
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
prototypeMethods.indexOf(getPropertyName(node.callee)) !== -1;
}
function isPromiseStaticMethod(node) {
return Boolean(node) &&
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'Promise' &&
knownNotMethods.indexOf(getPropertyName(node.callee)) === -1;
}
function isNewPromise(node) {
return Boolean(node) &&
node.type === 'NewExpression' &&
node.callee.type === 'Identifier' &&
node.callee.name === 'Promise';
}
function isPromise(node) {
return containsThenOrCatch(node) ||
isPromiseStaticMethod(node) ||
isNewPromise(node);
}
module.exports = isPromise;
+14
View File
@@ -0,0 +1,14 @@
'use strict';
function isStaticRequire(node) {
return Boolean(node &&
node.callee &&
node.callee.type === 'Identifier' &&
node.callee.name === 'require' &&
node.arguments.length === 1 &&
node.arguments[0].type === 'Literal' &&
typeof node.arguments[0].value === 'string'
);
}
module.exports = isStaticRequire;
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Jeroen Engels <jfm.engels@gmail.com> (github.com/jfmengels)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+89
View File
@@ -0,0 +1,89 @@
{
"_args": [
[
"eslint-ast-utils@1.1.0",
"/home/node/nuxt"
]
],
"_development": true,
"_from": "eslint-ast-utils@1.1.0",
"_id": "eslint-ast-utils@1.1.0",
"_inBundle": false,
"_integrity": "sha512-otzzTim2/1+lVrlH19EfQQJEhVJSu0zOb9ygb3iapN6UlyaDtyRq4b5U1FuW0v1lRa9Fp/GJyHkSwm6NqABgCA==",
"_location": "/eslint-ast-utils",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "eslint-ast-utils@1.1.0",
"name": "eslint-ast-utils",
"escapedName": "eslint-ast-utils",
"rawSpec": "1.1.0",
"saveSpec": null,
"fetchSpec": "1.1.0"
},
"_requiredBy": [
"/eslint-plugin-unicorn"
],
"_resolved": "https://registry.npmjs.org/eslint-ast-utils/-/eslint-ast-utils-1.1.0.tgz",
"_spec": "1.1.0",
"_where": "/home/node/nuxt",
"author": {
"name": "Jeroen Engels",
"email": "jfm.engels@gmail.com",
"url": "github.com/jfmengels"
},
"bugs": {
"url": "https://github.com/jfmengels/eslint-ast-utils/issues"
},
"dependencies": {
"lodash.get": "^4.4.2",
"lodash.zip": "^4.2.0"
},
"description": "Utility library to manipulate ASTs",
"devDependencies": {
"ava": "^0.16.0",
"babel-eslint": "^7.0.0",
"espree": "^3.3.2",
"nyc": "^7.1.0",
"xo": "^0.17.0"
},
"engines": {
"node": ">=4"
},
"files": [
"index.js",
"lib"
],
"homepage": "https://github.com/jfmengels/eslint-ast-utils#readme",
"keywords": [
"eslint",
"ast",
"utils",
"Utility"
],
"license": "MIT",
"name": "eslint-ast-utils",
"nyc": {
"reporter": [
"lcov",
"text"
],
"check-coverage": true,
"lines": 100,
"statements": 100,
"functions": 100,
"branches": 100
},
"repository": {
"type": "git",
"url": "git+https://github.com/jfmengels/eslint-ast-utils.git"
},
"scripts": {
"test": "xo && nyc ava"
},
"version": "1.1.0",
"xo": {
"esnext": true
}
}
+318
View File
@@ -0,0 +1,318 @@
# eslint-ast-utils [![Build Status](https://travis-ci.org/jfmengels/eslint-ast-utils.svg?branch=master)](https://travis-ci.org/jfmengels/eslint-ast-utils)
> Utility library to manipulate ASTs for ESLint projects
## Install
```
$ npm install --save eslint-ast-utils
```
## Usage
```js
const astUtils = require('eslint-ast-utils');
```
## API
### astUtils.isStaticRequire(node)
Checks whether `node` is a call to CommonJS's `require` function.
Returns `true` if and only if:
- `node` is a `CallExpression`
- `node`'s callee is an `Identifier` named `require`
- `node` has exactly 1 `Literal` argument whose value is a `string`
Example:
```js
require('lodash');
// => true
require(foo);
// => false
foo('lodash');
// => false
```
Usage example:
```js
function create(context) {
return {
CallExpression(node) {
if (astUtils.isStaticRequire(node)) {
context.report({
node: node,
message: 'Use import syntax rather than `require`'
});
}
}
};
}
```
### astUtils.getRequireSource(node)
Gets the source of a `require()` call. If `node` is not a `require` call (in the definition of [`isStaticRequire`](#astutilsisstaticrequirenode)), it will return `undefined`.
Example:
```js
require('lodash');
// => 'lodash'
require('./foo');
// => './foo'
```
Usage example:
```js
function create(context) {
return {
CallExpression(node) {
if (astUtils.isStaticRequire(node) && astUtils.getRequireSource(node) === 'underscore') {
context.report({
node: node,
message: 'Use `lodash` instead of `underscore`'
});
}
}
};
}
```
### astUtils.containsIdentifier(name, node)
Checks if there is a reference to a variable named `name` inside of `node`.
Returns true if and only if:
- There is an `Identifier` named `name` inside of `node`
- That `Identifier` is a variable (i.e. not a static property name for instance)
- That `Identifier` does not reference a different variable named `name` introduced in a sub-scope of `node`.
Example:
```js
foo(a);
// containsIdentifier('a', node) // => true
// containsIdentifier('b', node) // => true
function foo(fn) {
return function(a) {
return fn(a);
};
}
// containsIdentifier('a', node) // => false
```
Usage example:
```js
function create(context) {
return {
FunctionDeclaration(node) {
node.params.forEach(param => {
if (param.type === 'Identifier' && !astUtils.containsIdentifier(param.name, node.body)) {
context.report({
node: node,
message: `${name} is never used`
});
}
});
}
};
}
```
### astUtils.someContainIdentifier(name, nodes)
Checks if there is a reference to a variable named `name` inside any node of the `nodes` array. Will return `false` if `nodes` is not an array.
This is a shorthand version of [`containsIdentifier`](#astutilscontainsidentifier) that works for arrays. The following are equivalent:
```js
[node1, node2, node3].some(node => astUtils.containsIdentifier('a', node));
// equivalent to
astUtils.someContainIdentifier('a', [node1, node2, node3]);
```
### astUtils.getPropertyName(node)
Get the name of a `MemberExpression`'s property. Returns:
- a `string` if the property is accessed through dot notation.
- a `string` if the property is accessed through brackets and is a string.
- a `number` if the property is accessed through brackets and is a number.
- `undefined` if `node` is not a `MemberExpression`
- `undefined` if the property name is a hard to compute expression.
Example:
```js
foo.bar
// => 'bar'
foo['bar']
// => 'bar'
foo[bar]
// => undefined
foo[0]
// => 0 # Number
foo[null]
// => null
foo[undefined]
// => undefined
```
Usage example:
```js
function create(context) {
return {
MemberExpression(node) {
if (astUtils.getPropertyName(node).startsWith('_')) {
context.report({
node: node,
message: 'Don\'t access "private" fields'
});
}
}
};
}
```
### astUtils.computeStaticExpression(node)
Get the value of an expression that can be statically computed, i.e. without variables references or expressions too complex.
Returns:
- `undefined` if the value could not be statically computed.
- An object with a `value` property containing the computed value.
Example:
```js
foo
// => undefined
42
// => {value: 42}
'foo'
// => {value: 'foo'}
undefined
// => {value: undefined}
null
// => {value: null}
1 + 2 - 4 + (-1)
// => {value: -2}
true ? 1 : 2
// => {value: 1}
`foo ${'bar'}`
// => {value: 'foo bar'}
```
Usage example:
```js
function create(context) {
return {
TemplateLiteral(node) {
const expression = astUtils.computeStaticExpression(node);
if (expression) {
context.report({
node: node,
message: `You can replace this template literal by the regular string '${expression.value}'.`
});
}
}
};
}
```
### astUtils.isPromise(node)
Checks whether `node` is a Promise.
Returns `true` if and only if `node` is one of the following:
- a call of an expression's `then` or `catch` properties
- a call to a property of `Promise` (except `cancel`, `promisify`, `promisifyAll` and `is`)
- a call to `new Promise`
If `node` uses unknown properties of a value that would be considered a Promise, `node` itself would not be considered as a Promise.
Example:
```js
foo.then(fn);
// => true
foo.catch(fn);
// => true
foo.then(fn).catch(fn);
// => true
foo.then(fn).isFulfilled(fn); // isFulfilled(fn) may not return a Promise
// => false
Promise.resolve(value);
// => true
Promise.reject(value);
// => true
Promise.race(promises);
// => true
Promise.all(promises);
// => true
Promise.map(promises, fn); // Bluebird method
// => true
new Promise(fn);
// => true
new Promise.resolve(value);
// => false
```
Usage example:
```js
function create(context) {
function reportIfPromise(node) {
if (astUtils.isPromise(node)) {
context.report({
node: node,
message: 'Prefer using async/await'
});
}
}
return {
CallExpression: reportIfPromise,
NewExpression: reportIfPromise
};
}
```
### astUtils.isFunctionExpression(node)
Checks whether `node` is a function expression or an arrow function expression (not a function declaration).
If `node` uses unknown properties of a value that would be considered a Promise, `node` itself would not be considered as a Promise.
Example:
```js
(function foo() {})
// => true
() => {}
// => true
function foo() {} // function declaration
// => false
```
Usage example:
```js
function create(context) {
return {
CallExpression(node) {
if (node.callee.type === 'Identifier'
&& node.callee.name === 'test'
&& !astUtils.isFunctionExpression(node.arguments[0])
&& !astUtils.isFunctionExpression(node.arguments[1])
) {
context.report({
node: node,
message: 'You need to pass a function to test()'
});
}
}
};
}
```
## License
MIT © [Jeroen Engels](https://github.com/jfmengels)