UNPKG

4.07 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 const fixings = [
102 // Removes:
103 // map(…).flat();
104 // ^^^^^^^
105 // (map(…)).flat();
106 // ^^^^^^^
107 fixer.removeRange([fixStart, fixEnd]),
108
109 // Renames:
110 // map(…).flat();
111 // ^^^
112 // (map(…)).flat();
113 // ^^^
114 fixer.replaceText(mapProperty, 'flatMap')
115 ];
116
117 if (hasSemicolon) {
118 // Moves semicolon to:
119 // map(…).flat();
120 // ^
121 // (map(…)).flat();
122 // ^
123 fixings.push(fixer.insertTextAfter(beforeSemicolon, ';'));
124 fixings.push(fixer.remove(maybeSemicolon));
125 }
126
127 return fixings;
128 }
129 });
130};
131
132const create = context => ({
133 CallExpression: node => {
134 if (!isMethodNamed(node, 'flat')) {
135 return;
136 }
137
138 if (node.arguments.length > 1) {
139 return;
140 }
141
142 if (
143 node.arguments.length === 1 &&
144 node.arguments[0].type === 'Literal' &&
145 node.arguments[0].value !== 1
146 ) {
147 return;
148 }
149
150 const parent = node.callee.object;
151
152 if (!isMethodNamed(parent, 'map')) {
153 return;
154 }
155
156 reportFlatMap(context, node, parent);
157 },
158 [SELECTOR_SPREAD]: node => {
159 context.report({
160 node,
161 messageId: MESSAGE_ID_SPREAD
162 });
163 }
164});
165
166module.exports = {
167 create,
168 meta: {
169 type: 'suggestion',
170 docs: {
171 url: getDocumentationUrl(__filename)
172 },
173 fixable: 'code',
174 messages: {
175 [MESSAGE_ID_FLATMAP]: 'Prefer `.flatMap(…)` over `.map(…).flat()`.',
176 [MESSAGE_ID_SPREAD]: 'Prefer `.flatMap(…)` over `[].concat(...foo.map(…))`.'
177 }
178 }
179};