Skip to content

Commit f0d27d5

Browse files
authored
New: rule prefer-replace-text (#50)
* New: rule prefer-replace-text (fixes #47). * Docs: add prefer-replace-range.md. * Fix: add type check. * chore: update comment. * Fix: reveiew suggestions.
1 parent 86d3ebf commit f0d27d5

File tree

4 files changed

+263
-0
lines changed

4 files changed

+263
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Name | ✔️ | 🛠 | Description
5959
[no-useless-token-range](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/no-useless-token-range.md) | ✔️ | 🛠 | Disallow unnecessary calls to sourceCode.getFirstToken and sourceCode.getLastToken
6060
[prefer-output-null](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-output-null.md) | | 🛠 | disallows invalid RuleTester test cases with the output the same as the code.
6161
[prefer-placeholders](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-placeholders.md) | | | disallow template literals as report messages
62+
[prefer-replace-text](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-replace-text.md) | | | prefer using replaceText instead of replaceTextRange.
6263
[report-message-format](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/report-message-format.md) | | | enforce a consistent format for rule report messages
6364
[require-meta-fixable](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-fixable.md) | ✔️ | | require rules to implement a meta.fixable property
6465
[test-case-property-ordering](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/test-case-property-ordering.md) | | 🛠 | Requires the properties of a test case to be placed in a consistent order

docs/rules/prefer-replace-range.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# prefer using replaceText instead of replaceTextRange. (prefer-replace-text)
2+
3+
## Rule Details
4+
5+
The rule reports an error if `replaceTextRange`'s first argument is an array of identical array elements. It can be easily replaced by `replaceText` to improve readability.
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
/* eslint eslint-plugin/prefer-text-range: error */
11+
module.exports = {
12+
create(context) {
13+
context.report({
14+
fix(fixer) {
15+
       // error, can be written: return fixer.replaceText([node, '']);
16+
return fixer.replaceTextRange([node.range[0], node.range[1]], '');
17+
}
18+
});
19+
}
20+
};
21+
```
22+
23+
Examples of **correct** code for this rule:
24+
25+
```js
26+
/* eslint eslint-plugin/prefer-text-range: error */
27+
module.exports = {
28+
create(context) {
29+
context.report({
30+
fix(fixer) {
31+
return fixer.replaceText(node, '');
32+
}
33+
});
34+
}
35+
};
36+
37+
module.exports = {
38+
create(context) {
39+
context.report({
40+
fix(fixer) {
41+
// start = ...
42+
// end = ...
43+
return fixer.replaceTextRange([start, end], '');
44+
}
45+
});
46+
}
47+
};
48+
```
49+
50+
## Further Reading
51+
52+
* [Applying Fixes](https://eslint.org/docs/developer-guide/working-with-rules#applying-fixes)

lib/rules/prefer-replace-text.js

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* @fileoverview prefer using replaceText instead of replaceTextRange.
3+
* @author 薛定谔的猫<[email protected]>
4+
*/
5+
6+
'use strict';
7+
8+
const utils = require('../utils');
9+
10+
// ------------------------------------------------------------------------------
11+
// Rule Definition
12+
// ------------------------------------------------------------------------------
13+
14+
module.exports = {
15+
meta: {
16+
docs: {
17+
description: 'prefer using replaceText instead of replaceTextRange.',
18+
category: 'Rules',
19+
recommended: false,
20+
},
21+
fixable: null,
22+
schema: [],
23+
},
24+
25+
create (context) {
26+
const sourceCode = context.getSourceCode();
27+
const message = 'Use replaceText instead of replaceTextRange.';
28+
let funcInfo = {
29+
upper: null,
30+
codePath: null,
31+
shouldCheck: false,
32+
node: null,
33+
};
34+
let contextIdentifiers;
35+
36+
return {
37+
Program (node) {
38+
contextIdentifiers = utils.getContextIdentifiers(context, node);
39+
},
40+
41+
// Stacks this function's information.
42+
onCodePathStart (codePath, node) {
43+
const parent = node.parent;
44+
const shouldCheck = (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') &&
45+
parent.parent.type === 'ObjectExpression' &&
46+
parent.parent.parent.type === 'CallExpression' &&
47+
contextIdentifiers.has(parent.parent.parent.callee.object) &&
48+
parent.parent.parent.callee.property.name === 'report' &&
49+
utils.getReportInfo(parent.parent.parent.arguments).fix === node;
50+
51+
funcInfo = {
52+
upper: funcInfo,
53+
codePath,
54+
shouldCheck,
55+
node,
56+
};
57+
},
58+
59+
// Pops this function's information.
60+
onCodePathEnd () {
61+
funcInfo = funcInfo.upper;
62+
},
63+
64+
// Checks the replaceTextRange arguments.
65+
'CallExpression[arguments.length=2]' (node) {
66+
if (funcInfo.shouldCheck &&
67+
node.callee.type === 'MemberExpression' &&
68+
node.callee.property.name === 'replaceTextRange') {
69+
const arg = node.arguments[0];
70+
const isIdenticalNodeRange = arg.type === 'ArrayExpression' &&
71+
arg.elements[0].type === 'MemberExpression' && arg.elements[1].type === 'MemberExpression' &&
72+
sourceCode.getText(arg.elements[0].object) === sourceCode.getText(arg.elements[1].object);
73+
if (isIdenticalNodeRange) {
74+
context.report({
75+
node,
76+
message,
77+
});
78+
}
79+
}
80+
},
81+
};
82+
},
83+
};
+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* @fileoverview prefer using replaceText instead of replaceTextRange
3+
* @author 薛定谔的猫<[email protected]>
4+
*/
5+
6+
'use strict';
7+
8+
// ------------------------------------------------------------------------------
9+
// Requirements
10+
// ------------------------------------------------------------------------------
11+
12+
const rule = require('../../../lib/rules/prefer-replace-text');
13+
const RuleTester = require('eslint').RuleTester;
14+
15+
// ------------------------------------------------------------------------------
16+
// Tests
17+
// ------------------------------------------------------------------------------
18+
19+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
20+
const ERROR = { message: 'Use replaceText instead of replaceTextRange.' };
21+
22+
23+
ruleTester.run('prefer-placeholders', rule, {
24+
valid: [
25+
`
26+
module.exports = {
27+
create(context) {
28+
context.report({
29+
fix(fixer) {
30+
return fixer.replaceTextRange([start, end], '');
31+
}
32+
});
33+
}
34+
};
35+
`,
36+
`
37+
module.exports = {
38+
create(context) {
39+
context.report({
40+
fix(fixer) {
41+
return fixer.replaceTextRange([node1[0], node2[1]], '');
42+
}
43+
});
44+
}
45+
};
46+
`,
47+
`
48+
module.exports = {
49+
create(context) {}
50+
};
51+
`,
52+
`
53+
fixer.replaceTextRange([node.range[0], node.range[1]], '');
54+
`,
55+
],
56+
57+
invalid: [
58+
{
59+
code: `
60+
module.exports = {
61+
create(context) {
62+
context.report({
63+
fix(fixer) {
64+
return fixer.replaceTextRange([node.range[0], node.range[1]], '');
65+
}
66+
});
67+
}
68+
};
69+
`,
70+
errors: [ERROR],
71+
},
72+
{
73+
code: `
74+
module.exports = {
75+
create(context) {
76+
context.report({
77+
fix: function(fixer) {
78+
return fixer.replaceTextRange([node.range[0], node.range[1]], '');
79+
}
80+
});
81+
}
82+
};
83+
`,
84+
errors: [ERROR],
85+
},
86+
{
87+
code: `
88+
module.exports = {
89+
create(context) {
90+
context.report({
91+
fix: function(fixer) {
92+
if (foo) {return fixer.replaceTextRange([node.range[0], node.range[1]], '')}
93+
}
94+
});
95+
}
96+
};
97+
`,
98+
errors: [ERROR],
99+
},
100+
{
101+
code: `
102+
module.exports = {
103+
create(context) {
104+
context.report({
105+
fix: fixer => fixer.replaceTextRange([node.range[0], node.range[1]], '')
106+
});
107+
}
108+
};
109+
`,
110+
errors: [ERROR],
111+
},
112+
{
113+
code: `
114+
module.exports = {
115+
create(context) {
116+
context.report({
117+
fix(fixer) {
118+
return fixer.replaceTextRange([node.start, node.end], '');
119+
}
120+
});
121+
}
122+
};
123+
`,
124+
errors: [ERROR],
125+
},
126+
],
127+
});

0 commit comments

Comments
 (0)