]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Tests for CodePath. | |
3 | * @author Toru Nagashima | |
4 | */ | |
5 | ||
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Requirements | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
12 | const assert = require("assert"), | |
13 | { Linter } = require("../../../../lib/linter"); | |
14 | const linter = new Linter(); | |
15 | ||
16 | //------------------------------------------------------------------------------ | |
17 | // Helpers | |
18 | //------------------------------------------------------------------------------ | |
19 | ||
20 | /** | |
21 | * Gets the code path of a given source code. | |
22 | * @param {string} code A source code. | |
23 | * @returns {CodePath[]} A list of created code paths. | |
24 | */ | |
25 | function parseCodePaths(code) { | |
26 | const retv = []; | |
27 | ||
28 | linter.defineRule("test", () => ({ | |
29 | onCodePathStart(codePath) { | |
30 | retv.push(codePath); | |
31 | } | |
32 | })); | |
609c276f TL |
33 | |
34 | linter.verify(code, { | |
35 | rules: { test: 2 }, | |
36 | parserOptions: { ecmaVersion: "latest" } | |
37 | }); | |
eb39fafa DC |
38 | |
39 | return retv; | |
40 | } | |
41 | ||
42 | /** | |
43 | * Traverses a given code path then returns the order of traversing. | |
44 | * @param {CodePath} codePath A code path to traverse. | |
45 | * @param {Object|undefined} [options] The option object of | |
46 | * `codePath.traverseSegments()` method. | |
47 | * @param {Function|undefined} [callback] The callback function of | |
48 | * `codePath.traverseSegments()` method. | |
49 | * @returns {string[]} The list of segment's ids in the order traversed. | |
50 | */ | |
51 | function getOrderOfTraversing(codePath, options, callback) { | |
52 | const retv = []; | |
53 | ||
54 | codePath.traverseSegments(options, (segment, controller) => { | |
55 | retv.push(segment.id); | |
56 | if (callback) { | |
8f9d1d4d | 57 | callback(segment, controller); // eslint-disable-line n/callback-return -- At end of inner function |
eb39fafa DC |
58 | } |
59 | }); | |
60 | ||
61 | return retv; | |
62 | } | |
63 | ||
64 | //------------------------------------------------------------------------------ | |
65 | // Tests | |
66 | //------------------------------------------------------------------------------ | |
67 | ||
68 | describe("CodePathAnalyzer", () => { | |
609c276f TL |
69 | |
70 | /* | |
71 | * If you need to output the code paths and DOT graph information for a | |
8f9d1d4d | 72 | * particular piece of code, update and uncomment the following test and |
609c276f TL |
73 | * then run: |
74 | * DEBUG=eslint:code-path npx mocha tests/lib/linter/code-path-analysis/ | |
75 | * | |
76 | * All the information you need will be output to the console. | |
77 | */ | |
78 | /* | |
79 | * it.only("test", () => { | |
80 | * const codePaths = parseCodePaths("class Foo { a = () => b }"); | |
81 | * }); | |
82 | */ | |
83 | ||
84 | describe("CodePath#origin", () => { | |
85 | ||
86 | it("should be 'program' when code path starts at root node", () => { | |
87 | const codePath = parseCodePaths("foo(); bar(); baz();")[0]; | |
88 | ||
89 | assert.strictEqual(codePath.origin, "program"); | |
90 | }); | |
91 | ||
92 | it("should be 'function' when code path starts inside a function", () => { | |
93 | const codePath = parseCodePaths("function foo() {}")[1]; | |
94 | ||
95 | assert.strictEqual(codePath.origin, "function"); | |
96 | }); | |
97 | ||
98 | it("should be 'function' when code path starts inside an arrow function", () => { | |
99 | const codePath = parseCodePaths("let foo = () => {}")[1]; | |
100 | ||
101 | assert.strictEqual(codePath.origin, "function"); | |
102 | }); | |
103 | ||
104 | it("should be 'class-field-initializer' when code path starts inside a class field initializer", () => { | |
105 | const codePath = parseCodePaths("class Foo { a=1; }")[1]; | |
106 | ||
107 | assert.strictEqual(codePath.origin, "class-field-initializer"); | |
108 | }); | |
109 | ||
110 | it("should be 'class-static-block' when code path starts inside a class static block", () => { | |
111 | const codePath = parseCodePaths("class Foo { static { this.a=1; } }")[1]; | |
112 | ||
113 | assert.strictEqual(codePath.origin, "class-static-block"); | |
114 | }); | |
115 | }); | |
116 | ||
eb39fafa | 117 | describe(".traverseSegments()", () => { |
609c276f | 118 | |
eb39fafa | 119 | describe("should traverse segments from the first to the end:", () => { |
609c276f | 120 | /* eslint-disable internal-rules/multiline-comment-style -- Commenting out */ |
eb39fafa DC |
121 | it("simple", () => { |
122 | const codePath = parseCodePaths("foo(); bar(); baz();")[0]; | |
123 | const order = getOrderOfTraversing(codePath); | |
124 | ||
125 | assert.deepStrictEqual(order, ["s1_1"]); | |
126 | ||
127 | /* | |
128 | digraph { | |
129 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
130 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
131 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
132 | s1_1[label="Program\nExpressionStatement\nCallExpression\nIdentifier (foo)\nExpressionStatement\nCallExpression\nIdentifier (bar)\nExpressionStatement\nCallExpression\nIdentifier (baz)"]; | |
133 | initial->s1_1->final; | |
134 | } | |
135 | */ | |
136 | }); | |
137 | ||
138 | it("if", () => { | |
139 | const codePath = parseCodePaths("if (a) foo(); else bar(); baz();")[0]; | |
140 | const order = getOrderOfTraversing(codePath); | |
141 | ||
142 | assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4"]); | |
143 | ||
144 | /* | |
145 | digraph { | |
146 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
147 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
148 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
149 | s1_1[label="Program\nIfStatement\nIdentifier (a)"]; | |
150 | s1_2[label="ExpressionStatement\nCallExpression\nIdentifier (foo)"]; | |
151 | s1_4[label="ExpressionStatement\nCallExpression\nIdentifier (baz)"]; | |
152 | s1_3[label="ExpressionStatement\nCallExpression\nIdentifier (bar)"]; | |
153 | initial->s1_1->s1_2->s1_4; | |
154 | s1_1->s1_3->s1_4->final; | |
155 | } | |
156 | */ | |
157 | }); | |
158 | ||
159 | it("switch", () => { | |
160 | const codePath = parseCodePaths("switch (a) { case 0: foo(); break; case 1: bar(); } baz();")[0]; | |
161 | const order = getOrderOfTraversing(codePath); | |
162 | ||
163 | assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_4", "s1_5", "s1_6"]); | |
164 | ||
165 | /* | |
166 | digraph { | |
167 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
168 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
169 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
170 | s1_1[label="Program\nSwitchStatement\nIdentifier (a)\nSwitchCase\nLiteral (0)"]; | |
171 | s1_2[label="ExpressionStatement\nCallExpression\nIdentifier (foo)\nBreakStatement"]; | |
172 | s1_3[style="rounded,dashed,filled",fillcolor="#FF9800",label="<<unreachable>>\nSwitchCase:exit"]; | |
173 | s1_5[label="ExpressionStatement\nCallExpression\nIdentifier (bar)"]; | |
174 | s1_6[label="ExpressionStatement\nCallExpression\nIdentifier (baz)"]; | |
175 | s1_4[label="SwitchCase\nLiteral (1)"]; | |
176 | initial->s1_1->s1_2->s1_3->s1_5->s1_6; | |
177 | s1_1->s1_4->s1_5; | |
178 | s1_2->s1_6; | |
179 | s1_4->s1_6->final; | |
180 | } | |
181 | */ | |
182 | }); | |
183 | ||
184 | it("while", () => { | |
185 | const codePath = parseCodePaths("while (a) foo(); bar();")[0]; | |
186 | const order = getOrderOfTraversing(codePath); | |
187 | ||
188 | assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4"]); | |
189 | ||
190 | /* | |
191 | digraph { | |
192 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
193 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
194 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
195 | s1_1[label="Program\nWhileStatement"]; | |
196 | s1_2[label="Identifier (a)"]; | |
197 | s1_3[label="ExpressionStatement\nCallExpression\nIdentifier (foo)"]; | |
198 | s1_4[label="ExpressionStatement\nCallExpression\nIdentifier (bar)"]; | |
199 | initial->s1_1->s1_2->s1_3->s1_2->s1_4->final; | |
200 | } | |
201 | */ | |
202 | }); | |
203 | ||
204 | it("for", () => { | |
205 | const codePath = parseCodePaths("for (var i = 0; i < 10; ++i) foo(i); bar();")[0]; | |
206 | const order = getOrderOfTraversing(codePath); | |
207 | ||
208 | assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4", "s1_5"]); | |
209 | ||
210 | /* | |
211 | digraph { | |
212 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
213 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
214 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
215 | s1_1[label="Program\nForStatement\nVariableDeclaration\nVariableDeclarator\nIdentifier (i)\nLiteral (0)"]; | |
216 | s1_2[label="BinaryExpression\nIdentifier (i)\nLiteral (10)"]; | |
217 | s1_3[label="ExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier (i)"]; | |
218 | s1_4[label="UpdateExpression\nIdentifier (i)"]; | |
219 | s1_5[label="ExpressionStatement\nCallExpression\nIdentifier (bar)"]; | |
220 | initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5->final; | |
221 | } | |
222 | */ | |
223 | }); | |
224 | ||
225 | it("for-in", () => { | |
226 | const codePath = parseCodePaths("for (var key in obj) foo(key); bar();")[0]; | |
227 | const order = getOrderOfTraversing(codePath); | |
228 | ||
229 | assert.deepStrictEqual(order, ["s1_1", "s1_3", "s1_2", "s1_4", "s1_5"]); | |
230 | ||
231 | /* | |
232 | digraph { | |
233 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
234 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
235 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
236 | s1_1[label="Program\nForInStatement"]; | |
237 | s1_3[label="Identifier (obj)"]; | |
238 | s1_2[label="VariableDeclaration\nVariableDeclarator\nIdentifier (key)"]; | |
239 | s1_4[label="ExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier (key)"]; | |
240 | s1_5[label="ExpressionStatement\nCallExpression\nIdentifier (bar)"]; | |
241 | initial->s1_1->s1_3->s1_2->s1_4->s1_2; | |
242 | s1_3->s1_5; | |
243 | s1_4->s1_5->final; | |
244 | } | |
245 | */ | |
246 | }); | |
247 | ||
248 | it("try-catch", () => { | |
249 | const codePath = parseCodePaths("try { foo(); } catch (e) { bar(); } baz();")[0]; | |
250 | const order = getOrderOfTraversing(codePath); | |
251 | ||
252 | assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4"]); | |
253 | ||
254 | /* | |
255 | digraph { | |
256 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
257 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
258 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
259 | s1_1[label="Program\nTryStatement\nBlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)"]; | |
260 | s1_2[label="CallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; | |
261 | s1_3[label="CatchClause\nIdentifier (e)\nBlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)"]; | |
262 | s1_4[label="ExpressionStatement\nCallExpression\nIdentifier (baz)"]; | |
263 | initial->s1_1->s1_2->s1_3->s1_4; | |
264 | s1_1->s1_3; | |
265 | s1_2->s1_4->final; | |
266 | } | |
267 | */ | |
268 | }); | |
269 | }); | |
270 | ||
271 | it("should traverse segments from `options.first` to `options.last`.", () => { | |
272 | const codePath = parseCodePaths("if (a) { if (b) { foo(); } bar(); } else { out1(); } out2();")[0]; | |
273 | const order = getOrderOfTraversing(codePath, { | |
274 | first: codePath.initialSegment.nextSegments[0], | |
275 | last: codePath.initialSegment.nextSegments[0].nextSegments[1] | |
276 | }); | |
277 | ||
278 | assert.deepStrictEqual(order, ["s1_2", "s1_3", "s1_4"]); | |
279 | ||
280 | /* | |
281 | digraph { | |
282 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
283 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
284 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
285 | s1_1[label="Program\nIfStatement\nIdentifier (a)"]; | |
286 | s1_2[label="BlockStatement\nIfStatement\nIdentifier (b)"]; | |
287 | s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)"]; | |
288 | s1_4[label="ExpressionStatement\nCallExpression\nIdentifier (bar)"]; | |
289 | s1_6[label="ExpressionStatement\nCallExpression\nIdentifier (out2)"]; | |
290 | s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (out1)"]; | |
291 | initial->s1_1->s1_2->s1_3->s1_4->s1_6; | |
292 | s1_1->s1_5->s1_6; | |
293 | s1_2->s1_4; | |
294 | s1_6->final; | |
295 | } | |
296 | */ | |
297 | }); | |
298 | ||
299 | it("should stop immediately when 'controller.break()' was called.", () => { | |
300 | const codePath = parseCodePaths("if (a) { if (b) { foo(); } bar(); } else { out1(); } out2();")[0]; | |
301 | const order = getOrderOfTraversing(codePath, null, (segment, controller) => { | |
302 | if (segment.id === "s1_2") { | |
303 | controller.break(); | |
304 | } | |
305 | }); | |
306 | ||
307 | assert.deepStrictEqual(order, ["s1_1", "s1_2"]); | |
308 | ||
309 | /* | |
310 | digraph { | |
311 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
312 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
313 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
314 | s1_1[label="Program\nIfStatement\nIdentifier (a)"]; | |
315 | s1_2[label="BlockStatement\nIfStatement\nIdentifier (b)"]; | |
316 | s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)"]; | |
317 | s1_4[label="ExpressionStatement\nCallExpression\nIdentifier (bar)"]; | |
318 | s1_6[label="ExpressionStatement\nCallExpression\nIdentifier (out2)"]; | |
319 | s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (out1)"]; | |
320 | initial->s1_1->s1_2->s1_3->s1_4->s1_6; | |
321 | s1_1->s1_5->s1_6; | |
322 | s1_2->s1_4; | |
323 | s1_6->final; | |
324 | } | |
325 | */ | |
326 | }); | |
327 | ||
328 | it("should skip the current branch when 'controller.skip()' was called.", () => { | |
329 | const codePath = parseCodePaths("if (a) { if (b) { foo(); } bar(); } else { out1(); } out2();")[0]; | |
330 | const order = getOrderOfTraversing(codePath, null, (segment, controller) => { | |
331 | if (segment.id === "s1_2") { | |
332 | controller.skip(); | |
333 | } | |
334 | }); | |
335 | ||
336 | assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_5", "s1_6"]); | |
337 | ||
338 | /* | |
339 | digraph { | |
340 | node[shape=box,style="rounded,filled",fillcolor=white]; | |
341 | initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
342 | final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; | |
343 | s1_1[label="Program\nIfStatement\nIdentifier (a)"]; | |
344 | s1_2[label="BlockStatement\nIfStatement\nIdentifier (b)"]; | |
345 | s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)"]; | |
346 | s1_4[label="ExpressionStatement\nCallExpression\nIdentifier (bar)"]; | |
347 | s1_6[label="ExpressionStatement\nCallExpression\nIdentifier (out2)"]; | |
348 | s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (out1)"]; | |
349 | initial->s1_1->s1_2->s1_3->s1_4->s1_6; | |
350 | s1_1->s1_5->s1_6; | |
351 | s1_2->s1_4; | |
352 | s1_6->final; | |
353 | } | |
354 | */ | |
355 | }); | |
356 | ||
609c276f | 357 | /* eslint-enable internal-rules/multiline-comment-style -- Commenting out */ |
eb39fafa DC |
358 | }); |
359 | }); |