Skip to content

Commit 93bd142

Browse files
authored
New: fixer-return (#15)
* New: fixer-return * Update: support old-style context.report calls. * Update: check context.report({fix}) * Docs: fixer-return.md * Docs: fixer-return in readme.md * Fix: accept review suggestions.
1 parent 9f98ac4 commit 93bd142

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Then configure the rules you want to use under the rules section.
4949
Name | ✔️ | 🛠 | Description
5050
----- | ----- | ----- | -----
5151
[consistent-output](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/consistent-output.md) | | | Enforces consistent use of output assertions in rule tests
52+
[fixer-return](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/fixer-return.md) | | | Enforces always return from a fixer function
5253
[no-deprecated-report-api](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-deprecated-report-api.md) | ✔️ | 🛠 | Prohibits the deprecated `context.report(node, message)` API
5354
[no-identical-tests](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-identical-tests.md) | | 🛠 | Disallows identical tests
5455
[no-missing-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-missing-placeholders.md) | ✔️ | | Disallows missing placeholders in rule report messages

docs/rules/fixer-return.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Enforces always return from a fixer function (fixer-return)
2+
3+
In a fixable rule, missing return from a fixer function will not apply fixes.
4+
5+
## Rule Details
6+
7+
This rule enforces that fixer functions always return a value.
8+
9+
Examples of **incorrect** code for this rule:
10+
11+
```js
12+
/* eslint eslint-plugin/fixer-return: error */
13+
module.exports = {
14+
create: function(context) {
15+
context.report( {
16+
fix: function(fixer) {
17+
fixer.foo();
18+
}
19+
});
20+
}
21+
};
22+
```
23+
24+
Examples of **correct** code for this rule:
25+
26+
```js
27+
/* eslint eslint-plugin/fixer-return: error */
28+
module.exports = {
29+
create: function(context) {
30+
context.report( {
31+
fix: function(fixer) {
32+
return fixer.foo();
33+
}
34+
});
35+
}
36+
};
37+
```
38+
39+
## When Not To Use It
40+
41+
If you don't want to enforce always return from a fixer function, do not enable this rule.

lib/rules/fixer-return.js

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* @fileoverview Enforces always return from a fixer function
3+
* @author 薛定谔的猫<[email protected]>
4+
*/
5+
6+
'use strict';
7+
8+
// ------------------------------------------------------------------------------
9+
// Requirements
10+
// ------------------------------------------------------------------------------
11+
12+
const utils = require('../utils');
13+
14+
// ------------------------------------------------------------------------------
15+
// Rule Definition
16+
// ------------------------------------------------------------------------------
17+
18+
module.exports = {
19+
meta: {
20+
docs: {
21+
description: 'Expected fixer function to always return a value.',
22+
category: 'Possible Errors',
23+
recommended: false,
24+
},
25+
fixable: null,
26+
},
27+
28+
create (context) {
29+
const message = 'Expected fixer function to always return a value.';
30+
let funcInfo = {
31+
upper: null,
32+
codePath: null,
33+
hasReturn: false,
34+
shouldCheck: false,
35+
node: null,
36+
};
37+
let contextIdentifiers;
38+
39+
/**
40+
* Checks whether or not the last code path segment is reachable.
41+
* Then reports this function if the segment is reachable.
42+
*
43+
* If the last code path segment is reachable, there are paths which are not
44+
* returned or thrown.
45+
*
46+
* @param {ASTNode} node - A node to check.
47+
* @returns {void}
48+
*/
49+
function checkLastSegment (node) {
50+
if (funcInfo.shouldCheck && funcInfo.codePath.currentSegments.some(segment => segment.reachable)) {
51+
context.report({
52+
node,
53+
loc: (node.id || node).loc.start,
54+
message,
55+
});
56+
}
57+
}
58+
59+
return {
60+
Program (node) {
61+
contextIdentifiers = utils.getContextIdentifiers(context, node);
62+
},
63+
64+
// Stacks this function's information.
65+
onCodePathStart (codePath, node) {
66+
const parent = node.parent;
67+
const shouldCheck = node.type === 'FunctionExpression' &&
68+
parent.parent.type === 'ObjectExpression' &&
69+
parent.parent.parent.type === 'CallExpression' &&
70+
contextIdentifiers.has(parent.parent.parent.callee.object) &&
71+
parent.parent.parent.callee.property.name === 'report' &&
72+
utils.getReportInfo(parent.parent.parent.arguments).fix === node;
73+
74+
funcInfo = {
75+
upper: funcInfo,
76+
codePath,
77+
hasReturn: false,
78+
shouldCheck,
79+
node,
80+
};
81+
},
82+
83+
// Pops this function's information.
84+
onCodePathEnd () {
85+
funcInfo = funcInfo.upper;
86+
},
87+
88+
// Checks the return statement is valid.
89+
ReturnStatement (node) {
90+
if (funcInfo.shouldCheck) {
91+
funcInfo.hasReturn = true;
92+
93+
if (!node.argument) {
94+
context.report({
95+
node,
96+
message,
97+
});
98+
}
99+
}
100+
},
101+
102+
// Reports a given function if the last path is reachable.
103+
'FunctionExpression:exit': checkLastSegment,
104+
};
105+
},
106+
};

tests/lib/rules/fixer-return.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* @fileoverview enforces always return from a fixer function
3+
* @author 薛定谔的猫<[email protected]>
4+
*/
5+
6+
'use strict';
7+
8+
// ------------------------------------------------------------------------------
9+
// Requirements
10+
// ------------------------------------------------------------------------------
11+
12+
const rule = require('../../../lib/rules/fixer-return');
13+
const RuleTester = require('eslint').RuleTester;
14+
15+
const ERROR = { message: 'Expected fixer function to always return a value.' };
16+
17+
// ------------------------------------------------------------------------------
18+
// Tests
19+
// ------------------------------------------------------------------------------
20+
21+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
22+
ruleTester.run('fixer-return', rule, {
23+
valid: [
24+
`
25+
module.exports = {
26+
create: function(context) {
27+
context.report( {
28+
fix: function(fixer) {
29+
return fixer.foo();
30+
}
31+
});
32+
}
33+
};
34+
`,
35+
`
36+
module.exports = {
37+
create: function(context) {
38+
context.report({
39+
fix: fixer => fixer.foo()
40+
});
41+
}
42+
};
43+
`,
44+
],
45+
46+
invalid: [
47+
{
48+
code: `
49+
module.exports = {
50+
create: function(context) {
51+
context.report({
52+
fix(fixer) {
53+
fixer.foo();
54+
}
55+
});
56+
}
57+
};
58+
`,
59+
errors: [ERROR],
60+
},
61+
],
62+
});

0 commit comments

Comments
 (0)