]> git.proxmox.com Git - pve-eslint.git/blob - eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js
6460ba1b67682f12c08ff02a9795feb25b8dbfac
[pve-eslint.git] / eslint / tests / lib / linter / code-path-analysis / code-path-analyzer.js
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 }));
559 const messages = linter.verify(source, { rules: { test: 2 }, env: { es6: true } });
560
561 assert.strictEqual(messages.length, 0);
562 assert.strictEqual(actual.length, expected.length, "a count of code paths is wrong.");
563
564 for (let i = 0; i < actual.length; ++i) {
565 assert.strictEqual(actual[i], expected[i]);
566 }
567 });
568 });
569 });
570 });