Skip to content

Commit

Permalink
Merge pull request #288 from lo1tuma/no-mocha-arrows-perf
Browse files Browse the repository at this point in the history
Improve no-mocha-arrows performance
  • Loading branch information
lo1tuma committed May 26, 2021
2 parents 3d973ab + e499b27 commit 7a3d143
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 113 deletions.
11 changes: 7 additions & 4 deletions lib/rules/no-exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ module.exports = {
const astUtils = createAstUtils(context.settings);
const exportNodes = [];
let hasTestCase = false;
const isTestCase = astUtils.buildIsTestCaseAnswerer();
const isDescribe = astUtils.buildIsDescribeAnswerer();
const isMochaFunctionCall = astUtils.buildIsMochaFunctionCallAnswerer(
isTestCase,
isDescribe
);

function isCommonJsExport(node) {
if (node.type === 'MemberExpression') {
Expand All @@ -37,10 +43,7 @@ module.exports = {
},

CallExpression(node) {
if (
!hasTestCase &&
astUtils.isMochaFunctionCall(node, context.getScope())
) {
if (!hasTestCase && isMochaFunctionCall(node, context)) {
hasTestCase = true;
}
},
Expand Down
154 changes: 91 additions & 63 deletions lib/rules/no-mocha-arrows.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,83 +7,111 @@

const createAstUtils = require('../util/ast');

function extractSourceTextByRange(sourceCode, start, end) {
return sourceCode.text.slice(start, end).trim();
}

// eslint-disable-next-line max-statements
function formatFunctionHead(sourceCode, fn) {
const arrow = sourceCode.getTokenBefore(fn.body);
const beforeArrowToken = sourceCode.getTokenBefore(arrow);
let firstToken = sourceCode.getFirstToken(fn);

let functionKeyword = 'function';
let params = extractSourceTextByRange(
sourceCode,
firstToken.range[0],
beforeArrowToken.range[1]
);
if (fn.async) {
// When 'async' specified strip the token from the params text
// and prepend it to the function keyword
params = params.slice(firstToken.range[1] - firstToken.range[0]).trim();
functionKeyword = 'async function';

// Advance firstToken pointer
firstToken = sourceCode.getTokenAfter(firstToken);
}

const beforeArrowComment = extractSourceTextByRange(
sourceCode,
beforeArrowToken.range[1],
arrow.range[0]
);
const afterArrowComment = extractSourceTextByRange(
sourceCode,
arrow.range[1],
fn.body.range[0]
);
let paramsFullText;
if (firstToken.type !== 'Punctuator') {
paramsFullText = `(${params}${beforeArrowComment})${afterArrowComment}`;
} else {
paramsFullText = `${params}${beforeArrowComment}${afterArrowComment}`;
}

return `${functionKeyword}${paramsFullText} `;
}

function fixArrowFunction(fixer, sourceCode, fn) {
if (fn.body.type === 'BlockStatement') {
// When it((...) => { ... }),
// simply replace '(...) => ' with 'function () '
return fixer.replaceTextRange(
[ fn.range[0], fn.body.range[0] ],
formatFunctionHead(sourceCode, fn)
);
}

const bodyText = sourceCode.getText(fn.body);
return fixer.replaceTextRange(
fn.range,
`${formatFunctionHead(sourceCode, fn)}{ return ${bodyText}; }`
);
}

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'Disallow arrow functions as arguments to mocha functions'
description:
'Disallow arrow functions as arguments to mocha functions'
},
fixable: 'code'
},
create(context) {
const astUtils = createAstUtils(context.settings);
const sourceCode = context.getSourceCode();

function extractSourceTextByRange(start, end) {
return sourceCode.text.slice(start, end).trim();
}

// eslint-disable-next-line max-statements
function formatFunctionHead(fn) {
const arrow = sourceCode.getTokenBefore(fn.body);
const beforeArrowToken = sourceCode.getTokenBefore(arrow);
let firstToken = sourceCode.getFirstToken(fn);

let functionKeyword = 'function';
let params = extractSourceTextByRange(firstToken.range[0], beforeArrowToken.range[1]);
if (fn.async) {
// When 'async' specified strip the token from the params text
// and prepend it to the function keyword
params = params.slice(firstToken.range[1] - firstToken.range[0]).trim();
functionKeyword = 'async function';

// Advance firstToken pointer
firstToken = sourceCode.getTokenAfter(firstToken);
}

const beforeArrowComment = extractSourceTextByRange(beforeArrowToken.range[1], arrow.range[0]);
const afterArrowComment = extractSourceTextByRange(arrow.range[1], fn.body.range[0]);
let paramsFullText;
if (firstToken.type !== 'Punctuator') {
paramsFullText = `(${params}${beforeArrowComment})${afterArrowComment}`;
} else {
paramsFullText = `${params}${beforeArrowComment}${afterArrowComment}`;
}

return `${functionKeyword}${paramsFullText} `;
}

function fixArrowFunction(fixer, fn) {
if (fn.body.type === 'BlockStatement') {
// When it((...) => { ... }),
// simply replace '(...) => ' with 'function () '
return fixer.replaceTextRange(
[ fn.range[0], fn.body.range[0] ],
formatFunctionHead(fn)
);
}

const bodyText = sourceCode.text.slice(fn.body.range[0], fn.body.range[1]);
return fixer.replaceTextRange(
[ fn.range[0], fn.range[1] ],
`${formatFunctionHead(fn)}{ return ${ bodyText }; }`
);
}
const isTestCase = astUtils.buildIsTestCaseAnswerer();
const isDescribe = astUtils.buildIsDescribeAnswerer();
const isMochaFunctionCall = astUtils.buildIsMochaFunctionCallAnswerer(
isTestCase,
isDescribe
);

return {
CallExpression(node) {
const name = astUtils.getNodeName(node.callee);
if (isMochaFunctionCall(node, context)) {
const amountOfArguments = node.arguments.length;

if (amountOfArguments > 0) {
const lastArgument =
node.arguments[amountOfArguments - 1];

if (astUtils.isMochaFunctionCall(node, context.getScope())) {
const fnArg = node.arguments.slice(-1)[0];
if (fnArg && fnArg.type === 'ArrowFunctionExpression') {
context.report({
node,
message: `Do not pass arrow functions to ${ name }()`,
fix(fixer) {
return fixArrowFunction(fixer, fnArg);
}
});
if (lastArgument.type === 'ArrowFunctionExpression') {
const name = astUtils.getNodeName(node.callee);
context.report({
node,
message: `Do not pass arrow functions to ${name}()`,
fix(fixer) {
return fixArrowFunction(
fixer,
sourceCode,
lastArgument
);
}
});
}
}
}
}
Expand Down
18 changes: 15 additions & 3 deletions lib/rules/no-nested-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,20 @@ module.exports = {
return isNested && isTest;
}

function checkForAndReportErrors(node, isTestCase, isDescribe, isHookCall) {
function checkForAndReportErrors(
node,
isTestCase,
isDescribe,
isHookCall
) {
if (isNestedTest(isTestCase, isDescribe, testNestingLevel)) {
const message = isDescribe ?
'Unexpected suite nested within a test.' :
'Unexpected test nested within another test.';
report(node, message);
} else if (isNestedTest(isTestCase, isHookCall, hookCallNestingLevel)) {
} else if (
isNestedTest(isTestCase, isHookCall, hookCallNestingLevel)
) {
const message = isHookCall ?
'Unexpected test hook nested within a test hook.' :
'Unexpected test nested within a test hook.';
Expand All @@ -50,7 +57,12 @@ module.exports = {
const isHookCall = astUtils.isHookCall(node);
const isDescribe = astUtils.isDescribe(node);

checkForAndReportErrors(node, isTestCase, isDescribe, isHookCall);
checkForAndReportErrors(
node,
isTestCase,
isDescribe,
isHookCall
);

if (isTestCase) {
testNestingLevel += 1;
Expand Down

0 comments on commit 7a3d143

Please sign in to comment.