1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict'
|
7 |
|
8 | const getDocsUrl = require('./lib/get-docs-url')
|
9 | const {
|
10 | isPromiseConstructorWithInlineExecutor,
|
11 | } = require('./lib/is-promise-constructor')
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | function* iterateAllPrevPathSegments(segment) {
|
29 | yield* iterate(segment, [])
|
30 |
|
31 | |
32 |
|
33 |
|
34 |
|
35 | function* iterate(segment, processed) {
|
36 | if (processed.includes(segment)) {
|
37 | return
|
38 | }
|
39 | const nextProcessed = [segment, ...processed]
|
40 |
|
41 | for (const prev of segment.prevSegments) {
|
42 | if (prev.prevSegments.length === 0) {
|
43 | yield [prev]
|
44 | } else {
|
45 | for (const segments of iterate(prev, nextProcessed)) {
|
46 | yield [prev, ...segments]
|
47 | }
|
48 | }
|
49 | }
|
50 | }
|
51 | }
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 | function* iterateAllNextPathSegments(segment) {
|
58 | yield* iterate(segment, [])
|
59 |
|
60 | |
61 |
|
62 |
|
63 |
|
64 | function* iterate(segment, processed) {
|
65 | if (processed.includes(segment)) {
|
66 | return
|
67 | }
|
68 | const nextProcessed = [segment, ...processed]
|
69 |
|
70 | for (const next of segment.nextSegments) {
|
71 | if (next.nextSegments.length === 0) {
|
72 | yield [next]
|
73 | } else {
|
74 | for (const segments of iterate(next, nextProcessed)) {
|
75 | yield [next, ...segments]
|
76 | }
|
77 | }
|
78 | }
|
79 | }
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 | function findSameRoutePathSegment(segment) {
|
88 |
|
89 | const routeSegments = new Set()
|
90 | for (const route of iterateAllPrevPathSegments(segment)) {
|
91 | if (routeSegments.size === 0) {
|
92 |
|
93 | for (const seg of route) {
|
94 | routeSegments.add(seg)
|
95 | }
|
96 | continue
|
97 | }
|
98 | for (const seg of routeSegments) {
|
99 | if (!route.includes(seg)) {
|
100 | routeSegments.delete(seg)
|
101 | }
|
102 | }
|
103 | }
|
104 |
|
105 | for (const routeSegment of routeSegments) {
|
106 | let hasUnreached = false
|
107 | for (const segments of iterateAllNextPathSegments(routeSegment)) {
|
108 | if (!segments.includes(segment)) {
|
109 |
|
110 | hasUnreached = true
|
111 | break
|
112 | }
|
113 | }
|
114 | if (!hasUnreached) {
|
115 | return routeSegment
|
116 | }
|
117 | }
|
118 | return null
|
119 | }
|
120 |
|
121 | class CodePathInfo {
|
122 | |
123 |
|
124 |
|
125 | constructor(path) {
|
126 | this.path = path
|
127 |
|
128 | this.segmentInfos = new Map()
|
129 | this.resolvedCount = 0
|
130 |
|
131 | this.allSegments = []
|
132 | }
|
133 |
|
134 | getCurrentSegmentInfos() {
|
135 | return this.path.currentSegments.map((segment) => {
|
136 | const info = this.segmentInfos.get(segment)
|
137 | if (info) {
|
138 | return info
|
139 | }
|
140 | const newInfo = new CodePathSegmentInfo(this, segment)
|
141 | this.segmentInfos.set(segment, newInfo)
|
142 | return newInfo
|
143 | })
|
144 | }
|
145 | |
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | |
152 |
|
153 |
|
154 |
|
155 | *iterateReports() {
|
156 | const targets = [...this.segmentInfos.values()].filter(
|
157 | (info) => info.resolved
|
158 | )
|
159 | for (const segmentInfo of targets) {
|
160 | const result = this._getAlreadyResolvedData(segmentInfo.segment)
|
161 | if (result) {
|
162 | yield {
|
163 | node: segmentInfo.resolved,
|
164 | resolved: result.resolved,
|
165 | kind: result.kind,
|
166 | }
|
167 | }
|
168 | }
|
169 | }
|
170 | |
171 |
|
172 |
|
173 |
|
174 |
|
175 | _getAlreadyResolvedData(segment) {
|
176 | if (segment.prevSegments.length === 0) {
|
177 | return null
|
178 | }
|
179 | const prevSegmentInfos = segment.prevSegments.map((prev) =>
|
180 | this._getProcessedSegmentInfo(prev)
|
181 | )
|
182 | if (prevSegmentInfos.every((info) => info.resolved)) {
|
183 |
|
184 | return {
|
185 | resolved: prevSegmentInfos[0].resolved,
|
186 | kind: 'certain',
|
187 | }
|
188 | }
|
189 |
|
190 | for (const prevSegmentInfo of prevSegmentInfos) {
|
191 | if (prevSegmentInfo.resolved) {
|
192 |
|
193 |
|
194 | return {
|
195 | resolved: prevSegmentInfo.resolved,
|
196 | kind: 'potential',
|
197 | }
|
198 | }
|
199 | if (prevSegmentInfo.potentiallyResolved) {
|
200 | let potential = false
|
201 | if (prevSegmentInfo.segment.nextSegments.length === 1) {
|
202 |
|
203 |
|
204 | potential = true
|
205 | } else {
|
206 |
|
207 | const segmentInfo = this.segmentInfos.get(segment)
|
208 | if (segmentInfo && segmentInfo.resolved) {
|
209 | if (
|
210 | prevSegmentInfo.segment.nextSegments.every((next) => {
|
211 | const nextSegmentInfo = this.segmentInfos.get(next)
|
212 | return (
|
213 | nextSegmentInfo &&
|
214 | nextSegmentInfo.resolved === segmentInfo.resolved
|
215 | )
|
216 | })
|
217 | ) {
|
218 |
|
219 |
|
220 |
|
221 | potential = true
|
222 | }
|
223 | }
|
224 | }
|
225 |
|
226 | if (potential) {
|
227 | return {
|
228 | resolved: prevSegmentInfo.potentiallyResolved,
|
229 | kind: 'potential',
|
230 | }
|
231 | }
|
232 | }
|
233 | }
|
234 |
|
235 | const sameRoute = findSameRoutePathSegment(segment)
|
236 | if (sameRoute) {
|
237 | const sameRouteSegmentInfo = this._getProcessedSegmentInfo(sameRoute)
|
238 | if (sameRouteSegmentInfo.potentiallyResolved) {
|
239 | return {
|
240 | resolved: sameRouteSegmentInfo.potentiallyResolved,
|
241 | kind: 'potential',
|
242 | }
|
243 | }
|
244 | }
|
245 | return null
|
246 | }
|
247 | |
248 |
|
249 |
|
250 | _getProcessedSegmentInfo(segment) {
|
251 | const segmentInfo = this.segmentInfos.get(segment)
|
252 | if (segmentInfo) {
|
253 | return segmentInfo
|
254 | }
|
255 | const newInfo = new CodePathSegmentInfo(this, segment)
|
256 | this.segmentInfos.set(segment, newInfo)
|
257 |
|
258 | const alreadyResolvedData = this._getAlreadyResolvedData(segment)
|
259 | if (alreadyResolvedData) {
|
260 | if (alreadyResolvedData.kind === 'certain') {
|
261 | newInfo.resolved = alreadyResolvedData.resolved
|
262 | } else {
|
263 | newInfo.potentiallyResolved = alreadyResolvedData.resolved
|
264 | }
|
265 | }
|
266 | return newInfo
|
267 | }
|
268 | }
|
269 |
|
270 | class CodePathSegmentInfo {
|
271 | |
272 |
|
273 |
|
274 |
|
275 | constructor(pathInfo, segment) {
|
276 | this.pathInfo = pathInfo
|
277 | this.segment = segment
|
278 |
|
279 | this._resolved = null
|
280 |
|
281 | this.potentiallyResolved = null
|
282 | }
|
283 |
|
284 | get resolved() {
|
285 | return this._resolved
|
286 | }
|
287 |
|
288 | set resolved(identifier) {
|
289 | this._resolved = identifier
|
290 | this.pathInfo.resolvedCount++
|
291 | }
|
292 | }
|
293 |
|
294 | module.exports = {
|
295 | meta: {
|
296 | type: 'problem',
|
297 | docs: {
|
298 | url: getDocsUrl('no-multiple-resolved'),
|
299 | },
|
300 | messages: {
|
301 | alreadyResolved:
|
302 | 'Promise should not be resolved multiple times. Promise is already resolved on line {{line}}.',
|
303 | potentiallyAlreadyResolved:
|
304 | 'Promise should not be resolved multiple times. Promise is potentially resolved on line {{line}}.',
|
305 | },
|
306 | schema: [],
|
307 | },
|
308 |
|
309 | create(context) {
|
310 | const reported = new Set()
|
311 | |
312 |
|
313 |
|
314 |
|
315 |
|
316 | function report(node, resolved, kind) {
|
317 | if (reported.has(node)) {
|
318 | return
|
319 | }
|
320 | reported.add(node)
|
321 | context.report({
|
322 | node: node.parent,
|
323 | messageId:
|
324 | kind === 'certain' ? 'alreadyResolved' : 'potentiallyAlreadyResolved',
|
325 | data: {
|
326 | line: resolved.loc.start.line,
|
327 | },
|
328 | })
|
329 | }
|
330 |
|
331 | function verifyMultipleResolvedPath(codePathInfo) {
|
332 | for (const { node, resolved, kind } of codePathInfo.iterateReports()) {
|
333 | report(node, resolved, kind)
|
334 | }
|
335 | }
|
336 |
|
337 |
|
338 | const codePathInfoStack = []
|
339 |
|
340 | const resolverReferencesStack = [new Set()]
|
341 | return {
|
342 |
|
343 | 'FunctionExpression, ArrowFunctionExpression'(node) {
|
344 | if (!isPromiseConstructorWithInlineExecutor(node.parent)) {
|
345 | return
|
346 | }
|
347 |
|
348 |
|
349 | const resolverReferences = new Set()
|
350 | const resolvers = node.params.filter(
|
351 |
|
352 | (node) => node && node.type === 'Identifier'
|
353 | )
|
354 | for (const resolver of resolvers) {
|
355 | const variable = context.getScope().set.get(resolver.name)
|
356 |
|
357 | if (!variable) continue
|
358 | for (const reference of variable.references) {
|
359 | resolverReferences.add(reference.identifier)
|
360 | }
|
361 | }
|
362 |
|
363 | resolverReferencesStack.unshift(resolverReferences)
|
364 | },
|
365 |
|
366 | 'FunctionExpression, ArrowFunctionExpression:exit'(node) {
|
367 | if (!isPromiseConstructorWithInlineExecutor(node.parent)) {
|
368 | return
|
369 | }
|
370 | resolverReferencesStack.shift()
|
371 | },
|
372 |
|
373 | onCodePathStart(path) {
|
374 | codePathInfoStack.unshift(new CodePathInfo(path))
|
375 | },
|
376 | onCodePathEnd() {
|
377 | const codePathInfo = codePathInfoStack.shift()
|
378 | if (codePathInfo.resolvedCount > 1) {
|
379 | verifyMultipleResolvedPath(codePathInfo)
|
380 | }
|
381 | },
|
382 |
|
383 | 'CallExpression > Identifier.callee'(node) {
|
384 | const codePathInfo = codePathInfoStack[0]
|
385 | const resolverReferences = resolverReferencesStack[0]
|
386 | if (!resolverReferences.has(node)) {
|
387 | return
|
388 | }
|
389 | for (const segmentInfo of codePathInfo.getCurrentSegmentInfos()) {
|
390 |
|
391 |
|
392 | if (segmentInfo.resolved) {
|
393 | report(node, segmentInfo.resolved, 'certain')
|
394 | continue
|
395 | }
|
396 | segmentInfo.resolved = node
|
397 | }
|
398 | },
|
399 | }
|
400 | },
|
401 | }
|