Skip to content

Commit

Permalink
Merge pull request #228 from brettz9/fixable
Browse files Browse the repository at this point in the history
Add `fixable` property to fixable rules (and mention in docs)
  • Loading branch information
lo1tuma committed Feb 17, 2020
2 parents f8141df + f766402 commit a147956
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 157 deletions.
6 changes: 3 additions & 3 deletions docs/rules/README.md
Expand Up @@ -2,12 +2,13 @@

* [handle-done-callback](handle-done-callback.md) - enforces handling of callbacks for async tests
* [max-top-level-suites](max-top-level-suites.md) - limit the number of top-level suites in a single file
* [no-async-describe](no-async-describe.md) - disallow async functions passed to describe (fixable)
* [no-exclusive-tests](no-exclusive-tests.md) - disallow exclusive mocha tests
* [no-global-tests](no-global-tests.md) - disallow global tests
* [no-hooks](no-hooks.md) - disallow hooks
* [no-hooks-for-single-case](no-hooks-for-single-case.md) - disallow hooks for a single test or test suite
* [no-identical-title](no-identical-title.md) - disallow identical titles
* [no-mocha-arrows](no-mocha-arrows.md) - disallow arrow functions as arguments to mocha globals
* [no-mocha-arrows](no-mocha-arrows.md) - disallow arrow functions as arguments to mocha globals (fixable)
* [no-nested-tests](no-nested-tests.md) - disallow tests to be nested within other tests
* [no-pending-tests](no-pending-tests.md) - disallow pending/unimplemented mocha tests
* [no-return-and-callback](no-return-and-callback.md) - disallow returning in a test or hook function that uses a callback
Expand All @@ -17,7 +18,6 @@
* [no-skipped-tests](no-skipped-tests.md) - disallow skipped mocha tests (fixable)
* [no-synchronous-tests](no-synchronous-tests.md) - disallow synchronous tests
* [no-top-level-hooks](no-top-level-hooks.md) - disallow top-level hooks
* [prefer-arrow-callback](prefer-arrow-callback.md) - prefer arrow function callbacks (mocha-aware)
* [prefer-arrow-callback](prefer-arrow-callback.md) - prefer arrow function callbacks (mocha-aware) (fixable)
* [valid-suite-description](valid-suite-description.md) - match suite descriptions against a pre-configured regular expression
* [valid-test-description](valid-test-description.md) - match test descriptions against a pre-configured regular expression
* [no-async-describe](no-async-describe.md) - disallow async functions passed to describe
2 changes: 2 additions & 0 deletions docs/rules/no-async-describe.md
Expand Up @@ -15,6 +15,8 @@ describe('the thing', async function () {
});
```

**Fixable:** Problems detected by this rule are automatically fixable using the `--fix` flag on the command line.

## Rule Details

The rule supports "describe", "context" and "suite" suite function names and different valid suite name prefixes like "skip" or "only".
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/no-mocha-arrows.md
Expand Up @@ -2,6 +2,8 @@

Mocha [discourages](http://mochajs.org/#arrow-functions) passing it arrow functions as arguments. This rule prevents their use on the Mocha globals.

**Fixable:** Problems detected by this rule are automatically fixable using the `--fix` flag on the command line.

## Rule Details

This rule looks for occurrences of the Mocha functions (`describe`, `it`, `beforeEach`, etc.) within the source code.
Expand Down
2 changes: 2 additions & 0 deletions docs/rules/prefer-arrow-callback.md
Expand Up @@ -13,6 +13,8 @@ You will want to disable the original `prefer-arrow-callback` rule and configure
}
```

**Fixable:** Problems detected by this rule are automatically fixable using the `--fix` flag on the command line.

## Rule Overview

Arrow functions can be an attractive alternative to function expressions for callbacks or function arguments.
Expand Down
102 changes: 54 additions & 48 deletions lib/rules/no-async-describe.js
Expand Up @@ -9,62 +9,68 @@
const astUtils = require('../util/ast');
const { additionalSuiteNames } = require('../util/settings');

