-
Notifications
You must be signed in to change notification settings - Fork 6
/
from-map.js
126 lines (119 loc) · 5.91 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
/**
* @license MIT
* @author Martin Giger
*/
"use strict";
const { ARROW_FUNCTION_EXPRESSION } = require("../lib/type"),
ALL_PARAMS = [
{ name: 'item' },
{ name: 'index' }
];
module.exports = {
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" ||
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);
}
});
}
};
}
};