Skip to content

Commit 0520f31

Browse files
authored
A better fix for no-instanceof-array (#1020)
1 parent 21537d7 commit 0520f31

File tree

6 files changed

+351
-49
lines changed

6 files changed

+351
-49
lines changed

rules/no-instanceof-array.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
'use strict';
2+
const {isParenthesized, isOpeningParenToken, isClosingParenToken} = require('eslint-utils');
23
const getDocumentationUrl = require('./utils/get-documentation-url');
4+
const replaceNodeOrTokenAndSpacesBefore = require('./utils/replace-node-or-token-and-spaces-before');
5+
6+
const isInstanceofToken = token => token.value === 'instanceof' && token.type === 'Keyword';
37

48
const MESSAGE_ID = 'no-instanceof-array';
59
const messages = {
@@ -19,10 +23,23 @@ const create = context => {
1923
[selector]: node => context.report({
2024
node,
2125
messageId: MESSAGE_ID,
22-
fix: fixer => fixer.replaceText(
23-
node,
24-
`Array.isArray(${sourceCode.getText(node.left)})`
25-
)
26+
* fix(fixer) {
27+
const {left, right} = node;
28+
29+
let leftStartNodeOrToken = left;
30+
let leftEndNodeOrToken = left;
31+
if (isParenthesized(left, sourceCode)) {
32+
leftStartNodeOrToken = sourceCode.getTokenBefore(left, isOpeningParenToken);
33+
leftEndNodeOrToken = sourceCode.getTokenAfter(left, isClosingParenToken);
34+
}
35+
36+
yield fixer.insertTextBefore(leftStartNodeOrToken, 'Array.isArray(');
37+
yield fixer.insertTextAfter(leftEndNodeOrToken, ')');
38+
39+
const instanceofToken = sourceCode.getTokenAfter(left, isInstanceofToken);
40+
yield * replaceNodeOrTokenAndSpacesBefore(instanceofToken, '', fixer, sourceCode);
41+
yield * replaceNodeOrTokenAndSpacesBefore(right, '', fixer, sourceCode);
42+
}
2643
})
2744
};
2845
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
const {isParenthesized} = require('eslint-utils');
3+
4+
const getParenthesizedTimes = (node, sourceCode) => {
5+
let times = 0;
6+
while (isParenthesized(times + 1, node, sourceCode)) {
7+
times++;
8+
}
9+
10+
return times;
11+
};
12+
13+
module.exports = getParenthesizedTimes;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
const {isOpeningParenToken, isClosingParenToken} = require('eslint-utils');
3+
const getParenthesizedTimes = require('./get-parenthesized-times');
4+
5+
function * replaceNodeOrTokenAndSpacesBefore(nodeOrToken, replacement, fixer, sourceCode) {
6+
const parenthesizedTimes = getParenthesizedTimes(nodeOrToken, sourceCode);
7+
8+
if (parenthesizedTimes > 0) {
9+
let lastBefore = nodeOrToken;
10+
let lastAfter = nodeOrToken;
11+
for (let index = 0; index < parenthesizedTimes; index++) {
12+
const openingParenthesisToken = sourceCode.getTokenBefore(lastBefore, isOpeningParenToken);
13+
const closingParenthesisToken = sourceCode.getTokenAfter(lastAfter, isClosingParenToken);
14+
yield * replaceNodeOrTokenAndSpacesBefore(openingParenthesisToken, '', fixer, sourceCode);
15+
yield * replaceNodeOrTokenAndSpacesBefore(closingParenthesisToken, '', fixer, sourceCode);
16+
lastBefore = openingParenthesisToken;
17+
lastAfter = closingParenthesisToken;
18+
}
19+
}
20+
21+
let [start, end] = nodeOrToken.range;
22+
23+
const textBefore = sourceCode.text.slice(0, start);
24+
const [trailingSpaces] = textBefore.match(/\s*$/);
25+
const [lineBreak] = trailingSpaces.match(/(?:\r?\n|\r){0,1}/);
26+
start -= trailingSpaces.length;
27+
28+
yield fixer.replaceTextRange([start, end], `${lineBreak}${replacement}`);
29+
}
30+
31+
module.exports = replaceNodeOrTokenAndSpacesBefore;

test/no-instanceof-array.js

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1+
import {outdent} from 'outdent';
12
import {test} from './utils/test.js';
23

3-
const errors = [
4-
{
5-
messageId: 'no-instanceof-array'
6-
}
7-
];
8-
9-
test({
4+
test.visualize({
105
valid: [
116
'Array.isArray(arr)',
127
'arr instanceof Object',
@@ -18,39 +13,47 @@ test({
1813
'"arr instanceof Array"'
1914
],
2015
invalid: [
21-
{
22-
code: 'arr instanceof Array',
23-
output: 'Array.isArray(arr)',
24-
errors
25-
},
26-
{
27-
code: '[] instanceof Array',
28-
output: 'Array.isArray([])',
29-
errors
30-
},
31-
{
32-
code: '[1,2,3] instanceof Array === true',
33-
output: 'Array.isArray([1,2,3]) === true',
34-
errors
35-
},
36-
{
37-
code: 'fun.call(1, 2, 3) instanceof Array',
38-
output: 'Array.isArray(fun.call(1, 2, 3))',
39-
errors
40-
},
41-
{
42-
code: 'obj.arr instanceof Array',
43-
output: 'Array.isArray(obj.arr)',
44-
errors
45-
},
46-
{
47-
code: 'foo.bar[2] instanceof Array',
48-
output: 'Array.isArray(foo.bar[2])',
49-
errors
50-
}
16+
'arr instanceof Array',
17+
'[] instanceof Array',
18+
'[1,2,3] instanceof Array === true',
19+
'fun.call(1, 2, 3) instanceof Array',
20+
'obj.arr instanceof Array',
21+
'foo.bar[2] instanceof Array',
22+
'(0, array) instanceof Array',
23+
outdent`
24+
(
25+
// comment
26+
((
27+
// comment
28+
(
29+
// comment
30+
foo
31+
// comment
32+
)
33+
// comment
34+
))
35+
// comment
36+
)
37+
// comment before instanceof\r instanceof
38+
39+
// comment after instanceof
40+
41+
(
42+
// comment
43+
44+
(
45+
46+
// comment
47+
48+
Array
49+
50+
// comment
51+
)
52+
53+
// comment
54+
)
55+
56+
// comment
57+
`
5158
]
5259
});
53-
54-
test.visualize([
55-
'if (arr instanceof Array) {}'
56-
]);

0 commit comments

Comments
 (0)