0.2.0 - Mid migration

This commit is contained in:
Daniel Mason 2022-04-25 14:47:15 +12:00
parent 139e6a915e
commit 7e38fdbd7d
42393 changed files with 5358157 additions and 62 deletions

15
web/node_modules/eslint-plugin-jest/lib/globals.json generated vendored Normal file
View file

@ -0,0 +1,15 @@
{
"afterAll": false,
"afterEach": false,
"beforeAll": false,
"beforeEach": false,
"describe": false,
"expect": false,
"fit": false,
"it": false,
"jest": false,
"test": false,
"xdescribe": false,
"xit": false,
"xtest": false
}

71
web/node_modules/eslint-plugin-jest/lib/index.js generated vendored Normal file
View file

@ -0,0 +1,71 @@
"use strict";
var _fs = require("fs");
var _path = require("path");
var _globals = _interopRequireDefault(require("./globals.json"));
var snapshotProcessor = _interopRequireWildcard(require("./processors/snapshot-processor"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// copied from https://github.com/babel/babel/blob/d8da63c929f2d28c401571e2a43166678c555bc4/packages/babel-helpers/src/helpers.js#L602-L606
/* istanbul ignore next */
const interopRequireDefault = obj => obj && obj.__esModule ? obj : {
default: obj
};
const importDefault = moduleName => // eslint-disable-next-line @typescript-eslint/no-require-imports
interopRequireDefault(require(moduleName)).default;
const rulesDir = (0, _path.join)(__dirname, 'rules');
const excludedFiles = ['__tests__', 'utils'];
const rules = (0, _fs.readdirSync)(rulesDir).map(rule => (0, _path.parse)(rule).name).filter(rule => !excludedFiles.includes(rule)).reduce((acc, curr) => ({ ...acc,
[curr]: importDefault((0, _path.join)(rulesDir, curr))
}), {});
const recommendedRules = Object.entries(rules).filter(([, rule]) => rule.meta.docs.recommended).reduce((acc, [name, rule]) => ({ ...acc,
[`jest/${name}`]: rule.meta.docs.recommended
}), {});
const allRules = Object.entries(rules).filter(([, rule]) => !rule.meta.deprecated).reduce((acc, [name]) => ({ ...acc,
[`jest/${name}`]: 'error'
}), {});
const createConfig = rules => ({
plugins: ['jest'],
env: {
'jest/globals': true
},
rules
});
module.exports = {
configs: {
all: createConfig(allRules),
recommended: createConfig(recommendedRules),
style: {
plugins: ['jest'],
rules: {
'jest/no-alias-methods': 'warn',
'jest/prefer-to-be-null': 'error',
'jest/prefer-to-be-undefined': 'error',
'jest/prefer-to-contain': 'error',
'jest/prefer-to-have-length': 'error'
}
}
},
environments: {
globals: {
globals: _globals.default
}
},
processors: {
'.snap': snapshotProcessor
},
rules
};

View file

@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.postprocess = exports.preprocess = void 0;
// https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
// https://github.com/typescript-eslint/typescript-eslint/issues/808
const preprocess = source => [source];
exports.preprocess = preprocess;
const postprocess = messages => // snapshot files should only be linted with snapshot specific rules
messages[0].filter(message => message.ruleId === 'jest/no-large-snapshots');
exports.postprocess = postprocess;

View file

@ -0,0 +1,119 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const buildFixer = (callee, nodeName, preferredTestKeyword) => fixer => [fixer.replaceText(callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression ? callee.object : callee, getPreferredNodeName(nodeName, preferredTestKeyword))];
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Have control over `test` and `it` usages',
recommended: false
},
fixable: 'code',
messages: {
consistentMethod: "Prefer using '{{ testKeyword }}' instead of '{{ oppositeTestKeyword }}'",
consistentMethodWithinDescribe: "Prefer using '{{ testKeywordWithinDescribe }}' instead of '{{ oppositeTestKeyword }}' within describe"
},
schema: [{
type: 'object',
properties: {
fn: {
enum: [_utils.TestCaseName.it, _utils.TestCaseName.test]
},
withinDescribe: {
enum: [_utils.TestCaseName.it, _utils.TestCaseName.test]
}
},
additionalProperties: false
}],
type: 'suggestion'
},
defaultOptions: [{
fn: _utils.TestCaseName.test,
withinDescribe: _utils.TestCaseName.it
}],
create(context) {
const configObj = context.options[0] || {};
const testKeyword = configObj.fn || _utils.TestCaseName.test;
const testKeywordWithinDescribe = configObj.withinDescribe || configObj.fn || _utils.TestCaseName.it;
let describeNestingLevel = 0;
return {
CallExpression(node) {
const nodeName = (0, _utils.getNodeName)(node.callee);
if (!nodeName) {
return;
}
if ((0, _utils.isDescribeCall)(node)) {
describeNestingLevel++;
}
const funcNode = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
if ((0, _utils.isTestCaseCall)(node) && describeNestingLevel === 0 && !nodeName.includes(testKeyword)) {
const oppositeTestKeyword = getOppositeTestKeyword(testKeyword);
context.report({
messageId: 'consistentMethod',
node: node.callee,
data: {
testKeyword,
oppositeTestKeyword
},
fix: buildFixer(funcNode, nodeName, testKeyword)
});
}
if ((0, _utils.isTestCaseCall)(node) && describeNestingLevel > 0 && !nodeName.includes(testKeywordWithinDescribe)) {
const oppositeTestKeyword = getOppositeTestKeyword(testKeywordWithinDescribe);
context.report({
messageId: 'consistentMethodWithinDescribe',
node: node.callee,
data: {
testKeywordWithinDescribe,
oppositeTestKeyword
},
fix: buildFixer(funcNode, nodeName, testKeywordWithinDescribe)
});
}
},
'CallExpression:exit'(node) {
if ((0, _utils.isDescribeCall)(node)) {
describeNestingLevel--;
}
}
};
}
});
exports.default = _default;
function getPreferredNodeName(nodeName, preferredTestKeyword) {
if (nodeName === _utils.TestCaseName.fit) {
return 'test.only';
}
return nodeName.startsWith('f') || nodeName.startsWith('x') ? nodeName.charAt(0) + preferredTestKeyword : preferredTestKeyword;
}
function getOppositeTestKeyword(test) {
if (test === _utils.TestCaseName.test) {
return _utils.TestCaseName.it;
}
return _utils.TestCaseName.test;
}

View file

