Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fixable property to fixable rules (and mention in docs) #228

Merged
merged 1 commit into from
Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
}
});
}
}
}
}
};
};
}
};
Loading