-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathfrom-map.js
131 lines (123 loc) · 6.1 KB
/
from-map.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* @license MIT
* @author Martin Giger
*/
import { ARROW_FUNCTION_EXPRESSION } from "../lib/type.js";
const ALL_PARAMS = [
{ name: 'item' },
{ name: 'index' },
];
function isFunction(node) {
return node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression";
}
export default {
meta: {
docs: {
description: "Prefer using the mapFn callback of Array.from over an immediate .map() call.",
recommended: true,
},
fixable: "code",
type: "suggestion",
schema: [],
messages: {
useMapCb: "Use mapFn callback of Array.from instead of map()",
},
},
create(context) {
return {
'CallExpression[callee.type="MemberExpression"] > MemberExpression[property.name="map"][property] > CallExpression[callee.type="MemberExpression"][callee.property.name="from"][callee.object.type="Identifier"][callee.object.name="Array"]'(node) {
const parent = node,
callee = node.parent,
[
mapCallback,
mapThisArgument,
] = callee.parent.arguments;
node = callee.parent;
if(mapCallback.type === "Identifier" ||
(isFunction(mapCallback) && (
mapCallback.params.length > ALL_PARAMS.length ||
mapCallback.params.some((parameter) => parameter.type === "RestElement")
))
) {
return;
}
context.report({
node: callee.property,
loc: {
start: parent.callee.loc.start,
end: callee.loc.end,
},
messageId: "useMapCb",
fix(fixer) {
const HAS_CBK = 2,
PARAM_SEPARATOR = ", ",
FUNCTION_END = ")",
{ sourceCode } = context;
// Merge the from and map callbacks
if(parent.arguments.length >= HAS_CBK) {
const OMIT_ITEM = 1,
[
_, // eslint-disable-line no-unused-vars
callback,
thisArgument,
] = parent.arguments,
parameters = callback.type === "Identifier"
? ALL_PARAMS
: callback.params.length > mapCallback.params.length ? callback.params : mapCallback.params,
parameterString = parameters.map((p) => p.name).join(PARAM_SEPARATOR),
getCallback = (cbk, targ, ps) => {
const source = `(${sourceCode.getText(cbk)})`;
if(targ && cbk.type !== ARROW_FUNCTION_EXPRESSION) {
return `${source}.call(${targ.name}${PARAM_SEPARATOR}${ps})`;
}
return `${source}(${ps})`;
},
firstCallback = getCallback(callback, { name: 'this' }, parameterString);
// Try to use an arrow function for the wrapping function, fall back to a function expression if a this is specified.
let functionStart = `(${parameterString}) => `,
functionEnd = "",
restParameterString = '';
if(thisArgument && callback.type !== ARROW_FUNCTION_EXPRESSION) {
functionStart = `function(${parameterString}) { return `;
functionEnd = "; }";
}
if(parameters.length > OMIT_ITEM) {
const restParameters_ = parameters
.slice(OMIT_ITEM)
.map((p) => p.name);
restParameterString = PARAM_SEPARATOR + restParameters_.join(PARAM_SEPARATOR);
}
// The original map callback from Array.from gets nested as a parameter to the callback from map.
const lastCallback = getCallback(mapCallback, mapThisArgument, `${firstCallback}${restParameterString}`),
[
callbackStartLocation
, callbackEndLocation,
] = callback.range,
[
, parentEndLocation,
] = parent.range,
[
, nodeEndLocation,
] = node.range,
restParameters = sourceCode.getText().slice(callbackEndLocation, parentEndLocation);
return fixer.replaceTextRange([
callbackStartLocation,
nodeEndLocation,
], `${functionStart}${lastCallback}${functionEnd}${restParameters}`);
}
// Move the map arguments to from.
const [ firstArgument ] = node.arguments,
[ argumentStartLocation ] = firstArgument.range,
[
, parentEndLocation,
] = parent.range;
return fixer.replaceTextRange([
parentEndLocation - FUNCTION_END.length,
argumentStartLocation,
], PARAM_SEPARATOR);
},
});
},
};
},
};