UNPKG

4 kBJavaScriptView Raw
1'use strict';
2const getDocumentationUrl = require('./utils/get-documentation-url');
3const isMethodNamed = require('./utils/is-method-named');
4
5const MESSAGE_ID_FLATMAP = 'flat-map';
6const MESSAGE_ID_SPREAD = 'spread';
7
8const SELECTOR_SPREAD = [
9 // [].concat(...bar.map((i) => i))
10 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 'CallExpression',
12
13 // [].concat(...bar.map((i) => i))
14 // ^^
15 '[callee.object.type="ArrayExpression"]',
16 '[callee.object.elements.length=0]',
17
18 // [].concat(...bar.map((i) => i))
19 // ^^^^^^
20 '[callee.type="MemberExpression"]',
21 '[callee.computed=false]',
22 '[callee.property.type="Identifier"]',
23 '[callee.property.name="concat"]',
24
25 // [].concat(...bar.map((i) => i))
26 // ^^^^^^^^^^^^^^^^^^^^
27 '[arguments.0.type="SpreadElement"]',
28
29 // [].concat(...bar.map((i) => i))
30 // ^^^^^^^^^^^^^^^^^
31 '[arguments.0.argument.type="CallExpression"]',
32
33 // [].concat(...bar.map((i) => i))
34 // ^^^^^^^
35 '[arguments.0.argument.callee.type="MemberExpression"]',
36 '[arguments.0.argument.callee.computed=false]',
37
38 // [].concat(...bar.map((i) => i))
39 // ^^^
40 '[arguments.0.argument.callee.property.type="Identifier"]',
41 '[arguments.0.argument.callee.property.name="map"]'
42].join('');
43
44const reportFlatMap = (context, nodeFlat, nodeMap) => {
45 const source = context.getSourceCode();
46
47 // Node covers:
48 // map(…).flat();
49 // ^^^^
50 // (map(…)).flat();
51 // ^^^^
52 const flatIdentifer = nodeFlat.callee.property;
53
54 // Location will be:
55 // map(…).flat();
56 // ^
57 // (map(…)).flat();
58 // ^
59 const dot = source.getTokenBefore(flatIdentifer);
60
61 // Location will be:
62 // map(…).flat();
63 // ^
64 // (map(…)).flat();
65 // ^
66 const maybeSemicolon = source.getTokenAfter(nodeFlat);
67 const hasSemicolon = Boolean(maybeSemicolon) && maybeSemicolon.value === ';';
68
69 // Location will be:
70 // (map(…)).flat();
71 // ^
72 const tokenBetween = source.getLastTokenBetween(nodeMap, dot);
73
74 // Location will be:
75 // map(…).flat();
76 // ^
77 // (map(…)).flat();
78 // ^
79 const beforeSemicolon = tokenBetween || nodeMap;
80
81 // Location will be:
82 // map(…).flat();
83 // ^
84 // (map(…)).flat();
85 // ^
86 const fixEnd = nodeFlat.range[1];
87
88 // Location will be:
89 // map(…).flat();
90 // ^
91 // (map(…)).flat();
92 // ^
93 const fixStart = dot.range[0];
94
95 const mapProperty = nodeMap.callee.property;
96
97 context.report({
98 node: nodeFlat,
99 messageId: MESSAGE_ID_FLATMAP,
100 * fix(fixer) {
101 // Removes:
102 // map(…).flat();
103 // ^^^^^^^
104 // (map(…)).flat();
105 // ^^^^^^^
106 yield fixer.removeRange([fixStart, fixEnd]);
107
108 // Renames:
109 // map(…).flat();
110 // ^^^
111 // (map(…)).flat();
112 // ^^^
113 yield fixer.replaceText(mapProperty, 'flatMap');
114
115 if (hasSemicolon) {
116 // Moves semicolon to:
117 // map(…).flat();
118 // ^
119 // (map(…)).flat();
120 // ^
121 yield fixer.insertTextAfter(beforeSemicolon, ';');
122 yield fixer.remove(maybeSemicolon);
123 }
124 }
125 });
126};
127
128const create = context => ({
129 CallExpression: node => {
130 if (!isMethodNamed(node, 'flat')) {
131 return;
132 }
133
134 if (node.arguments.length > 1) {
135 return;
136 }
137
138 if (
139 node.arguments.length === 1 &&
140 node.arguments[0].type === 'Literal' &&
141 node.arguments[0].value !== 1
142 ) {
143 return;
144 }
145
146 const parent = node.callee.object;
147
148 if (!isMethodNamed(parent, 'map')) {
149 return;
150 }
151
152 reportFlatMap(context, node, parent);
153 },
154 [SELECTOR_SPREAD]: node => {
155 context.report({
156 node,
157 messageId: MESSAGE_ID_SPREAD
158 });
159 }
160});
161
162module.exports = {
163 create,
164 meta: {
165 type: 'suggestion',
166 docs: {
167 url: getDocumentationUrl(__filename)
168 },
169 fixable: 'code',
170 messages: {
171 [MESSAGE_ID_FLATMAP]: 'Prefer `.flatMap(…)` over `.map(…).flat()`.',
172 [MESSAGE_ID_SPREAD]: 'Prefer `.flatMap(…)` over `[].concat(...foo.map(…))`.'
173 }
174 }
175};