]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Tests for CodePathAnalyzer. | |
3 | * @author Toru Nagashima | |
4 | */ | |
5 | ||
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Requirements | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
12 | const assert = require("assert"), | |
13 | fs = require("fs"), | |
14 | path = require("path"), | |
15 | { Linter } = require("../../../../lib/linter"), | |
16 | EventGeneratorTester = require("../../../../tools/internal-testers/event-generator-tester"), | |
17 | createEmitter = require("../../../../lib/linter/safe-emitter"), | |
18 | debug = require("../../../../lib/linter/code-path-analysis/debug-helpers"), | |
19 | CodePath = require("../../../../lib/linter/code-path-analysis/code-path"), | |
20 | CodePathAnalyzer = require("../../../../lib/linter/code-path-analysis/code-path-analyzer"), | |
21 | CodePathSegment = require("../../../../lib/linter/code-path-analysis/code-path-segment"), | |
22 | NodeEventGenerator = require("../../../../lib/linter/node-event-generator"); | |
23 | ||
24 | //------------------------------------------------------------------------------ | |
25 | // Helpers | |
26 | //------------------------------------------------------------------------------ | |
27 | ||
28 | const expectedPattern = /\/\*expected\s+((?:.|[\r\n])+?)\s*\*\//gu; | |
29 | const lineEndingPattern = /\r?\n/gu; | |
30 | const linter = new Linter(); | |
31 | ||
32 | /** | |
33 | * Extracts the content of `/*expected` comments from a given source code. | |
34 | * It's expected DOT arrows. | |
35 | * @param {string} source A source code text. | |
36 | * @returns {string[]} DOT arrows. | |
37 | */ | |
38 | function getExpectedDotArrows(source) { | |
39 | expectedPattern.lastIndex = 0; | |
40 | ||
41 | const retv = []; | |
42 | let m; | |
43 | ||
44 | while ((m = expectedPattern.exec(source)) !== null) { | |
45 | retv.push(m[1].replace(lineEndingPattern, "\n")); | |
46 | } | |
47 | ||
48 | return retv; | |
49 | } | |
50 | ||
51 | //------------------------------------------------------------------------------ | |
52 | // Tests | |
53 | //------------------------------------------------------------------------------ | |
54 | ||
55 | describe("CodePathAnalyzer", () => { | |
56 | EventGeneratorTester.testEventGeneratorInterface( | |
57 | new CodePathAnalyzer(new NodeEventGenerator(createEmitter())) | |
58 | ); | |
59 | ||
60 | describe("interface of code paths", () => { | |
61 | let actual = []; | |
62 | ||
63 | beforeEach(() => { | |
64 | actual = []; | |
65 | linter.defineRule("test", () => ({ | |
66 | onCodePathStart(codePath) { | |
67 | actual.push(codePath); | |
68 | } | |
69 | })); | |
70 | linter.verify( | |
71 | "function foo(a) { if (a) return 0; else throw new Error(); }", | |
72 | { rules: { test: 2 } } | |
73 | ); | |
74 | }); | |
75 | ||
76 | it("should have `id` as unique string", () => { | |
77 | assert(typeof actual[0].id === "string"); | |
78 | assert(typeof actual[1].id === "string"); | |
79 | assert(actual[0].id !== actual[1].id); | |
80 | }); | |
81 | ||
82 | it("should have `upper` as CodePath", () => { | |
83 | assert(actual[0].upper === null); | |
84 | assert(actual[1].upper === actual[0]); | |
85 | }); | |
86 | ||
87 | it("should have `childCodePaths` as CodePath[]", () => { | |
88 | assert(Array.isArray(actual[0].childCodePaths)); | |
89 | assert(Array.isArray(actual[1].childCodePaths)); | |
90 | assert(actual[0].childCodePaths.length === 1); | |
91 | assert(actual[1].childCodePaths.length === 0); | |
92 | assert(actual[0].childCodePaths[0] === actual[1]); | |
93 | }); | |
94 | ||
95 | it("should have `initialSegment` as CodePathSegment", () => { | |
96 | assert(actual[0].initialSegment instanceof CodePathSegment); | |
97 | assert(actual[1].initialSegment instanceof CodePathSegment); | |
98 | assert(actual[0].initialSegment.prevSegments.length === 0); | |
99 | assert(actual[1].initialSegment.prevSegments.length === 0); | |
100 | }); | |
101 | ||
102 | it("should have `finalSegments` as CodePathSegment[]", () => { | |
103 | assert(Array.isArray(actual[0].finalSegments)); | |
104 | assert(Array.isArray(actual[1].finalSegments)); | |
105 | assert(actual[0].finalSegments.length === 1); | |
106 | assert(actual[1].finalSegments.length === 2); | |
107 | assert(actual[0].finalSegments[0].nextSegments.length === 0); | |
108 | assert(actual[1].finalSegments[0].nextSegments.length === 0); | |
109 | assert(actual[1].finalSegments[1].nextSegments.length === 0); | |
110 | ||
111 | // finalSegments should include returnedSegments and thrownSegments. | |
112 | assert(actual[0].finalSegments[0] === actual[0].returnedSegments[0]); | |
113 | assert(actual[1].finalSegments[0] === actual[1].returnedSegments[0]); | |
114 | assert(actual[1].finalSegments[1] === actual[1].thrownSegments[0]); | |
115 | }); | |
116 | ||
117 | it("should have `returnedSegments` as CodePathSegment[]", () => { | |
118 | assert(Array.isArray(actual[0].returnedSegments)); | |
119 | assert(Array.isArray(actual[1].returnedSegments)); | |
120 | assert(actual[0].returnedSegments.length === 1); | |
121 | assert(actual[1].returnedSegments.length === 1); | |
122 | assert(actual[0].returnedSegments[0] instanceof CodePathSegment); | |
123 | assert(actual[1].returnedSegments[0] instanceof CodePathSegment); | |
124 | }); | |
125 | ||
126 | it("should have `thrownSegments` as CodePathSegment[]", () => { | |
127 | assert(Array.isArray(actual[0].thrownSegments)); | |
128 | assert(Array.isArray(actual[1].thrownSegments)); | |
129 | assert(actual[0].thrownSegments.length === 0); | |
130 | assert(actual[1].thrownSegments.length === 1); | |
131 | assert(actual[1].thrownSegments[0] instanceof CodePathSegment); | |
132 | }); | |
133 | ||
134 | it("should have `currentSegments` as CodePathSegment[]", () => { | |
135 | assert(Array.isArray(actual[0].currentSegments)); | |
136 | assert(Array.isArray(actual[1].currentSegments)); | |
137 | assert(actual[0].currentSegments.length === 0); | |
138 | assert(actual[1].currentSegments.length === 0); | |
139 | ||
140 | // there is the current segment in progress. | |
141 | linter.defineRule("test", () => { | |
142 | let codePath = null; | |
143 | ||
144 | return { | |
145 | onCodePathStart(cp) { | |
146 | codePath = cp; | |
147 | }, | |
148 | ReturnStatement() { | |
149 | assert(codePath.currentSegments.length === 1); | |
150 | assert(codePath.currentSegments[0] instanceof CodePathSegment); | |
151 | }, | |
152 | ThrowStatement() { | |
153 | assert(codePath.currentSegments.length === 1); | |
154 | assert(codePath.currentSegments[0] instanceof CodePathSegment); | |
155 | } | |
156 | }; | |
157 | }); | |
158 | linter.verify( | |
159 | "function foo(a) { if (a) return 0; else throw new Error(); }", | |
160 | { rules: { test: 2 } } | |
161 | ); | |
162 | }); | |
163 | }); | |
164 | ||
165 | describe("interface of code path segments", () => { | |
166 | let actual = []; | |
167 | ||
168 | beforeEach(() => { | |
169 | actual = []; | |
170 | linter.defineRule("test", () => ({ | |
171 | onCodePathSegmentStart(segment) { | |
172 | actual.push(segment); | |
173 | } | |
174 | })); | |
175 | linter.verify( | |
176 | "function foo(a) { if (a) return 0; else throw new Error(); }", | |
177 | { rules: { test: 2 } } | |
178 | ); | |
179 | }); | |
180 | ||
181 | it("should have `id` as unique string", () => { | |
182 | assert(typeof actual[0].id === "string"); | |
183 | assert(typeof actual[1].id === "string"); | |
184 | assert(typeof actual[2].id === "string"); | |
185 | assert(typeof actual[3].id === "string"); | |
186 | assert(actual[0].id !== actual[1].id); | |
187 | assert(actual[0].id !== actual[2].id); | |
188 | assert(actual[0].id !== actual[3].id); | |
189 | assert(actual[1].id !== actual[2].id); | |
190 | assert(actual[1].id !== actual[3].id); | |
191 | assert(actual[2].id !== actual[3].id); | |
192 | }); | |
193 | ||
194 | it("should have `nextSegments` as CodePathSegment[]", () => { | |
195 | assert(Array.isArray(actual[0].nextSegments)); | |
196 | assert(Array.isArray(actual[1].nextSegments)); | |
197 | assert(Array.isArray(actual[2].nextSegments)); | |
198 | assert(Array.isArray(actual[3].nextSegments)); | |
199 | assert(actual[0].nextSegments.length === 0); | |
200 | assert(actual[1].nextSegments.length === 2); | |
201 | assert(actual[2].nextSegments.length === 0); | |
202 | assert(actual[3].nextSegments.length === 0); | |
203 | assert(actual[1].nextSegments[0] === actual[2]); | |
204 | assert(actual[1].nextSegments[1] === actual[3]); | |
205 | }); | |
206 | ||
207 | it("should have `allNextSegments` as CodePathSegment[]", () => { | |
208 | assert(Array.isArray(actual[0].allNextSegments)); | |
209 | assert(Array.isArray(actual[1].allNextSegments)); | |
210 | assert(Array.isArray(actual[2].allNextSegments)); | |
211 | assert(Array.isArray(actual[3].allNextSegments)); | |
212 | assert(actual[0].allNextSegments.length === 0); | |
213 | assert(actual[1].allNextSegments.length === 2); | |
214 | assert(actual[2].allNextSegments.length === 1); | |
215 | assert(actual[3].allNextSegments.length === 1); | |
216 | assert(actual[2].allNextSegments[0].reachable === false); | |
217 | assert(actual[3].allNextSegments[0].reachable === false); | |
218 | }); | |
219 | ||
220 | it("should have `prevSegments` as CodePathSegment[]", () => { | |
221 | assert(Array.isArray(actual[0].prevSegments)); | |
222 | assert(Array.isArray(actual[1].prevSegments)); | |
223 | assert(Array.isArray(actual[2].prevSegments)); | |
224 | assert(Array.isArray(actual[3].prevSegments)); | |
225 | assert(actual[0].prevSegments.length === 0); | |
226 | assert(actual[1].prevSegments.length === 0); | |
227 | assert(actual[2].prevSegments.length === 1); | |
228 | assert(actual[3].prevSegments.length === 1); | |
229 | assert(actual[2].prevSegments[0] === actual[1]); | |
230 | assert(actual[3].prevSegments[0] === actual[1]); | |
231 | }); | |
232 | ||
233 | it("should have `allPrevSegments` as CodePathSegment[]", () => { | |
234 | assert(Array.isArray(actual[0].allPrevSegments)); | |
235 | assert(Array.isArray(actual[1].allPrevSegments)); | |
236 | assert(Array.isArray(actual[2].allPrevSegments)); | |
237 | assert(Array.isArray(actual[3].allPrevSegments)); | |
238 | assert(actual[0].allPrevSegments.length === 0); | |
239 | assert(actual[1].allPrevSegments.length === 0); | |
240 | assert(actual[2].allPrevSegments.length === 1); | |
241 | assert(actual[3].allPrevSegments.length === 1); | |
242 | }); | |
243 | ||
244 | it("should have `reachable` as boolean", () => { | |
245 | assert(actual[0].reachable === true); | |
246 | assert(actual[1].reachable === true); | |
247 | assert(actual[2].reachable === true); | |
248 | assert(actual[3].reachable === true); | |
249 | }); | |
250 | }); | |
251 | ||
252 | describe("onCodePathStart", () => { | |
253 | it("should be fired at the head of programs/functions", () => { | |
254 | let count = 0; | |
255 | let lastCodePathNodeType = null; | |
256 | ||
257 | linter.defineRule("test", () => ({ | |
258 | onCodePathStart(cp, node) { | |
259 | count += 1; | |
260 | lastCodePathNodeType = node.type; | |
261 | ||
262 | assert(cp instanceof CodePath); | |
263 | if (count === 1) { | |
264 | assert(node.type === "Program"); | |
265 | } else if (count === 2) { | |
266 | assert(node.type === "FunctionDeclaration"); | |
267 | } else if (count === 3) { | |
268 | assert(node.type === "FunctionExpression"); | |
269 | } else if (count === 4) { | |
270 | assert(node.type === "ArrowFunctionExpression"); | |
271 | } | |
272 | }, | |
273 | Program() { | |
274 | assert(lastCodePathNodeType === "Program"); | |
275 | }, | |
276 | FunctionDeclaration() { | |
277 | assert(lastCodePathNodeType === "FunctionDeclaration"); | |
278 | }, | |
279 | FunctionExpression() { | |
280 | assert(lastCodePathNodeType === "FunctionExpression"); | |
281 | }, | |
282 | ArrowFunctionExpression() { | |
283 | assert(lastCodePathNodeType === "ArrowFunctionExpression"); | |
284 | } | |
285 | })); | |
286 | linter.verify( | |
287 | "foo(); function foo() {} var foo = function() {}; var foo = () => {};", | |
288 | { rules: { test: 2 }, env: { es6: true } } | |
289 | ); | |
290 | ||
291 | assert(count === 4); | |
292 | }); | |
293 | }); | |
294 | ||
295 | describe("onCodePathEnd", () => { | |
296 | it("should be fired at the end of programs/functions", () => { | |
297 | let count = 0; | |
298 | let lastNodeType = null; | |
299 | ||
300 | linter.defineRule("test", () => ({ | |
301 | onCodePathEnd(cp, node) { | |
302 | count += 1; | |
303 | ||
304 | assert(cp instanceof CodePath); | |
305 | if (count === 4) { | |
306 | assert(node.type === "Program"); | |
307 | } else if (count === 1) { | |
308 | assert(node.type === "FunctionDeclaration"); | |
309 | } else if (count === 2) { | |
310 | assert(node.type === "FunctionExpression"); | |
311 | } else if (count === 3) { | |
312 | assert(node.type === "ArrowFunctionExpression"); | |
313 | } | |
314 | assert(node.type === lastNodeType); | |
315 | }, | |
316 | "Program:exit"() { | |
317 | lastNodeType = "Program"; | |
318 | }, | |
319 | "FunctionDeclaration:exit"() { | |
320 | lastNodeType = "FunctionDeclaration"; | |
321 | }, | |
322 | "FunctionExpression:exit"() { | |
323 | lastNodeType = "FunctionExpression"; | |
324 | }, | |
325 | "ArrowFunctionExpression:exit"() { | |
326 | lastNodeType = "ArrowFunctionExpression"; | |
327 | } | |
328 | })); | |
329 | linter.verify( | |
330 | "foo(); function foo() {} var foo = function() {}; var foo = () => {};", | |
331 | { rules: { test: 2 }, env: { es6: true } } | |
332 | ); | |
333 | ||
334 | assert(count === 4); | |
335 | }); | |
336 | }); | |
337 | ||
338 | describe("onCodePathSegmentStart", () => { | |
339 | it("should be fired at the head of programs/functions for the initial segment", () => { | |
340 | let count = 0; | |
341 | let lastCodePathNodeType = null; | |
342 | ||
343 | linter.defineRule("test", () => ({ | |
344 | onCodePathSegmentStart(segment, node) { | |
345 | count += 1; | |
346 | lastCodePathNodeType = node.type; | |
347 | ||
348 | assert(segment instanceof CodePathSegment); | |
349 | if (count === 1) { | |
350 | assert(node.type === "Program"); | |
351 | } else if (count === 2) { | |
352 | assert(node.type === "FunctionDeclaration"); | |
353 | } else if (count === 3) { | |
354 | assert(node.type === "FunctionExpression"); | |
355 | } else if (count === 4) { | |
356 | assert(node.type === "ArrowFunctionExpression"); | |
357 | } | |
358 | }, | |
359 | Program() { | |
360 | assert(lastCodePathNodeType === "Program"); | |
361 | }, | |
362 | FunctionDeclaration() { | |
363 | assert(lastCodePathNodeType === "FunctionDeclaration"); | |
364 | }, | |
365 | FunctionExpression() { | |
366 | assert(lastCodePathNodeType === "FunctionExpression"); | |
367 | }, | |
368 | ArrowFunctionExpression() { | |
369 | assert(lastCodePathNodeType === "ArrowFunctionExpression"); | |
370 | } | |
371 | })); | |
372 | linter.verify( | |
373 | "foo(); function foo() {} var foo = function() {}; var foo = () => {};", | |
374 | { rules: { test: 2 }, env: { es6: true } } | |
375 | ); | |
376 | ||
377 | assert(count === 4); | |
378 | }); | |
379 | }); | |
380 | ||
381 | describe("onCodePathSegmentEnd", () => { | |
382 | it("should be fired at the end of programs/functions for the final segment", () => { | |
383 | let count = 0; | |
384 | let lastNodeType = null; | |
385 | ||
386 | linter.defineRule("test", () => ({ | |
387 | onCodePathSegmentEnd(cp, node) { | |
388 | count += 1; | |
389 | ||
390 | assert(cp instanceof CodePathSegment); | |
391 | if (count === 4) { | |
392 | assert(node.type === "Program"); | |
393 | } else if (count === 1) { | |
394 | assert(node.type === "FunctionDeclaration"); | |
395 | } else if (count === 2) { | |
396 | assert(node.type === "FunctionExpression"); | |
397 | } else if (count === 3) { | |
398 | assert(node.type === "ArrowFunctionExpression"); | |
399 | } | |
400 | assert(node.type === lastNodeType); | |
401 | }, | |
402 | "Program:exit"() { | |
403 | lastNodeType = "Program"; | |
404 | }, | |
405 | "FunctionDeclaration:exit"() { | |
406 | lastNodeType = "FunctionDeclaration"; | |
407 | }, | |
408 | "FunctionExpression:exit"() { | |
409 | lastNodeType = "FunctionExpression"; | |
410 | }, | |
411 | "ArrowFunctionExpression:exit"() { | |
412 | lastNodeType = "ArrowFunctionExpression"; | |
413 | } | |
414 | })); | |
415 | linter.verify( | |
416 | "foo(); function foo() {} var foo = function() {}; var foo = () => {};", | |
417 | { rules: { test: 2 }, env: { es6: true } } | |
418 | ); | |
419 | ||
420 | assert(count === 4); | |
421 | }); | |
422 | }); | |
423 | ||
424 | describe("onCodePathSegmentLoop", () => { | |
425 | it("should be fired in `while` loops", () => { | |
426 | let count = 0; | |
427 | ||
428 | linter.defineRule("test", () => ({ | |
429 | onCodePathSegmentLoop(fromSegment, toSegment, node) { | |
430 | count += 1; | |
431 | assert(fromSegment instanceof CodePathSegment); | |
432 | assert(toSegment instanceof CodePathSegment); | |
433 | assert(node.type === "WhileStatement"); | |
434 | } | |
435 | })); | |
436 | linter.verify( | |
437 | "while (a) { foo(); }", | |
438 | { rules: { test: 2 } } | |
439 | ); | |
440 | ||
441 | assert(count === 1); | |
442 | }); | |
443 | ||
444 | it("should be fired in `do-while` loops", () => { | |
445 | let count = 0; | |
446 | ||
447 | linter.defineRule("test", () => ({ | |
448 | onCodePathSegmentLoop(fromSegment, toSegment, node) { | |
449 | count += 1; | |
450 | assert(fromSegment instanceof CodePathSegment); | |
451 | assert(toSegment instanceof CodePathSegment); | |
452 | assert(node.type === "DoWhileStatement"); | |
453 | } | |
454 | })); | |
455 | linter.verify( | |
456 | "do { foo(); } while (a);", | |
457 | { rules: { test: 2 } } | |
458 | ); | |
459 | ||
460 | assert(count === 1); | |
461 | }); | |
462 | ||
463 | it("should be fired in `for` loops", () => { | |
464 | let count = 0; | |
465 | ||
466 | linter.defineRule("test", () => ({ | |
467 | onCodePathSegmentLoop(fromSegment, toSegment, node) { | |
468 | count += 1; | |
469 | assert(fromSegment instanceof CodePathSegment); | |
470 | assert(toSegment instanceof CodePathSegment); | |
471 | ||
472 | if (count === 1) { | |
473 | ||
474 | // connect path: "update" -> "test" | |
475 | assert(node.parent.type === "ForStatement"); | |
476 | } else if (count === 2) { | |
477 | assert(node.type === "ForStatement"); | |
478 | } | |
479 | } | |
480 | })); | |
481 | linter.verify( | |
482 | "for (var i = 0; i < 10; ++i) { foo(); }", | |
483 | { rules: { test: 2 } } | |
484 | ); | |
485 | ||
486 | assert(count === 2); | |
487 | }); | |
488 | ||
489 | it("should be fired in `for-in` loops", () => { | |
490 | let count = 0; | |
491 | ||
492 | linter.defineRule("test", () => ({ | |
493 | onCodePathSegmentLoop(fromSegment, toSegment, node) { | |
494 | count += 1; | |
495 | assert(fromSegment instanceof CodePathSegment); | |
496 | assert(toSegment instanceof CodePathSegment); | |
497 | ||
498 | if (count === 1) { | |
499 | ||
500 | // connect path: "right" -> "left" | |
501 | assert(node.parent.type === "ForInStatement"); | |
502 | } else if (count === 2) { | |
503 | assert(node.type === "ForInStatement"); | |
504 | } | |
505 | } | |
506 | })); | |
507 | linter.verify( | |
508 | "for (var k in obj) { foo(); }", | |
509 | { rules: { test: 2 } } | |
510 | ); | |
511 | ||
512 | assert(count === 2); | |
513 | }); | |
514 | ||
515 | it("should be fired in `for-of` loops", () => { | |
516 | let count = 0; | |
517 | ||
518 | linter.defineRule("test", () => ({ | |
519 | onCodePathSegmentLoop(fromSegment, toSegment, node) { | |
520 | count += 1; | |
521 | assert(fromSegment instanceof CodePathSegment); | |
522 | assert(toSegment instanceof CodePathSegment); | |
523 | ||
524 | if (count === 1) { | |
525 | ||
526 | // connect path: "right" -> "left" | |
527 | assert(node.parent.type === "ForOfStatement"); | |
528 | } else if (count === 2) { | |
529 | assert(node.type === "ForOfStatement"); | |
530 | } | |
531 | } | |
532 | })); | |
533 | linter.verify( | |
534 | "for (var x of xs) { foo(); }", | |
535 | { rules: { test: 2 }, env: { es6: true } } | |
536 | ); | |
537 | ||
538 | assert(count === 2); | |
539 | }); | |
540 | }); | |
541 | ||
542 | describe("completed code paths are correct", () => { | |
543 | const testDataDir = path.join(__dirname, "../../../fixtures/code-path-analysis/"); | |
544 | const testDataFiles = fs.readdirSync(testDataDir); | |
545 | ||
546 | testDataFiles.forEach(file => { | |
547 | it(file, () => { | |
548 | const source = fs.readFileSync(path.join(testDataDir, file), { encoding: "utf8" }); | |
549 | const expected = getExpectedDotArrows(source); | |
550 | const actual = []; | |
551 | ||
552 | assert(expected.length > 0, "/*expected */ comments not found."); | |
553 | ||
554 | linter.defineRule("test", () => ({ | |
555 | onCodePathEnd(codePath) { | |
556 | actual.push(debug.makeDotArrows(codePath)); | |
557 | } | |
558 | })); | |
d3726936 | 559 | const messages = linter.verify(source, { |
6f036462 | 560 | parserOptions: { ecmaVersion: 2021 }, |
d3726936 TL |
561 | rules: { test: 2 } |
562 | }); | |
eb39fafa DC |
563 | |
564 | assert.strictEqual(messages.length, 0); | |
565 | assert.strictEqual(actual.length, expected.length, "a count of code paths is wrong."); | |
566 | ||
567 | for (let i = 0; i < actual.length; ++i) { | |
568 | assert.strictEqual(actual[i], expected[i]); | |
569 | } | |
570 | }); | |
571 | }); | |
572 | }); | |
573 | }); |