module.exports = function (context) {
const sourceCode = context.getSourceCode();
module.exports = {
meta: {
fixable: 'code'
},
create(context) {
const sourceCode = context.getSourceCode();

function isFunction(node) {
return (
node.type === 'FunctionExpression' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ArrowFunctionExpression'
);
}
function isFunction(node) {
return (
node.type === 'FunctionExpression' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ArrowFunctionExpression'
);
}

function containsDirectAwait(node) {
if (node.type === 'AwaitExpression') {
return true;
} else if (node.type && !isFunction(node)) {
return Object.keys(node).some(function (key) {
if (Array.isArray(node[key])) {
return node[key].some(containsDirectAwait);
} else if (key !== 'parent' && node[key] && typeof node[key] === 'object') {
return containsDirectAwait(node[key]);
}
return false;
});
function containsDirectAwait(node) {
if (node.type === 'AwaitExpression') {
return true;
} else if (node.type && !isFunction(node)) {
return Object.keys(node).some(function (key) {
if (Array.isArray(node[key])) {
return node[key].some(containsDirectAwait);
} else if (key !== 'parent' && node[key] && typeof node[key] === 'object') {
return containsDirectAwait(node[key]);
}
return false;
});
}
return false;
}
return false;
}

function fixAsyncFunction(fixer, fn) {
if (!containsDirectAwait(fn.body)) {
// Remove the "async" token and all the whitespace before "function":
const [ asyncToken, functionToken ] = sourceCode.getFirstTokens(fn, 2);
return fixer.removeRange([ asyncToken.range[0], functionToken.range[0] ]);
function fixAsyncFunction(fixer, fn) {
if (!containsDirectAwait(fn.body)) {
// Remove the "async" token and all the whitespace before "function":
const [ asyncToken, functionToken ] = sourceCode.getFirstTokens(fn, 2);
return fixer.removeRange([ asyncToken.range[0], functionToken.range[0] ]);
}
return undefined;
}
return undefined;
}

function isAsyncFunction(node) {
return node && (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && node.async;
}
function isAsyncFunction(node) {
return node && (node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression') && node.async;
}

return {
CallExpression(node) {
const name = astUtils.getNodeName(node.callee);
return {
CallExpression(node) {
const name = astUtils.getNodeName(node.callee);

if (astUtils.isDescribe(node, additionalSuiteNames(context.settings))) {
const fnArg = node.arguments.slice(-1)[0];
if (isAsyncFunction(fnArg)) {
context.report({
node: fnArg,
message: `Unexpected async function in ${name}()`,
fix(fixer) {
return fixAsyncFunction(fixer, fnArg);
}
});
if (astUtils.isDescribe(node, additionalSuiteNames(context.settings))) {
const fnArg = node.arguments.slice(-1)[0];
if (isAsyncFunction(fnArg)) {
context.report({
node: fnArg,
message: `Unexpected async function in ${name}()`,
fix(fixer) {
return fixAsyncFunction(fixer, fnArg);
}
});
}
}
}
}
};
};
}
};
95 changes: 50 additions & 45 deletions lib/rules/no-mocha-arrows.js
Expand Up @@ -8,62 +8,67 @@
const last = require('ramda/src/last');
const astUtils = require('../util/ast');

module.exports = function (context) {
const sourceCode = context.getSourceCode();
module.exports = {
meta: {
fixable: 'code'
},
create(context) {
const sourceCode = context.getSourceCode();

function formatFunctionHead(fn) {
const paramsLeftParen = sourceCode.getFirstToken(fn);
const paramsRightParen = sourceCode.getTokenBefore(sourceCode.getTokenBefore(fn.body));
let paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]);
let functionKeyword = 'function';
function formatFunctionHead(fn) {
const paramsLeftParen = sourceCode.getFirstToken(fn);
const paramsRightParen = sourceCode.getTokenBefore(sourceCode.getTokenBefore(fn.body));
let paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]);
let functionKeyword = 'function';

if (fn.async) {
// When 'async' specified, take care about the keyword.
functionKeyword = 'async function';
// Strip 'async (...)' to ' (...)'
paramsFullText = paramsFullText.slice(5);
}
if (fn.async) {
// When 'async' specified, take care about the keyword.
functionKeyword = 'async function';
// Strip 'async (...)' to ' (...)'
paramsFullText = paramsFullText.slice(5);
}

if (fn.params.length > 0) {
paramsFullText = `(${ sourceCode.text.slice(fn.params[0].start, last(fn.params).end) })`;
if (fn.params.length > 0) {
paramsFullText = `(${ sourceCode.text.slice(fn.params[0].start, last(fn.params).end) })`;
}

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

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

function fixArrowFunction(fixer, fn) {
if (fn.body.type === 'BlockStatement') {
// When it((...) => { ... }),
// simply replace '(...) => ' with 'function () '
const bodyText = sourceCode.text.slice(fn.body.range[0], fn.body.range[1]);
return fixer.replaceTextRange(
[ fn.start, fn.body.start ],
formatFunctionHead(fn)
[ fn.start, fn.end ],
`${formatFunctionHead(fn)}{ return ${ bodyText }; }`
);
}

const bodyText = sourceCode.text.slice(fn.body.range[0], fn.body.range[1]);
return fixer.replaceTextRange(
[ fn.start, fn.end ],
`${formatFunctionHead(fn)}{ return ${ bodyText }; }`
);
}

return {
CallExpression(node) {
const name = astUtils.getNodeName(node.callee);
return {
CallExpression(node) {
const name = astUtils.getNodeName(node.callee);

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 (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);
}
});
}
}
}
}
};
};
}
};

0 comments on commit a147956

Please sign in to comment.