diff --git a/.eslintrc.js b/.eslintrc.js index 182cb83b1..39ce6d46c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,6 +17,7 @@ module.exports = { }, rules: { strict: 'error', + 'eslint-plugin/require-meta-docs-url': 'error', 'prettier/prettier': 'error', }, overrides: [ diff --git a/rules/__tests__/no_large_snapshots.test.js b/rules/__tests__/no_large_snapshots.test.js index 6edb42f35..d1b67656b 100644 --- a/rules/__tests__/no_large_snapshots.test.js +++ b/rules/__tests__/no_large_snapshots.test.js @@ -1,6 +1,6 @@ 'use strict'; -const noLargeSnapshots = require('../no_large_snapshots'); +const noLargeSnapshots = require('../no_large_snapshots').create; // was not able to use https://eslint.org/docs/developer-guide/nodejs-api#ruletester for these because there is no way to configure RuleTester to run non .js files describe('no-large-snapshots', () => { diff --git a/rules/no_disabled_tests.js b/rules/no_disabled_tests.js index 8e2457f31..5104a26b9 100644 --- a/rules/no_disabled_tests.js +++ b/rules/no_disabled_tests.js @@ -17,77 +17,85 @@ function getName(node) { return null; } -module.exports = context => { - let suiteDepth = 0; - let testDepth = 0; +module.exports = { + meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/no-disabled-tests.md', + }, + }, + create(context) { + let suiteDepth = 0; + let testDepth = 0; - return { - CallExpression: node => { - const functionName = getName(node.callee); + return { + CallExpression: node => { + const functionName = getName(node.callee); - switch (functionName) { - case 'describe': - suiteDepth++; - break; + switch (functionName) { + case 'describe': + suiteDepth++; + break; - case 'describe.skip': - context.report({ message: 'Skipped test suite', node }); - break; + case 'describe.skip': + context.report({ message: 'Skipped test suite', node }); + break; - case 'it': - case 'test': - testDepth++; - if (node.arguments.length < 2) { - context.report({ - message: 'Test is missing function argument', - node, - }); - } - break; + case 'it': + case 'test': + testDepth++; + if (node.arguments.length < 2) { + context.report({ + message: 'Test is missing function argument', + node, + }); + } + break; - case 'it.skip': - case 'test.skip': - context.report({ message: 'Skipped test', node }); - break; + case 'it.skip': + case 'test.skip': + context.report({ message: 'Skipped test', node }); + break; - case 'pending': - if (testDepth > 0) { - context.report({ - message: 'Call to pending() within test', - node, - }); - } else if (suiteDepth > 0) { - context.report({ - message: 'Call to pending() within test suite', - node, - }); - } - break; + case 'pending': + if (testDepth > 0) { + context.report({ + message: 'Call to pending() within test', + node, + }); + } else if (suiteDepth > 0) { + context.report({ + message: 'Call to pending() within test suite', + node, + }); + } + break; - case 'xdescribe': - context.report({ message: 'Disabled test suite', node }); - break; + case 'xdescribe': + context.report({ message: 'Disabled test suite', node }); + break; - case 'xit': - case 'xtest': - context.report({ message: 'Disabled test', node }); - break; - } - }, + case 'xit': + case 'xtest': + context.report({ message: 'Disabled test', node }); + break; + } + }, - 'CallExpression:exit': node => { - const functionName = getName(node.callee); + 'CallExpression:exit': node => { + const functionName = getName(node.callee); - switch (functionName) { - case 'describe': - suiteDepth--; - break; + switch (functionName) { + case 'describe': + suiteDepth--; + break; - case 'it': - case 'test': - testDepth--; - break; - } - }, - }; + case 'it': + case 'test': + testDepth--; + break; + } + }, + }; + }, }; diff --git a/rules/no_focused_tests.js b/rules/no_focused_tests.js index 16ac9d7ab..14935386b 100644 --- a/rules/no_focused_tests.js +++ b/rules/no_focused_tests.js @@ -17,30 +17,38 @@ const isPropertyNamedOnly = property => const isCallToTestOnlyFunction = callee => matchesTestFunction(callee.object) && isPropertyNamedOnly(callee.property); -module.exports = context => ({ - CallExpression(node) { - const callee = node.callee; - if (!callee) { - return; - } - - if ( - callee.type === 'MemberExpression' && - isCallToTestOnlyFunction(callee) - ) { - context.report({ - message: 'Unexpected focused test.', - node: callee.property, - }); - return; - } - - if (callee.type === 'Identifier' && isCallToFocusedTestFunction(callee)) { - context.report({ - message: 'Unexpected focused test.', - node: callee, - }); - return; - } +module.exports = { + meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/no-focused-tests.md', + }, }, -}); + create: context => ({ + CallExpression(node) { + const callee = node.callee; + if (!callee) { + return; + } + + if ( + callee.type === 'MemberExpression' && + isCallToTestOnlyFunction(callee) + ) { + context.report({ + message: 'Unexpected focused test.', + node: callee.property, + }); + return; + } + + if (callee.type === 'Identifier' && isCallToFocusedTestFunction(callee)) { + context.report({ + message: 'Unexpected focused test.', + node: callee, + }); + return; + } + }, + }), +}; diff --git a/rules/no_identical_title.js b/rules/no_identical_title.js index 7b8f32055..137b1813c 100644 --- a/rules/no_identical_title.js +++ b/rules/no_identical_title.js @@ -70,26 +70,39 @@ const handleTestSuiteTitles = (context, titles, node, title) => { const isFirstArgLiteral = node => node.arguments && node.arguments[0] && node.arguments[0].type === 'Literal'; -module.exports = context => { - const contexts = [newDescribeContext()]; - return { - CallExpression(node) { - const currentLayer = contexts[contexts.length - 1]; - if (isDescribe(node)) { - contexts.push(newDescribeContext()); - } - if (!isFirstArgLiteral(node)) { - return; - } - - const title = node.arguments[0].value; - handleTestCaseTitles(context, currentLayer.testTitles, node, title); - handleTestSuiteTitles(context, currentLayer.describeTitles, node, title); - }, - 'CallExpression:exit'(node) { - if (isDescribe(node)) { - contexts.pop(); - } +module.exports = { + meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/no-identical-title.md', }, - }; + }, + create(context) { + const contexts = [newDescribeContext()]; + return { + CallExpression(node) { + const currentLayer = contexts[contexts.length - 1]; + if (isDescribe(node)) { + contexts.push(newDescribeContext()); + } + if (!isFirstArgLiteral(node)) { + return; + } + + const title = node.arguments[0].value; + handleTestCaseTitles(context, currentLayer.testTitles, node, title); + handleTestSuiteTitles( + context, + currentLayer.describeTitles, + node, + title + ); + }, + 'CallExpression:exit'(node) { + if (isDescribe(node)) { + contexts.pop(); + } + }, + }; + }, }; diff --git a/rules/no_large_snapshots.js b/rules/no_large_snapshots.js index be7b2faff..e227668d9 100644 --- a/rules/no_large_snapshots.js +++ b/rules/no_large_snapshots.js @@ -1,26 +1,35 @@ 'use strict'; -module.exports = context => { - if (context.getFilename().endsWith('.snap')) { - const lineLimit = (context.options[0] && context.options[0].maxSize) || 50; +module.exports = { + meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/no-large-snapshots.md', + }, + }, + create(context) { + if (context.getFilename().endsWith('.snap')) { + const lineLimit = + (context.options[0] && context.options[0].maxSize) || 50; - return { - ExpressionStatement: node => { - const startLine = node.loc.start.line; - const endLine = node.loc.end.line; - const lineCount = endLine - startLine; + return { + ExpressionStatement: node => { + const startLine = node.loc.start.line; + const endLine = node.loc.end.line; + const lineCount = endLine - startLine; - if (lineCount > lineLimit) { - context.report({ - message: - 'Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long', - data: { lineLimit, lineCount }, - node, - }); - } - }, - }; - } + if (lineCount > lineLimit) { + context.report({ + message: + 'Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long', + data: { lineLimit, lineCount }, + node, + }); + } + }, + }; + } - return {}; + return {}; + }, }; diff --git a/rules/prefer_expect_assertions.js b/rules/prefer_expect_assertions.js index d8f128f8e..c1c4901f8 100644 --- a/rules/prefer_expect_assertions.js +++ b/rules/prefer_expect_assertions.js @@ -60,22 +60,30 @@ const reportMsg = (context, node) => { }); }; -module.exports = context => { - return { - CallExpression(node) { - if (isTestOrItFunction(node)) { - const testFuncBody = getTestFunctionBody(node); - if (testFuncBody) { - if (!isFirstLineExprStmt(testFuncBody)) { - reportMsg(context, node); - } else { - const testFuncFirstLine = getFunctionFirstLine(testFuncBody); - if (!isExpectAssertionsOrHasAssertionsCall(testFuncFirstLine)) { +module.exports = { + meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/prefer-expect-assertions.md', + }, + }, + create(context) { + return { + CallExpression(node) { + if (isTestOrItFunction(node)) { + const testFuncBody = getTestFunctionBody(node); + if (testFuncBody) { + if (!isFirstLineExprStmt(testFuncBody)) { reportMsg(context, node); + } else { + const testFuncFirstLine = getFunctionFirstLine(testFuncBody); + if (!isExpectAssertionsOrHasAssertionsCall(testFuncFirstLine)) { + reportMsg(context, node); + } } } } - } - }, - }; + }, + }; + }, }; diff --git a/rules/prefer_to_be_null.js b/rules/prefer_to_be_null.js index b7f4c8fbe..c200f9ef5 100644 --- a/rules/prefer_to_be_null.js +++ b/rules/prefer_to_be_null.js @@ -10,6 +10,10 @@ const method2 = require('./util').method2; module.exports = { meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/prefer-to-be-null.md', + }, fixable: 'code', }, create(context) { diff --git a/rules/prefer_to_be_undefined.js b/rules/prefer_to_be_undefined.js index 4c516e97c..2556a4ad3 100644 --- a/rules/prefer_to_be_undefined.js +++ b/rules/prefer_to_be_undefined.js @@ -10,6 +10,10 @@ const method2 = require('./util').method2; module.exports = { meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/prefer-to-be-undefined.md', + }, fixable: 'code', }, create(context) { diff --git a/rules/prefer_to_have_length.js b/rules/prefer_to_have_length.js index 7810cbd05..85a93677f 100644 --- a/rules/prefer_to_have_length.js +++ b/rules/prefer_to_have_length.js @@ -7,6 +7,10 @@ const method = require('./util').method; module.exports = { meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/prefer-to-have-length.md', + }, fixable: 'code', }, create(context) { diff --git a/rules/valid_expect.js b/rules/valid_expect.js index 401c013b3..416b23b6e 100644 --- a/rules/valid_expect.js +++ b/rules/valid_expect.js @@ -7,115 +7,123 @@ const expectProperties = ['not', 'resolves', 'rejects']; -module.exports = context => { - return { - CallExpression(node) { - const calleeName = node.callee.name; +module.exports = { + meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/valid-expect.md', + }, + }, + create(context) { + return { + CallExpression(node) { + const calleeName = node.callee.name; - if (calleeName === 'expect') { - // checking "expect()" arguments - if (node.arguments.length > 1) { - const secondArgumentLocStart = node.arguments[1].loc.start; - const lastArgumentLocEnd = - node.arguments[node.arguments.length - 1].loc.end; + if (calleeName === 'expect') { + // checking "expect()" arguments + if (node.arguments.length > 1) { + const secondArgumentLocStart = node.arguments[1].loc.start; + const lastArgumentLocEnd = + node.arguments[node.arguments.length - 1].loc.end; - context.report({ - loc: { - end: { - column: lastArgumentLocEnd.column - 1, - line: lastArgumentLocEnd.line, - }, - start: secondArgumentLocStart, - }, - message: 'More than one argument was passed to expect().', - node, - }); - } else if (node.arguments.length === 0) { - const expectLength = calleeName.length; - context.report({ - loc: { - end: { - column: node.loc.start.column + expectLength + 1, - line: node.loc.start.line, + context.report({ + loc: { + end: { + column: lastArgumentLocEnd.column - 1, + line: lastArgumentLocEnd.line, + }, + start: secondArgumentLocStart, }, - start: { - column: node.loc.start.column + expectLength, - line: node.loc.start.line, + message: 'More than one argument was passed to expect().', + node, + }); + } else if (node.arguments.length === 0) { + const expectLength = calleeName.length; + context.report({ + loc: { + end: { + column: node.loc.start.column + expectLength + 1, + line: node.loc.start.line, + }, + start: { + column: node.loc.start.column + expectLength, + line: node.loc.start.line, + }, }, - }, - message: 'No arguments were passed to expect().', - node, - }); - } + message: 'No arguments were passed to expect().', + node, + }); + } - // something was called on `expect()` - if ( - node.parent && - node.parent.type === 'MemberExpression' && - node.parent.parent - ) { - let parentNode = node.parent; - let parentProperty = parentNode.property; - let propertyName = parentProperty.name; - let grandParent = parentNode.parent; + // something was called on `expect()` + if ( + node.parent && + node.parent.type === 'MemberExpression' && + node.parent.parent + ) { + let parentNode = node.parent; + let parentProperty = parentNode.property; + let propertyName = parentProperty.name; + let grandParent = parentNode.parent; + + // a property is accessed, get the next node + if (grandParent.type === 'MemberExpression') { + // a modifier is used, just get the next one + if (expectProperties.indexOf(propertyName) > -1) { + grandParent = grandParent.parent; + } else { + // only a few properties are allowed + context.report({ + // For some reason `endColumn` isn't set in tests if `loc` is + // not added + loc: parentProperty.loc, + message: `"${propertyName}" is not a valid property of expect.`, + node: parentProperty, + }); + } + + // this next one should be the matcher + parentNode = parentNode.parent; + parentProperty = parentNode.property; + propertyName = parentProperty.name; + } + + // matcher was not called + if (grandParent.type === 'ExpressionStatement') { + let message; + if (expectProperties.indexOf(propertyName) > -1) { + message = `"${propertyName}" needs to call a matcher.`; + } else { + message = `"${propertyName}" was not called.`; + } - // a property is accessed, get the next node - if (grandParent.type === 'MemberExpression') { - // a modifier is used, just get the next one - if (expectProperties.indexOf(propertyName) > -1) { - grandParent = grandParent.parent; - } else { - // only a few properties are allowed context.report({ - // For some reason `endColumn` isn't set in tests if `loc` is - // not added + // For some reason `endColumn` isn't set in tests if `loc` is not + // added loc: parentProperty.loc, - message: `"${propertyName}" is not a valid property of expect.`, + message, node: parentProperty, }); } - - // this next one should be the matcher - parentNode = parentNode.parent; - parentProperty = parentNode.property; - propertyName = parentProperty.name; - } - - // matcher was not called - if (grandParent.type === 'ExpressionStatement') { - let message; - if (expectProperties.indexOf(propertyName) > -1) { - message = `"${propertyName}" needs to call a matcher.`; - } else { - message = `"${propertyName}" was not called.`; - } - - context.report({ - // For some reason `endColumn` isn't set in tests if `loc` is not - // added - loc: parentProperty.loc, - message, - node: parentProperty, - }); } } - } - }, + }, - // nothing called on "expect()" - 'CallExpression:exit'(node) { - if ( - node.callee.name === 'expect' && - node.parent.type === 'ExpressionStatement' - ) { - context.report({ - // For some reason `endColumn` isn't set in tests if `loc` is not - // added - loc: node.loc, - message: 'No assertion was called on expect().', - node, - }); - } - }, - }; + // nothing called on "expect()" + 'CallExpression:exit'(node) { + if ( + node.callee.name === 'expect' && + node.parent.type === 'ExpressionStatement' + ) { + context.report({ + // For some reason `endColumn` isn't set in tests if `loc` is not + // added + loc: node.loc, + message: 'No assertion was called on expect().', + node, + }); + } + }, + }; + }, }; diff --git a/rules/valid_expect_in_promise.js b/rules/valid_expect_in_promise.js index 0d6f0242c..822400bd7 100644 --- a/rules/valid_expect_in_promise.js +++ b/rules/valid_expect_in_promise.js @@ -126,34 +126,42 @@ const isHavingAsyncCallBackParam = testFunction => { } }; -module.exports = context => { - return { - MemberExpression(node) { - if ( - node.type == 'MemberExpression' && - isThenOrCatch(node) && - node.parent.type == 'CallExpression' && - !isAwaitExpression(node) - ) { - const testFunction = getTestFunction(node); - if (testFunction && !isHavingAsyncCallBackParam(testFunction)) { - const testFunctionBody = getFunctionBody(testFunction); - const parent = node.parent; - const fulfillmentCallback = parent.arguments[0]; - const rejectionCallback = parent.arguments[1]; - - // 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( - [fulfillmentCallback, rejectionCallback], - node, - context, - testFunctionBody - ); - } - } +module.exports = { + meta: { + docs: { + url: + 'https://github.com/jest-community/eslint-plugin-jest/blob/master/docs/rules/valid-expect-in-promise.md', }, - }; + }, + create(context) { + return { + MemberExpression(node) { + if ( + node.type == 'MemberExpression' && + isThenOrCatch(node) && + node.parent.type == 'CallExpression' && + !isAwaitExpression(node) + ) { + const testFunction = getTestFunction(node); + if (testFunction && !isHavingAsyncCallBackParam(testFunction)) { + const testFunctionBody = getFunctionBody(testFunction); + const parent = node.parent; + const fulfillmentCallback = parent.arguments[0]; + const rejectionCallback = parent.arguments[1]; + + // 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( + [fulfillmentCallback, rejectionCallback], + node, + context, + testFunctionBody + ); + } + } + }, + }; + }, };