@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
/*
* This implementation is adapted from eslint-plugin-jasmine.
* MIT license, Remco Haszing.
*/
/**
* Checks if node names returned by getNodeName matches any of the given star patterns
* Pattern examples:
* request.*.expect
* request.**.expect
* request.**.expect*
*/
function matchesAssertFunctionName(nodeName, patterns) {
return patterns.some(p => new RegExp(`^${p.split('.').map(x => {
if (x === '**') return '[a-z\\.]*';
return x.replace(/\*/gu, '[a-z]*');
}).join('\\.')}(\\.|$)`, 'ui').test(nodeName));
}
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Enforce assertion to be made in a test body',
recommended: 'warn'
},
messages: {
noAssertions: 'Test has no assertions'
},
schema: [{
type: 'object',
properties: {
assertFunctionNames: {
type: 'array',
items: [{
type: 'string'
}]
}
},
additionalProperties: false
}],
type: 'suggestion'
},
defaultOptions: [{
assertFunctionNames: ['expect']
}],
create(context, [{
assertFunctionNames = ['expect']
}]) {
const unchecked = [];
function checkCallExpressionUsed(nodes) {
for (const node of nodes) {
const index = node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? unchecked.indexOf(node) : -1;
if (node.type === _experimentalUtils.AST_NODE_TYPES.FunctionDeclaration) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
checkCallExpressionUsed(testCallExpressions);
}
if (index !== -1) {
unchecked.splice(index, 1);
break;
}
}
}
return {
CallExpression(node) {
const name = (0, _utils.getNodeName)(node.callee);
if (name === _utils.TestCaseName.it || name === _utils.TestCaseName.test) {
unchecked.push(node);
} else if (name && matchesAssertFunctionName(name, assertFunctionNames)) {
// Return early in case of nested `it` statements.
checkCallExpressionUsed(context.getAncestors());
}
},
'Program:exit'() {
unchecked.forEach(node => context.report({
messageId: 'noAssertions',
node
}));
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,131 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
const hasStringAsFirstArgument = node => node.arguments[0] && (0, _utils.isStringNode)(node.arguments[0]);
const findNodeNameAndArgument = node => {
if (!((0, _utils.isTestCaseCall)(node) || (0, _utils.isDescribeCall)(node))) {
return null;
}
if (!hasStringAsFirstArgument(node)) {
return null;
}
return [(0, _utils.getNodeName)(node).split('.')[0], node.arguments[0]];
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
type: 'suggestion',
docs: {
description: 'Enforce lowercase test names',
category: 'Best Practices',
recommended: false
},
fixable: 'code',
messages: {
unexpectedLowercase: '`{{ method }}`s should begin with lowercase'
},
schema: [{
type: 'object',
properties: {
ignore: {
type: 'array',
items: {
enum: [_utils.DescribeAlias.describe, _utils.TestCaseName.test, _utils.TestCaseName.it]
},
additionalItems: false
},
allowedPrefixes: {
type: 'array',
items: {
type: 'string'
},
additionalItems: false
},
ignoreTopLevelDescribe: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}]
},
defaultOptions: [{
ignore: [],
allowedPrefixes: [],
ignoreTopLevelDescribe: false
}],
create(context, [{
ignore = [],
allowedPrefixes = [],
ignoreTopLevelDescribe
}]) {
let numberOfDescribeBlocks = 0;
return {
CallExpression(node) {
if ((0, _utils.isDescribeCall)(node)) {
numberOfDescribeBlocks++;
if (ignoreTopLevelDescribe && numberOfDescribeBlocks === 1) {
return;
}
}
const results = findNodeNameAndArgument(node);
if (!results) {
return;
}
const [name, firstArg] = results;
const description = (0, _utils.getStringValue)(firstArg);
if (allowedPrefixes.some(name => description.startsWith(name))) {
return;
}
const firstCharacter = description.charAt(0);
if (!firstCharacter || firstCharacter === firstCharacter.toLowerCase() || ignore.includes(name)) {
return;
}
context.report({
messageId: 'unexpectedLowercase',
node: node.arguments[0],
data: {
method: name
},
fix(fixer) {
const description = (0, _utils.getStringValue)(firstArg);
const rangeIgnoringQuotes = [firstArg.range[0] + 1, firstArg.range[1] - 1];
const newDescription = description.substring(0, 1).toLowerCase() + description.substring(1);
return [fixer.replaceTextRange(rangeIgnoringQuotes, newDescription)];
}
});
},
'CallExpression:exit'(node) {
if ((0, _utils.isDescribeCall)(node)) {
numberOfDescribeBlocks--;
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,87 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Enforces a maximum depth to nested describe calls',
recommended: false
},
messages: {
exceededMaxDepth: 'Too many nested describe calls ({{ depth }}). Maximum allowed is {{ max }}.'
},
type: 'suggestion',
schema: [{
type: 'object',
properties: {
max: {
type: 'integer',
minimum: 0
}
},
additionalProperties: false
}]
},
defaultOptions: [{
max: 5
}],
create(context, [{
max
}]) {
const describeCallbackStack = [];
function pushDescribeCallback(node) {
const {
parent
} = node;
if ((parent === null || parent === void 0 ? void 0 : parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression || !(0, _utils.isDescribeCall)(parent)) {
return;
}
describeCallbackStack.push(0);
if (describeCallbackStack.length > max) {
context.report({
node: parent,
messageId: 'exceededMaxDepth',
data: {
depth: describeCallbackStack.length,
max
}
});
}
}
function popDescribeCallback(node) {
const {
parent
} = node;
if ((parent === null || parent === void 0 ? void 0 : parent.type) === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isDescribeCall)(parent)) {
describeCallbackStack.pop();
}
}
return {
FunctionExpression: pushDescribeCallback,
'FunctionExpression:exit': popDescribeCallback,
ArrowFunctionExpression: pushDescribeCallback,
'ArrowFunctionExpression:exit': popDescribeCallback
};
}
});
exports.default = _default;

View file

@ -0,0 +1,77 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow alias methods',
recommended: false
},
messages: {
replaceAlias: `Replace {{ alias }}() with its canonical name of {{ canonical }}()`
},
fixable: 'code',
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
// map of jest matcher aliases & their canonical names
const methodNames = {
toBeCalled: 'toHaveBeenCalled',
toBeCalledTimes: 'toHaveBeenCalledTimes',
toBeCalledWith: 'toHaveBeenCalledWith',
lastCalledWith: 'toHaveBeenLastCalledWith',
nthCalledWith: 'toHaveBeenNthCalledWith',
toReturn: 'toHaveReturned',
toReturnTimes: 'toHaveReturnedTimes',
toReturnWith: 'toHaveReturnedWith',
lastReturnedWith: 'toHaveLastReturnedWith',
nthReturnedWith: 'toHaveNthReturnedWith',
toThrowError: 'toThrow'
};
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
if (!matcher) {
return;
}
const alias = matcher.name;
if (alias in methodNames) {
const canonical = methodNames[alias];
context.report({
messageId: 'replaceAlias',
data: {
alias,
canonical
},
node: matcher.node.property,
fix: fixer => [fixer.replaceText(matcher.node.property, canonical)]
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,55 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
function hasTests(node) {
return /^\s*[xf]?(test|it|describe)(\.\w+|\[['"]\w+['"]\])?\s*\(/mu.test(node.value);
}
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow commented out tests',
recommended: 'warn'
},
messages: {
commentedTests: 'Some tests seem to be commented'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();
function checkNode(node) {
if (!hasTests(node)) {
return;
}
context.report({
messageId: 'commentedTests',
node
});
}
return {
Program() {
const comments = sourceCode.getAllComments();
comments.forEach(checkNode);
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,98 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const isCatchCall = node => node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property, 'catch');
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
description: 'Prevent calling `expect` conditionally',
category: 'Best Practices',
recommended: 'error'
},
messages: {
conditionalExpect: 'Avoid calling `expect` conditionally`'
},
type: 'problem',
schema: []
},
defaultOptions: [],
create(context) {
let conditionalDepth = 0;
let inTestCase = false;
let inPromiseCatch = false;
const increaseConditionalDepth = () => inTestCase && conditionalDepth++;
const decreaseConditionalDepth = () => inTestCase && conditionalDepth--;
return {
FunctionDeclaration(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
if (testCallExpressions.length > 0) {
inTestCase = true;
}
},
CallExpression(node) {
if ((0, _utils.isTestCaseCall)(node)) {
inTestCase = true;
}
if (isCatchCall(node)) {
inPromiseCatch = true;
}
if (inTestCase && (0, _utils.isExpectCall)(node) && conditionalDepth > 0) {
context.report({
messageId: 'conditionalExpect',
node
});
}
if (inPromiseCatch && (0, _utils.isExpectCall)(node)) {
context.report({
messageId: 'conditionalExpect',
node
});
}
},
'CallExpression:exit'(node) {
if ((0, _utils.isTestCaseCall)(node)) {
inTestCase = false;
}
if (isCatchCall(node)) {
inPromiseCatch = false;
}
},
CatchClause: increaseConditionalDepth,
'CatchClause:exit': decreaseConditionalDepth,
IfStatement: increaseConditionalDepth,
'IfStatement:exit': decreaseConditionalDepth,
SwitchStatement: increaseConditionalDepth,
'SwitchStatement:exit': decreaseConditionalDepth,
ConditionalExpression: increaseConditionalDepth,
'ConditionalExpression:exit': decreaseConditionalDepth,
LogicalExpression: increaseConditionalDepth,
'LogicalExpression:exit': decreaseConditionalDepth
};
}
});
exports.default = _default;

View file

@ -0,0 +1,121 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports._clearCachedJestVersion = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
let cachedJestVersion = null;
/** @internal */
const _clearCachedJestVersion = () => cachedJestVersion = null;
exports._clearCachedJestVersion = _clearCachedJestVersion;
const detectJestVersion = () => {
if (cachedJestVersion) {
return cachedJestVersion;
}
try {
const jestPath = require.resolve('jest/package.json', {
paths: [process.cwd()]
});
const jestPackageJson = // eslint-disable-next-line @typescript-eslint/no-require-imports
require(jestPath);
if (jestPackageJson.version) {
const [majorVersion] = jestPackageJson.version.split('.');
return cachedJestVersion = parseInt(majorVersion, 10);
}
} catch {}
throw new Error('Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly');
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow use of deprecated functions',
recommended: 'error'
},
messages: {
deprecatedFunction: '`{{ deprecation }}` has been deprecated in favor of `{{ replacement }}`'
},
type: 'suggestion',
schema: [],
fixable: 'code'
},
defaultOptions: [],
create(context) {
var _context$settings, _context$settings$jes;
const jestVersion = ((_context$settings = context.settings) === null || _context$settings === void 0 ? void 0 : (_context$settings$jes = _context$settings.jest) === null || _context$settings$jes === void 0 ? void 0 : _context$settings$jes.version) || detectJestVersion();
const deprecations = { ...(jestVersion >= 15 && {
'jest.resetModuleRegistry': 'jest.resetModules'
}),
...(jestVersion >= 17 && {
'jest.addMatchers': 'expect.extend'
}),
...(jestVersion >= 21 && {
'require.requireMock': 'jest.requireMock',
'require.requireActual': 'jest.requireActual'
}),
...(jestVersion >= 22 && {
'jest.runTimersToTime': 'jest.advanceTimersByTime'
}),
...(jestVersion >= 26 && {
'jest.genMockFromModule': 'jest.createMockFromModule'
})
};
return {
CallExpression(node) {
if (node.callee.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
return;
}
const deprecation = (0, _utils.getNodeName)(node);
if (!deprecation || !(deprecation in deprecations)) {
return;
}
const replacement = deprecations[deprecation];
const {
callee
} = node;
context.report({
messageId: 'deprecatedFunction',
data: {
deprecation,
replacement
},
node,
fix(fixer) {
let [name, func] = replacement.split('.');
if (callee.property.type === _experimentalUtils.AST_NODE_TYPES.Literal) {
func = `'${func}'`;
}
return [fixer.replaceText(callee.object, name), fixer.replaceText(callee.property, func)];
}
});
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,135 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow disabled tests',
recommended: 'warn'
},
messages: {
missingFunction: 'Test is missing function argument',
skippedTestSuite: 'Skipped test suite',
skippedTest: 'Skipped test',
pending: 'Call to pending()',
pendingSuite: 'Call to pending() within test suite',
pendingTest: 'Call to pending() within test',
disabledSuite: 'Disabled test suite',
disabledTest: 'Disabled test'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
let suiteDepth = 0;
let testDepth = 0;
return {
'CallExpression[callee.name="describe"]'() {
suiteDepth++;
},
'CallExpression[callee.name=/^(it|test)$/]'() {
testDepth++;
},
'CallExpression[callee.name=/^(it|test)$/][arguments.length<2]'(node) {
context.report({
messageId: 'missingFunction',
node
});
},
CallExpression(node) {
const functionName = (0, _utils.getNodeName)(node.callee); // prevent duplicate warnings for it.each()()
if (node.callee.type === 'CallExpression') {
return;
}
switch (functionName) {
case 'describe.skip.each':
case 'xdescribe.each':
case 'describe.skip':
context.report({
messageId: 'skippedTestSuite',
node
});
break;
case 'it.skip':
case 'it.concurrent.skip':
case 'test.skip':
case 'test.concurrent.skip':
case 'it.skip.each':
case 'test.skip.each':
case 'xit.each':
case 'xtest.each':
context.report({
messageId: 'skippedTest',
node
});
break;
}
},
'CallExpression[callee.name="pending"]'(node) {
if ((0, _utils.scopeHasLocalReference)(context.getScope(), 'pending')) {
return;
}
if (testDepth > 0) {
context.report({
messageId: 'pendingTest',
node
});
} else if (suiteDepth > 0) {
context.report({
messageId: 'pendingSuite',
node
});
} else {
context.report({
messageId: 'pending',
node
});
}
},
'CallExpression[callee.name="xdescribe"]'(node) {
context.report({
messageId: 'disabledSuite',
node
});
},
'CallExpression[callee.name=/^(xit|xtest)$/]'(node) {
context.report({
messageId: 'disabledTest',
node
});
},
'CallExpression[callee.name="describe"]:exit'() {
suiteDepth--;
},
'CallExpression[callee.name=/^(it|test)$/]:exit'() {
testDepth--;
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,147 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const findCallbackArg = (node, isJestEach) => {
if (isJestEach) {
return node.arguments[1];
}
if ((0, _utils.isHook)(node) && node.arguments.length >= 1) {
return node.arguments[0];
}
if ((0, _utils.isTestCaseCall)(node) && node.arguments.length >= 2) {
return node.arguments[1];
}
return null;
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Avoid using a callback in asynchronous tests and hooks',
recommended: 'error',
suggestion: true
},
messages: {
noDoneCallback: 'Return a Promise instead of relying on callback parameter',
suggestWrappingInPromise: 'Wrap in `new Promise({{ callback }} => ...`',
useAwaitInsteadOfCallback: 'Use await instead of callback in async functions'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
var _getNodeName$endsWith, _getNodeName;
// done is the second argument for it.each, not the first
const isJestEach = (_getNodeName$endsWith = (_getNodeName = (0, _utils.getNodeName)(node.callee)) === null || _getNodeName === void 0 ? void 0 : _getNodeName.endsWith('.each')) !== null && _getNodeName$endsWith !== void 0 ? _getNodeName$endsWith : false;
if (isJestEach && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) {
// isJestEach but not a TaggedTemplateExpression, so this must be
// the `jest.each([])()` syntax which this rule doesn't support due
// to its complexity (see jest-community/eslint-plugin-jest#710)
return;
}
const callback = findCallbackArg(node, isJestEach);
const callbackArgIndex = Number(isJestEach);
if (!callback || !(0, _utils.isFunction)(callback) || callback.params.length !== 1 + callbackArgIndex) {
return;
}
const argument = callback.params[callbackArgIndex];
if (argument.type !== _experimentalUtils.AST_NODE_TYPES.Identifier) {
context.report({
node: argument,
messageId: 'noDoneCallback'
});
return;
}
if (callback.async) {
context.report({
node: argument,
messageId: 'useAwaitInsteadOfCallback'
});
return;
}
context.report({
node: argument,
messageId: 'noDoneCallback',
suggest: [{
messageId: 'suggestWrappingInPromise',
data: {
callback: argument.name
},
fix(fixer) {
const {
body
} = callback;
const sourceCode = context.getSourceCode();
const firstBodyToken = sourceCode.getFirstToken(body);
const lastBodyToken = sourceCode.getLastToken(body);
const tokenBeforeArgument = sourceCode.getTokenBefore(argument);
const tokenAfterArgument = sourceCode.getTokenAfter(argument);
/* istanbul ignore if */
if (!firstBodyToken || !lastBodyToken || !tokenBeforeArgument || !tokenAfterArgument) {
throw new Error(`Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
}
const argumentInParens = tokenBeforeArgument.value === '(' && tokenAfterArgument.value === ')';
let argumentFix = fixer.replaceText(argument, '()');
if (argumentInParens) {
argumentFix = fixer.remove(argument);
}
let newCallback = argument.name;
if (argumentInParens) {
newCallback = `(${newCallback})`;
}
let beforeReplacement = `new Promise(${newCallback} => `;
let afterReplacement = ')';
let replaceBefore = true;
if (body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
const keyword = 'return';
beforeReplacement = `${keyword} ${beforeReplacement}{`;
afterReplacement += '}';
replaceBefore = false;
}
return [argumentFix, replaceBefore ? fixer.insertTextBefore(firstBodyToken, beforeReplacement) : fixer.insertTextAfter(firstBodyToken, beforeReplacement), fixer.insertTextAfter(lastBodyToken, afterReplacement)];
}
}]
});
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
const newHookContext = () => ({
beforeAll: 0,
beforeEach: 0,
afterAll: 0,
afterEach: 0
});
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow duplicate setup and teardown hooks',
recommended: false
},
messages: {
noDuplicateHook: 'Duplicate {{hook}} in describe block'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
const hookContexts = [newHookContext()];
return {
CallExpression(node) {
if ((0, _utils.isDescribeCall)(node)) {
hookContexts.push(newHookContext());
}
if ((0, _utils.isHook)(node)) {
const currentLayer = hookContexts[hookContexts.length - 1];
currentLayer[node.callee.name] += 1;
if (currentLayer[node.callee.name] > 1) {
context.report({
messageId: 'noDuplicateHook',
data: {
hook: node.callee.name
},
node
});
}
}
},
'CallExpression:exit'(node) {
if ((0, _utils.isDescribeCall)(node)) {
hookContexts.pop();
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow expect.resolves',
recommended: false
},
deprecated: true,
replacedBy: ['no-restricted-matchers'],
messages: {
expectResolves: 'Use `expect(await promise)` instead.'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create: context => ({
MemberExpression(node) {
if ((0, _utils.isExpectCall)(node.object) && (0, _utils.isSupportedAccessor)(node.property, _utils.ModifierName.resolves)) {
context.report({
node: node.property,
messageId: 'expectResolves'
});
}
}
})
});
exports.default = _default;

View file

@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow using `exports` in files containing tests',
recommended: 'error'
},
messages: {
unexpectedExport: `Do not export from a test file.`
},
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
const exportNodes = [];
let hasTestCase = false;
return {
'Program:exit'() {
if (hasTestCase && exportNodes.length > 0) {
for (const node of exportNodes) {
context.report({
node,
messageId: 'unexpectedExport'
});
}
}
},
CallExpression(node) {
if ((0, _utils.isTestCaseCall)(node)) {
hasTestCase = true;
}
},
'ExportNamedDeclaration, ExportDefaultDeclaration'(node) {
exportNodes.push(node);
},
'AssignmentExpression > MemberExpression'(node) {
let {
object,
property
} = node;
if (object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
({
object,
property
} = object);
}
if ('name' in object && object.name === 'module' && property.type === _experimentalUtils.AST_NODE_TYPES.Identifier && /^exports?$/u.test(property.name)) {
exportNodes.push(node);
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,84 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const findOnlyNode = node => {
const callee = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
if (callee.object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
if ((0, _utils.isSupportedAccessor)(callee.object.property, 'only')) {
return callee.object.property;
}
}
if ((0, _utils.isSupportedAccessor)(callee.property, 'only')) {
return callee.property;
}
}
return null;
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow focused tests',
recommended: 'error',
suggestion: true
},
messages: {
focusedTest: 'Unexpected focused test.',
suggestRemoveFocus: 'Remove focus from test.'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create: context => ({
CallExpression(node) {
if (!(0, _utils.isDescribeCall)(node) && !(0, _utils.isTestCaseCall)(node)) {
return;
}
if ((0, _utils.getNodeName)(node).startsWith('f')) {
context.report({
messageId: 'focusedTest',
node,
suggest: [{
messageId: 'suggestRemoveFocus',
fix: fixer => fixer.removeRange([node.range[0], node.range[0] + 1])
}]
});
return;
}
const onlyNode = findOnlyNode(node);
if (!onlyNode) {
return;
}
context.report({
messageId: 'focusedTest',
node: onlyNode,
suggest: [{
messageId: 'suggestRemoveFocus',
fix: fixer => fixer.removeRange([onlyNode.range[0] - 1, onlyNode.range[1] + Number(onlyNode.type !== _experimentalUtils.AST_NODE_TYPES.Identifier)])
}]
});
}
})
});
exports.default = _default;

View file

@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow setup and teardown hooks',
recommended: false
},
messages: {
unexpectedHook: "Unexpected '{{ hookName }}' hook"
},
schema: [{
type: 'object',
properties: {
allow: {
type: 'array',
contains: ['beforeAll', 'beforeEach', 'afterAll', 'afterEach']
}
},
additionalProperties: false
}],
type: 'suggestion'
},
defaultOptions: [{
allow: []
}],
create(context, [{
allow = []
}]) {
return {
CallExpression(node) {
if ((0, _utils.isHook)(node) && !allow.includes(node.callee.name)) {
context.report({
node,
messageId: 'unexpectedHook',
data: {
hookName: node.callee.name
}
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,92 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const newDescribeContext = () => ({
describeTitles: [],
testTitles: []
});
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow identical titles',
recommended: 'error'
},
messages: {
multipleTestTitle: 'Test title is used multiple times in the same describe block.',
multipleDescribeTitle: 'Describe block title is used multiple times in the same describe block.'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
const contexts = [newDescribeContext()];
return {
CallExpression(node) {
const currentLayer = contexts[contexts.length - 1];
if ((0, _utils.isDescribeCall)(node)) {
contexts.push(newDescribeContext());
}
if (node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) {
return;
}
const [argument] = node.arguments;
if (!argument || !(0, _utils.isStringNode)(argument)) {
return;
}
const title = (0, _utils.getStringValue)(argument);
if ((0, _utils.isTestCaseCall)(node)) {
if (currentLayer.testTitles.includes(title)) {
context.report({
messageId: 'multipleTestTitle',
node: argument
});
}
currentLayer.testTitles.push(title);
}
if (!(0, _utils.isDescribeCall)(node)) {
return;
}
if (currentLayer.describeTitles.includes(title)) {
context.report({
messageId: 'multipleDescribeTitle',
node: argument
});
}
currentLayer.describeTitles.push(title);
},
'CallExpression:exit'(node) {
if ((0, _utils.isDescribeCall)(node)) {
contexts.pop();
}
}
};
}
});
exports.default = _default;

107
web/node_modules/eslint-plugin-jest/lib/rules/no-if.js generated vendored Normal file
View file

@ -0,0 +1,107 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const testCaseNames = new Set([...Object.keys(_utils.TestCaseName), 'it.only', 'it.concurrent.only', 'it.skip', 'it.concurrent.skip', 'test.only', 'test.concurrent.only', 'test.skip', 'test.concurrent.skip', 'fit.concurrent']);
const isTestFunctionExpression = node => node.parent !== undefined && node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && testCaseNames.has((0, _utils.getNodeName)(node.parent.callee));
const conditionName = {
[_experimentalUtils.AST_NODE_TYPES.ConditionalExpression]: 'conditional',
[_experimentalUtils.AST_NODE_TYPES.SwitchStatement]: 'switch',
[_experimentalUtils.AST_NODE_TYPES.IfStatement]: 'if'
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
description: 'Disallow conditional logic',
category: 'Best Practices',
recommended: false
},
messages: {
conditionalInTest: 'Test should not contain {{ condition }} statements.'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
const stack = [];
function validate(node) {
const lastElementInStack = stack[stack.length - 1];
if (stack.length === 0 || !lastElementInStack) {
return;
}
context.report({
data: {
condition: conditionName[node.type]
},
messageId: 'conditionalInTest',
node
});
}
return {
CallExpression(node) {
if ((0, _utils.isTestCaseCall)(node)) {
stack.push(true);
if ((0, _utils.getNodeName)(node).endsWith('each')) {
stack.push(true);
}
}
},
FunctionExpression(node) {
stack.push(isTestFunctionExpression(node));
},
FunctionDeclaration(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
stack.push(testCallExpressions.length > 0);
},
ArrowFunctionExpression(node) {
stack.push(isTestFunctionExpression(node));
},
IfStatement: validate,
SwitchStatement: validate,
ConditionalExpression: validate,
'CallExpression:exit'() {
stack.pop();
},
'FunctionExpression:exit'() {
stack.pop();
},
'FunctionDeclaration:exit'() {
stack.pop();
},
'ArrowFunctionExpression:exit'() {
stack.pop();
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,63 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow string interpolation inside snapshots',
recommended: 'error'
},
messages: {
noInterpolation: 'Do not use string interpolation inside of snapshots'
},
schema: [],
type: 'problem'
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
if (!matcher) {
return;
}
if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes(matcher.name)) {
var _matcher$arguments;
// Check all since the optional 'propertyMatchers' argument might be present
(_matcher$arguments = matcher.arguments) === null || _matcher$arguments === void 0 ? void 0 : _matcher$arguments.forEach(argument => {
if (argument.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && argument.expressions.length > 0) {
context.report({
messageId: 'noInterpolation',
node: argument
});
}
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,165 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow Jasmine globals',
recommended: 'error'
},
messages: {
illegalGlobal: 'Illegal usage of global `{{ global }}`, prefer `{{ replacement }}`',
illegalMethod: 'Illegal usage of `{{ method }}`, prefer `{{ replacement }}`',
illegalFail: 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
illegalPending: 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
illegalJasmine: 'Illegal usage of jasmine global'
},
fixable: 'code',
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
const {
callee
} = node;
const calleeName = (0, _utils.getNodeName)(callee);
if (!calleeName) {
return;
}
if (calleeName === 'spyOn' || calleeName === 'spyOnProperty' || calleeName === 'fail' || calleeName === 'pending') {
if ((0, _utils.scopeHasLocalReference)(context.getScope(), calleeName)) {
// It's a local variable, not a jasmine global.
return;
}
switch (calleeName) {
case 'spyOn':
case 'spyOnProperty':
context.report({
node,
messageId: 'illegalGlobal',
data: {
global: calleeName,
replacement: 'jest.spyOn'
}
});
break;
case 'fail':
context.report({
node,
messageId: 'illegalFail'
});
break;
case 'pending':
context.report({
node,
messageId: 'illegalPending'
});
break;
}
return;
}
if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && calleeName.startsWith('jasmine.')) {
const functionName = calleeName.replace('jasmine.', '');
if (functionName === 'any' || functionName === 'anything' || functionName === 'arrayContaining' || functionName === 'objectContaining' || functionName === 'stringMatching') {
context.report({
fix: fixer => [fixer.replaceText(callee.object, 'expect')],
node,
messageId: 'illegalMethod',
data: {
method: calleeName,
replacement: `expect.${functionName}`
}
});
return;
}
if (functionName === 'addMatchers') {
context.report({
node,
messageId: 'illegalMethod',
data: {
method: calleeName,
replacement: 'expect.extend'
}
});
return;
}
if (functionName === 'createSpy') {
context.report({
node,
messageId: 'illegalMethod',
data: {
method: calleeName,
replacement: 'jest.fn'
}
});
return;
}
context.report({
node,
messageId: 'illegalJasmine'
});
}
},
MemberExpression(node) {
if ((0, _utils.isSupportedAccessor)(node.object, 'jasmine')) {
const {
parent,
property
} = node;
if (parent && parent.type === _experimentalUtils.AST_NODE_TYPES.AssignmentExpression) {
if ((0, _utils.isSupportedAccessor)(property, 'DEFAULT_TIMEOUT_INTERVAL')) {
const {
right
} = parent;
if (right.type === _experimentalUtils.AST_NODE_TYPES.Literal) {
context.report({
fix: fixer => [fixer.replaceText(parent, `jest.setTimeout(${right.value})`)],
node,
messageId: 'illegalJasmine'
});
return;
}
}
context.report({
node,
messageId: 'illegalJasmine'
});
}
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
type: 'problem',
docs: {
description: 'Disallow importing Jest',
category: 'Best Practices',
recommended: 'error'
},
messages: {
unexpectedImport: `Jest is automatically in scope. Do not import "jest", as Jest doesn't export anything.`
},
schema: []
},
defaultOptions: [],
create(context) {
return {
'ImportDeclaration[source.value="jest"]'(node) {
context.report({
node,
messageId: 'unexpectedImport'
});
},
'CallExpression[callee.name="require"][arguments.0.value="jest"]'(node) {
context.report({
loc: node.arguments[0].loc,
messageId: 'unexpectedImport',
node
});
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,131 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _path = require("path");
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const reportOnViolation = (context, node, {
maxSize: lineLimit = 50,
allowedSnapshots = {}
}) => {
const startLine = node.loc.start.line;
const endLine = node.loc.end.line;
const lineCount = endLine - startLine;
const allPathsAreAbsolute = Object.keys(allowedSnapshots).every(_path.isAbsolute);
if (!allPathsAreAbsolute) {
throw new Error('All paths for allowedSnapshots must be absolute. You can use JS config and `path.resolve`');
}
let isAllowed = false;
if (node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement && 'left' in node.expression && (0, _utils.isExpectMember)(node.expression.left)) {
const fileName = context.getFilename();
const allowedSnapshotsInFile = allowedSnapshots[fileName];
if (allowedSnapshotsInFile) {
const snapshotName = (0, _utils.getAccessorValue)(node.expression.left.property);
isAllowed = allowedSnapshotsInFile.some(name => {
if (name instanceof RegExp) {
return name.test(snapshotName);
}
return snapshotName === name;
});
}
}
if (!isAllowed && lineCount > lineLimit) {
context.report({
messageId: lineLimit === 0 ? 'noSnapshot' : 'tooLongSnapshots',
data: {
lineLimit,
lineCount
},
node
});
}
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'disallow large snapshots',
recommended: false
},
messages: {
noSnapshot: '`{{ lineCount }}`s should begin with lowercase',
tooLongSnapshots: 'Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long'
},
type: 'suggestion',
schema: [{
type: 'object',
properties: {
maxSize: {
type: 'number'
},
inlineMaxSize: {
type: 'number'
},
allowedSnapshots: {
type: 'object',
additionalProperties: {
type: 'array'
}
}
},
additionalProperties: false
}]
},
defaultOptions: [{}],
create(context, [options]) {
if (context.getFilename().endsWith('.snap')) {
return {
ExpressionStatement(node) {
reportOnViolation(context, node, options);
}
};
}
return {
CallExpression(node) {
var _matcher$node$parent;
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
if ((matcher === null || matcher === void 0 ? void 0 : (_matcher$node$parent = matcher.node.parent) === null || _matcher$node$parent === void 0 ? void 0 : _matcher$node$parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
return;
}
if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes(matcher.name)) {
var _options$inlineMaxSiz;
reportOnViolation(context, matcher.node.parent, { ...options,
maxSize: (_options$inlineMaxSiz = options.inlineMaxSize) !== null && _options$inlineMaxSiz !== void 0 ? _options$inlineMaxSiz : options.maxSize
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _path = require("path");
var _utils = require("./utils");
const mocksDirName = '__mocks__';
const isMockPath = path => path.split(_path.posix.sep).includes(mocksDirName);
const isMockImportLiteral = expression => (0, _utils.isStringNode)(expression) && isMockPath((0, _utils.getStringValue)(expression));
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
type: 'problem',
docs: {
category: 'Best Practices',
description: 'Disallow manually importing from `__mocks__`',
recommended: 'error'
},
messages: {
noManualImport: `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use \`jest.mock\` and import from the original module path.`
},
schema: []
},
defaultOptions: [],
create(context) {
return {
ImportDeclaration(node) {
if (isMockImportLiteral(node.source)) {
context.report({
node,
messageId: 'noManualImport'
});
}
},
'CallExpression[callee.name="require"]'(node) {
const [arg] = node.arguments;
if (arg && isMockImportLiteral(arg)) {
context.report({
node: arg,
messageId: 'noManualImport'
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow specific matchers & modifiers',
recommended: false
},
type: 'suggestion',
schema: [{
type: 'object',
additionalProperties: {
type: ['string', 'null']
}
}],
messages: {
restrictedChain: 'Use of `{{ chain }}` is disallowed',
restrictedChainWithMessage: '{{ message }}'
}
},
defaultOptions: [{}],
create(context, [restrictedChains]) {
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
matcher,
modifier
} = (0, _utils.parseExpectCall)(node);
if (matcher) {
const chain = matcher.name;
if (chain in restrictedChains) {
const message = restrictedChains[chain];
context.report({
messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain',
data: {
message,
chain
},
node: matcher.node.property
});
return;
}
}
if (modifier) {
const chain = modifier.name;
if (chain in restrictedChains) {
const message = restrictedChains[chain];
context.report({
messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain',
data: {
message,
chain
},
node: modifier.node.property
});
return;
}
}
if (matcher && modifier) {
const chain = `${modifier.name}.${matcher.name}`;
if (chain in restrictedChains) {
const message = restrictedChains[chain];
context.report({
messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain',
data: {
message,
chain
},
loc: {
start: modifier.node.property.loc.start,
end: matcher.node.property.loc.end
}
});
return;
}
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,143 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const getBlockType = statement => {
const func = statement.parent;
/* istanbul ignore if */
if (!func) {
throw new Error(`Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
} // functionDeclaration: function func() {}
if (func.type === _experimentalUtils.AST_NODE_TYPES.FunctionDeclaration) {
return 'function';
}
if ((0, _utils.isFunction)(func) && func.parent) {
const expr = func.parent; // arrow function or function expr
if (expr.type === _experimentalUtils.AST_NODE_TYPES.VariableDeclarator) {
return 'function';
} // if it's not a variable, it will be callExpr, we only care about describe
if (expr.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isDescribeCall)(expr)) {
return 'describe';
}
}
return null;
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow using `expect` outside of `it` or `test` blocks',
recommended: 'error'
},
messages: {
unexpectedExpect: 'Expect must be inside of a test block.'
},
type: 'suggestion',
schema: [{
properties: {
additionalTestBlockFunctions: {
type: 'array',
items: {
type: 'string'
}
}
},
additionalProperties: false
}]
},
defaultOptions: [{
additionalTestBlockFunctions: []
}],
create(context, [{
additionalTestBlockFunctions = []
}]) {
const callStack = [];
const isCustomTestBlockFunction = node => additionalTestBlockFunctions.includes((0, _utils.getNodeName)(node) || '');
const isTestBlock = node => (0, _utils.isTestCaseCall)(node) || isCustomTestBlockFunction(node);
return {
CallExpression(node) {
if ((0, _utils.isExpectCall)(node)) {
const parent = callStack[callStack.length - 1];
if (!parent || parent === _utils.DescribeAlias.describe) {
context.report({
node,
messageId: 'unexpectedExpect'
});
}
return;
}
if (isTestBlock(node)) {
callStack.push('test');
}
if (node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) {
callStack.push('template');
}
},
'CallExpression:exit'(node) {
const top = callStack[callStack.length - 1];
if (top === 'test' && isTestBlock(node) && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression || top === 'template' && node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) {
callStack.pop();
}
},
BlockStatement(statement) {
const blockType = getBlockType(statement);
if (blockType) {
callStack.push(blockType);
}
},
'BlockStatement:exit'(statement) {
if (callStack[callStack.length - 1] === getBlockType(statement)) {
callStack.pop();
}
},
ArrowFunctionExpression(node) {
var _node$parent;
if (((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
callStack.push('arrow');
}
},
'ArrowFunctionExpression:exit'() {
if (callStack[callStack.length - 1] === 'arrow') {
callStack.pop();
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Use `.only` and `.skip` over `f` and `x`',
recommended: 'error'
},
messages: {
usePreferredName: 'Use "{{ preferredNodeName }}" instead'
},
fixable: 'code',
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
const nodeName = (0, _utils.getNodeName)(node.callee);
if (!nodeName || !(0, _utils.isDescribeCall)(node) && !(0, _utils.isTestCaseCall)(node)) return;
const preferredNodeName = getPreferredNodeName(nodeName);
if (!preferredNodeName) return;
const funcNode = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
context.report({
messageId: 'usePreferredName',
node: node.callee,
data: {
preferredNodeName
},
fix(fixer) {
return [fixer.replaceText(funcNode, preferredNodeName)];
}
});
}
};
}
});
exports.default = _default;
function getPreferredNodeName(nodeName) {
const firstChar = nodeName.charAt(0);
const suffix = nodeName.endsWith('.each') ? '.each' : '';
if (firstChar === 'f') {
return `${nodeName.slice(1).replace('.each', '')}.only${suffix}`;
}
if (firstChar === 'x') {
return `${nodeName.slice(1).replace('.each', '')}.skip${suffix}`;
}
return null;
}

View file

@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const getBody = args => {
const [, secondArg] = args;
if (secondArg && (0, _utils.isFunction)(secondArg) && secondArg.body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
return secondArg.body.body;
}
return [];
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow explicitly returning from tests',
recommended: false
},
messages: {
noReturnValue: 'Jest tests should not return a value.'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isTestCaseCall)(node)) return;
const body = getBody(node.arguments);
const returnStmt = body.find(t => t.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement);
if (!returnStmt) return;
context.report({
messageId: 'noReturnValue',
node: returnStmt
});
},
FunctionDeclaration(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
if (testCallExpressions.length === 0) return;
const returnStmt = node.body.body.find(t => t.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement);
if (!returnStmt) return;
context.report({
messageId: 'noReturnValue',
node: returnStmt
});
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
// todo: refactor into "ban-matchers"
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Disallow using `toBeTruthy()` & `toBeFalsy()`',
recommended: false
},
deprecated: true,
replacedBy: ['no-restricted-matchers'],
messages: {
avoidMatcher: 'Avoid {{ matcherName }}'
},
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
if (!matcher || !['toBeTruthy', 'toBeFalsy'].includes(matcher.name)) {
return;
}
context.report({
messageId: 'avoidMatcher',
node: matcher.node.property,
data: {
matcherName: matcher.name
}
});
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
description: 'Prefer using toThrow for exception tests',
category: 'Best Practices',
recommended: 'error'
},
deprecated: true,
replacedBy: ['no-conditional-expect'],
messages: {
noTryExpect: ['Tests should use Jests exception helpers.', 'Use "expect(() => yourFunction()).toThrow()" for synchronous tests,', 'or "await expect(yourFunction()).rejects.toThrow()" for async tests'].join(' ')
},
type: 'problem',
schema: []
},
defaultOptions: [],
create(context) {
let isTest = false;
let catchDepth = 0;
function isThrowExpectCall(node) {
return catchDepth > 0 && (0, _utils.isExpectCall)(node);
}
return {
CallExpression(node) {
if ((0, _utils.isTestCaseCall)(node)) {
isTest = true;
} else if (isTest && isThrowExpectCall(node)) {
context.report({
messageId: 'noTryExpect',
node
});
}
},
FunctionDeclaration(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
if (testCallExpressions.length > 0) {
isTest = true;
}
},
CatchClause() {
if (isTest) {
++catchDepth;
}
},
'CatchClause:exit'() {
if (isTest) {
--catchDepth;
}
},
'CallExpression:exit'(node) {
if ((0, _utils.isTestCaseCall)(node)) {
isTest = false;
}
},
'FunctionDeclaration:exit'(node) {
const declaredVariables = context.getDeclaredVariables(node);
const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
if (testCallExpressions.length > 0) {
isTest = false;
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using `toBeCalledWith()` or `toHaveBeenCalledWith()`',
recommended: false
},
messages: {
preferCalledWith: 'Prefer {{name}}With(/* expected args */)'
},
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
modifier,
matcher
} = (0, _utils.parseExpectCall)(node); // Could check resolves/rejects here but not a likely idiom.
if (matcher && !modifier) {
if (['toBeCalled', 'toHaveBeenCalled'].includes(matcher.name)) {
context.report({
data: {
name: matcher.name
},
// todo: rename to 'matcherName'
messageId: 'preferCalledWith',
node: matcher.node.property
});
}
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,149 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const isExpectAssertionsOrHasAssertionsCall = expression => expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && expression.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(expression.callee.object, 'expect') && (0, _utils.isSupportedAccessor)(expression.callee.property) && ['assertions', 'hasAssertions'].includes((0, _utils.getAccessorValue)(expression.callee.property));
const isFirstLineExprStmt = functionBody => functionBody[0] && functionBody[0].type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement;
const suggestRemovingExtraArguments = (args, extraArgsStartAt) => ({
messageId: 'suggestRemovingExtraArguments',
fix: fixer => fixer.removeRange([args[extraArgsStartAt].range[0] - Math.sign(extraArgsStartAt), args[args.length - 1].range[1]])
});
const suggestions = [['suggestAddingHasAssertions', 'expect.hasAssertions();'], ['suggestAddingAssertions', 'expect.assertions();']];
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using `expect.assertions()` OR `expect.hasAssertions()`',
recommended: false,
suggestion: true
},
messages: {
hasAssertionsTakesNoArguments: '`expect.hasAssertions` expects no arguments',
assertionsRequiresOneArgument: '`expect.assertions` excepts a single argument of type number',
assertionsRequiresNumberArgument: 'This argument should be a number',
haveExpectAssertions: 'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression',
suggestAddingHasAssertions: 'Add `expect.hasAssertions()`',
suggestAddingAssertions: 'Add `expect.assertions(<number of assertions>)`',
suggestRemovingExtraArguments: 'Remove extra arguments'
},
type: 'suggestion',
schema: [{
type: 'object',
properties: {
onlyFunctionsWithAsyncKeyword: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
defaultOptions: [{
onlyFunctionsWithAsyncKeyword: false
}],
create(context, [options]) {
return {
CallExpression(node) {
if (!(0, _utils.isTestCaseCall)(node)) {
return;
}
if (node.arguments.length < 2) {
return;
}
const [, testFn] = node.arguments;
if (!(0, _utils.isFunction)(testFn) || testFn.body.type !== _experimentalUtils.AST_NODE_TYPES.BlockStatement || options.onlyFunctionsWithAsyncKeyword && !testFn.async) {
return;
}
const testFuncBody = testFn.body.body;
if (!isFirstLineExprStmt(testFuncBody)) {
context.report({
messageId: 'haveExpectAssertions',
node,
suggest: suggestions.map(([messageId, text]) => ({
messageId,
fix: fixer => fixer.insertTextBeforeRange([testFn.body.range[0] + 1, testFn.body.range[1]], text)
}))
});
return;
}
const testFuncFirstLine = testFuncBody[0].expression;
if (!isExpectAssertionsOrHasAssertionsCall(testFuncFirstLine)) {
context.report({
messageId: 'haveExpectAssertions',
node,
suggest: suggestions.map(([messageId, text]) => ({
messageId,
fix: fixer => fixer.insertTextBefore(testFuncBody[0], text)
}))
});
return;
}
if ((0, _utils.isSupportedAccessor)(testFuncFirstLine.callee.property, 'hasAssertions')) {
if (testFuncFirstLine.arguments.length) {
context.report({
messageId: 'hasAssertionsTakesNoArguments',
node: testFuncFirstLine.callee.property,
suggest: [suggestRemovingExtraArguments(testFuncFirstLine.arguments, 0)]
});
}
return;
}
if (!(0, _utils.hasOnlyOneArgument)(testFuncFirstLine)) {
let {
loc
} = testFuncFirstLine.callee.property;
const suggest = [];
if (testFuncFirstLine.arguments.length) {
loc = testFuncFirstLine.arguments[1].loc;
suggest.push(suggestRemovingExtraArguments(testFuncFirstLine.arguments, 1));
}
context.report({
messageId: 'assertionsRequiresOneArgument',
suggest,
loc
});
return;
}
const [arg] = testFuncFirstLine.arguments;
if (arg.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof arg.value === 'number' && Number.isInteger(arg.value)) {
return;
}
context.report({
messageId: 'assertionsRequiresNumberArgument',
node: arg
});
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest having hooks before any test cases',
recommended: false
},
messages: {
noHookOnTop: 'Move all hooks before test cases'
},
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
const hooksContext = [false];
return {
CallExpression(node) {
if (!(0, _utils.isHook)(node) && (0, _utils.isTestCaseCall)(node)) {
hooksContext[hooksContext.length - 1] = true;
}
if (hooksContext[hooksContext.length - 1] && (0, _utils.isHook)(node)) {
context.report({
messageId: 'noHookOnTop',
node
});
}
hooksContext.push(false);
},
'CallExpression:exit'() {
hooksContext.pop();
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,69 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using inline snapshots',
recommended: false
},
deprecated: true,
replacedBy: ['no-restricted-matchers'],
messages: {
toMatch: 'Use toMatchInlineSnapshot() instead',
toMatchError: 'Use toThrowErrorMatchingInlineSnapshot() instead'
},
fixable: 'code',
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
const {
callee
} = node;
if (callee.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression || callee.property.type !== _experimentalUtils.AST_NODE_TYPES.Identifier) {
return;
}
if (callee.property.name === 'toMatchSnapshot') {
context.report({
fix(fixer) {
return [fixer.replaceText(callee.property, 'toMatchInlineSnapshot')];
},
messageId: 'toMatch',
node: callee.property
});
} else if (callee.property.name === 'toThrowErrorMatchingSnapshot') {
context.report({
fix(fixer) {
return [fixer.replaceText(callee.property, 'toThrowErrorMatchingInlineSnapshot')];
},
messageId: 'toMatchError',
node: callee.property
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,89 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const findNodeObject = node => {
if ('object' in node) {
return node.object;
}
if (node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
return node.callee.object;
}
return null;
};
const getJestFnCall = node => {
if (node.type !== _experimentalUtils.AST_NODE_TYPES.CallExpression && node.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
return null;
}
const obj = findNodeObject(node);
if (!obj) {
return null;
}
if (obj.type === _experimentalUtils.AST_NODE_TYPES.Identifier) {
return node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.getNodeName)(node.callee) === 'jest.fn' ? node : null;
}
return getJestFnCall(obj);
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using `jest.spyOn()`',
recommended: false
},
messages: {
useJestSpyOn: 'Use jest.spyOn() instead.'
},
fixable: 'code',
schema: [],
type: 'suggestion'
},
defaultOptions: [],
create(context) {
return {
AssignmentExpression(node) {
const {
left,
right
} = node;
if (left.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) return;
const jestFnCall = getJestFnCall(right);
if (!jestFnCall) return;
context.report({
node,
messageId: 'useJestSpyOn',
fix(fixer) {
const leftPropQuote = left.property.type === _experimentalUtils.AST_NODE_TYPES.Identifier ? "'" : '';
const [arg] = jestFnCall.arguments;
const argSource = arg && context.getSourceCode().getText(arg);
const mockImplementation = argSource ? `.mockImplementation(${argSource})` : '.mockImplementation()';
return [fixer.insertTextBefore(left, `jest.spyOn(`), fixer.replaceTextRange([left.object.range[1], left.property.range[0]], `, ${leftPropQuote}`), fixer.replaceTextRange([left.property.range[1], jestFnCall.range[1]], `${leftPropQuote})${mockImplementation}`)];
}
});
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using `toStrictEqual()`',
recommended: false,
suggestion: true
},
messages: {
useToStrictEqual: 'Use `toStrictEqual()` instead',
suggestReplaceWithStrictEqual: 'Replace with `toStrictEqual()`'
},
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
if (matcher && (0, _utils.isParsedEqualityMatcherCall)(matcher, _utils.EqualityMatcher.toEqual)) {
context.report({
messageId: 'useToStrictEqual',
node: matcher.node.property,
suggest: [{
messageId: 'suggestReplaceWithStrictEqual',
fix: fixer => [fixer.replaceText(matcher.node.property, _utils.EqualityMatcher.toStrictEqual)]
}]
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const isNullLiteral = node => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && node.value === null;
/**
* Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
* with a `null` literal as the sole argument.
*
* @param {ParsedExpectMatcher} matcher
*
* @return {matcher is ParsedEqualityMatcherCall<MaybeTypeCast<NullLiteral>>}
*/
const isNullEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCall)(matcher) && isNullLiteral((0, _utils.followTypeAssertionChain)(matcher.arguments[0]));
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using `toBeNull()`',
recommended: false
},
messages: {
useToBeNull: 'Use toBeNull() instead'
},
fixable: 'code',
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
if (matcher && isNullEqualityMatcher(matcher)) {
context.report({
fix: fixer => [fixer.replaceText(matcher.node.property, 'toBeNull'), fixer.remove(matcher.arguments[0])],
messageId: 'useToBeNull',
node: matcher.node.property
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const isUndefinedIdentifier = node => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && node.name === 'undefined';
/**
* Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
* with a `undefined` identifier as the sole argument.
*
* @param {ParsedExpectMatcher} matcher
*
* @return {matcher is ParsedEqualityMatcherCall<MaybeTypeCast<UndefinedIdentifier>>}
*/
const isUndefinedEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCall)(matcher) && isUndefinedIdentifier((0, _utils.followTypeAssertionChain)(matcher.arguments[0]));
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using `toBeUndefined()`',
recommended: false
},
messages: {
useToBeUndefined: 'Use toBeUndefined() instead'
},
fixable: 'code',
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
if (matcher && isUndefinedEqualityMatcher(matcher)) {
context.report({
fix: fixer => [fixer.replaceText(matcher.node.property, 'toBeUndefined'), fixer.remove(matcher.arguments[0])],
messageId: 'useToBeUndefined',
node: matcher.node.property
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,146 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const isBooleanLiteral = node => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
/**
* Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
* with a boolean literal as the sole argument.
*
* @example javascript
* toBe(true);
* toEqual(false);
*
* @param {ParsedExpectMatcher} matcher
*
* @return {matcher is ParsedBooleanEqualityMatcher}
*/
const isBooleanEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils.followTypeAssertionChain)(matcher.arguments[0]));
/**
* Checks if the given `node` is a `CallExpression` representing the calling
* of an `includes`-like method that can be 'fixed' (using `toContain`).
*
* @param {CallExpression} node
*
* @return {node is FixableIncludesCallExpression}
*
* @todo support `['includes']()` syntax (remove last property.type check to begin)
* @todo break out into `isMethodCall<Name extends string>(node: TSESTree.Node, method: Name)` util-fn
*/
const isFixableIncludesCallExpression = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property, 'includes') && node.callee.property.type === _experimentalUtils.AST_NODE_TYPES.Identifier && (0, _utils.hasOnlyOneArgument)(node);
const buildToContainFuncExpectation = negated => negated ? `${_utils.ModifierName.not}.toContain` : 'toContain';
/**
* Finds the first `.` character token between the `object` & `property` of the given `member` expression.
*
* @param {TSESTree.MemberExpression} member
* @param {SourceCode} sourceCode
*
* @return {Token | null}
*/
const findPropertyDotToken = (member, sourceCode) => sourceCode.getFirstTokenBetween(member.object, member.property, token => token.value === '.');
const getNegationFixes = (node, modifier, matcher, sourceCode, fixer, fileName) => {
const [containArg] = node.arguments;
const negationPropertyDot = findPropertyDotToken(modifier.node, sourceCode);
const toContainFunc = buildToContainFuncExpectation((0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value);
/* istanbul ignore if */
if (negationPropertyDot === null) {
throw new Error(`Unexpected null when attempting to fix ${fileName} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
}
return [fixer.remove(negationPropertyDot), fixer.remove(modifier.node.property), fixer.replaceText(matcher.node.property, toContainFunc), fixer.replaceText(matcher.arguments[0], sourceCode.getText(containArg))];
};
const getCommonFixes = (node, sourceCode, fileName) => {
const [containArg] = node.arguments;
const includesCallee = node.callee;
const propertyDot = findPropertyDotToken(includesCallee, sourceCode);
const closingParenthesis = sourceCode.getTokenAfter(containArg);
const openParenthesis = sourceCode.getTokenBefore(containArg);
/* istanbul ignore if */
if (propertyDot === null || closingParenthesis === null || openParenthesis === null) {
throw new Error(`Unexpected null when attempting to fix ${fileName} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
}
return [containArg, includesCallee.property, propertyDot, closingParenthesis, openParenthesis];
}; // expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>)
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using `toContain()`',
recommended: false
},
messages: {
useToContain: 'Use toContain() instead'
},
fixable: 'code',
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
expect: {
arguments: [includesCall]
},
matcher,
modifier
} = (0, _utils.parseExpectCall)(node);
if (!matcher || !includesCall || modifier && modifier.name !== _utils.ModifierName.not || !isBooleanEqualityMatcher(matcher) || !isFixableIncludesCallExpression(includesCall)) {
return;
}
context.report({
fix(fixer) {
const sourceCode = context.getSourceCode();
const fileName = context.getFilename();
const fixArr = getCommonFixes(includesCall, sourceCode, fileName).map(target => fixer.remove(target));
if (modifier) {
return getNegationFixes(includesCall, modifier, matcher, sourceCode, fixer, fileName).concat(fixArr);
}
const toContainFunc = buildToContainFuncExpectation(!(0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value);
const [containArg] = includesCall.arguments;
fixArr.push(fixer.replaceText(matcher.node.property, toContainFunc));
fixArr.push(fixer.replaceText(matcher.arguments[0], sourceCode.getText(containArg)));
return fixArr;
},
messageId: 'useToContain',
node: (modifier || matcher).node.property
});
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,69 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using `toHaveLength()`',
recommended: false
},
messages: {
useToHaveLength: 'Use toHaveLength() instead'
},
fixable: 'code',
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
expect: {
arguments: [argument]
},
matcher
} = (0, _utils.parseExpectCall)(node);
if (!matcher || !(0, _utils.isParsedEqualityMatcherCall)(matcher) || (argument === null || argument === void 0 ? void 0 : argument.type) !== _experimentalUtils.AST_NODE_TYPES.MemberExpression || !(0, _utils.isSupportedAccessor)(argument.property, 'length') || argument.property.type !== _experimentalUtils.AST_NODE_TYPES.Identifier) {
return;
}
context.report({
fix(fixer) {
const propertyDot = context.getSourceCode().getFirstTokenBetween(argument.object, argument.property, token => token.value === '.');
/* istanbul ignore if */
if (propertyDot === null) {
throw new Error(`Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
}
return [fixer.remove(propertyDot), fixer.remove(argument.property), fixer.replaceText(matcher.node.property, 'toHaveLength')];
},
messageId: 'useToHaveLength',
node: matcher.node.property
});
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
function isEmptyFunction(node) {
if (!(0, _utils.isFunction)(node)) {
return false;
}
return node.body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement && !node.body.body.length;
}
function createTodoFixer(node, fixer) {
const testName = (0, _utils.getNodeName)(node).split('.').shift();
return fixer.replaceText(node.callee, `${testName}.todo`);
}
const isTargetedTestCase = node => (0, _utils.isTestCaseCall)(node) && [_utils.TestCaseName.it, _utils.TestCaseName.test, 'it.skip', 'test.skip'].includes((0, _utils.getNodeName)(node));
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Suggest using `test.todo`',
recommended: false
},
messages: {
emptyTest: 'Prefer todo test case over empty test case',
unimplementedTest: 'Prefer todo test case over unimplemented test case'
},
fixable: 'code',
schema: [],
type: 'layout'
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
const [title, callback] = node.arguments;
if (!title || !isTargetedTestCase(node) || !(0, _utils.isStringNode)(title)) {
return;
}
if (callback && isEmptyFunction(callback)) {
context.report({
messageId: 'emptyTest',
node,
fix: fixer => [fixer.removeRange([title.range[1], callback.range[1]]), createTodoFixer(node, fixer)]
});
}
if ((0, _utils.hasOnlyOneArgument)(node)) {
context.report({
messageId: 'unimplementedTest',
node,
fix: fixer => [createTodoFixer(node, fixer)]
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Require a message for `toThrow()`',
recommended: false
},
messages: {
addErrorMessage: 'Add an error message to {{ matcherName }}()'
},
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
var _matcher$arguments;
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
matcher,
modifier
} = (0, _utils.parseExpectCall)(node);
if ((matcher === null || matcher === void 0 ? void 0 : (_matcher$arguments = matcher.arguments) === null || _matcher$arguments === void 0 ? void 0 : _matcher$arguments.length) === 0 && ['toThrow', 'toThrowError'].includes(matcher.name) && (!modifier || !(modifier.name === _utils.ModifierName.not || modifier.negation))) {
// Look for `toThrow` calls with no arguments.
context.report({
messageId: 'addErrorMessage',
data: {
matcherName: matcher.name
},
node: matcher.node.property
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Require test cases and hooks to be inside a `describe` block',
recommended: false
},
messages: {
unexpectedTestCase: 'All test cases must be wrapped in a describe block.',
unexpectedHook: 'All hooks must be wrapped in a describe block.'
},
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
let numberOfDescribeBlocks = 0;
return {
CallExpression(node) {
if ((0, _utils.isDescribeCall)(node)) {
numberOfDescribeBlocks++;
return;
}
if (numberOfDescribeBlocks === 0) {
if ((0, _utils.isTestCaseCall)(node)) {
context.report({
node,
messageId: 'unexpectedTestCase'
});
return;
}
if ((0, _utils.isHook)(node)) {
context.report({
node,
messageId: 'unexpectedHook'
});
return;
}
}
},
'CallExpression:exit'(node) {
if ((0, _utils.isDescribeCall)(node)) {
numberOfDescribeBlocks--;
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,113 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _utils = require("./utils");
const toThrowMatchers = ['toThrow', 'toThrowError', 'toThrowErrorMatchingSnapshot', 'toThrowErrorMatchingInlineSnapshot'];
const isJestExpectToThrowCall = node => {
if (!(0, _utils.isExpectCall)(node)) {
return false;
}
const {
matcher
} = (0, _utils.parseExpectCall)(node);
if (!matcher) {
return false;
}
return !toThrowMatchers.includes(matcher.name);
};
const baseRule = (() => {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const TSESLintPlugin = require('@typescript-eslint/eslint-plugin');
return TSESLintPlugin.rules['unbound-method'];
} catch (e) {
const error = e;
if (error.code === 'MODULE_NOT_FOUND') {
return null;
}
throw error;
}
})();
const tryCreateBaseRule = context => {
try {
return baseRule === null || baseRule === void 0 ? void 0 : baseRule.create(context);
} catch {
return null;
}
};
const DEFAULT_MESSAGE = 'This rule requires `@typescript-eslint/eslint-plugin`';
var _default = (0, _utils.createRule)({
defaultOptions: [{
ignoreStatic: false
}],
...baseRule,
name: __filename,
meta: {
messages: {
unbound: DEFAULT_MESSAGE,
unboundWithoutThisAnnotation: DEFAULT_MESSAGE
},
schema: [],
type: 'problem',
...(baseRule === null || baseRule === void 0 ? void 0 : baseRule.meta),
docs: {
category: 'Best Practices',
description: 'Enforces unbound methods are called with their expected scope',
requiresTypeChecking: true,
...(baseRule === null || baseRule === void 0 ? void 0 : baseRule.meta.docs),
recommended: false
}
},
create(context) {
const baseSelectors = tryCreateBaseRule(context);
if (!baseSelectors) {
return {};
}
let inExpectToThrowCall = false;
return { ...baseSelectors,
CallExpression(node) {
inExpectToThrowCall = isJestExpectToThrowCall(node);
},
'CallExpression:exit'(node) {
if (inExpectToThrowCall && isJestExpectToThrowCall(node)) {
inExpectToThrowCall = false;
}
},
MemberExpression(node) {
var _baseSelectors$Member;
if (inExpectToThrowCall) {
return;
}
(_baseSelectors$Member = baseSelectors.MemberExpression) === null || _baseSelectors$Member === void 0 ? void 0 : _baseSelectors$Member.call(baseSelectors, node);
}
};
}
});
exports.default = _default;

510
web/node_modules/eslint-plugin-jest/lib/rules/utils.js generated vendored Normal file
View file

@ -0,0 +1,510 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getNodeName = getNodeName;
exports.scopeHasLocalReference = exports.isDescribeCall = exports.isTestCaseCall = exports.getTestCallExpressionsFromDeclaredVariables = exports.isHook = exports.isFunction = exports.TestCaseProperty = exports.DescribeProperty = exports.HookName = exports.TestCaseName = exports.DescribeAlias = exports.parseExpectCall = exports.isParsedEqualityMatcherCall = exports.EqualityMatcher = exports.ModifierName = exports.isExpectMember = exports.isExpectCall = exports.getAccessorValue = exports.isSupportedAccessor = exports.hasOnlyOneArgument = exports.getStringValue = exports.isStringNode = exports.followTypeAssertionChain = exports.createRule = void 0;
var _path = require("path");
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _package = require("../../package.json");
const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest';
const createRule = _experimentalUtils.ESLintUtils.RuleCreator(name => {
const ruleName = (0, _path.parse)(name).name;
return `${REPO_URL}/blob/v${_package.version}/docs/rules/${ruleName}.md`;
});
exports.createRule = createRule;
const isTypeCastExpression = node => node.type === _experimentalUtils.AST_NODE_TYPES.TSAsExpression || node.type === _experimentalUtils.AST_NODE_TYPES.TSTypeAssertion;
const followTypeAssertionChain = expression => isTypeCastExpression(expression) ? followTypeAssertionChain(expression.expression) : expression;
/**
* A `Literal` with a `value` of type `string`.
*/
exports.followTypeAssertionChain = followTypeAssertionChain;
/**
* Checks if the given `node` is a `StringLiteral`.
*
* If a `value` is provided & the `node` is a `StringLiteral`,
* the `value` will be compared to that of the `StringLiteral`.
*
* @param {Node} node
* @param {V} [value]
*
* @return {node is StringLiteral<V>}
*
* @template V
*/
const isStringLiteral = (node, value) => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'string' && (value === undefined || node.value === value);
/**
* Checks if the given `node` is a `TemplateLiteral`.
*
* Complex `TemplateLiteral`s are not considered specific, and so will return `false`.
*
* If a `value` is provided & the `node` is a `TemplateLiteral`,
* the `value` will be compared to that of the `TemplateLiteral`.
*
* @param {Node} node
* @param {V} [value]
*
* @return {node is TemplateLiteral<V>}
*
* @template V
*/
const isTemplateLiteral = (node, value) => node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && node.quasis.length === 1 && ( // bail out if not simple
value === undefined || node.quasis[0].value.raw === value);
/**
* Checks if the given `node` is a {@link StringNode}.
*
* @param {Node} node
* @param {V} [specifics]
*
* @return {node is StringNode}
*
* @template V
*/
const isStringNode = (node, specifics) => isStringLiteral(node, specifics) || isTemplateLiteral(node, specifics);
/**
* Gets the value of the given `StringNode`.
*
* If the `node` is a `TemplateLiteral`, the `raw` value is used;
* otherwise, `value` is returned instead.
*
* @param {StringNode<S>} node
*
* @return {S}
*
* @template S
*/
exports.isStringNode = isStringNode;
const getStringValue = node => isTemplateLiteral(node) ? node.quasis[0].value.raw : node.value;
/**
* Represents a `MemberExpression` with a "known" `property`.
*/
exports.getStringValue = getStringValue;
/**
* Guards that the given `call` has only one `argument`.
*
* @param {CallExpression} call
*
* @return {call is CallExpressionWithSingleArgument}
*/
const hasOnlyOneArgument = call => call.arguments.length === 1;
/**
* An `Identifier` with a known `name` value - i.e `expect`.
*/
exports.hasOnlyOneArgument = hasOnlyOneArgument;
/**
* Checks if the given `node` is an `Identifier`.
*
* If a `name` is provided, & the `node` is an `Identifier`,
* the `name` will be compared to that of the `identifier`.
*
* @param {Node} node
* @param {V} [name]
*
* @return {node is KnownIdentifier<Name>}
*
* @template V
*/
const isIdentifier = (node, name) => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && (name === undefined || node.name === name);
/**
* Checks if the given `node` is a "supported accessor".
*
* This means that it's a node can be used to access properties,
* and who's "value" can be statically determined.
*
* `MemberExpression` nodes most commonly contain accessors,
* but it's possible for other nodes to contain them.
*
* If a `value` is provided & the `node` is an `AccessorNode`,
* the `value` will be compared to that of the `AccessorNode`.
*
* Note that `value` here refers to the normalised value.
* The property that holds the value is not always called `name`.
*
* @param {Node} node
* @param {V} [value]
*
* @return {node is AccessorNode<V>}
*
* @template V
*/
const isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
/**
* Gets the value of the given `AccessorNode`,
* account for the different node types.
*
* @param {AccessorNode<S>} accessor
*
* @return {S}
*
* @template S
*/
exports.isSupportedAccessor = isSupportedAccessor;
const getAccessorValue = accessor => accessor.type === _experimentalUtils.AST_NODE_TYPES.Identifier ? accessor.name : getStringValue(accessor);
exports.getAccessorValue = getAccessorValue;
/**
* Checks if the given `node` is a valid `ExpectCall`.
*
* In order to be an `ExpectCall`, the `node` must:
* * be a `CallExpression`,
* * have an accessor named 'expect',
* * have a `parent`.
*
* @param {Node} node
*
* @return {node is ExpectCall}
*/
const isExpectCall = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && isSupportedAccessor(node.callee, 'expect') && node.parent !== undefined;
exports.isExpectCall = isExpectCall;
const isExpectMember = (node, name) => node.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.property, name);
/**
* Represents all the jest matchers.
*/
exports.isExpectMember = isExpectMember;
let ModifierName;
exports.ModifierName = ModifierName;
(function (ModifierName) {
ModifierName["not"] = "not";
ModifierName["rejects"] = "rejects";
ModifierName["resolves"] = "resolves";
})(ModifierName || (exports.ModifierName = ModifierName = {}));
let EqualityMatcher;
exports.EqualityMatcher = EqualityMatcher;
(function (EqualityMatcher) {
EqualityMatcher["toBe"] = "toBe";
EqualityMatcher["toEqual"] = "toEqual";
EqualityMatcher["toStrictEqual"] = "toStrictEqual";
})(EqualityMatcher || (exports.EqualityMatcher = EqualityMatcher = {}));
const isParsedEqualityMatcherCall = (matcher, name) => (name ? matcher.name === name : EqualityMatcher.hasOwnProperty(matcher.name)) && matcher.arguments !== null && matcher.arguments.length === 1;
/**
* Represents a parsed expect matcher, such as `toBe`, `toContain`, and so on.
*/
exports.isParsedEqualityMatcherCall = isParsedEqualityMatcherCall;
const parseExpectMember = expectMember => ({
name: getAccessorValue(expectMember.property),
node: expectMember
});
const reparseAsMatcher = parsedMember => ({ ...parsedMember,
/**
* The arguments being passed to this `Matcher`, if any.
*
* If this matcher isn't called, this will be `null`.
*/
arguments: parsedMember.node.parent && parsedMember.node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? parsedMember.node.parent.arguments : null
});
/**
* Re-parses the given `parsedMember` as a `ParsedExpectModifier`.
*
* If the given `parsedMember` does not have a `name` of a valid `Modifier`,
* an exception will be thrown.
*
* @param {ParsedExpectMember<ModifierName>} parsedMember
*
* @return {ParsedExpectModifier}
*/
const reparseMemberAsModifier = parsedMember => {
if (isSpecificMember(parsedMember, ModifierName.not)) {
return parsedMember;
}
/* istanbul ignore if */
if (!isSpecificMember(parsedMember, ModifierName.resolves) && !isSpecificMember(parsedMember, ModifierName.rejects)) {
// ts doesn't think that the ModifierName.not check is the direct inverse as the above two checks
// todo: impossible at runtime, but can't be typed w/o negation support
throw new Error(`modifier name must be either "${ModifierName.resolves}" or "${ModifierName.rejects}" (got "${parsedMember.name}")`);
}
const negation = parsedMember.node.parent && isExpectMember(parsedMember.node.parent, ModifierName.not) ? parsedMember.node.parent : undefined;
return { ...parsedMember,
negation
};
};
const isSpecificMember = (member, specific) => member.name === specific;
/**
* Checks if the given `ParsedExpectMember` should be re-parsed as an `ParsedExpectModifier`.
*
* @param {ParsedExpectMember} member
*
* @return {member is ParsedExpectMember<ModifierName>}
*/
const shouldBeParsedExpectModifier = member => ModifierName.hasOwnProperty(member.name);
const parseExpectCall = expect => {
const expectation = {
expect
};
if (!isExpectMember(expect.parent)) {
return expectation;
}
const parsedMember = parseExpectMember(expect.parent);
if (!shouldBeParsedExpectModifier(parsedMember)) {
expectation.matcher = reparseAsMatcher(parsedMember);
return expectation;
}
const modifier = expectation.modifier = reparseMemberAsModifier(parsedMember);
const memberNode = modifier.negation || modifier.node;
if (!memberNode.parent || !isExpectMember(memberNode.parent)) {
return expectation;
}
expectation.matcher = reparseAsMatcher(parseExpectMember(memberNode.parent));
return expectation;
};
exports.parseExpectCall = parseExpectCall;
let DescribeAlias;
exports.DescribeAlias = DescribeAlias;
(function (DescribeAlias) {
DescribeAlias["describe"] = "describe";
DescribeAlias["fdescribe"] = "fdescribe";
DescribeAlias["xdescribe"] = "xdescribe";
})(DescribeAlias || (exports.DescribeAlias = DescribeAlias = {}));
let TestCaseName;
exports.TestCaseName = TestCaseName;
(function (TestCaseName) {
TestCaseName["fit"] = "fit";
TestCaseName["it"] = "it";
TestCaseName["test"] = "test";
TestCaseName["xit"] = "xit";
TestCaseName["xtest"] = "xtest";
})(TestCaseName || (exports.TestCaseName = TestCaseName = {}));
let HookName;
exports.HookName = HookName;
(function (HookName) {
HookName["beforeAll"] = "beforeAll";
HookName["beforeEach"] = "beforeEach";
HookName["afterAll"] = "afterAll";
HookName["afterEach"] = "afterEach";
})(HookName || (exports.HookName = HookName = {}));
let DescribeProperty;
exports.DescribeProperty = DescribeProperty;
(function (DescribeProperty) {
DescribeProperty["each"] = "each";
DescribeProperty["only"] = "only";
DescribeProperty["skip"] = "skip";
})(DescribeProperty || (exports.DescribeProperty = DescribeProperty = {}));
let TestCaseProperty;
exports.TestCaseProperty = TestCaseProperty;
(function (TestCaseProperty) {
TestCaseProperty["each"] = "each";
TestCaseProperty["concurrent"] = "concurrent";
TestCaseProperty["only"] = "only";
TestCaseProperty["skip"] = "skip";
TestCaseProperty["todo"] = "todo";
})(TestCaseProperty || (exports.TestCaseProperty = TestCaseProperty = {}));
const joinNames = (a, b) => a && b ? `${a}.${b}` : null;
function getNodeName(node) {
if (isSupportedAccessor(node)) {
return getAccessorValue(node);
}
switch (node.type) {
case _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression:
return getNodeName(node.tag);
case _experimentalUtils.AST_NODE_TYPES.MemberExpression:
return joinNames(getNodeName(node.object), getNodeName(node.property));
case _experimentalUtils.AST_NODE_TYPES.NewExpression:
case _experimentalUtils.AST_NODE_TYPES.CallExpression:
return getNodeName(node.callee);
}
return null;
}
const isFunction = node => node.type === _experimentalUtils.AST_NODE_TYPES.FunctionExpression || node.type === _experimentalUtils.AST_NODE_TYPES.ArrowFunctionExpression;
exports.isFunction = isFunction;
const isHook = node => node.callee.type === _experimentalUtils.AST_NODE_TYPES.Identifier && HookName.hasOwnProperty(node.callee.name);
exports.isHook = isHook;
const getTestCallExpressionsFromDeclaredVariables = declaredVariables => {
return declaredVariables.reduce((acc, {
references
}) => acc.concat(references.map(({
identifier
}) => identifier.parent).filter(node => !!node && node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && isTestCaseCall(node))), []);
};
exports.getTestCallExpressionsFromDeclaredVariables = getTestCallExpressionsFromDeclaredVariables;
const isTestCaseName = node => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && TestCaseName.hasOwnProperty(node.name);
const isTestCaseProperty = node => isSupportedAccessor(node) && TestCaseProperty.hasOwnProperty(getAccessorValue(node));
/**
* Checks if the given `node` is a *call* to a test case function that would
* result in tests being run by `jest`.
*
* Note that `.each()` does not count as a call in this context, as it will not
* result in `jest` running any tests.
*
* @param {TSESTree.CallExpression} node
*
* @return {node is JestFunctionCallExpression<TestCaseName>}
*/
const isTestCaseCall = node => {
if (isTestCaseName(node.callee)) {
return true;
}
const callee = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isTestCaseProperty(callee.property)) {
// if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
if (getAccessorValue(callee.property) === 'each' && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
return false;
}
return callee.object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression ? isTestCaseName(callee.object.object) : isTestCaseName(callee.object);
}
return false;
};
exports.isTestCaseCall = isTestCaseCall;
const isDescribeAlias = node => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && DescribeAlias.hasOwnProperty(node.name);
const isDescribeProperty = node => isSupportedAccessor(node) && DescribeProperty.hasOwnProperty(getAccessorValue(node));
/**
* Checks if the given `node` is a *call* to a `describe` function that would
* result in a `describe` block being created by `jest`.
*
* Note that `.each()` does not count as a call in this context, as it will not
* result in `jest` creating any `describe` blocks.
*
* @param {TSESTree.CallExpression} node
*
* @return {node is JestFunctionCallExpression<TestCaseName>}
*/
const isDescribeCall = node => {
if (isDescribeAlias(node.callee)) {
return true;
}
const callee = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isDescribeProperty(callee.property)) {
// if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
if (getAccessorValue(callee.property) === 'each' && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
return false;
}
return callee.object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression ? isDescribeAlias(callee.object.object) : isDescribeAlias(callee.object);
}
return false;
};
exports.isDescribeCall = isDescribeCall;
const collectReferences = scope => {
const locals = new Set();
const unresolved = new Set();
let currentScope = scope;
while (currentScope !== null) {
for (const ref of currentScope.variables) {
const isReferenceDefined = ref.defs.some(def => {
return def.type !== 'ImplicitGlobalVariable';
});
if (isReferenceDefined) {
locals.add(ref.name);
}
}
for (const ref of currentScope.through) {
unresolved.add(ref.identifier.name);
}
currentScope = currentScope.upper;
}
return {
locals,
unresolved
};
};
const scopeHasLocalReference = (scope, referenceName) => {
const references = collectReferences(scope);
return (// referenceName was found as a local variable or function declaration.
references.locals.has(referenceName) || // referenceName was not found as an unresolved reference,
// meaning it is likely not an implicit global reference.
!references.unresolved.has(referenceName)
);
};
exports.scopeHasLocalReference = scopeHasLocalReference;

View file

@ -0,0 +1,111 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const paramsLocation = params => {
const [first] = params;
const last = params[params.length - 1];
return {
start: first.loc.start,
end: last.loc.end
};
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
type: 'problem',
docs: {
category: 'Possible Errors',
description: 'Enforce valid `describe()` callback',
recommended: 'error'
},
messages: {
nameAndCallback: 'Describe requires name and callback arguments',
secondArgumentMustBeFunction: 'Second argument must be function',
noAsyncDescribeCallback: 'No async describe callback',
unexpectedDescribeArgument: 'Unexpected argument(s) in describe callback',
unexpectedReturnInDescribe: 'Unexpected return statement in describe callback'
},
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!(0, _utils.isDescribeCall)(node)) {
return;
}
if (node.arguments.length < 1) {
return context.report({
messageId: 'nameAndCallback',
loc: node.loc
});
}
const [, callback] = node.arguments;
if (!callback) {
context.report({
messageId: 'nameAndCallback',
loc: paramsLocation(node.arguments)
});
return;
}
if (!(0, _utils.isFunction)(callback)) {
context.report({
messageId: 'secondArgumentMustBeFunction',
loc: paramsLocation(node.arguments)
});
return;
}
if (callback.async) {
context.report({
messageId: 'noAsyncDescribeCallback',
node: callback
});
}
if (!(0, _utils.getNodeName)(node).endsWith('each') && callback.params.length) {
context.report({
messageId: 'unexpectedDescribeArgument',
loc: paramsLocation(callback.params)
});
}
if (callback.body.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
context.report({
messageId: 'unexpectedReturnInDescribe',
node: callback
});
}
if (callback.body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
callback.body.body.forEach(node => {
if (node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement) {
context.report({
messageId: 'unexpectedReturnInDescribe',
node
});
}
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,140 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const isThenOrCatchCall = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property) && ['then', 'catch'].includes((0, _utils.getAccessorValue)(node.callee.property));
const isExpectCallPresentInFunction = body => {
if (body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
return body.body.find(line => {
if (line.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement) {
return isFullExpectCall(line.expression);
}
if (line.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement && line.argument) {
return isFullExpectCall(line.argument);
}
return false;
});
}
return isFullExpectCall(body);
};
const isFullExpectCall = expression => expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isExpectMember)(expression.callee);
const reportReturnRequired = (context, node) => {
context.report({
loc: {
end: {
column: node.loc.end.column,
line: node.loc.end.line
},
start: node.loc.start
},
messageId: 'returnPromise',
node
});
};
const isPromiseReturnedLater = (node, testFunctionBody) => {
let promiseName;
if (node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement && node.expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.expression.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.expression.callee.object)) {
promiseName = (0, _utils.getAccessorValue)(node.expression.callee.object);
} else if (node.type === _experimentalUtils.AST_NODE_TYPES.VariableDeclarator && node.id.type === _experimentalUtils.AST_NODE_TYPES.Identifier) {
promiseName = node.id.name;
}
const lastLineInTestFunc = testFunctionBody[testFunctionBody.length - 1];
return lastLineInTestFunc.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement && lastLineInTestFunc.argument && ('name' in lastLineInTestFunc.argument && lastLineInTestFunc.argument.name === promiseName || !promiseName);
};
const isTestFunc = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isSupportedAccessor)(node.callee) && [_utils.TestCaseName.it, _utils.TestCaseName.test].includes((0, _utils.getAccessorValue)(node.callee));
const findTestFunction = node => {
while (node) {
if ((0, _utils.isFunction)(node) && node.parent && isTestFunc(node.parent)) {
return node;
}
node = node.parent;
}
return null;
};
const isParentThenOrPromiseReturned = (node, testFunctionBody) => node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement || isPromiseReturnedLater(node, testFunctionBody);
const verifyExpectWithReturn = (promiseCallbacks, node, context, testFunctionBody) => {
promiseCallbacks.some(promiseCallback => {
if (promiseCallback && (0, _utils.isFunction)(promiseCallback)) {
if (isExpectCallPresentInFunction(promiseCallback.body) && node.parent.parent && !isParentThenOrPromiseReturned(node.parent.parent, testFunctionBody)) {
reportReturnRequired(context, node.parent.parent);
return true;
}
}
return false;
});
};
const isHavingAsyncCallBackParam = testFunction => testFunction.params[0] && testFunction.params[0].type === _experimentalUtils.AST_NODE_TYPES.Identifier;
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Enforce having return statement when testing with promises',
recommended: 'error'
},
messages: {
returnPromise: 'Promise should be returned to test its fulfillment or rejection'
},
type: 'suggestion',
schema: []
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (!isThenOrCatchCall(node) || node.parent && node.parent.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression) {
return;
}
const testFunction = findTestFunction(node);
if (testFunction && !isHavingAsyncCallBackParam(testFunction)) {
const {
body
} = testFunction;
if (body.type !== _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
return;
}
const testFunctionBody = body.body; // then block can have two args, fulfillment & rejection
// then block can have one args, fulfillment
// catch block can have one args, rejection
// ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
verifyExpectWithReturn(node.arguments.slice(0, 2), node.callee, context, testFunctionBody);
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,276 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
/*
* This implementation is ported from from eslint-plugin-jasmine.
* MIT license, Tom Vincent.
*/
/**
* Async assertions might be called in Promise
* methods like `Promise.x(expect1)` or `Promise.x([expect1, expect2])`.
* If that's the case, Promise node have to be awaited or returned.
*
* @Returns CallExpressionNode
*/
const getPromiseCallExpressionNode = node => {
if (node.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression && node.parent && node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
node = node.parent;
}
if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.object) && (0, _utils.getAccessorValue)(node.callee.object) === 'Promise' && node.parent) {
return node;
}
return null;
};
const findPromiseCallExpressionNode = node => node.parent && node.parent.parent && [_experimentalUtils.AST_NODE_TYPES.CallExpression, _experimentalUtils.AST_NODE_TYPES.ArrayExpression].includes(node.parent.type) ? getPromiseCallExpressionNode(node.parent) : null;
const getParentIfThenified = node => {
const grandParentNode = node.parent && node.parent.parent;
if (grandParentNode && grandParentNode.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && grandParentNode.callee && (0, _utils.isExpectMember)(grandParentNode.callee) && ['then', 'catch'].includes((0, _utils.getAccessorValue)(grandParentNode.callee.property)) && grandParentNode.parent) {
// Just in case `then`s are chained look one above.
return getParentIfThenified(grandParentNode);
}
return node;
};
const isAcceptableReturnNode = (node, allowReturn) => {
if (allowReturn && node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement) {
return true;
}
if (node.type === _experimentalUtils.AST_NODE_TYPES.ConditionalExpression && node.parent) {
return isAcceptableReturnNode(node.parent, allowReturn);
}
return [_experimentalUtils.AST_NODE_TYPES.ArrowFunctionExpression, _experimentalUtils.AST_NODE_TYPES.AwaitExpression].includes(node.type);
};
const isNoAssertionsParentNode = node => node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement || node.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression && node.parent !== undefined && node.parent.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement;
const promiseArrayExceptionKey = ({
start,
end
}) => `${start.line}:${start.column}-${end.line}:${end.column}`;
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Enforce valid `expect()` usage',
recommended: 'error'
},
messages: {
tooManyArgs: 'Expect takes at most {{ amount }} argument{{ s }}.',
notEnoughArgs: 'Expect requires at least {{ amount }} argument{{ s }}.',
modifierUnknown: 'Expect has no modifier named "{{ modifierName }}".',
matcherNotFound: 'Expect must have a corresponding matcher call.',
matcherNotCalled: 'Matchers must be called to assert.',
asyncMustBeAwaited: 'Async assertions must be awaited{{ orReturned }}.',
promisesWithAsyncAssertionsMustBeAwaited: 'Promises which return async assertions must be awaited{{ orReturned }}.'
},
type: 'suggestion',
schema: [{
type: 'object',
properties: {
alwaysAwait: {
type: 'boolean',
default: false
},
minArgs: {
type: 'number',
minimum: 1
},
maxArgs: {
type: 'number',
minimum: 1
}
},
additionalProperties: false
}]
},
defaultOptions: [{
alwaysAwait: false,
minArgs: 1,
maxArgs: 1
}],
create(context, [{
alwaysAwait,
minArgs = 1,
maxArgs = 1
}]) {
// Context state
const arrayExceptions = new Set();
const pushPromiseArrayException = loc => arrayExceptions.add(promiseArrayExceptionKey(loc));
/**
* Promise method that accepts an array of promises,
* (eg. Promise.all), will throw warnings for the each
* unawaited or non-returned promise. To avoid throwing
* multiple warnings, we check if there is a warning in
* the given location.
*/
const promiseArrayExceptionExists = loc => arrayExceptions.has(promiseArrayExceptionKey(loc));
return {
CallExpression(node) {
if (!(0, _utils.isExpectCall)(node)) {
return;
}
const {
expect,
modifier,
matcher
} = (0, _utils.parseExpectCall)(node);
if (expect.arguments.length < minArgs) {
const expectLength = (0, _utils.getAccessorValue)(expect.callee).length;
const loc = {
start: {
column: node.loc.start.column + expectLength,
line: node.loc.start.line
},
end: {
column: node.loc.start.column + expectLength + 1,
line: node.loc.start.line
}
};
context.report({
messageId: 'notEnoughArgs',
data: {
amount: minArgs,
s: minArgs === 1 ? '' : 's'
},
node,
loc
});
}
if (expect.arguments.length > maxArgs) {
const {
start
} = expect.arguments[maxArgs].loc;
const {
end
} = expect.arguments[node.arguments.length - 1].loc;
const loc = {
start,
end: {
column: end.column - 1,
line: end.line
}
};
context.report({
messageId: 'tooManyArgs',
data: {
amount: maxArgs,
s: maxArgs === 1 ? '' : 's'
},
node,
loc
});
} // something was called on `expect()`
if (!matcher) {
if (modifier) {
context.report({
messageId: 'matcherNotFound',
node: modifier.node.property
});
}
return;
}
if ((0, _utils.isExpectMember)(matcher.node.parent)) {
context.report({
messageId: 'modifierUnknown',
data: {
modifierName: matcher.name
},
node: matcher.node.property
});
return;
}
if (!matcher.arguments) {
context.report({
messageId: 'matcherNotCalled',
node: matcher.node.property
});
}
const parentNode = matcher.node.parent;
if (!parentNode.parent || !modifier || modifier.name === _utils.ModifierName.not) {
return;
}
/**
* If parent node is an array expression, we'll report the warning,
* for the array object, not for each individual assertion.
*/
const isParentArrayExpression = parentNode.parent.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression;
const orReturned = alwaysAwait ? '' : ' or returned';
/**
* An async assertion can be chained with `then` or `catch` statements.
* In that case our target CallExpression node is the one with
* the last `then` or `catch` statement.
*/
const targetNode = getParentIfThenified(parentNode);
const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode;
if (finalNode.parent && // If node is not awaited or returned
!isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && // if we didn't warn user already
!promiseArrayExceptionExists(finalNode.loc)) {
context.report({
loc: finalNode.loc,
data: {
orReturned
},
messageId: finalNode === targetNode ? 'asyncMustBeAwaited' : 'promisesWithAsyncAssertionsMustBeAwaited',
node
});
if (isParentArrayExpression) {
pushPromiseArrayException(finalNode.loc);
}
}
},
// nothing called on "expect()"
'CallExpression:exit'(node) {
if ((0, _utils.isExpectCall)(node) && isNoAssertionsParentNode(node.parent)) {
context.report({
messageId: 'matcherNotFound',
node
});
}
}
};
}
});
exports.default = _default;

View file

@ -0,0 +1,246 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _experimentalUtils = require("@typescript-eslint/experimental-utils");
var _utils = require("./utils");
const trimFXprefix = word => ['f', 'x'].includes(word.charAt(0)) ? word.substr(1) : word;
const doesBinaryExpressionContainStringNode = binaryExp => {
if ((0, _utils.isStringNode)(binaryExp.right)) {
return true;
}
if (binaryExp.left.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression) {
return doesBinaryExpressionContainStringNode(binaryExp.left);
}
return (0, _utils.isStringNode)(binaryExp.left);
};
const quoteStringValue = node => node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw;
const compileMatcherPatterns = matchers => {
if (typeof matchers === 'string') {
const matcher = new RegExp(matchers, 'u');
return {
describe: matcher,
test: matcher,
it: matcher
};
}
return {
describe: matchers.describe ? new RegExp(matchers.describe, 'u') : null,
test: matchers.test ? new RegExp(matchers.test, 'u') : null,
it: matchers.it ? new RegExp(matchers.it, 'u') : null
};
};
var _default = (0, _utils.createRule)({
name: __filename,
meta: {
docs: {
category: 'Best Practices',
description: 'Enforce valid titles',
recommended: 'error'
},
messages: {
titleMustBeString: 'Title must be a string',
emptyTitle: '{{ jestFunctionName }} should not have an empty title',
duplicatePrefix: 'should not have duplicate prefix',
accidentalSpace: 'should not have leading or trailing spaces',
disallowedWord: '"{{ word }}" is not allowed in test titles.',
mustNotMatch: '{{ jestFunctionName }} should not match {{ pattern }}',
mustMatch: '{{ jestFunctionName }} should match {{ pattern }}'
},
type: 'suggestion',
schema: [{
type: 'object',
properties: {
ignoreTypeOfDescribeName: {
type: 'boolean',
default: false
},
disallowedWords: {
type: 'array',
items: {
type: 'string'
}
},
mustNotMatch: {
oneOf: [{
type: 'string'
}, {
type: 'object',
properties: {
describe: {
type: 'string'
},
test: {
type: 'string'
},
it: {
type: 'string'
}
},
additionalProperties: false
}]
},
mustMatch: {
oneOf: [{
type: 'string'
}, {
type: 'object',
properties: {
describe: {
type: 'string'
},
test: {
type: 'string'
},
it: {
type: 'string'
}
},
additionalProperties: false
}]
}
},
additionalProperties: false
}],
fixable: 'code'
},
defaultOptions: [{
ignoreTypeOfDescribeName: false,
disallowedWords: []
}],
create(context, [{
ignoreTypeOfDescribeName,
disallowedWords = [],
mustNotMatch,
mustMatch
}]) {
const disallowedWordsRegexp = new RegExp(`\\b(${disallowedWords.join('|')})\\b`, 'iu');
const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch !== null && mustNotMatch !== void 0 ? mustNotMatch : {});
const mustMatchPatterns = compileMatcherPatterns(mustMatch !== null && mustMatch !== void 0 ? mustMatch : {});
return {
CallExpression(node) {
if (!(0, _utils.isDescribeCall)(node) && !(0, _utils.isTestCaseCall)(node)) {
return;
}
const [argument] = node.arguments;
if (!argument) {
return;
}
if (!(0, _utils.isStringNode)(argument)) {
if (argument.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression && doesBinaryExpressionContainStringNode(argument)) {
return;
}
if (argument.type !== _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && !(ignoreTypeOfDescribeName && (0, _utils.isDescribeCall)(node))) {
context.report({
messageId: 'titleMustBeString',
loc: argument.loc
});
}
return;
}
const title = (0, _utils.getStringValue)(argument);
if (!title) {
context.report({
messageId: 'emptyTitle',
data: {
jestFunctionName: (0, _utils.isDescribeCall)(node) ? _utils.DescribeAlias.describe : _utils.TestCaseName.test
},
node
});
return;
}
if (disallowedWords.length > 0) {
const disallowedMatch = disallowedWordsRegexp.exec(title);
if (disallowedMatch) {
context.report({
data: {
word: disallowedMatch[1]
},
messageId: 'disallowedWord',
node: argument
});
return;
}
}
if (title.trim().length !== title.length) {
context.report({
messageId: 'accidentalSpace',
node: argument,
fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]) +?/u, '$1').replace(/ +?([`'"])$/u, '$1'))]
});
}
const nodeName = trimFXprefix((0, _utils.getNodeName)(node));
const [firstWord] = title.split(' ');
if (firstWord.toLowerCase() === nodeName) {
context.report({
messageId: 'duplicatePrefix',
node: argument,
fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]).+? /u, '$1'))]
});
}
const [jestFunctionName] = nodeName.split('.');
const mustNotMatchPattern = mustNotMatchPatterns[jestFunctionName];
if (mustNotMatchPattern) {
if (mustNotMatchPattern.test(title)) {
context.report({
messageId: 'mustNotMatch',
node: argument,
data: {
jestFunctionName,
pattern: mustNotMatchPattern
}
});
return;
}
}
const mustMatchPattern = mustMatchPatterns[jestFunctionName];
if (mustMatchPattern) {
if (!mustMatchPattern.test(title)) {
context.report({
messageId: 'mustMatch',
node: argument,
data: {
jestFunctionName,
pattern: mustMatchPattern
}
});
return;
}
}
}
};
}
});
exports.default = _default;