]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Tests for eslint object. | |
3 | * @author Nicholas C. Zakas | |
4 | */ | |
5 | ||
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Helper | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
12 | /** | |
13 | * To make sure this works in both browsers and Node.js | |
14 | * @param {string} name Name of the module to require | |
15 | * @param {Object} windowName name of the window | |
16 | * @returns {Object} Required object | |
17 | * @private | |
18 | */ | |
19 | function compatRequire(name, windowName) { | |
20 | if (typeof window === "object") { // eslint-disable-line no-undef | |
21 | return window[windowName || name]; // eslint-disable-line no-undef | |
22 | } | |
23 | if (typeof require === "function") { | |
24 | return require(name); | |
25 | } | |
26 | throw new Error(`Cannot find object '${name}'.`); | |
27 | } | |
28 | ||
29 | //------------------------------------------------------------------------------ | |
30 | // Requirements | |
31 | //------------------------------------------------------------------------------ | |
32 | ||
33 | const assert = require("chai").assert, | |
34 | sinon = require("sinon"), | |
35 | esprima = require("esprima"), | |
36 | testParsers = require("../../fixtures/parsers/linter-test-parsers"); | |
37 | ||
38 | const { Linter } = compatRequire("../../../lib/linter", "eslint"); | |
39 | ||
40 | //------------------------------------------------------------------------------ | |
41 | // Constants | |
42 | //------------------------------------------------------------------------------ | |
43 | ||
44 | const TEST_CODE = "var answer = 6 * 7;", | |
45 | BROKEN_TEST_CODE = "var;"; | |
46 | ||
47 | //------------------------------------------------------------------------------ | |
48 | // Helpers | |
49 | //------------------------------------------------------------------------------ | |
50 | ||
51 | /** | |
52 | * Get variables in the current scope | |
53 | * @param {Object} scope current scope | |
54 | * @param {string} name name of the variable to look for | |
55 | * @returns {ASTNode|null} The variable object | |
56 | * @private | |
57 | */ | |
58 | function getVariable(scope, name) { | |
59 | return scope.variables.find(v => v.name === name) || null; | |
60 | } | |
61 | ||
62 | /** | |
63 | * `eslint-env` comments are processed by doing a full source text match before parsing. | |
64 | * As a result, if this source file contains `eslint- env` followed by an environment in a string, | |
65 | * it will actually enable the given envs for this source file. This variable is used to avoid having a string | |
66 | * like that appear in the code. | |
67 | */ | |
68 | const ESLINT_ENV = "eslint-env"; | |
69 | ||
70 | //------------------------------------------------------------------------------ | |
71 | // Tests | |
72 | //------------------------------------------------------------------------------ | |
73 | ||
74 | describe("Linter", () => { | |
75 | const filename = "filename.js"; | |
76 | ||
77 | /** @type {InstanceType<import("../../../lib/linter/linter.js")["Linter"]>} */ | |
78 | let linter; | |
79 | ||
80 | beforeEach(() => { | |
81 | linter = new Linter(); | |
82 | }); | |
83 | ||
84 | afterEach(() => { | |
85 | sinon.verifyAndRestore(); | |
86 | }); | |
87 | ||
88 | describe("Static Members", () => { | |
89 | describe("version", () => { | |
90 | it("should return same version as instance property", () => { | |
91 | assert.strictEqual(Linter.version, linter.version); | |
92 | }); | |
93 | }); | |
94 | }); | |
95 | ||
96 | describe("when using events", () => { | |
97 | const code = TEST_CODE; | |
98 | ||
99 | it("an error should be thrown when an error occurs inside of an event handler", () => { | |
100 | const config = { rules: { checker: "error" } }; | |
101 | ||
102 | linter.defineRule("checker", () => ({ | |
103 | Program() { | |
104 | throw new Error("Intentional error."); | |
105 | } | |
106 | })); | |
107 | ||
108 | assert.throws(() => { | |
109 | linter.verify(code, config, filename); | |
110 | }, `Intentional error.\nOccurred while linting ${filename}:1`); | |
111 | }); | |
112 | ||
113 | it("does not call rule listeners with a `this` value", () => { | |
114 | const spy = sinon.spy(); | |
115 | ||
116 | linter.defineRule("checker", () => ({ Program: spy })); | |
117 | linter.verify("foo", { rules: { checker: "error" } }); | |
118 | assert(spy.calledOnce); | |
119 | assert.strictEqual(spy.firstCall.thisValue, void 0); | |
120 | }); | |
121 | ||
122 | it("does not allow listeners to use special EventEmitter values", () => { | |
123 | const spy = sinon.spy(); | |
124 | ||
125 | linter.defineRule("checker", () => ({ newListener: spy })); | |
126 | linter.verify("foo", { rules: { checker: "error", "no-undef": "error" } }); | |
127 | assert(spy.notCalled); | |
128 | }); | |
129 | ||
130 | it("has all the `parent` properties on nodes when the rule listeners are created", () => { | |
131 | const spy = sinon.spy(context => { | |
132 | const ast = context.getSourceCode().ast; | |
133 | ||
134 | assert.strictEqual(ast.body[0].parent, ast); | |
135 | assert.strictEqual(ast.body[0].expression.parent, ast.body[0]); | |
136 | assert.strictEqual(ast.body[0].expression.left.parent, ast.body[0].expression); | |
137 | assert.strictEqual(ast.body[0].expression.right.parent, ast.body[0].expression); | |
138 | ||
139 | return {}; | |
140 | }); | |
141 | ||
142 | linter.defineRule("checker", spy); | |
143 | ||
144 | linter.verify("foo + bar", { rules: { checker: "error" } }); | |
145 | assert(spy.calledOnce); | |
146 | }); | |
147 | }); | |
148 | ||
149 | describe("context.getSourceLines()", () => { | |
150 | ||
151 | it("should get proper lines when using \\n as a line break", () => { | |
152 | const code = "a;\nb;"; | |
153 | const spy = sinon.spy(context => { | |
154 | assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); | |
155 | return {}; | |
156 | }); | |
157 | ||
158 | linter.defineRule("checker", spy); | |
159 | linter.verify(code, { rules: { checker: "error" } }); | |
160 | assert(spy.calledOnce); | |
161 | }); | |
162 | ||
163 | it("should get proper lines when using \\r\\n as a line break", () => { | |
164 | const code = "a;\r\nb;"; | |
165 | const spy = sinon.spy(context => { | |
166 | assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); | |
167 | return {}; | |
168 | }); | |
169 | ||
170 | linter.defineRule("checker", spy); | |
171 | linter.verify(code, { rules: { checker: "error" } }); | |
172 | assert(spy.calledOnce); | |
173 | }); | |
174 | ||
175 | it("should get proper lines when using \\r as a line break", () => { | |
176 | const code = "a;\rb;"; | |
177 | const spy = sinon.spy(context => { | |
178 | assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); | |
179 | return {}; | |
180 | }); | |
181 | ||
182 | linter.defineRule("checker", spy); | |
183 | linter.verify(code, { rules: { checker: "error" } }); | |
184 | assert(spy.calledOnce); | |
185 | }); | |
186 | ||
187 | it("should get proper lines when using \\u2028 as a line break", () => { | |
188 | const code = "a;\u2028b;"; | |
189 | const spy = sinon.spy(context => { | |
190 | assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); | |
191 | return {}; | |
192 | }); | |
193 | ||
194 | linter.defineRule("checker", spy); | |
195 | linter.verify(code, { rules: { checker: "error" } }); | |
196 | assert(spy.calledOnce); | |
197 | }); | |
198 | ||
199 | it("should get proper lines when using \\u2029 as a line break", () => { | |
200 | const code = "a;\u2029b;"; | |
201 | const spy = sinon.spy(context => { | |
202 | assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); | |
203 | return {}; | |
204 | }); | |
205 | ||
206 | linter.defineRule("checker", spy); | |
207 | linter.verify(code, { rules: { checker: "error" } }); | |
208 | assert(spy.calledOnce); | |
209 | }); | |
210 | ||
211 | ||
212 | }); | |
213 | ||
214 | describe("getSourceCode()", () => { | |
215 | const code = TEST_CODE; | |
216 | ||
217 | it("should retrieve SourceCode object after reset", () => { | |
218 | linter.verify(code, {}, filename, true); | |
219 | ||
220 | const sourceCode = linter.getSourceCode(); | |
221 | ||
222 | assert.isObject(sourceCode); | |
223 | assert.strictEqual(sourceCode.text, code); | |
224 | assert.isObject(sourceCode.ast); | |
225 | }); | |
226 | ||
227 | it("should retrieve SourceCode object without reset", () => { | |
228 | linter.verify(code, {}, filename); | |
229 | ||
230 | const sourceCode = linter.getSourceCode(); | |
231 | ||
232 | assert.isObject(sourceCode); | |
233 | assert.strictEqual(sourceCode.text, code); | |
234 | assert.isObject(sourceCode.ast); | |
235 | }); | |
236 | ||
237 | }); | |
238 | ||
239 | describe("context.getSource()", () => { | |
240 | const code = TEST_CODE; | |
241 | ||
242 | it("should retrieve all text when used without parameters", () => { | |
243 | ||
244 | const config = { rules: { checker: "error" } }; | |
245 | let spy; | |
246 | ||
247 | linter.defineRule("checker", context => { | |
248 | spy = sinon.spy(() => { | |
249 | assert.strictEqual(context.getSource(), TEST_CODE); | |
250 | }); | |
251 | return { Program: spy }; | |
252 | }); | |
253 | ||
254 | linter.verify(code, config); | |
255 | assert(spy && spy.calledOnce); | |
256 | }); | |
257 | ||
258 | it("should retrieve all text for root node", () => { | |
259 | const config = { rules: { checker: "error" } }; | |
260 | let spy; | |
261 | ||
262 | linter.defineRule("checker", context => { | |
263 | spy = sinon.spy(node => { | |
264 | assert.strictEqual(context.getSource(node), TEST_CODE); | |
265 | }); | |
266 | return { Program: spy }; | |
267 | }); | |
268 | ||
269 | linter.verify(code, config); | |
270 | assert(spy && spy.calledOnce); | |
271 | }); | |
272 | ||
273 | it("should clamp to valid range when retrieving characters before start of source", () => { | |
274 | const config = { rules: { checker: "error" } }; | |
275 | let spy; | |
276 | ||
277 | linter.defineRule("checker", context => { | |
278 | spy = sinon.spy(node => { | |
279 | assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); | |
280 | }); | |
281 | return { Program: spy }; | |
282 | }); | |
283 | ||
284 | linter.verify(code, config); | |
285 | assert(spy && spy.calledOnce); | |
286 | }); | |
287 | ||
288 | it("should retrieve all text for binary expression", () => { | |
289 | const config = { rules: { checker: "error" } }; | |
290 | let spy; | |
291 | ||
292 | linter.defineRule("checker", context => { | |
293 | spy = sinon.spy(node => { | |
294 | assert.strictEqual(context.getSource(node), "6 * 7"); | |
295 | }); | |
296 | return { BinaryExpression: spy }; | |
297 | }); | |
298 | ||
299 | linter.verify(code, config); | |
300 | assert(spy && spy.calledOnce); | |
301 | }); | |
302 | ||
303 | it("should retrieve all text plus two characters before for binary expression", () => { | |
304 | const config = { rules: { checker: "error" } }; | |
305 | let spy; | |
306 | ||
307 | linter.defineRule("checker", context => { | |
308 | spy = sinon.spy(node => { | |
309 | assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); | |
310 | }); | |
311 | return { BinaryExpression: spy }; | |
312 | }); | |
313 | ||
314 | linter.verify(code, config); | |
315 | assert(spy && spy.calledOnce); | |
316 | }); | |
317 | ||
318 | it("should retrieve all text plus one character after for binary expression", () => { | |
319 | const config = { rules: { checker: "error" } }; | |
320 | let spy; | |
321 | ||
322 | linter.defineRule("checker", context => { | |
323 | spy = sinon.spy(node => { | |
324 | assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); | |
325 | }); | |
326 | return { BinaryExpression: spy }; | |
327 | }); | |
328 | ||
329 | linter.verify(code, config); | |
330 | assert(spy && spy.calledOnce); | |
331 | }); | |
332 | ||
333 | it("should retrieve all text plus two characters before and one character after for binary expression", () => { | |
334 | const config = { rules: { checker: "error" } }; | |
335 | let spy; | |
336 | ||
337 | linter.defineRule("checker", context => { | |
338 | spy = sinon.spy(node => { | |
339 | assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); | |
340 | }); | |
341 | return { BinaryExpression: spy }; | |
342 | }); | |
343 | ||
344 | linter.verify(code, config); | |
345 | assert(spy && spy.calledOnce); | |
346 | }); | |
347 | ||
348 | }); | |
349 | ||
350 | describe("when calling context.getAncestors", () => { | |
351 | const code = TEST_CODE; | |
352 | ||
353 | it("should retrieve all ancestors when used", () => { | |
354 | ||
355 | const config = { rules: { checker: "error" } }; | |
356 | let spy; | |
357 | ||
358 | linter.defineRule("checker", context => { | |
359 | spy = sinon.spy(() => { | |
360 | const ancestors = context.getAncestors(); | |
361 | ||
362 | assert.strictEqual(ancestors.length, 3); | |
363 | }); | |
364 | return { BinaryExpression: spy }; | |
365 | }); | |
366 | ||
367 | linter.verify(code, config, filename, true); | |
368 | assert(spy && spy.calledOnce); | |
369 | }); | |
370 | ||
371 | it("should retrieve empty ancestors for root node", () => { | |
372 | const config = { rules: { checker: "error" } }; | |
373 | let spy; | |
374 | ||
375 | linter.defineRule("checker", context => { | |
376 | spy = sinon.spy(() => { | |
377 | const ancestors = context.getAncestors(); | |
378 | ||
379 | assert.strictEqual(ancestors.length, 0); | |
380 | }); | |
381 | ||
382 | return { Program: spy }; | |
383 | }); | |
384 | ||
385 | linter.verify(code, config); | |
386 | assert(spy && spy.calledOnce); | |
387 | }); | |
388 | }); | |
389 | ||
390 | describe("when calling context.getNodeByRangeIndex", () => { | |
391 | const code = TEST_CODE; | |
392 | ||
393 | it("should retrieve a node starting at the given index", () => { | |
394 | const config = { rules: { checker: "error" } }; | |
395 | const spy = sinon.spy(context => { | |
396 | assert.strictEqual(context.getNodeByRangeIndex(4).type, "Identifier"); | |
397 | return {}; | |
398 | }); | |
399 | ||
400 | linter.defineRule("checker", spy); | |
401 | linter.verify(code, config); | |
402 | assert(spy.calledOnce); | |
403 | }); | |
404 | ||
405 | it("should retrieve a node containing the given index", () => { | |
406 | const config = { rules: { checker: "error" } }; | |
407 | const spy = sinon.spy(context => { | |
408 | assert.strictEqual(context.getNodeByRangeIndex(6).type, "Identifier"); | |
409 | return {}; | |
410 | }); | |
411 | ||
412 | linter.defineRule("checker", spy); | |
413 | linter.verify(code, config); | |
414 | assert(spy.calledOnce); | |
415 | }); | |
416 | ||
417 | it("should retrieve a node that is exactly the given index", () => { | |
418 | const config = { rules: { checker: "error" } }; | |
419 | const spy = sinon.spy(context => { | |
420 | const node = context.getNodeByRangeIndex(13); | |
421 | ||
422 | assert.strictEqual(node.type, "Literal"); | |
423 | assert.strictEqual(node.value, 6); | |
424 | return {}; | |
425 | }); | |
426 | ||
427 | linter.defineRule("checker", spy); | |
428 | linter.verify(code, config); | |
429 | assert(spy.calledOnce); | |
430 | }); | |
431 | ||
432 | it("should retrieve a node ending with the given index", () => { | |
433 | const config = { rules: { checker: "error" } }; | |
434 | const spy = sinon.spy(context => { | |
435 | assert.strictEqual(context.getNodeByRangeIndex(9).type, "Identifier"); | |
436 | return {}; | |
437 | }); | |
438 | ||
439 | linter.defineRule("checker", spy); | |
440 | linter.verify(code, config); | |
441 | assert(spy.calledOnce); | |
442 | }); | |
443 | ||
444 | it("should retrieve the deepest node containing the given index", () => { | |
445 | const config = { rules: { checker: "error" } }; | |
446 | const spy = sinon.spy(context => { | |
447 | const node1 = context.getNodeByRangeIndex(14); | |
448 | ||
449 | assert.strictEqual(node1.type, "BinaryExpression"); | |
450 | ||
451 | const node2 = context.getNodeByRangeIndex(3); | |
452 | ||
453 | assert.strictEqual(node2.type, "VariableDeclaration"); | |
454 | return {}; | |
455 | }); | |
456 | ||
457 | linter.defineRule("checker", spy); | |
458 | linter.verify(code, config); | |
459 | assert(spy.calledOnce); | |
460 | }); | |
461 | ||
462 | it("should return null if the index is outside the range of any node", () => { | |
463 | const config = { rules: { checker: "error" } }; | |
464 | const spy = sinon.spy(context => { | |
465 | const node1 = context.getNodeByRangeIndex(-1); | |
466 | ||
467 | assert.isNull(node1); | |
468 | ||
469 | const node2 = context.getNodeByRangeIndex(-99); | |
470 | ||
471 | assert.isNull(node2); | |
472 | return {}; | |
473 | }); | |
474 | ||
475 | linter.defineRule("checker", spy); | |
476 | linter.verify(code, config); | |
477 | assert(spy.calledOnce); | |
478 | }); | |
479 | }); | |
480 | ||
481 | ||
482 | describe("when calling context.getScope", () => { | |
483 | const code = "function foo() { q: for(;;) { break q; } } function bar () { var q = t; } var baz = (() => { return 1; });"; | |
484 | ||
485 | it("should retrieve the global scope correctly from a Program", () => { | |
486 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
487 | let spy; | |
488 | ||
489 | linter.defineRule("checker", context => { | |
490 | spy = sinon.spy(() => { | |
491 | const scope = context.getScope(); | |
492 | ||
493 | assert.strictEqual(scope.type, "global"); | |
494 | }); | |
495 | return { Program: spy }; | |
496 | }); | |
497 | ||
498 | linter.verify(code, config); | |
499 | assert(spy && spy.calledOnce); | |
500 | }); | |
501 | ||
502 | it("should retrieve the function scope correctly from a FunctionDeclaration", () => { | |
503 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
504 | let spy; | |
505 | ||
506 | linter.defineRule("checker", context => { | |
507 | spy = sinon.spy(() => { | |
508 | const scope = context.getScope(); | |
509 | ||
510 | assert.strictEqual(scope.type, "function"); | |
511 | }); | |
512 | return { FunctionDeclaration: spy }; | |
513 | }); | |
514 | ||
515 | linter.verify(code, config); | |
516 | assert(spy && spy.calledTwice); | |
517 | }); | |
518 | ||
519 | it("should retrieve the function scope correctly from a LabeledStatement", () => { | |
520 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
521 | let spy; | |
522 | ||
523 | linter.defineRule("checker", context => { | |
524 | spy = sinon.spy(() => { | |
525 | const scope = context.getScope(); | |
526 | ||
527 | assert.strictEqual(scope.type, "function"); | |
528 | assert.strictEqual(scope.block.id.name, "foo"); | |
529 | }); | |
530 | return { LabeledStatement: spy }; | |
531 | }); | |
532 | ||
533 | linter.verify(code, config); | |
534 | assert(spy && spy.calledOnce); | |
535 | }); | |
536 | ||
537 | it("should retrieve the function scope correctly from within an ArrowFunctionExpression", () => { | |
538 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
539 | let spy; | |
540 | ||
541 | linter.defineRule("checker", context => { | |
542 | spy = sinon.spy(() => { | |
543 | const scope = context.getScope(); | |
544 | ||
545 | assert.strictEqual(scope.type, "function"); | |
546 | assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); | |
547 | }); | |
548 | ||
549 | return { ReturnStatement: spy }; | |
550 | }); | |
551 | ||
552 | linter.verify(code, config); | |
553 | assert(spy && spy.calledOnce); | |
554 | }); | |
555 | ||
556 | it("should retrieve the function scope correctly from within an SwitchStatement", () => { | |
557 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
558 | let spy; | |
559 | ||
560 | linter.defineRule("checker", context => { | |
561 | spy = sinon.spy(() => { | |
562 | const scope = context.getScope(); | |
563 | ||
564 | assert.strictEqual(scope.type, "switch"); | |
565 | assert.strictEqual(scope.block.type, "SwitchStatement"); | |
566 | }); | |
567 | ||
568 | return { SwitchStatement: spy }; | |
569 | }); | |
570 | ||
571 | linter.verify("switch(foo){ case 'a': var b = 'foo'; }", config); | |
572 | assert(spy && spy.calledOnce); | |
573 | }); | |
574 | ||
575 | it("should retrieve the function scope correctly from within a BlockStatement", () => { | |
576 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
577 | let spy; | |
578 | ||
579 | linter.defineRule("checker", context => { | |
580 | spy = sinon.spy(() => { | |
581 | const scope = context.getScope(); | |
582 | ||
583 | assert.strictEqual(scope.type, "block"); | |
584 | assert.strictEqual(scope.block.type, "BlockStatement"); | |
585 | }); | |
586 | ||
587 | return { BlockStatement: spy }; | |
588 | }); | |
589 | ||
590 | linter.verify("var x; {let y = 1}", config); | |
591 | assert(spy && spy.calledOnce); | |
592 | }); | |
593 | ||
594 | it("should retrieve the function scope correctly from within a nested block statement", () => { | |
595 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
596 | let spy; | |
597 | ||
598 | linter.defineRule("checker", context => { | |
599 | spy = sinon.spy(() => { | |
600 | const scope = context.getScope(); | |
601 | ||
602 | assert.strictEqual(scope.type, "block"); | |
603 | assert.strictEqual(scope.block.type, "BlockStatement"); | |
604 | }); | |
605 | ||
606 | return { BlockStatement: spy }; | |
607 | }); | |
608 | ||
609 | linter.verify("if (true) { let x = 1 }", config); | |
610 | assert(spy && spy.calledOnce); | |
611 | }); | |
612 | ||
613 | it("should retrieve the function scope correctly from within a FunctionDeclaration", () => { | |
614 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
615 | let spy; | |
616 | ||
617 | linter.defineRule("checker", context => { | |
618 | spy = sinon.spy(() => { | |
619 | const scope = context.getScope(); | |
620 | ||
621 | assert.strictEqual(scope.type, "function"); | |
622 | assert.strictEqual(scope.block.type, "FunctionDeclaration"); | |
623 | }); | |
624 | ||
625 | return { FunctionDeclaration: spy }; | |
626 | }); | |
627 | ||
628 | linter.verify("function foo() {}", config); | |
629 | assert(spy && spy.calledOnce); | |
630 | }); | |
631 | ||
632 | it("should retrieve the function scope correctly from within a FunctionExpression", () => { | |
633 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
634 | let spy; | |
635 | ||
636 | linter.defineRule("checker", context => { | |
637 | spy = sinon.spy(() => { | |
638 | const scope = context.getScope(); | |
639 | ||
640 | assert.strictEqual(scope.type, "function"); | |
641 | assert.strictEqual(scope.block.type, "FunctionExpression"); | |
642 | }); | |
643 | ||
644 | return { FunctionExpression: spy }; | |
645 | }); | |
646 | ||
647 | linter.verify("(function foo() {})();", config); | |
648 | assert(spy && spy.calledOnce); | |
649 | }); | |
650 | ||
651 | it("should retrieve the catch scope correctly from within a CatchClause", () => { | |
652 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; | |
653 | let spy; | |
654 | ||
655 | linter.defineRule("checker", context => { | |
656 | spy = sinon.spy(() => { | |
657 | const scope = context.getScope(); | |
658 | ||
659 | assert.strictEqual(scope.type, "catch"); | |
660 | assert.strictEqual(scope.block.type, "CatchClause"); | |
661 | }); | |
662 | ||
663 | return { CatchClause: spy }; | |
664 | }); | |
665 | ||
666 | linter.verify("try {} catch (err) {}", config); | |
667 | assert(spy && spy.calledOnce); | |
668 | }); | |
669 | ||
670 | it("should retrieve module scope correctly from an ES6 module", () => { | |
671 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }; | |
672 | let spy; | |
673 | ||
674 | linter.defineRule("checker", context => { | |
675 | spy = sinon.spy(() => { | |
676 | const scope = context.getScope(); | |
677 | ||
678 | assert.strictEqual(scope.type, "module"); | |
679 | }); | |
680 | ||
681 | return { AssignmentExpression: spy }; | |
682 | }); | |
683 | ||
684 | linter.verify("var foo = {}; foo.bar = 1;", config); | |
685 | assert(spy && spy.calledOnce); | |
686 | }); | |
687 | ||
688 | it("should retrieve function scope correctly when globalReturn is true", () => { | |
689 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, ecmaFeatures: { globalReturn: true } } }; | |
690 | let spy; | |
691 | ||
692 | linter.defineRule("checker", context => { | |
693 | spy = sinon.spy(() => { | |
694 | const scope = context.getScope(); | |
695 | ||
696 | assert.strictEqual(scope.type, "function"); | |
697 | }); | |
698 | ||
699 | return { AssignmentExpression: spy }; | |
700 | }); | |
701 | ||
702 | linter.verify("var foo = {}; foo.bar = 1;", config); | |
703 | assert(spy && spy.calledOnce); | |
704 | }); | |
705 | }); | |
706 | ||
707 | describe("marking variables as used", () => { | |
708 | it("should mark variables in current scope as used", () => { | |
709 | const code = "var a = 1, b = 2;"; | |
710 | let spy; | |
711 | ||
712 | linter.defineRule("checker", context => { | |
713 | spy = sinon.spy(() => { | |
714 | assert.isTrue(context.markVariableAsUsed("a")); | |
715 | ||
716 | const scope = context.getScope(); | |
717 | ||
718 | assert.isTrue(getVariable(scope, "a").eslintUsed); | |
719 | assert.notOk(getVariable(scope, "b").eslintUsed); | |
720 | }); | |
721 | ||
722 | return { "Program:exit": spy }; | |
723 | }); | |
724 | ||
725 | linter.verify(code, { rules: { checker: "error" } }); | |
726 | assert(spy && spy.calledOnce); | |
727 | }); | |
728 | it("should mark variables in function args as used", () => { | |
729 | const code = "function abc(a, b) { return 1; }"; | |
730 | let spy; | |
731 | ||
732 | linter.defineRule("checker", context => { | |
733 | spy = sinon.spy(() => { | |
734 | assert.isTrue(context.markVariableAsUsed("a")); | |
735 | ||
736 | const scope = context.getScope(); | |
737 | ||
738 | assert.isTrue(getVariable(scope, "a").eslintUsed); | |
739 | assert.notOk(getVariable(scope, "b").eslintUsed); | |
740 | }); | |
741 | ||
742 | return { ReturnStatement: spy }; | |
743 | }); | |
744 | ||
745 | linter.verify(code, { rules: { checker: "error" } }); | |
746 | assert(spy && spy.calledOnce); | |
747 | }); | |
748 | it("should mark variables in higher scopes as used", () => { | |
749 | const code = "var a, b; function abc() { return 1; }"; | |
750 | let returnSpy, exitSpy; | |
751 | ||
752 | linter.defineRule("checker", context => { | |
753 | returnSpy = sinon.spy(() => { | |
754 | assert.isTrue(context.markVariableAsUsed("a")); | |
755 | }); | |
756 | exitSpy = sinon.spy(() => { | |
757 | const scope = context.getScope(); | |
758 | ||
759 | assert.isTrue(getVariable(scope, "a").eslintUsed); | |
760 | assert.notOk(getVariable(scope, "b").eslintUsed); | |
761 | }); | |
762 | ||
763 | return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; | |
764 | }); | |
765 | ||
766 | linter.verify(code, { rules: { checker: "error" } }); | |
767 | assert(returnSpy && returnSpy.calledOnce); | |
768 | assert(exitSpy && exitSpy.calledOnce); | |
769 | }); | |
770 | ||
771 | it("should mark variables in Node.js environment as used", () => { | |
772 | const code = "var a = 1, b = 2;"; | |
773 | let spy; | |
774 | ||
775 | linter.defineRule("checker", context => { | |
776 | spy = sinon.spy(() => { | |
777 | const globalScope = context.getScope(), | |
778 | childScope = globalScope.childScopes[0]; | |
779 | ||
780 | assert.isTrue(context.markVariableAsUsed("a")); | |
781 | ||
782 | assert.isTrue(getVariable(childScope, "a").eslintUsed); | |
783 | assert.isUndefined(getVariable(childScope, "b").eslintUsed); | |
784 | }); | |
785 | ||
786 | return { "Program:exit": spy }; | |
787 | }); | |
788 | ||
789 | linter.verify(code, { rules: { checker: "error" }, env: { node: true } }); | |
790 | assert(spy && spy.calledOnce); | |
791 | }); | |
792 | ||
793 | it("should mark variables in modules as used", () => { | |
794 | const code = "var a = 1, b = 2;"; | |
795 | let spy; | |
796 | ||
797 | linter.defineRule("checker", context => { | |
798 | spy = sinon.spy(() => { | |
799 | const globalScope = context.getScope(), | |
800 | childScope = globalScope.childScopes[0]; | |
801 | ||
802 | assert.isTrue(context.markVariableAsUsed("a")); | |
803 | ||
804 | assert.isTrue(getVariable(childScope, "a").eslintUsed); | |
805 | assert.isUndefined(getVariable(childScope, "b").eslintUsed); | |
806 | }); | |
807 | ||
808 | return { "Program:exit": spy }; | |
809 | }); | |
810 | ||
811 | linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }, filename, true); | |
812 | assert(spy && spy.calledOnce); | |
813 | }); | |
814 | ||
815 | it("should return false if the given variable is not found", () => { | |
816 | const code = "var a = 1, b = 2;"; | |
817 | let spy; | |
818 | ||
819 | linter.defineRule("checker", context => { | |
820 | spy = sinon.spy(() => { | |
821 | assert.isFalse(context.markVariableAsUsed("c")); | |
822 | }); | |
823 | ||
824 | return { "Program:exit": spy }; | |
825 | }); | |
826 | ||
827 | linter.verify(code, { rules: { checker: "error" } }); | |
828 | assert(spy && spy.calledOnce); | |
829 | }); | |
830 | }); | |
831 | ||
832 | describe("when evaluating code", () => { | |
833 | const code = TEST_CODE; | |
834 | ||
835 | it("events for each node type should fire", () => { | |
836 | const config = { rules: { checker: "error" } }; | |
837 | ||
838 | // spies for various AST node types | |
839 | const spyLiteral = sinon.spy(), | |
840 | spyVariableDeclarator = sinon.spy(), | |
841 | spyVariableDeclaration = sinon.spy(), | |
842 | spyIdentifier = sinon.spy(), | |
843 | spyBinaryExpression = sinon.spy(); | |
844 | ||
845 | linter.defineRule("checker", () => ({ | |
846 | Literal: spyLiteral, | |
847 | VariableDeclarator: spyVariableDeclarator, | |
848 | VariableDeclaration: spyVariableDeclaration, | |
849 | Identifier: spyIdentifier, | |
850 | BinaryExpression: spyBinaryExpression | |
851 | })); | |
852 | ||
853 | const messages = linter.verify(code, config, filename, true); | |
854 | ||
855 | assert.strictEqual(messages.length, 0); | |
856 | sinon.assert.calledOnce(spyVariableDeclaration); | |
857 | sinon.assert.calledOnce(spyVariableDeclarator); | |
858 | sinon.assert.calledOnce(spyIdentifier); | |
859 | sinon.assert.calledTwice(spyLiteral); | |
860 | sinon.assert.calledOnce(spyBinaryExpression); | |
861 | }); | |
862 | ||
863 | it("should throw an error if a rule reports a problem without a message", () => { | |
864 | linter.defineRule("invalid-report", context => ({ | |
865 | Program(node) { | |
866 | context.report({ node }); | |
867 | } | |
868 | })); | |
869 | ||
870 | assert.throws( | |
871 | () => linter.verify("foo", { rules: { "invalid-report": "error" } }), | |
872 | TypeError, | |
873 | "Missing `message` property in report() call; add a message that describes the linting problem." | |
874 | ); | |
875 | }); | |
876 | }); | |
877 | ||
878 | describe("when config has shared settings for rules", () => { | |
879 | const code = "test-rule"; | |
880 | ||
881 | it("should pass settings to all rules", () => { | |
882 | linter.defineRule(code, context => ({ | |
883 | Literal(node) { | |
884 | context.report(node, context.settings.info); | |
885 | } | |
886 | })); | |
887 | ||
888 | const config = { rules: {}, settings: { info: "Hello" } }; | |
889 | ||
890 | config.rules[code] = 1; | |
891 | ||
892 | const messages = linter.verify("0", config, filename); | |
893 | ||
894 | assert.strictEqual(messages.length, 1); | |
895 | assert.strictEqual(messages[0].message, "Hello"); | |
896 | }); | |
897 | ||
898 | it("should not have any settings if they were not passed in", () => { | |
899 | linter.defineRule(code, context => ({ | |
900 | Literal(node) { | |
901 | if (Object.getOwnPropertyNames(context.settings).length !== 0) { | |
902 | context.report(node, "Settings should be empty"); | |
903 | } | |
904 | } | |
905 | })); | |
906 | ||
907 | const config = { rules: {} }; | |
908 | ||
909 | config.rules[code] = 1; | |
910 | ||
911 | const messages = linter.verify("0", config, filename); | |
912 | ||
913 | assert.strictEqual(messages.length, 0); | |
914 | }); | |
915 | }); | |
916 | ||
917 | describe("when config has parseOptions", () => { | |
918 | ||
919 | it("should pass ecmaFeatures to all rules when provided on config", () => { | |
920 | ||
921 | const parserOptions = { | |
922 | ecmaFeatures: { | |
923 | jsx: true, | |
924 | globalReturn: true | |
925 | } | |
926 | }; | |
927 | ||
928 | linter.defineRule("test-rule", sinon.mock().withArgs( | |
929 | sinon.match({ parserOptions }) | |
930 | ).returns({})); | |
931 | ||
932 | const config = { rules: { "test-rule": 2 }, parserOptions }; | |
933 | ||
934 | linter.verify("0", config, filename); | |
935 | }); | |
936 | ||
937 | it("should pass parserOptions to all rules when default parserOptions is used", () => { | |
938 | ||
939 | const parserOptions = {}; | |
940 | ||
941 | linter.defineRule("test-rule", sinon.mock().withArgs( | |
942 | sinon.match({ parserOptions }) | |
943 | ).returns({})); | |
944 | ||
945 | const config = { rules: { "test-rule": 2 } }; | |
946 | ||
947 | linter.verify("0", config, filename); | |
948 | }); | |
949 | ||
950 | }); | |
951 | ||
952 | describe("when a custom parser is defined using defineParser", () => { | |
953 | ||
954 | it("should be able to define a custom parser", () => { | |
955 | const parser = { | |
956 | parseForESLint: function parse(code, options) { | |
957 | return { | |
958 | ast: esprima.parse(code, options), | |
959 | services: { | |
960 | test: { | |
961 | getMessage() { | |
962 | return "Hi!"; | |
963 | } | |
964 | } | |
965 | } | |
966 | }; | |
967 | } | |
968 | }; | |
969 | ||
970 | linter.defineParser("test-parser", parser); | |
971 | const config = { rules: {}, parser: "test-parser" }; | |
972 | const messages = linter.verify("0", config, filename); | |
973 | ||
974 | assert.strictEqual(messages.length, 0); | |
975 | }); | |
976 | ||
977 | }); | |
978 | ||
979 | describe("when config has parser", () => { | |
980 | ||
981 | it("should pass parser as parserPath to all rules when provided on config", () => { | |
982 | ||
983 | const alternateParser = "esprima"; | |
984 | ||
985 | linter.defineParser("esprima", esprima); | |
986 | linter.defineRule("test-rule", sinon.mock().withArgs( | |
987 | sinon.match({ parserPath: alternateParser }) | |
988 | ).returns({})); | |
989 | ||
990 | const config = { rules: { "test-rule": 2 }, parser: alternateParser }; | |
991 | ||
992 | linter.verify("0", config, filename); | |
993 | }); | |
994 | ||
995 | it("should use parseForESLint() in custom parser when custom parser is specified", () => { | |
996 | const config = { rules: {}, parser: "enhanced-parser" }; | |
997 | ||
998 | linter.defineParser("enhanced-parser", testParsers.enhancedParser); | |
999 | const messages = linter.verify("0", config, filename); | |
1000 | ||
1001 | assert.strictEqual(messages.length, 0); | |
1002 | }); | |
1003 | ||
1004 | it("should expose parser services when using parseForESLint() and services are specified", () => { | |
1005 | linter.defineParser("enhanced-parser", testParsers.enhancedParser); | |
1006 | linter.defineRule("test-service-rule", context => ({ | |
1007 | Literal(node) { | |
1008 | context.report({ | |
1009 | node, | |
1010 | message: context.parserServices.test.getMessage() | |
1011 | }); | |
1012 | } | |
1013 | })); | |
1014 | ||
1015 | const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" }; | |
1016 | const messages = linter.verify("0", config, filename); | |
1017 | ||
1018 | assert.strictEqual(messages.length, 1); | |
1019 | assert.strictEqual(messages[0].message, "Hi!"); | |
1020 | }); | |
1021 | ||
1022 | it("should use the same parserServices if source code object is reused", () => { | |
1023 | linter.defineParser("enhanced-parser", testParsers.enhancedParser); | |
1024 | linter.defineRule("test-service-rule", context => ({ | |
1025 | Literal(node) { | |
1026 | context.report({ | |
1027 | node, | |
1028 | message: context.parserServices.test.getMessage() | |
1029 | }); | |
1030 | } | |
1031 | })); | |
1032 | ||
1033 | const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" }; | |
1034 | const messages = linter.verify("0", config, filename); | |
1035 | ||
1036 | assert.strictEqual(messages.length, 1); | |
1037 | assert.strictEqual(messages[0].message, "Hi!"); | |
1038 | ||
1039 | const messages2 = linter.verify(linter.getSourceCode(), config, filename); | |
1040 | ||
1041 | assert.strictEqual(messages2.length, 1); | |
1042 | assert.strictEqual(messages2[0].message, "Hi!"); | |
1043 | }); | |
1044 | ||
1045 | it("should pass parser as parserPath to all rules when default parser is used", () => { | |
1046 | linter.defineRule("test-rule", sinon.mock().withArgs( | |
1047 | sinon.match({ parserPath: "espree" }) | |
1048 | ).returns({})); | |
1049 | ||
1050 | const config = { rules: { "test-rule": 2 } }; | |
1051 | ||
1052 | linter.verify("0", config, filename); | |
1053 | }); | |
1054 | ||
1055 | }); | |
1056 | ||
1057 | ||
1058 | describe("when passing in configuration values for rules", () => { | |
1059 | const code = "var answer = 6 * 7"; | |
1060 | ||
1061 | it("should be configurable by only setting the integer value", () => { | |
1062 | const rule = "semi", | |
1063 | config = { rules: {} }; | |
1064 | ||
1065 | config.rules[rule] = 1; | |
1066 | ||
1067 | const messages = linter.verify(code, config, filename, true); | |
1068 | ||
1069 | assert.strictEqual(messages.length, 1); | |
1070 | assert.strictEqual(messages[0].ruleId, rule); | |
1071 | }); | |
1072 | ||
1073 | it("should be configurable by only setting the string value", () => { | |
1074 | const rule = "semi", | |
1075 | config = { rules: {} }; | |
1076 | ||
1077 | config.rules[rule] = "warn"; | |
1078 | ||
1079 | const messages = linter.verify(code, config, filename, true); | |
1080 | ||
1081 | assert.strictEqual(messages.length, 1); | |
1082 | assert.strictEqual(messages[0].severity, 1); | |
1083 | assert.strictEqual(messages[0].ruleId, rule); | |
1084 | }); | |
1085 | ||
1086 | it("should be configurable by passing in values as an array", () => { | |
1087 | const rule = "semi", | |
1088 | config = { rules: {} }; | |
1089 | ||
1090 | config.rules[rule] = [1]; | |
1091 | ||
1092 | const messages = linter.verify(code, config, filename, true); | |
1093 | ||
1094 | assert.strictEqual(messages.length, 1); | |
1095 | assert.strictEqual(messages[0].ruleId, rule); | |
1096 | }); | |
1097 | ||
1098 | it("should be configurable by passing in string value as an array", () => { | |
1099 | const rule = "semi", | |
1100 | config = { rules: {} }; | |
1101 | ||
1102 | config.rules[rule] = ["warn"]; | |
1103 | ||
1104 | const messages = linter.verify(code, config, filename, true); | |
1105 | ||
1106 | assert.strictEqual(messages.length, 1); | |
1107 | assert.strictEqual(messages[0].severity, 1); | |
1108 | assert.strictEqual(messages[0].ruleId, rule); | |
1109 | }); | |
1110 | ||
1111 | it("should not be configurable by setting other value", () => { | |
1112 | const rule = "semi", | |
1113 | config = { rules: {} }; | |
1114 | ||
1115 | config.rules[rule] = "1"; | |
1116 | ||
1117 | const messages = linter.verify(code, config, filename, true); | |
1118 | ||
1119 | assert.strictEqual(messages.length, 0); | |
1120 | }); | |
1121 | ||
1122 | it("should process empty config", () => { | |
1123 | const config = {}; | |
1124 | const messages = linter.verify(code, config, filename, true); | |
1125 | ||
1126 | assert.strictEqual(messages.length, 0); | |
1127 | }); | |
1128 | }); | |
1129 | ||
1130 | describe("when evaluating code containing /*global */ and /*globals */ blocks", () => { | |
1131 | ||
1132 | it("variables should be available in global scope", () => { | |
1133 | const config = { rules: { checker: "error" }, globals: { Array: "off", ConfigGlobal: "writeable" } }; | |
1134 | const code = ` | |
1135 | /*global a b:true c:false d:readable e:writeable Math:off */ | |
1136 | function foo() {} | |
1137 | /*globals f:true*/ | |
1138 | /* global ConfigGlobal : readable */ | |
1139 | `; | |
1140 | let spy; | |
1141 | ||
1142 | linter.defineRule("checker", context => { | |
1143 | spy = sinon.spy(() => { | |
1144 | const scope = context.getScope(); | |
1145 | const a = getVariable(scope, "a"), | |
1146 | b = getVariable(scope, "b"), | |
1147 | c = getVariable(scope, "c"), | |
1148 | d = getVariable(scope, "d"), | |
1149 | e = getVariable(scope, "e"), | |
1150 | f = getVariable(scope, "f"), | |
1151 | mathGlobal = getVariable(scope, "Math"), | |
1152 | arrayGlobal = getVariable(scope, "Array"), | |
1153 | configGlobal = getVariable(scope, "ConfigGlobal"); | |
1154 | ||
1155 | assert.strictEqual(a.name, "a"); | |
1156 | assert.strictEqual(a.writeable, false); | |
1157 | assert.strictEqual(b.name, "b"); | |
1158 | assert.strictEqual(b.writeable, true); | |
1159 | assert.strictEqual(c.name, "c"); | |
1160 | assert.strictEqual(c.writeable, false); | |
1161 | assert.strictEqual(d.name, "d"); | |
1162 | assert.strictEqual(d.writeable, false); | |
1163 | assert.strictEqual(e.name, "e"); | |
1164 | assert.strictEqual(e.writeable, true); | |
1165 | assert.strictEqual(f.name, "f"); | |
1166 | assert.strictEqual(f.writeable, true); | |
1167 | assert.strictEqual(mathGlobal, null); | |
1168 | assert.strictEqual(arrayGlobal, null); | |
1169 | assert.strictEqual(configGlobal.name, "ConfigGlobal"); | |
1170 | assert.strictEqual(configGlobal.writeable, false); | |
1171 | }); | |
1172 | ||
1173 | return { Program: spy }; | |
1174 | }); | |
1175 | ||
1176 | linter.verify(code, config); | |
1177 | assert(spy && spy.calledOnce); | |
1178 | }); | |
1179 | }); | |
1180 | ||
1181 | describe("when evaluating code containing a /*global */ block with sloppy whitespace", () => { | |
1182 | const code = "/* global a b : true c: false*/"; | |
1183 | ||
1184 | it("variables should be available in global scope", () => { | |
1185 | const config = { rules: { checker: "error" } }; | |
1186 | let spy; | |
1187 | ||
1188 | linter.defineRule("checker", context => { | |
1189 | spy = sinon.spy(() => { | |
1190 | const scope = context.getScope(), | |
1191 | a = getVariable(scope, "a"), | |
1192 | b = getVariable(scope, "b"), | |
1193 | c = getVariable(scope, "c"); | |
1194 | ||
1195 | assert.strictEqual(a.name, "a"); | |
1196 | assert.strictEqual(a.writeable, false); | |
1197 | assert.strictEqual(b.name, "b"); | |
1198 | assert.strictEqual(b.writeable, true); | |
1199 | assert.strictEqual(c.name, "c"); | |
1200 | assert.strictEqual(c.writeable, false); | |
1201 | }); | |
1202 | ||
1203 | return { Program: spy }; | |
1204 | }); | |
1205 | ||
1206 | linter.verify(code, config); | |
1207 | assert(spy && spy.calledOnce); | |
1208 | }); | |
1209 | }); | |
1210 | ||
1211 | describe("when evaluating code containing a /*global */ block with specific variables", () => { | |
1212 | const code = "/* global toString hasOwnProperty valueOf: true */"; | |
1213 | ||
1214 | it("should not throw an error if comment block has global variables which are Object.prototype contains", () => { | |
1215 | const config = { rules: { checker: "error" } }; | |
1216 | ||
1217 | linter.verify(code, config); | |
1218 | }); | |
1219 | }); | |
1220 | ||
1221 | describe("when evaluating code containing /*eslint-env */ block", () => { | |
1222 | it("variables should be available in global scope", () => { | |
1223 | const code = `/*${ESLINT_ENV} node*/ function f() {} /*${ESLINT_ENV} browser, foo*/`; | |
1224 | const config = { rules: { checker: "error" } }; | |
1225 | let spy; | |
1226 | ||
1227 | linter.defineRule("checker", context => { | |
1228 | spy = sinon.spy(() => { | |
1229 | const scope = context.getScope(), | |
1230 | exports = getVariable(scope, "exports"), | |
1231 | window = getVariable(scope, "window"); | |
1232 | ||
1233 | assert.strictEqual(exports.writeable, true); | |
1234 | assert.strictEqual(window.writeable, false); | |
1235 | }); | |
1236 | ||
1237 | return { Program: spy }; | |
1238 | }); | |
1239 | ||
1240 | linter.verify(code, config); | |
1241 | assert(spy && spy.calledOnce); | |
1242 | }); | |
1243 | }); | |
1244 | ||
1245 | describe("when evaluating code containing /*eslint-env */ block with sloppy whitespace", () => { | |
1246 | const code = `/* ${ESLINT_ENV} ,, node , no-browser ,, */`; | |
1247 | ||
1248 | it("variables should be available in global scope", () => { | |
1249 | const config = { rules: { checker: "error" } }; | |
1250 | let spy; | |
1251 | ||
1252 | linter.defineRule("checker", context => { | |
1253 | spy = sinon.spy(() => { | |
1254 | const scope = context.getScope(), | |
1255 | exports = getVariable(scope, "exports"), | |
1256 | window = getVariable(scope, "window"); | |
1257 | ||
1258 | assert.strictEqual(exports.writeable, true); | |
1259 | assert.strictEqual(window, null); | |
1260 | }); | |
1261 | ||
1262 | return { Program: spy }; | |
1263 | }); | |
1264 | ||
1265 | linter.verify(code, config); | |
1266 | assert(spy && spy.calledOnce); | |
1267 | }); | |
1268 | }); | |
1269 | ||
1270 | describe("when evaluating code containing /*exported */ block", () => { | |
1271 | ||
1272 | it("we should behave nicely when no matching variable is found", () => { | |
1273 | const code = "/* exported horse */"; | |
1274 | const config = { rules: {} }; | |
1275 | ||
1276 | linter.verify(code, config, filename, true); | |
1277 | }); | |
1278 | ||
1279 | it("variables should be exported", () => { | |
1280 | const code = "/* exported horse */\n\nvar horse = 'circus'"; | |
1281 | const config = { rules: { checker: "error" } }; | |
1282 | let spy; | |
1283 | ||
1284 | linter.defineRule("checker", context => { | |
1285 | spy = sinon.spy(() => { | |
1286 | const scope = context.getScope(), | |
1287 | horse = getVariable(scope, "horse"); | |
1288 | ||
1289 | assert.strictEqual(horse.eslintUsed, true); | |
1290 | }); | |
1291 | ||
1292 | return { Program: spy }; | |
1293 | }); | |
1294 | ||
1295 | linter.verify(code, config); | |
1296 | assert(spy && spy.calledOnce); | |
1297 | }); | |
1298 | ||
1299 | it("undefined variables should not be exported", () => { | |
1300 | const code = "/* exported horse */\n\nhorse = 'circus'"; | |
1301 | const config = { rules: { checker: "error" } }; | |
1302 | let spy; | |
1303 | ||
1304 | linter.defineRule("checker", context => { | |
1305 | spy = sinon.spy(() => { | |
1306 | const scope = context.getScope(), | |
1307 | horse = getVariable(scope, "horse"); | |
1308 | ||
1309 | assert.strictEqual(horse, null); | |
1310 | }); | |
1311 | ||
1312 | return { Program: spy }; | |
1313 | }); | |
1314 | ||
1315 | linter.verify(code, config); | |
1316 | assert(spy && spy.calledOnce); | |
1317 | }); | |
1318 | ||
1319 | it("variables should be exported in strict mode", () => { | |
1320 | const code = "/* exported horse */\n'use strict';\nvar horse = 'circus'"; | |
1321 | const config = { rules: { checker: "error" } }; | |
1322 | let spy; | |
1323 | ||
1324 | linter.defineRule("checker", context => { | |
1325 | spy = sinon.spy(() => { | |
1326 | const scope = context.getScope(), | |
1327 | horse = getVariable(scope, "horse"); | |
1328 | ||
1329 | assert.strictEqual(horse.eslintUsed, true); | |
1330 | }); | |
1331 | ||
1332 | return { Program: spy }; | |
1333 | }); | |
1334 | ||
1335 | linter.verify(code, config); | |
1336 | assert(spy && spy.calledOnce); | |
1337 | }); | |
1338 | ||
1339 | it("variables should not be exported in the es6 module environment", () => { | |
1340 | const code = "/* exported horse */\nvar horse = 'circus'"; | |
1341 | const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }; | |
1342 | let spy; | |
1343 | ||
1344 | linter.defineRule("checker", context => { | |
1345 | spy = sinon.spy(() => { | |
1346 | const scope = context.getScope(), | |
1347 | horse = getVariable(scope, "horse"); | |
1348 | ||
1349 | assert.strictEqual(horse, null); // there is no global scope at all | |
1350 | }); | |
1351 | ||
1352 | return { Program: spy }; | |
1353 | }); | |
1354 | ||
1355 | linter.verify(code, config); | |
1356 | assert(spy && spy.calledOnce); | |
1357 | }); | |
1358 | ||
1359 | it("variables should not be exported when in the node environment", () => { | |
1360 | const code = "/* exported horse */\nvar horse = 'circus'"; | |
1361 | const config = { rules: { checker: "error" }, env: { node: true } }; | |
1362 | let spy; | |
1363 | ||
1364 | linter.defineRule("checker", context => { | |
1365 | spy = sinon.spy(() => { | |
1366 | const scope = context.getScope(), | |
1367 | horse = getVariable(scope, "horse"); | |
1368 | ||
1369 | assert.strictEqual(horse, null); // there is no global scope at all | |
1370 | }); | |
1371 | ||
1372 | return { Program: spy }; | |
1373 | }); | |
1374 | ||
1375 | linter.verify(code, config); | |
1376 | assert(spy && spy.calledOnce); | |
1377 | }); | |
1378 | }); | |
1379 | ||
1380 | describe("when evaluating code containing a line comment", () => { | |
1381 | const code = "//global a \n function f() {}"; | |
1382 | ||
1383 | it("should not introduce a global variable", () => { | |
1384 | const config = { rules: { checker: "error" } }; | |
1385 | let spy; | |
1386 | ||
1387 | linter.defineRule("checker", context => { | |
1388 | spy = sinon.spy(() => { | |
1389 | const scope = context.getScope(); | |
1390 | ||
1391 | assert.strictEqual(getVariable(scope, "a"), null); | |
1392 | }); | |
1393 | ||
1394 | return { Program: spy }; | |
1395 | }); | |
1396 | ||
1397 | linter.verify(code, config); | |
1398 | assert(spy && spy.calledOnce); | |
1399 | }); | |
1400 | }); | |
1401 | ||
1402 | describe("when evaluating code containing normal block comments", () => { | |
1403 | const code = "/**/ /*a*/ /*b:true*/ /*foo c:false*/"; | |
1404 | ||
1405 | it("should not introduce a global variable", () => { | |
1406 | const config = { rules: { checker: "error" } }; | |
1407 | let spy; | |
1408 | ||
1409 | linter.defineRule("checker", context => { | |
1410 | spy = sinon.spy(() => { | |
1411 | const scope = context.getScope(); | |
1412 | ||
1413 | assert.strictEqual(getVariable(scope, "a"), null); | |
1414 | assert.strictEqual(getVariable(scope, "b"), null); | |
1415 | assert.strictEqual(getVariable(scope, "foo"), null); | |
1416 | assert.strictEqual(getVariable(scope, "c"), null); | |
1417 | }); | |
1418 | ||
1419 | return { Program: spy }; | |
1420 | }); | |
1421 | ||
1422 | linter.verify(code, config); | |
1423 | assert(spy && spy.calledOnce); | |
1424 | }); | |
1425 | }); | |
1426 | ||
1427 | describe("when evaluating any code", () => { | |
1428 | const code = "x"; | |
1429 | ||
1430 | it("builtin global variables should be available in the global scope", () => { | |
1431 | const config = { rules: { checker: "error" } }; | |
1432 | let spy; | |
1433 | ||
1434 | linter.defineRule("checker", context => { | |
1435 | spy = sinon.spy(() => { | |
1436 | const scope = context.getScope(); | |
1437 | ||
1438 | assert.notStrictEqual(getVariable(scope, "Object"), null); | |
1439 | assert.notStrictEqual(getVariable(scope, "Array"), null); | |
1440 | assert.notStrictEqual(getVariable(scope, "undefined"), null); | |
1441 | }); | |
1442 | ||
1443 | return { Program: spy }; | |
1444 | }); | |
1445 | ||
1446 | linter.verify(code, config); | |
1447 | assert(spy && spy.calledOnce); | |
1448 | }); | |
1449 | ||
1450 | it("ES6 global variables should not be available by default", () => { | |
1451 | const config = { rules: { checker: "error" } }; | |
1452 | let spy; | |
1453 | ||
1454 | linter.defineRule("checker", context => { | |
1455 | spy = sinon.spy(() => { | |
1456 | const scope = context.getScope(); | |
1457 | ||
1458 | assert.strictEqual(getVariable(scope, "Promise"), null); | |
1459 | assert.strictEqual(getVariable(scope, "Symbol"), null); | |
1460 | assert.strictEqual(getVariable(scope, "WeakMap"), null); | |
1461 | }); | |
1462 | ||
1463 | return { Program: spy }; | |
1464 | }); | |
1465 | ||
1466 | linter.verify(code, config); | |
1467 | assert(spy && spy.calledOnce); | |
1468 | }); | |
1469 | ||
1470 | it("ES6 global variables should be available in the es6 environment", () => { | |
1471 | const config = { rules: { checker: "error" }, env: { es6: true } }; | |
1472 | let spy; | |
1473 | ||
1474 | linter.defineRule("checker", context => { | |
1475 | spy = sinon.spy(() => { | |
1476 | const scope = context.getScope(); | |
1477 | ||
1478 | assert.notStrictEqual(getVariable(scope, "Promise"), null); | |
1479 | assert.notStrictEqual(getVariable(scope, "Symbol"), null); | |
1480 | assert.notStrictEqual(getVariable(scope, "WeakMap"), null); | |
1481 | }); | |
1482 | ||
1483 | return { Program: spy }; | |
1484 | }); | |
1485 | ||
1486 | linter.verify(code, config); | |
1487 | assert(spy && spy.calledOnce); | |
1488 | }); | |
1489 | ||
1490 | it("ES6 global variables can be disabled when the es6 environment is enabled", () => { | |
1491 | const config = { rules: { checker: "error" }, globals: { Promise: "off", Symbol: "off", WeakMap: "off" }, env: { es6: true } }; | |
1492 | let spy; | |
1493 | ||
1494 | linter.defineRule("checker", context => { | |
1495 | spy = sinon.spy(() => { | |
1496 | const scope = context.getScope(); | |
1497 | ||
1498 | assert.strictEqual(getVariable(scope, "Promise"), null); | |
1499 | assert.strictEqual(getVariable(scope, "Symbol"), null); | |
1500 | assert.strictEqual(getVariable(scope, "WeakMap"), null); | |
1501 | }); | |
1502 | ||
1503 | return { Program: spy }; | |
1504 | }); | |
1505 | ||
1506 | linter.verify(code, config); | |
1507 | assert(spy && spy.calledOnce); | |
1508 | }); | |
1509 | }); | |
1510 | ||
1511 | describe("at any time", () => { | |
1512 | const code = "new-rule"; | |
1513 | ||
1514 | it("can add a rule dynamically", () => { | |
1515 | linter.defineRule(code, context => ({ | |
1516 | Literal(node) { | |
1517 | context.report(node, "message"); | |
1518 | } | |
1519 | })); | |
1520 | ||
1521 | const config = { rules: {} }; | |
1522 | ||
1523 | config.rules[code] = 1; | |
1524 | ||
1525 | const messages = linter.verify("0", config, filename); | |
1526 | ||
1527 | assert.strictEqual(messages.length, 1); | |
1528 | assert.strictEqual(messages[0].ruleId, code); | |
1529 | assert.strictEqual(messages[0].nodeType, "Literal"); | |
1530 | }); | |
1531 | }); | |
1532 | ||
1533 | describe("at any time", () => { | |
1534 | const code = ["new-rule-0", "new-rule-1"]; | |
1535 | ||
1536 | it("can add multiple rules dynamically", () => { | |
1537 | const config = { rules: {} }; | |
1538 | const newRules = {}; | |
1539 | ||
1540 | code.forEach(item => { | |
1541 | config.rules[item] = 1; | |
1542 | newRules[item] = function(context) { | |
1543 | return { | |
1544 | Literal(node) { | |
1545 | context.report(node, "message"); | |
1546 | } | |
1547 | }; | |
1548 | }; | |
1549 | }); | |
1550 | linter.defineRules(newRules); | |
1551 | ||
1552 | const messages = linter.verify("0", config, filename); | |
1553 | ||
1554 | assert.strictEqual(messages.length, code.length); | |
1555 | code.forEach(item => { | |
1556 | assert.ok(messages.some(message => message.ruleId === item)); | |
1557 | }); | |
1558 | messages.forEach(message => { | |
1559 | assert.strictEqual(message.nodeType, "Literal"); | |
1560 | }); | |
1561 | }); | |
1562 | }); | |
1563 | ||
1564 | describe("at any time", () => { | |
1565 | const code = "filename-rule"; | |
1566 | ||
1567 | it("has access to the filename", () => { | |
1568 | linter.defineRule(code, context => ({ | |
1569 | Literal(node) { | |
1570 | context.report(node, context.getFilename()); | |
1571 | } | |
1572 | })); | |
1573 | ||
1574 | const config = { rules: {} }; | |
1575 | ||
1576 | config.rules[code] = 1; | |
1577 | ||
1578 | const messages = linter.verify("0", config, filename); | |
1579 | ||
1580 | assert.strictEqual(messages[0].message, filename); | |
1581 | }); | |
1582 | ||
1583 | it("defaults filename to '<input>'", () => { | |
1584 | linter.defineRule(code, context => ({ | |
1585 | Literal(node) { | |
1586 | context.report(node, context.getFilename()); | |
1587 | } | |
1588 | })); | |
1589 | ||
1590 | const config = { rules: {} }; | |
1591 | ||
1592 | config.rules[code] = 1; | |
1593 | ||
1594 | const messages = linter.verify("0", config); | |
1595 | ||
1596 | assert.strictEqual(messages[0].message, "<input>"); | |
1597 | }); | |
1598 | }); | |
1599 | ||
1600 | describe("when evaluating code with comments to enable rules", () => { | |
1601 | ||
1602 | it("should report a violation", () => { | |
1603 | const code = "/*eslint no-alert:1*/ alert('test');"; | |
1604 | const config = { rules: {} }; | |
1605 | ||
1606 | const messages = linter.verify(code, config, filename); | |
1607 | ||
1608 | assert.strictEqual(messages.length, 1); | |
1609 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
1610 | assert.strictEqual(messages[0].message, "Unexpected alert."); | |
1611 | assert.include(messages[0].nodeType, "CallExpression"); | |
1612 | }); | |
1613 | ||
1614 | it("rules should not change initial config", () => { | |
1615 | const config = { rules: { strict: 2 } }; | |
1616 | const codeA = "/*eslint strict: 0*/ function bar() { return 2; }"; | |
1617 | const codeB = "function foo() { return 1; }"; | |
1618 | let messages = linter.verify(codeA, config, filename, false); | |
1619 | ||
1620 | assert.strictEqual(messages.length, 0); | |
1621 | ||
1622 | messages = linter.verify(codeB, config, filename, false); | |
1623 | assert.strictEqual(messages.length, 1); | |
1624 | }); | |
1625 | ||
1626 | it("rules should not change initial config", () => { | |
1627 | const config = { rules: { quotes: [2, "double"] } }; | |
1628 | const codeA = "/*eslint quotes: 0*/ function bar() { return '2'; }"; | |
1629 | const codeB = "function foo() { return '1'; }"; | |
1630 | let messages = linter.verify(codeA, config, filename, false); | |
1631 | ||
1632 | assert.strictEqual(messages.length, 0); | |
1633 | ||
1634 | messages = linter.verify(codeB, config, filename, false); | |
1635 | assert.strictEqual(messages.length, 1); | |
1636 | }); | |
1637 | ||
1638 | it("rules should not change initial config", () => { | |
1639 | const config = { rules: { quotes: [2, "double"] } }; | |
1640 | const codeA = "/*eslint quotes: [0, \"single\"]*/ function bar() { return '2'; }"; | |
1641 | const codeB = "function foo() { return '1'; }"; | |
1642 | let messages = linter.verify(codeA, config, filename, false); | |
1643 | ||
1644 | assert.strictEqual(messages.length, 0); | |
1645 | ||
1646 | messages = linter.verify(codeB, config, filename, false); | |
1647 | assert.strictEqual(messages.length, 1); | |
1648 | }); | |
1649 | ||
1650 | it("rules should not change initial config", () => { | |
1651 | const config = { rules: { "no-unused-vars": [2, { vars: "all" }] } }; | |
1652 | const codeA = "/*eslint no-unused-vars: [0, {\"vars\": \"local\"}]*/ var a = 44;"; | |
1653 | const codeB = "var b = 55;"; | |
1654 | let messages = linter.verify(codeA, config, filename, false); | |
1655 | ||
1656 | assert.strictEqual(messages.length, 0); | |
1657 | ||
1658 | messages = linter.verify(codeB, config, filename, false); | |
1659 | assert.strictEqual(messages.length, 1); | |
1660 | }); | |
1661 | }); | |
1662 | ||
1663 | describe("when evaluating code with invalid comments to enable rules", () => { | |
1664 | it("should report a violation when the config is not a valid rule configuration", () => { | |
1665 | assert.deepStrictEqual( | |
1666 | linter.verify("/*eslint no-alert:true*/ alert('test');", {}), | |
1667 | [ | |
1668 | { | |
1669 | severity: 2, | |
1670 | ruleId: "no-alert", | |
1671 | message: "Configuration for rule \"no-alert\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed 'true').\n", | |
1672 | line: 1, | |
1673 | column: 1, | |
1674 | endLine: 1, | |
1675 | endColumn: 25, | |
1676 | nodeType: null | |
1677 | } | |
1678 | ] | |
1679 | ); | |
1680 | }); | |
1681 | ||
1682 | it("should report a violation when the config violates a rule's schema", () => { | |
1683 | assert.deepStrictEqual( | |
1684 | linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {}), | |
1685 | [ | |
1686 | { | |
1687 | severity: 2, | |
1688 | ruleId: "no-alert", | |
1689 | message: "Configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n", | |
1690 | line: 1, | |
1691 | column: 1, | |
1692 | endLine: 1, | |
1693 | endColumn: 63, | |
1694 | nodeType: null | |
1695 | } | |
1696 | ] | |
1697 | ); | |
1698 | }); | |
1699 | }); | |
1700 | ||
1701 | describe("when evaluating code with comments to disable rules", () => { | |
1702 | const code = "/*eslint no-alert:0*/ alert('test');"; | |
1703 | ||
1704 | it("should not report a violation", () => { | |
1705 | const config = { rules: { "no-alert": 1 } }; | |
1706 | ||
1707 | const messages = linter.verify(code, config, filename); | |
1708 | ||
1709 | assert.strictEqual(messages.length, 0); | |
1710 | }); | |
1711 | }); | |
1712 | ||
1713 | describe("when evaluating code with comments to disable rules", () => { | |
1714 | let code, messages; | |
1715 | ||
1716 | it("should report an error when disabling a non-existent rule in inline comment", () => { | |
1717 | code = "/*eslint foo:0*/ ;"; | |
1718 | messages = linter.verify(code, {}, filename); | |
1719 | assert.strictEqual(messages.length, 1); | |
1720 | assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); | |
1721 | ||
1722 | code = "/*eslint-disable foo*/ ;"; | |
1723 | messages = linter.verify(code, {}, filename); | |
1724 | assert.strictEqual(messages.length, 1); | |
1725 | assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); | |
1726 | ||
1727 | code = "/*eslint-disable-line foo*/ ;"; | |
1728 | messages = linter.verify(code, {}, filename); | |
1729 | assert.strictEqual(messages.length, 1); | |
1730 | assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); | |
1731 | ||
1732 | code = "/*eslint-disable-next-line foo*/ ;"; | |
1733 | messages = linter.verify(code, {}, filename); | |
1734 | assert.strictEqual(messages.length, 1); | |
1735 | assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); | |
1736 | }); | |
1737 | ||
1738 | it("should not report an error, when disabling a non-existent rule in config", () => { | |
1739 | messages = linter.verify("", { rules: { foo: 0 } }, filename); | |
1740 | ||
1741 | assert.strictEqual(messages.length, 0); | |
1742 | }); | |
1743 | ||
1744 | it("should report an error, when config a non-existent rule in config", () => { | |
1745 | messages = linter.verify("", { rules: { foo: 1 } }, filename); | |
1746 | assert.strictEqual(messages.length, 1); | |
1747 | assert.strictEqual(messages[0].severity, 2); | |
1748 | assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); | |
1749 | ||
1750 | messages = linter.verify("", { rules: { foo: 2 } }, filename); | |
1751 | assert.strictEqual(messages.length, 1); | |
1752 | assert.strictEqual(messages[0].severity, 2); | |
1753 | assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); | |
1754 | }); | |
1755 | }); | |
1756 | ||
1757 | describe("when evaluating code with comments to enable multiple rules", () => { | |
1758 | const code = "/*eslint no-alert:1 no-console:1*/ alert('test'); console.log('test');"; | |
1759 | ||
1760 | it("should report a violation", () => { | |
1761 | const config = { rules: {} }; | |
1762 | ||
1763 | const messages = linter.verify(code, config, filename); | |
1764 | ||
1765 | assert.strictEqual(messages.length, 2); | |
1766 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
1767 | assert.strictEqual(messages[0].message, "Unexpected alert."); | |
1768 | assert.include(messages[0].nodeType, "CallExpression"); | |
1769 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
1770 | }); | |
1771 | }); | |
1772 | ||
1773 | describe("when evaluating code with comments to enable and disable multiple rules", () => { | |
1774 | const code = "/*eslint no-alert:1 no-console:0*/ alert('test'); console.log('test');"; | |
1775 | ||
1776 | it("should report a violation", () => { | |
1777 | const config = { rules: { "no-console": 1, "no-alert": 0 } }; | |
1778 | ||
1779 | const messages = linter.verify(code, config, filename); | |
1780 | ||
1781 | assert.strictEqual(messages.length, 1); | |
1782 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
1783 | assert.strictEqual(messages[0].message, "Unexpected alert."); | |
1784 | assert.include(messages[0].nodeType, "CallExpression"); | |
1785 | }); | |
1786 | }); | |
1787 | ||
1788 | describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => { | |
1789 | ||
1790 | beforeEach(() => { | |
1791 | linter.defineRule("test-plugin/test-rule", context => ({ | |
1792 | Literal(node) { | |
1793 | if (node.value === "trigger violation") { | |
1794 | context.report(node, "Reporting violation."); | |
1795 | } | |
1796 | } | |
1797 | })); | |
1798 | }); | |
1799 | ||
1800 | it("should not report a violation when inline comment enables plugin rule and there's no violation", () => { | |
1801 | const config = { rules: {} }; | |
1802 | const code = "/*eslint test-plugin/test-rule: 2*/ var a = \"no violation\";"; | |
1803 | ||
1804 | const messages = linter.verify(code, config, filename); | |
1805 | ||
1806 | assert.strictEqual(messages.length, 0); | |
1807 | }); | |
1808 | ||
1809 | it("should not report a violation when inline comment disables plugin rule", () => { | |
1810 | const code = "/*eslint test-plugin/test-rule:0*/ var a = \"trigger violation\""; | |
1811 | const config = { rules: { "test-plugin/test-rule": 1 } }; | |
1812 | ||
1813 | const messages = linter.verify(code, config, filename); | |
1814 | ||
1815 | assert.strictEqual(messages.length, 0); | |
1816 | }); | |
1817 | ||
1818 | it("should report a violation when the report is right before the comment", () => { | |
1819 | const code = " /* eslint-disable */ "; | |
1820 | ||
1821 | linter.defineRule("checker", context => ({ | |
1822 | Program() { | |
1823 | context.report({ loc: { line: 1, column: 0 }, message: "foo" }); | |
1824 | } | |
1825 | })); | |
1826 | const problems = linter.verify(code, { rules: { checker: "error" } }); | |
1827 | ||
1828 | assert.strictEqual(problems.length, 1); | |
1829 | assert.strictEqual(problems[0].message, "foo"); | |
1830 | }); | |
1831 | ||
1832 | it("should not report a violation when the report is right at the start of the comment", () => { | |
1833 | const code = " /* eslint-disable */ "; | |
1834 | ||
1835 | linter.defineRule("checker", context => ({ | |
1836 | Program() { | |
1837 | context.report({ loc: { line: 1, column: 1 }, message: "foo" }); | |
1838 | } | |
1839 | })); | |
1840 | const problems = linter.verify(code, { rules: { checker: "error" } }); | |
1841 | ||
1842 | assert.strictEqual(problems.length, 0); | |
1843 | }); | |
1844 | ||
1845 | it("rules should not change initial config", () => { | |
1846 | const config = { rules: { "test-plugin/test-rule": 2 } }; | |
1847 | const codeA = "/*eslint test-plugin/test-rule: 0*/ var a = \"trigger violation\";"; | |
1848 | const codeB = "var a = \"trigger violation\";"; | |
1849 | let messages = linter.verify(codeA, config, filename, false); | |
1850 | ||
1851 | assert.strictEqual(messages.length, 0); | |
1852 | ||
1853 | messages = linter.verify(codeB, config, filename, false); | |
1854 | assert.strictEqual(messages.length, 1); | |
1855 | }); | |
1856 | }); | |
1857 | ||
1858 | describe("when evaluating code with comments to enable and disable all reporting", () => { | |
1859 | it("should report a violation", () => { | |
1860 | ||
1861 | const code = [ | |
1862 | "/*eslint-disable */", | |
1863 | "alert('test');", | |
1864 | "/*eslint-enable */", | |
1865 | "alert('test');" | |
1866 | ].join("\n"); | |
1867 | const config = { rules: { "no-alert": 1 } }; | |
1868 | ||
1869 | const messages = linter.verify(code, config, filename); | |
1870 | ||
1871 | assert.strictEqual(messages.length, 1); | |
1872 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
1873 | assert.strictEqual(messages[0].message, "Unexpected alert."); | |
1874 | assert.include(messages[0].nodeType, "CallExpression"); | |
1875 | assert.strictEqual(messages[0].line, 4); | |
1876 | }); | |
1877 | ||
1878 | it("should not report a violation", () => { | |
1879 | const code = [ | |
1880 | "/*eslint-disable */", | |
1881 | "alert('test');", | |
1882 | "alert('test');" | |
1883 | ].join("\n"); | |
1884 | const config = { rules: { "no-alert": 1 } }; | |
1885 | ||
1886 | const messages = linter.verify(code, config, filename); | |
1887 | ||
1888 | assert.strictEqual(messages.length, 0); | |
1889 | }); | |
1890 | ||
1891 | it("should not report a violation", () => { | |
1892 | const code = [ | |
1893 | " alert('test1');/*eslint-disable */\n", | |
1894 | "alert('test');", | |
1895 | " alert('test');\n", | |
1896 | "/*eslint-enable */alert('test2');" | |
1897 | ].join(""); | |
1898 | const config = { rules: { "no-alert": 1 } }; | |
1899 | ||
1900 | const messages = linter.verify(code, config, filename); | |
1901 | ||
1902 | assert.strictEqual(messages.length, 2); | |
1903 | assert.strictEqual(messages[0].column, 21); | |
1904 | assert.strictEqual(messages[1].column, 19); | |
1905 | }); | |
1906 | ||
1907 | it("should report a violation", () => { | |
1908 | ||
1909 | const code = [ | |
1910 | "/*eslint-disable */", | |
1911 | "alert('test');", | |
1912 | "/*eslint-disable */", | |
1913 | "alert('test');", | |
1914 | "/*eslint-enable*/", | |
1915 | "alert('test');", | |
1916 | "/*eslint-enable*/" | |
1917 | ].join("\n"); | |
1918 | ||
1919 | const config = { rules: { "no-alert": 1 } }; | |
1920 | ||
1921 | const messages = linter.verify(code, config, filename); | |
1922 | ||
1923 | assert.strictEqual(messages.length, 1); | |
1924 | }); | |
1925 | ||
1926 | ||
1927 | it("should not report a violation", () => { | |
1928 | const code = [ | |
1929 | "/*eslint-disable */", | |
1930 | "(function(){ var b = 44;})()", | |
1931 | "/*eslint-enable */;any();" | |
1932 | ].join("\n"); | |
1933 | ||
1934 | const config = { rules: { "no-unused-vars": 1 } }; | |
1935 | ||
1936 | const messages = linter.verify(code, config, filename); | |
1937 | ||
1938 | assert.strictEqual(messages.length, 0); | |
1939 | }); | |
1940 | ||
1941 | it("should not report a violation", () => { | |
1942 | const code = [ | |
1943 | "(function(){ /*eslint-disable */ var b = 44;})()", | |
1944 | "/*eslint-enable */;any();" | |
1945 | ].join("\n"); | |
1946 | ||
1947 | const config = { rules: { "no-unused-vars": 1 } }; | |
1948 | ||
1949 | const messages = linter.verify(code, config, filename); | |
1950 | ||
1951 | assert.strictEqual(messages.length, 0); | |
1952 | }); | |
1953 | }); | |
1954 | ||
1955 | describe("when evaluating code with comments to ignore reporting on specific rules on a specific line", () => { | |
1956 | ||
1957 | describe("eslint-disable-line", () => { | |
1958 | it("should report a violation", () => { | |
1959 | const code = [ | |
1960 | "alert('test'); // eslint-disable-line no-alert", | |
1961 | "console.log('test');" // here | |
1962 | ].join("\n"); | |
1963 | const config = { | |
1964 | rules: { | |
1965 | "no-alert": 1, | |
1966 | "no-console": 1 | |
1967 | } | |
1968 | }; | |
1969 | ||
1970 | const messages = linter.verify(code, config, filename); | |
1971 | ||
1972 | assert.strictEqual(messages.length, 1); | |
1973 | ||
1974 | assert.strictEqual(messages[0].ruleId, "no-console"); | |
1975 | }); | |
1976 | ||
1977 | it("should report a violation", () => { | |
1978 | const code = [ | |
1979 | "alert('test'); // eslint-disable-line no-alert", | |
1980 | "console.log('test'); // eslint-disable-line no-console", | |
1981 | "alert('test');" // here | |
1982 | ].join("\n"); | |
1983 | const config = { | |
1984 | rules: { | |
1985 | "no-alert": 1, | |
1986 | "no-console": 1 | |
1987 | } | |
1988 | }; | |
1989 | ||
1990 | const messages = linter.verify(code, config, filename); | |
1991 | ||
1992 | assert.strictEqual(messages.length, 1); | |
1993 | ||
1994 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
1995 | }); | |
1996 | ||
1997 | it("should report a violation if eslint-disable-line in a block comment is not on a single line", () => { | |
1998 | const code = [ | |
1999 | "/* eslint-disable-line", | |
2000 | "*", | |
2001 | "*/ console.log('test');" // here | |
2002 | ].join("\n"); | |
2003 | const config = { | |
2004 | rules: { | |
2005 | "no-console": 1 | |
2006 | } | |
2007 | }; | |
2008 | ||
2009 | const messages = linter.verify(code, config, filename); | |
2010 | ||
2011 | assert.strictEqual(messages.length, 2); | |
2012 | ||
2013 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
2014 | }); | |
2015 | ||
2016 | it("should not disable rule and add an extra report if eslint-disable-line in a block comment is not on a single line", () => { | |
2017 | const code = [ | |
2018 | "alert('test'); /* eslint-disable-line ", | |
2019 | "no-alert */" | |
2020 | ].join("\n"); | |
2021 | const config = { | |
2022 | rules: { | |
2023 | "no-alert": 1 | |
2024 | } | |
2025 | }; | |
2026 | ||
2027 | const messages = linter.verify(code, config); | |
2028 | ||
2029 | assert.deepStrictEqual(messages, [ | |
2030 | { | |
2031 | ruleId: "no-alert", | |
2032 | severity: 1, | |
2033 | line: 1, | |
2034 | column: 1, | |
2035 | endLine: 1, | |
2036 | endColumn: 14, | |
2037 | message: "Unexpected alert.", | |
2038 | messageId: "unexpected", | |
2039 | nodeType: "CallExpression" | |
2040 | }, | |
2041 | { | |
2042 | ruleId: null, | |
2043 | severity: 2, | |
2044 | message: "eslint-disable-line comment should not span multiple lines.", | |
2045 | line: 1, | |
2046 | column: 16, | |
2047 | endLine: 2, | |
2048 | endColumn: 12, | |
2049 | nodeType: null | |
2050 | } | |
2051 | ]); | |
2052 | }); | |
2053 | ||
2054 | it("should not report a violation for eslint-disable-line in block comment", () => { | |
2055 | const code = [ | |
2056 | "alert('test'); // eslint-disable-line no-alert", | |
2057 | "alert('test'); /*eslint-disable-line no-alert*/" | |
2058 | ].join("\n"); | |
2059 | const config = { | |
2060 | rules: { | |
2061 | "no-alert": 1 | |
2062 | } | |
2063 | }; | |
2064 | ||
2065 | const messages = linter.verify(code, config, filename); | |
2066 | ||
2067 | assert.strictEqual(messages.length, 0); | |
2068 | }); | |
2069 | ||
2070 | it("should not report a violation", () => { | |
2071 | const code = [ | |
2072 | "alert('test'); // eslint-disable-line no-alert", | |
2073 | "console.log('test'); // eslint-disable-line no-console" | |
2074 | ].join("\n"); | |
2075 | const config = { | |
2076 | rules: { | |
2077 | "no-alert": 1, | |
2078 | "no-console": 1 | |
2079 | } | |
2080 | }; | |
2081 | ||
2082 | const messages = linter.verify(code, config, filename); | |
2083 | ||
2084 | assert.strictEqual(messages.length, 0); | |
2085 | }); | |
2086 | ||
2087 | it("should not report a violation", () => { | |
2088 | const code = [ | |
2089 | "alert('test') // eslint-disable-line no-alert, quotes, semi", | |
2090 | "console.log('test'); // eslint-disable-line" | |
2091 | ].join("\n"); | |
2092 | const config = { | |
2093 | rules: { | |
2094 | "no-alert": 1, | |
2095 | quotes: [1, "double"], | |
2096 | semi: [1, "always"], | |
2097 | "no-console": 1 | |
2098 | } | |
2099 | }; | |
2100 | ||
2101 | const messages = linter.verify(code, config, filename); | |
2102 | ||
2103 | assert.strictEqual(messages.length, 0); | |
2104 | }); | |
2105 | ||
2106 | it("should not report a violation", () => { | |
2107 | const code = [ | |
2108 | "alert('test') /* eslint-disable-line no-alert, quotes, semi */", | |
2109 | "console.log('test'); /* eslint-disable-line */" | |
2110 | ].join("\n"); | |
2111 | const config = { | |
2112 | rules: { | |
2113 | "no-alert": 1, | |
2114 | quotes: [1, "double"], | |
2115 | semi: [1, "always"], | |
2116 | "no-console": 1 | |
2117 | } | |
2118 | }; | |
2119 | ||
2120 | const messages = linter.verify(code, config, filename); | |
2121 | ||
2122 | assert.strictEqual(messages.length, 0); | |
2123 | }); | |
2124 | ||
2125 | it("should ignore violations of multiple rules when specified in mixed comments", () => { | |
2126 | const code = [ | |
2127 | " alert(\"test\"); /* eslint-disable-line no-alert */ // eslint-disable-line quotes" | |
2128 | ].join("\n"); | |
2129 | const config = { | |
2130 | rules: { | |
2131 | "no-alert": 1, | |
2132 | quotes: [1, "single"] | |
2133 | } | |
2134 | }; | |
2135 | const messages = linter.verify(code, config, filename); | |
2136 | ||
2137 | assert.strictEqual(messages.length, 0); | |
2138 | }); | |
2139 | }); | |
2140 | ||
2141 | describe("eslint-disable-next-line", () => { | |
2142 | it("should ignore violation of specified rule on next line", () => { | |
2143 | const code = [ | |
2144 | "// eslint-disable-next-line no-alert", | |
2145 | "alert('test');", | |
2146 | "console.log('test');" | |
2147 | ].join("\n"); | |
2148 | const config = { | |
2149 | rules: { | |
2150 | "no-alert": 1, | |
2151 | "no-console": 1 | |
2152 | } | |
2153 | }; | |
2154 | const messages = linter.verify(code, config, filename); | |
2155 | ||
2156 | assert.strictEqual(messages.length, 1); | |
2157 | assert.strictEqual(messages[0].ruleId, "no-console"); | |
2158 | }); | |
2159 | ||
2160 | it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { | |
2161 | const code = [ | |
2162 | "/* eslint-disable-next-line no-alert */", | |
2163 | "alert('test');", | |
2164 | "console.log('test');" | |
2165 | ].join("\n"); | |
2166 | const config = { | |
2167 | rules: { | |
2168 | "no-alert": 1, | |
2169 | "no-console": 1 | |
2170 | } | |
2171 | }; | |
2172 | const messages = linter.verify(code, config, filename); | |
2173 | ||
2174 | assert.strictEqual(messages.length, 1); | |
2175 | assert.strictEqual(messages[0].ruleId, "no-console"); | |
2176 | }); | |
2177 | it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { | |
2178 | const code = [ | |
2179 | "/* eslint-disable-next-line no-alert */", | |
2180 | "alert('test');" | |
2181 | ].join("\n"); | |
2182 | const config = { | |
2183 | rules: { | |
2184 | "no-alert": 1 | |
2185 | } | |
2186 | }; | |
2187 | const messages = linter.verify(code, config, filename); | |
2188 | ||
2189 | assert.strictEqual(messages.length, 0); | |
2190 | }); | |
2191 | ||
2192 | it("should not ignore violation if block comment is not on a single line", () => { | |
2193 | const code = [ | |
2194 | "/* eslint-disable-next-line", | |
2195 | "no-alert */alert('test');" | |
2196 | ].join("\n"); | |
2197 | const config = { | |
2198 | rules: { | |
2199 | "no-alert": 1 | |
2200 | } | |
2201 | }; | |
2202 | const messages = linter.verify(code, config, filename); | |
2203 | ||
2204 | assert.strictEqual(messages.length, 2); | |
2205 | assert.strictEqual(messages[1].ruleId, "no-alert"); | |
2206 | }); | |
2207 | ||
2208 | it("should ignore violations only of specified rule", () => { | |
2209 | const code = [ | |
2210 | "// eslint-disable-next-line no-console", | |
2211 | "alert('test');", | |
2212 | "console.log('test');" | |
2213 | ].join("\n"); | |
2214 | const config = { | |
2215 | rules: { | |
2216 | "no-alert": 1, | |
2217 | "no-console": 1 | |
2218 | } | |
2219 | }; | |
2220 | const messages = linter.verify(code, config, filename); | |
2221 | ||
2222 | assert.strictEqual(messages.length, 2); | |
2223 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2224 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
2225 | }); | |
2226 | ||
2227 | it("should ignore violations of multiple rules when specified", () => { | |
2228 | const code = [ | |
2229 | "// eslint-disable-next-line no-alert, quotes", | |
2230 | "alert(\"test\");", | |
2231 | "console.log('test');" | |
2232 | ].join("\n"); | |
2233 | const config = { | |
2234 | rules: { | |
2235 | "no-alert": 1, | |
2236 | quotes: [1, "single"], | |
2237 | "no-console": 1 | |
2238 | } | |
2239 | }; | |
2240 | const messages = linter.verify(code, config, filename); | |
2241 | ||
2242 | assert.strictEqual(messages.length, 1); | |
2243 | assert.strictEqual(messages[0].ruleId, "no-console"); | |
2244 | }); | |
2245 | ||
2246 | it("should ignore violations of multiple rules when specified in mixed comments", () => { | |
2247 | const code = [ | |
2248 | "/* eslint-disable-next-line no-alert */ // eslint-disable-next-line quotes", | |
2249 | "alert(\"test\");" | |
2250 | ].join("\n"); | |
2251 | const config = { | |
2252 | rules: { | |
2253 | "no-alert": 1, | |
2254 | quotes: [1, "single"] | |
2255 | } | |
2256 | }; | |
2257 | const messages = linter.verify(code, config, filename); | |
2258 | ||
2259 | assert.strictEqual(messages.length, 0); | |
2260 | }); | |
2261 | ||
2262 | it("should ignore violations of only the specified rule on next line", () => { | |
2263 | const code = [ | |
2264 | "// eslint-disable-next-line quotes", | |
2265 | "alert(\"test\");", | |
2266 | "console.log('test');" | |
2267 | ].join("\n"); | |
2268 | const config = { | |
2269 | rules: { | |
2270 | "no-alert": 1, | |
2271 | quotes: [1, "single"], | |
2272 | "no-console": 1 | |
2273 | } | |
2274 | }; | |
2275 | const messages = linter.verify(code, config, filename); | |
2276 | ||
2277 | assert.strictEqual(messages.length, 2); | |
2278 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2279 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
2280 | }); | |
2281 | ||
2282 | it("should ignore violations of specified rule on next line only", () => { | |
2283 | const code = [ | |
2284 | "alert('test');", | |
2285 | "// eslint-disable-next-line no-alert", | |
2286 | "alert('test');", | |
2287 | "console.log('test');" | |
2288 | ].join("\n"); | |
2289 | const config = { | |
2290 | rules: { | |
2291 | "no-alert": 1, | |
2292 | "no-console": 1 | |
2293 | } | |
2294 | }; | |
2295 | const messages = linter.verify(code, config, filename); | |
2296 | ||
2297 | assert.strictEqual(messages.length, 2); | |
2298 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2299 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
2300 | }); | |
2301 | ||
2302 | it("should ignore all rule violations on next line if none specified", () => { | |
2303 | const code = [ | |
2304 | "// eslint-disable-next-line", | |
2305 | "alert(\"test\");", | |
2306 | "console.log('test')" | |
2307 | ].join("\n"); | |
2308 | const config = { | |
2309 | rules: { | |
2310 | semi: [1, "never"], | |
2311 | quotes: [1, "single"], | |
2312 | "no-alert": 1, | |
2313 | "no-console": 1 | |
2314 | } | |
2315 | }; | |
2316 | const messages = linter.verify(code, config, filename); | |
2317 | ||
2318 | assert.strictEqual(messages.length, 1); | |
2319 | assert.strictEqual(messages[0].ruleId, "no-console"); | |
2320 | }); | |
2321 | ||
2322 | it("should ignore violations if eslint-disable-next-line is a block comment", () => { | |
2323 | const code = [ | |
2324 | "alert('test');", | |
2325 | "/* eslint-disable-next-line no-alert */", | |
2326 | "alert('test');", | |
2327 | "console.log('test');" | |
2328 | ].join("\n"); | |
2329 | const config = { | |
2330 | rules: { | |
2331 | "no-alert": 1, | |
2332 | "no-console": 1 | |
2333 | } | |
2334 | }; | |
2335 | const messages = linter.verify(code, config, filename); | |
2336 | ||
2337 | assert.strictEqual(messages.length, 2); | |
2338 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2339 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
2340 | }); | |
2341 | ||
2342 | it("should report a violation", () => { | |
2343 | const code = [ | |
2344 | "/* eslint-disable-next-line", | |
2345 | "*", | |
2346 | "*/", | |
2347 | "console.log('test');" // here | |
2348 | ].join("\n"); | |
2349 | const config = { | |
2350 | rules: { | |
2351 | "no-alert": 1, | |
2352 | "no-console": 1 | |
2353 | } | |
2354 | }; | |
2355 | ||
2356 | const messages = linter.verify(code, config, filename); | |
2357 | ||
2358 | assert.strictEqual(messages.length, 2); | |
2359 | ||
2360 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
2361 | }); | |
2362 | ||
2363 | it("should not ignore violations if comment is of the type Shebang", () => { | |
2364 | const code = [ | |
2365 | "#! eslint-disable-next-line no-alert", | |
2366 | "alert('test');", | |
2367 | "console.log('test');" | |
2368 | ].join("\n"); | |
2369 | const config = { | |
2370 | rules: { | |
2371 | "no-alert": 1, | |
2372 | "no-console": 1 | |
2373 | } | |
2374 | }; | |
2375 | const messages = linter.verify(code, config, filename); | |
2376 | ||
2377 | assert.strictEqual(messages.length, 2); | |
2378 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2379 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
2380 | }); | |
2381 | }); | |
2382 | }); | |
2383 | ||
2384 | describe("when evaluating code with comments to enable and disable reporting of specific rules", () => { | |
2385 | ||
2386 | it("should report a violation", () => { | |
2387 | const code = [ | |
2388 | "/*eslint-disable no-alert */", | |
2389 | "alert('test');", | |
2390 | "console.log('test');" // here | |
2391 | ].join("\n"); | |
2392 | const config = { rules: { "no-alert": 1, "no-console": 1 } }; | |
2393 | ||
2394 | const messages = linter.verify(code, config, filename); | |
2395 | ||
2396 | assert.strictEqual(messages.length, 1); | |
2397 | ||
2398 | assert.strictEqual(messages[0].ruleId, "no-console"); | |
2399 | }); | |
2400 | ||
2401 | it("should report no violation", () => { | |
2402 | const code = [ | |
2403 | "/*eslint-disable no-unused-vars */", | |
2404 | "var foo; // eslint-disable-line no-unused-vars", | |
2405 | "var bar;", | |
2406 | "/* eslint-enable no-unused-vars */" // here | |
2407 | ].join("\n"); | |
2408 | const config = { rules: { "no-unused-vars": 2 } }; | |
2409 | ||
2410 | const messages = linter.verify(code, config, filename); | |
2411 | ||
2412 | assert.strictEqual(messages.length, 0); | |
2413 | }); | |
2414 | ||
2415 | it("should report no violation", () => { | |
2416 | const code = [ | |
2417 | "var foo1; // eslint-disable-line no-unused-vars", | |
2418 | "var foo2; // eslint-disable-line no-unused-vars", | |
2419 | "var foo3; // eslint-disable-line no-unused-vars", | |
2420 | "var foo4; // eslint-disable-line no-unused-vars", | |
2421 | "var foo5; // eslint-disable-line no-unused-vars" | |
2422 | ].join("\n"); | |
2423 | const config = { rules: { "no-unused-vars": 2 } }; | |
2424 | ||
2425 | const messages = linter.verify(code, config, filename); | |
2426 | ||
2427 | assert.strictEqual(messages.length, 0); | |
2428 | }); | |
2429 | ||
2430 | it("should report no violation", () => { | |
2431 | const code = [ | |
2432 | "/* eslint-disable quotes */", | |
2433 | "console.log(\"foo\");", | |
2434 | "/* eslint-enable quotes */" | |
2435 | ].join("\n"); | |
2436 | const config = { rules: { quotes: 2 } }; | |
2437 | ||
2438 | const messages = linter.verify(code, config, filename); | |
2439 | ||
2440 | assert.strictEqual(messages.length, 0); | |
2441 | }); | |
2442 | ||
2443 | it("should report a violation", () => { | |
2444 | const code = [ | |
2445 | "/*eslint-disable no-alert, no-console */", | |
2446 | "alert('test');", | |
2447 | "console.log('test');", | |
2448 | "/*eslint-enable*/", | |
2449 | ||
2450 | "alert('test');", // here | |
2451 | "console.log('test');" // here | |
2452 | ].join("\n"); | |
2453 | const config = { rules: { "no-alert": 1, "no-console": 1 } }; | |
2454 | ||
2455 | const messages = linter.verify(code, config, filename); | |
2456 | ||
2457 | assert.strictEqual(messages.length, 2); | |
2458 | ||
2459 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2460 | assert.strictEqual(messages[0].line, 5); | |
2461 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
2462 | assert.strictEqual(messages[1].line, 6); | |
2463 | }); | |
2464 | ||
2465 | it("should report a violation", () => { | |
2466 | const code = [ | |
2467 | "/*eslint-disable no-alert */", | |
2468 | "alert('test');", | |
2469 | "console.log('test');", | |
2470 | "/*eslint-enable no-console */", | |
2471 | ||
2472 | "alert('test');" // here | |
2473 | ].join("\n"); | |
2474 | const config = { rules: { "no-alert": 1, "no-console": 1 } }; | |
2475 | ||
2476 | const messages = linter.verify(code, config, filename); | |
2477 | ||
2478 | assert.strictEqual(messages.length, 1); | |
2479 | ||
2480 | assert.strictEqual(messages[0].ruleId, "no-console"); | |
2481 | }); | |
2482 | ||
2483 | ||
2484 | it("should report a violation", () => { | |
2485 | const code = [ | |
2486 | "/*eslint-disable no-alert, no-console */", | |
2487 | "alert('test');", | |
2488 | "console.log('test');", | |
2489 | "/*eslint-enable no-alert*/", | |
2490 | ||
2491 | "alert('test');", // here | |
2492 | "console.log('test');" | |
2493 | ].join("\n"); | |
2494 | const config = { rules: { "no-alert": 1, "no-console": 1 } }; | |
2495 | ||
2496 | const messages = linter.verify(code, config, filename); | |
2497 | ||
2498 | assert.strictEqual(messages.length, 1); | |
2499 | ||
2500 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2501 | assert.strictEqual(messages[0].line, 5); | |
2502 | }); | |
2503 | ||
2504 | ||
2505 | it("should report a violation", () => { | |
2506 | const code = [ | |
2507 | "/*eslint-disable no-alert */", | |
2508 | ||
2509 | "/*eslint-disable no-console */", | |
2510 | "alert('test');", | |
2511 | "console.log('test');", | |
2512 | "/*eslint-enable */", | |
2513 | ||
2514 | "alert('test');", // here | |
2515 | "console.log('test');", // here | |
2516 | ||
2517 | "/*eslint-enable */", | |
2518 | ||
2519 | "alert('test');", // here | |
2520 | "console.log('test');", // here | |
2521 | ||
2522 | "/*eslint-enable*/" | |
2523 | ].join("\n"); | |
2524 | const config = { rules: { "no-alert": 1, "no-console": 1 } }; | |
2525 | ||
2526 | const messages = linter.verify(code, config, filename); | |
2527 | ||
2528 | assert.strictEqual(messages.length, 4); | |
2529 | ||
2530 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2531 | assert.strictEqual(messages[0].line, 6); | |
2532 | ||
2533 | assert.strictEqual(messages[1].ruleId, "no-console"); | |
2534 | assert.strictEqual(messages[1].line, 7); | |
2535 | ||
2536 | assert.strictEqual(messages[2].ruleId, "no-alert"); | |
2537 | assert.strictEqual(messages[2].line, 9); | |
2538 | ||
2539 | assert.strictEqual(messages[3].ruleId, "no-console"); | |
2540 | assert.strictEqual(messages[3].line, 10); | |
2541 | ||
2542 | }); | |
2543 | ||
2544 | it("should report a violation", () => { | |
2545 | const code = [ | |
2546 | "/*eslint-disable no-alert, no-console */", | |
2547 | "alert('test');", | |
2548 | "console.log('test');", | |
2549 | ||
2550 | "/*eslint-enable no-alert */", | |
2551 | ||
2552 | "alert('test');", // here | |
2553 | "console.log('test');", | |
2554 | ||
2555 | "/*eslint-enable no-console */", | |
2556 | ||
2557 | "alert('test');", // here | |
2558 | "console.log('test');", // here | |
2559 | "/*eslint-enable no-console */" | |
2560 | ].join("\n"); | |
2561 | const config = { rules: { "no-alert": 1, "no-console": 1 } }; | |
2562 | ||
2563 | const messages = linter.verify(code, config, filename); | |
2564 | ||
2565 | assert.strictEqual(messages.length, 3); | |
2566 | ||
2567 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2568 | assert.strictEqual(messages[0].line, 5); | |
2569 | ||
2570 | assert.strictEqual(messages[1].ruleId, "no-alert"); | |
2571 | assert.strictEqual(messages[1].line, 8); | |
2572 | ||
2573 | assert.strictEqual(messages[2].ruleId, "no-console"); | |
2574 | assert.strictEqual(messages[2].line, 9); | |
2575 | ||
2576 | }); | |
2577 | ||
2578 | it("should report a violation when severity is warn", () => { | |
2579 | const code = [ | |
2580 | "/*eslint-disable no-alert, no-console */", | |
2581 | "alert('test');", | |
2582 | "console.log('test');", | |
2583 | ||
2584 | "/*eslint-enable no-alert */", | |
2585 | ||
2586 | "alert('test');", // here | |
2587 | "console.log('test');", | |
2588 | ||
2589 | "/*eslint-enable no-console */", | |
2590 | ||
2591 | "alert('test');", // here | |
2592 | "console.log('test');", // here | |
2593 | "/*eslint-enable no-console */" | |
2594 | ].join("\n"); | |
2595 | const config = { rules: { "no-alert": "warn", "no-console": "warn" } }; | |
2596 | ||
2597 | const messages = linter.verify(code, config, filename); | |
2598 | ||
2599 | assert.strictEqual(messages.length, 3); | |
2600 | ||
2601 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2602 | assert.strictEqual(messages[0].line, 5); | |
2603 | ||
2604 | assert.strictEqual(messages[1].ruleId, "no-alert"); | |
2605 | assert.strictEqual(messages[1].line, 8); | |
2606 | ||
2607 | assert.strictEqual(messages[2].ruleId, "no-console"); | |
2608 | assert.strictEqual(messages[2].line, 9); | |
2609 | ||
2610 | }); | |
2611 | }); | |
2612 | ||
2613 | describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => { | |
2614 | const code = "/*eslint no-alert:1, no-console:0*/ alert('test'); console.log('test');"; | |
2615 | ||
2616 | it("should report a violation", () => { | |
2617 | const config = { rules: { "no-console": 1, "no-alert": 0 } }; | |
2618 | ||
2619 | const messages = linter.verify(code, config, filename); | |
2620 | ||
2621 | assert.strictEqual(messages.length, 1); | |
2622 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
2623 | assert.strictEqual(messages[0].message, "Unexpected alert."); | |
2624 | assert.include(messages[0].nodeType, "CallExpression"); | |
2625 | }); | |
2626 | }); | |
2627 | ||
2628 | describe("when evaluating code with comments to enable configurable rule", () => { | |
2629 | const code = "/*eslint quotes:[2, \"double\"]*/ alert('test');"; | |
2630 | ||
2631 | it("should report a violation", () => { | |
2632 | const config = { rules: { quotes: [2, "single"] } }; | |
2633 | ||
2634 | const messages = linter.verify(code, config, filename); | |
2635 | ||
2636 | assert.strictEqual(messages.length, 1); | |
2637 | assert.strictEqual(messages[0].ruleId, "quotes"); | |
2638 | assert.strictEqual(messages[0].message, "Strings must use doublequote."); | |
2639 | assert.include(messages[0].nodeType, "Literal"); | |
2640 | }); | |
2641 | }); | |
2642 | ||
2643 | describe("when evaluating code with comments to enable configurable rule using string severity", () => { | |
2644 | const code = "/*eslint quotes:[\"error\", \"double\"]*/ alert('test');"; | |
2645 | ||
2646 | it("should report a violation", () => { | |
2647 | const config = { rules: { quotes: [2, "single"] } }; | |
2648 | ||
2649 | const messages = linter.verify(code, config, filename); | |
2650 | ||
2651 | assert.strictEqual(messages.length, 1); | |
2652 | assert.strictEqual(messages[0].ruleId, "quotes"); | |
2653 | assert.strictEqual(messages[0].message, "Strings must use doublequote."); | |
2654 | assert.include(messages[0].nodeType, "Literal"); | |
2655 | }); | |
2656 | }); | |
2657 | ||
2658 | describe("when evaluating code with incorrectly formatted comments to disable rule", () => { | |
2659 | it("should report a violation", () => { | |
2660 | const code = "/*eslint no-alert:'1'*/ alert('test');"; | |
2661 | ||
2662 | const config = { rules: { "no-alert": 1 } }; | |
2663 | ||
2664 | const messages = linter.verify(code, config, filename); | |
2665 | ||
2666 | assert.strictEqual(messages.length, 2); | |
2667 | ||
2668 | /* | |
2669 | * Incorrectly formatted comment threw error; | |
2670 | * message from caught exception | |
2671 | * may differ amongst UAs, so verifying | |
2672 | * first part only as defined in the | |
2673 | * parseJsonConfig function in lib/eslint.js | |
2674 | */ | |
2675 | assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":'1'':/u); | |
2676 | assert.strictEqual(messages[0].line, 1); | |
2677 | assert.strictEqual(messages[0].column, 1); | |
2678 | ||
2679 | assert.strictEqual(messages[1].ruleId, "no-alert"); | |
2680 | assert.strictEqual(messages[1].message, "Unexpected alert."); | |
2681 | assert.include(messages[1].nodeType, "CallExpression"); | |
2682 | }); | |
2683 | ||
2684 | it("should report a violation", () => { | |
2685 | const code = "/*eslint no-alert:abc*/ alert('test');"; | |
2686 | ||
2687 | const config = { rules: { "no-alert": 1 } }; | |
2688 | ||
2689 | const messages = linter.verify(code, config, filename); | |
2690 | ||
2691 | assert.strictEqual(messages.length, 2); | |
2692 | ||
2693 | /* | |
2694 | * Incorrectly formatted comment threw error; | |
2695 | * message from caught exception | |
2696 | * may differ amongst UAs, so verifying | |
2697 | * first part only as defined in the | |
2698 | * parseJsonConfig function in lib/eslint.js | |
2699 | */ | |
2700 | assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":abc':/u); | |
2701 | assert.strictEqual(messages[0].line, 1); | |
2702 | assert.strictEqual(messages[0].column, 1); | |
2703 | ||
2704 | assert.strictEqual(messages[1].ruleId, "no-alert"); | |
2705 | assert.strictEqual(messages[1].message, "Unexpected alert."); | |
2706 | assert.include(messages[1].nodeType, "CallExpression"); | |
2707 | }); | |
2708 | ||
2709 | it("should report a violation", () => { | |
2710 | const code = "/*eslint no-alert:0 2*/ alert('test');"; | |
2711 | ||
2712 | const config = { rules: { "no-alert": 1 } }; | |
2713 | ||
2714 | const messages = linter.verify(code, config, filename); | |
2715 | ||
2716 | assert.strictEqual(messages.length, 2); | |
2717 | ||
2718 | /* | |
2719 | * Incorrectly formatted comment threw error; | |
2720 | * message from caught exception | |
2721 | * may differ amongst UAs, so verifying | |
2722 | * first part only as defined in the | |
2723 | * parseJsonConfig function in lib/eslint.js | |
2724 | */ | |
2725 | assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":0 2':/u); | |
2726 | assert.strictEqual(messages[0].line, 1); | |
2727 | assert.strictEqual(messages[0].column, 1); | |
2728 | ||
2729 | assert.strictEqual(messages[1].ruleId, "no-alert"); | |
2730 | assert.strictEqual(messages[1].message, "Unexpected alert."); | |
2731 | assert.include(messages[1].nodeType, "CallExpression"); | |
2732 | }); | |
2733 | }); | |
2734 | ||
2735 | describe("when evaluating code with comments which have colon in its value", () => { | |
56c4a2cb DC |
2736 | const code = String.raw` |
2737 | /* eslint max-len: [2, 100, 2, {ignoreUrls: true, ignorePattern: "data:image\\/|\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}] */ | |
2738 | alert('test'); | |
2739 | `; | |
eb39fafa DC |
2740 | |
2741 | it("should not parse errors, should report a violation", () => { | |
2742 | const messages = linter.verify(code, {}, filename); | |
2743 | ||
2744 | assert.strictEqual(messages.length, 1); | |
2745 | assert.strictEqual(messages[0].ruleId, "max-len"); | |
56c4a2cb | 2746 | assert.strictEqual(messages[0].message, "This line has a length of 129. Maximum allowed is 100."); |
eb39fafa DC |
2747 | assert.include(messages[0].nodeType, "Program"); |
2748 | }); | |
2749 | }); | |
2750 | ||
56c4a2cb DC |
2751 | describe("when evaluating code with comments that contain escape sequences", () => { |
2752 | const code = String.raw` | |
2753 | /* eslint max-len: ["error", 1, { ignoreComments: true, ignorePattern: "console\\.log\\(" }] */ | |
2754 | console.log("test"); | |
2755 | consolexlog("test2"); | |
2756 | var a = "test2"; | |
2757 | `; | |
2758 | ||
2759 | it("should validate correctly", () => { | |
2760 | const config = { rules: {} }; | |
2761 | const messages = linter.verify(code, config, filename); | |
2762 | const [message1, message2] = messages; | |
2763 | ||
2764 | assert.strictEqual(messages.length, 2); | |
2765 | assert.strictEqual(message1.ruleId, "max-len"); | |
2766 | assert.strictEqual(message1.message, "This line has a length of 21. Maximum allowed is 1."); | |
2767 | assert.strictEqual(message1.line, 4); | |
2768 | assert.strictEqual(message1.column, 1); | |
2769 | assert.include(message1.nodeType, "Program"); | |
2770 | assert.strictEqual(message2.ruleId, "max-len"); | |
2771 | assert.strictEqual(message2.message, "This line has a length of 16. Maximum allowed is 1."); | |
2772 | assert.strictEqual(message2.line, 5); | |
2773 | assert.strictEqual(message2.column, 1); | |
2774 | assert.include(message2.nodeType, "Program"); | |
2775 | }); | |
2776 | }); | |
2777 | ||
eb39fafa DC |
2778 | describe("when evaluating a file with a shebang", () => { |
2779 | const code = "#!bin/program\n\nvar foo;;"; | |
2780 | ||
2781 | it("should preserve line numbers", () => { | |
2782 | const config = { rules: { "no-extra-semi": 1 } }; | |
2783 | const messages = linter.verify(code, config); | |
2784 | ||
2785 | assert.strictEqual(messages.length, 1); | |
2786 | assert.strictEqual(messages[0].ruleId, "no-extra-semi"); | |
2787 | assert.strictEqual(messages[0].nodeType, "EmptyStatement"); | |
2788 | assert.strictEqual(messages[0].line, 3); | |
2789 | }); | |
2790 | ||
2791 | it("should have a comment with the shebang in it", () => { | |
2792 | const config = { rules: { checker: "error" } }; | |
2793 | const spy = sinon.spy(context => { | |
2794 | const comments = context.getAllComments(); | |
2795 | ||
2796 | assert.strictEqual(comments.length, 1); | |
2797 | assert.strictEqual(comments[0].type, "Shebang"); | |
2798 | return {}; | |
2799 | }); | |
2800 | ||
2801 | linter.defineRule("checker", spy); | |
2802 | linter.verify(code, config); | |
2803 | assert(spy.calledOnce); | |
2804 | }); | |
2805 | }); | |
2806 | ||
2807 | describe("when evaluating broken code", () => { | |
2808 | const code = BROKEN_TEST_CODE; | |
2809 | ||
2810 | it("should report a violation with a useful parse error prefix", () => { | |
2811 | const messages = linter.verify(code); | |
2812 | ||
2813 | assert.strictEqual(messages.length, 1); | |
2814 | assert.strictEqual(messages[0].severity, 2); | |
2815 | assert.isNull(messages[0].ruleId); | |
2816 | assert.strictEqual(messages[0].line, 1); | |
2817 | assert.strictEqual(messages[0].column, 4); | |
2818 | assert.isTrue(messages[0].fatal); | |
2819 | assert.match(messages[0].message, /^Parsing error:/u); | |
2820 | }); | |
2821 | ||
2822 | it("should report source code where the issue is present", () => { | |
2823 | const inValidCode = [ | |
2824 | "var x = 20;", | |
2825 | "if (x ==4 {", | |
2826 | " x++;", | |
2827 | "}" | |
2828 | ]; | |
2829 | const messages = linter.verify(inValidCode.join("\n")); | |
2830 | ||
2831 | assert.strictEqual(messages.length, 1); | |
2832 | assert.strictEqual(messages[0].severity, 2); | |
2833 | assert.isTrue(messages[0].fatal); | |
2834 | assert.match(messages[0].message, /^Parsing error:/u); | |
2835 | }); | |
2836 | }); | |
2837 | ||
2838 | describe("when using an invalid (undefined) rule", () => { | |
2839 | linter = new Linter(); | |
2840 | ||
2841 | const code = TEST_CODE; | |
2842 | const results = linter.verify(code, { rules: { foobar: 2 } }); | |
2843 | const result = results[0]; | |
2844 | const warningResult = linter.verify(code, { rules: { foobar: 1 } })[0]; | |
2845 | const arrayOptionResults = linter.verify(code, { rules: { foobar: [2, "always"] } }); | |
2846 | const objectOptionResults = linter.verify(code, { rules: { foobar: [1, { bar: false }] } }); | |
2847 | const resultsMultiple = linter.verify(code, { rules: { foobar: 2, barfoo: 1 } }); | |
2848 | ||
2849 | it("should report a problem", () => { | |
2850 | assert.isNotNull(result); | |
2851 | assert.isArray(results); | |
2852 | assert.isObject(result); | |
2853 | assert.property(result, "ruleId"); | |
2854 | assert.strictEqual(result.ruleId, "foobar"); | |
2855 | }); | |
2856 | ||
2857 | it("should report that the rule does not exist", () => { | |
2858 | assert.property(result, "message"); | |
2859 | assert.strictEqual(result.message, "Definition for rule 'foobar' was not found."); | |
2860 | }); | |
2861 | ||
2862 | it("should report at the correct severity", () => { | |
2863 | assert.property(result, "severity"); | |
2864 | assert.strictEqual(result.severity, 2); | |
2865 | assert.strictEqual(warningResult.severity, 2); // this is 2, since the rulename is very likely to be wrong | |
2866 | }); | |
2867 | ||
2868 | it("should accept any valid rule configuration", () => { | |
2869 | assert.isObject(arrayOptionResults[0]); | |
2870 | assert.isObject(objectOptionResults[0]); | |
2871 | }); | |
2872 | ||
2873 | it("should report multiple missing rules", () => { | |
2874 | assert.isArray(resultsMultiple); | |
2875 | ||
2876 | assert.deepStrictEqual( | |
2877 | resultsMultiple[1], | |
2878 | { | |
2879 | ruleId: "barfoo", | |
2880 | message: "Definition for rule 'barfoo' was not found.", | |
2881 | line: 1, | |
2882 | column: 1, | |
2883 | endLine: 1, | |
2884 | endColumn: 2, | |
2885 | severity: 2, | |
2886 | nodeType: null | |
2887 | } | |
2888 | ); | |
2889 | }); | |
2890 | }); | |
2891 | ||
2892 | describe("when using a rule which has been replaced", () => { | |
2893 | const code = TEST_CODE; | |
2894 | const results = linter.verify(code, { rules: { "no-comma-dangle": 2 } }); | |
2895 | ||
2896 | it("should report the new rule", () => { | |
2897 | assert.strictEqual(results[0].ruleId, "no-comma-dangle"); | |
2898 | assert.strictEqual(results[0].message, "Rule 'no-comma-dangle' was removed and replaced by: comma-dangle"); | |
2899 | }); | |
2900 | }); | |
2901 | ||
2902 | describe("when calling getRules", () => { | |
2903 | it("should return all loaded rules", () => { | |
2904 | const rules = linter.getRules(); | |
2905 | ||
2906 | assert.isAbove(rules.size, 230); | |
2907 | assert.isObject(rules.get("no-alert")); | |
2908 | }); | |
2909 | }); | |
2910 | ||
2911 | describe("when calling version", () => { | |
2912 | it("should return current version number", () => { | |
2913 | const version = linter.version; | |
2914 | ||
2915 | assert.isString(version); | |
2916 | assert.isTrue(parseInt(version[0], 10) >= 3); | |
2917 | }); | |
2918 | }); | |
2919 | ||
2920 | describe("when evaluating an empty string", () => { | |
2921 | it("runs rules", () => { | |
2922 | linter.defineRule("no-programs", context => ({ | |
2923 | Program(node) { | |
2924 | context.report({ node, message: "No programs allowed." }); | |
2925 | } | |
2926 | })); | |
2927 | ||
2928 | assert.strictEqual( | |
2929 | linter.verify("", { rules: { "no-programs": "error" } }).length, | |
2930 | 1 | |
2931 | ); | |
2932 | }); | |
2933 | }); | |
2934 | ||
2935 | describe("when evaluating code without comments to environment", () => { | |
2936 | it("should report a violation when using typed array", () => { | |
2937 | const code = "var array = new Uint8Array();"; | |
2938 | ||
2939 | const config = { rules: { "no-undef": 1 } }; | |
2940 | ||
2941 | const messages = linter.verify(code, config, filename); | |
2942 | ||
2943 | assert.strictEqual(messages.length, 1); | |
2944 | }); | |
2945 | ||
2946 | it("should report a violation when using Promise", () => { | |
2947 | const code = "new Promise();"; | |
2948 | ||
2949 | const config = { rules: { "no-undef": 1 } }; | |
2950 | ||
2951 | const messages = linter.verify(code, config, filename); | |
2952 | ||
2953 | assert.strictEqual(messages.length, 1); | |
2954 | }); | |
2955 | }); | |
2956 | ||
2957 | describe("when evaluating code with comments to environment", () => { | |
2958 | it("should not support legacy config", () => { | |
2959 | const code = "/*jshint mocha:true */ describe();"; | |
2960 | ||
2961 | const config = { rules: { "no-undef": 1 } }; | |
2962 | ||
2963 | const messages = linter.verify(code, config, filename); | |
2964 | ||
2965 | assert.strictEqual(messages.length, 1); | |
2966 | assert.strictEqual(messages[0].ruleId, "no-undef"); | |
2967 | assert.strictEqual(messages[0].nodeType, "Identifier"); | |
2968 | assert.strictEqual(messages[0].line, 1); | |
2969 | }); | |
2970 | ||
2971 | it("should not report a violation", () => { | |
2972 | const code = "/*eslint-env es6 */ new Promise();"; | |
2973 | ||
2974 | const config = { rules: { "no-undef": 1 } }; | |
2975 | ||
2976 | const messages = linter.verify(code, config, filename); | |
2977 | ||
2978 | assert.strictEqual(messages.length, 0); | |
2979 | }); | |
2980 | ||
2981 | it("should not report a violation", () => { | |
2982 | const code = `/*${ESLINT_ENV} mocha,node */ require();describe();`; | |
2983 | ||
2984 | const config = { rules: { "no-undef": 1 } }; | |
2985 | ||
2986 | const messages = linter.verify(code, config, filename); | |
2987 | ||
2988 | assert.strictEqual(messages.length, 0); | |
2989 | }); | |
2990 | ||
2991 | it("should not report a violation", () => { | |
2992 | const code = "/*eslint-env mocha */ suite();test();"; | |
2993 | ||
2994 | const config = { rules: { "no-undef": 1 } }; | |
2995 | ||
2996 | const messages = linter.verify(code, config, filename); | |
2997 | ||
2998 | assert.strictEqual(messages.length, 0); | |
2999 | }); | |
3000 | ||
3001 | it("should not report a violation", () => { | |
3002 | const code = `/*${ESLINT_ENV} amd */ define();require();`; | |
3003 | ||
3004 | const config = { rules: { "no-undef": 1 } }; | |
3005 | ||
3006 | const messages = linter.verify(code, config, filename); | |
3007 | ||
3008 | assert.strictEqual(messages.length, 0); | |
3009 | }); | |
3010 | ||
3011 | it("should not report a violation", () => { | |
3012 | const code = `/*${ESLINT_ENV} jasmine */ expect();spyOn();`; | |
3013 | ||
3014 | const config = { rules: { "no-undef": 1 } }; | |
3015 | ||
3016 | const messages = linter.verify(code, config, filename); | |
3017 | ||
3018 | assert.strictEqual(messages.length, 0); | |
3019 | }); | |
3020 | ||
3021 | it("should not report a violation", () => { | |
3022 | const code = `/*globals require: true */ /*${ESLINT_ENV} node */ require = 1;`; | |
3023 | ||
3024 | const config = { rules: { "no-undef": 1 } }; | |
3025 | ||
3026 | const messages = linter.verify(code, config, filename); | |
3027 | ||
3028 | assert.strictEqual(messages.length, 0); | |
3029 | }); | |
3030 | ||
3031 | it("should not report a violation", () => { | |
3032 | const code = `/*${ESLINT_ENV} node */ process.exit();`; | |
3033 | ||
3034 | const config = { rules: {} }; | |
3035 | ||
3036 | const messages = linter.verify(code, config, filename); | |
3037 | ||
3038 | assert.strictEqual(messages.length, 0); | |
3039 | }); | |
3040 | ||
3041 | it("should not report a violation", () => { | |
3042 | const code = `/*eslint no-process-exit: 0 */ /*${ESLINT_ENV} node */ process.exit();`; | |
3043 | ||
3044 | const config = { rules: { "no-undef": 1 } }; | |
3045 | ||
3046 | const messages = linter.verify(code, config, filename); | |
3047 | ||
3048 | assert.strictEqual(messages.length, 0); | |
3049 | }); | |
3050 | }); | |
3051 | ||
3052 | describe("when evaluating code with comments to change config when allowInlineConfig is enabled", () => { | |
3053 | it("should report a violation for disabling rules", () => { | |
3054 | const code = [ | |
3055 | "alert('test'); // eslint-disable-line no-alert" | |
3056 | ].join("\n"); | |
3057 | const config = { | |
3058 | rules: { | |
3059 | "no-alert": 1 | |
3060 | } | |
3061 | }; | |
3062 | ||
3063 | const messages = linter.verify(code, config, { | |
3064 | filename, | |
3065 | allowInlineConfig: false | |
3066 | }); | |
3067 | ||
3068 | assert.strictEqual(messages.length, 1); | |
3069 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
3070 | }); | |
3071 | ||
3072 | it("should report a violation for global variable declarations", () => { | |
3073 | const code = [ | |
3074 | "/* global foo */" | |
3075 | ].join("\n"); | |
3076 | const config = { | |
3077 | rules: { | |
3078 | test: 2 | |
3079 | } | |
3080 | }; | |
3081 | let ok = false; | |
3082 | ||
3083 | linter.defineRules({ | |
3084 | test(context) { | |
3085 | return { | |
3086 | Program() { | |
3087 | const scope = context.getScope(); | |
3088 | const sourceCode = context.getSourceCode(); | |
3089 | const comments = sourceCode.getAllComments(); | |
3090 | ||
3091 | assert.strictEqual(1, comments.length); | |
3092 | ||
3093 | const foo = getVariable(scope, "foo"); | |
3094 | ||
3095 | assert.notOk(foo); | |
3096 | ||
3097 | ok = true; | |
3098 | } | |
3099 | }; | |
3100 | } | |
3101 | }); | |
3102 | ||
3103 | linter.verify(code, config, { allowInlineConfig: false }); | |
3104 | assert(ok); | |
3105 | }); | |
3106 | ||
3107 | it("should report a violation for eslint-disable", () => { | |
3108 | const code = [ | |
3109 | "/* eslint-disable */", | |
3110 | "alert('test');" | |
3111 | ].join("\n"); | |
3112 | const config = { | |
3113 | rules: { | |
3114 | "no-alert": 1 | |
3115 | } | |
3116 | }; | |
3117 | ||
3118 | const messages = linter.verify(code, config, { | |
3119 | filename, | |
3120 | allowInlineConfig: false | |
3121 | }); | |
3122 | ||
3123 | assert.strictEqual(messages.length, 1); | |
3124 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
3125 | }); | |
3126 | ||
3127 | it("should not report a violation for rule changes", () => { | |
3128 | const code = [ | |
3129 | "/*eslint no-alert:2*/", | |
3130 | "alert('test');" | |
3131 | ].join("\n"); | |
3132 | const config = { | |
3133 | rules: { | |
3134 | "no-alert": 0 | |
3135 | } | |
3136 | }; | |
3137 | ||
3138 | const messages = linter.verify(code, config, { | |
3139 | filename, | |
3140 | allowInlineConfig: false | |
3141 | }); | |
3142 | ||
3143 | assert.strictEqual(messages.length, 0); | |
3144 | }); | |
3145 | ||
3146 | it("should report a violation for disable-line", () => { | |
3147 | const code = [ | |
3148 | "alert('test'); // eslint-disable-line" | |
3149 | ].join("\n"); | |
3150 | const config = { | |
3151 | rules: { | |
3152 | "no-alert": 2 | |
3153 | } | |
3154 | }; | |
3155 | ||
3156 | const messages = linter.verify(code, config, { | |
3157 | filename, | |
3158 | allowInlineConfig: false | |
3159 | }); | |
3160 | ||
3161 | assert.strictEqual(messages.length, 1); | |
3162 | assert.strictEqual(messages[0].ruleId, "no-alert"); | |
3163 | }); | |
3164 | ||
3165 | it("should report a violation for env changes", () => { | |
3166 | const code = [ | |
3167 | `/*${ESLINT_ENV} browser*/ window` | |
3168 | ].join("\n"); | |
3169 | const config = { | |
3170 | rules: { | |
3171 | "no-undef": 2 | |
3172 | } | |
3173 | }; | |
3174 | const messages = linter.verify(code, config, { allowInlineConfig: false }); | |
3175 | ||
3176 | assert.strictEqual(messages.length, 1); | |
3177 | assert.strictEqual(messages[0].ruleId, "no-undef"); | |
3178 | }); | |
3179 | }); | |
3180 | ||
3181 | describe("when evaluating code with 'noInlineComment'", () => { | |
3182 | for (const directive of [ | |
3183 | "globals foo", | |
3184 | "global foo", | |
3185 | "exported foo", | |
3186 | "eslint eqeqeq: error", | |
3187 | "eslint-disable eqeqeq", | |
3188 | "eslint-disable-line eqeqeq", | |
3189 | "eslint-disable-next-line eqeqeq", | |
3190 | "eslint-enable eqeqeq", | |
3191 | "eslint-env es6" | |
3192 | ]) { | |
3193 | // eslint-disable-next-line no-loop-func | |
3194 | it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => { | |
3195 | const messages = linter.verify(`/* ${directive} */`, { noInlineConfig: true }); | |
3196 | ||
3197 | assert.deepStrictEqual(messages.length, 1); | |
3198 | assert.deepStrictEqual(messages[0].fatal, void 0); | |
3199 | assert.deepStrictEqual(messages[0].ruleId, null); | |
3200 | assert.deepStrictEqual(messages[0].severity, 1); | |
3201 | assert.deepStrictEqual(messages[0].message, `'/*${directive.split(" ")[0]}*/' has no effect because you have 'noInlineConfig' setting in your config.`); | |
3202 | }); | |
3203 | } | |
3204 | ||
3205 | for (const directive of [ | |
3206 | "eslint-disable-line eqeqeq", | |
3207 | "eslint-disable-next-line eqeqeq" | |
3208 | ]) { | |
3209 | // eslint-disable-next-line no-loop-func | |
3210 | it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => { | |
3211 | const messages = linter.verify(`// ${directive}`, { noInlineConfig: true }); | |
3212 | ||
3213 | assert.deepStrictEqual(messages.length, 1); | |
3214 | assert.deepStrictEqual(messages[0].fatal, void 0); | |
3215 | assert.deepStrictEqual(messages[0].ruleId, null); | |
3216 | assert.deepStrictEqual(messages[0].severity, 1); | |
3217 | assert.deepStrictEqual(messages[0].message, `'//${directive.split(" ")[0]}' has no effect because you have 'noInlineConfig' setting in your config.`); | |
3218 | }); | |
3219 | } | |
3220 | ||
3221 | it("should not warn if 'noInlineConfig' and '--no-inline-config' were given.", () => { | |
3222 | const messages = linter.verify("/* globals foo */", { noInlineConfig: true }, { allowInlineConfig: false }); | |
3223 | ||
3224 | assert.deepStrictEqual(messages.length, 0); | |
3225 | }); | |
3226 | }); | |
3227 | ||
3228 | describe("when receiving cwd in options during instantiation", () => { | |
3229 | const code = "a;\nb;"; | |
3230 | const config = { rules: { checker: "error" } }; | |
3231 | ||
3232 | it("should get cwd correctly in the context", () => { | |
3233 | const cwd = "cwd"; | |
3234 | const linterWithOption = new Linter({ cwd }); | |
3235 | let spy; | |
3236 | ||
3237 | linterWithOption.defineRule("checker", context => { | |
3238 | spy = sinon.spy(() => { | |
3239 | assert.strictEqual(context.getCwd(), cwd); | |
3240 | }); | |
3241 | return { Program: spy }; | |
3242 | }); | |
3243 | ||
3244 | linterWithOption.verify(code, config); | |
3245 | assert(spy && spy.calledOnce); | |
3246 | }); | |
3247 | ||
3248 | it("should assign process.cwd() to it if cwd is undefined", () => { | |
3249 | let spy; | |
3250 | const linterWithOption = new Linter({ }); | |
3251 | ||
3252 | linterWithOption.defineRule("checker", context => { | |
3253 | ||
3254 | spy = sinon.spy(() => { | |
3255 | assert.strictEqual(context.getCwd(), process.cwd()); | |
3256 | }); | |
3257 | return { Program: spy }; | |
3258 | }); | |
3259 | ||
3260 | linterWithOption.verify(code, config); | |
3261 | assert(spy && spy.calledOnce); | |
3262 | }); | |
3263 | ||
3264 | it("should assign process.cwd() to it if the option is undefined", () => { | |
3265 | let spy; | |
3266 | ||
3267 | linter.defineRule("checker", context => { | |
3268 | ||
3269 | spy = sinon.spy(() => { | |
3270 | assert.strictEqual(context.getCwd(), process.cwd()); | |
3271 | }); | |
3272 | return { Program: spy }; | |
3273 | }); | |
3274 | ||
3275 | linter.verify(code, config); | |
3276 | assert(spy && spy.calledOnce); | |
3277 | }); | |
3278 | }); | |
3279 | ||
3280 | describe("reportUnusedDisable option", () => { | |
3281 | it("reports problems for unused eslint-disable comments", () => { | |
3282 | assert.deepStrictEqual( | |
3283 | linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: true }), | |
3284 | [ | |
3285 | { | |
3286 | ruleId: null, | |
3287 | message: "Unused eslint-disable directive (no problems were reported).", | |
3288 | line: 1, | |
3289 | column: 1, | |
3290 | severity: 2, | |
3291 | nodeType: null | |
3292 | } | |
3293 | ] | |
3294 | ); | |
3295 | }); | |
3296 | ||
3297 | it("reports problems for unused eslint-disable comments (error)", () => { | |
3298 | assert.deepStrictEqual( | |
3299 | linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "error" }), | |
3300 | [ | |
3301 | { | |
3302 | ruleId: null, | |
3303 | message: "Unused eslint-disable directive (no problems were reported).", | |
3304 | line: 1, | |
3305 | column: 1, | |
3306 | severity: 2, | |
3307 | nodeType: null | |
3308 | } | |
3309 | ] | |
3310 | ); | |
3311 | }); | |
3312 | ||
3313 | it("reports problems for unused eslint-disable comments (warn)", () => { | |
3314 | assert.deepStrictEqual( | |
3315 | linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "warn" }), | |
3316 | [ | |
3317 | { | |
3318 | ruleId: null, | |
3319 | message: "Unused eslint-disable directive (no problems were reported).", | |
3320 | line: 1, | |
3321 | column: 1, | |
3322 | severity: 1, | |
3323 | nodeType: null | |
3324 | } | |
3325 | ] | |
3326 | ); | |
3327 | }); | |
3328 | ||
3329 | it("reports problems for unused eslint-disable comments (in config)", () => { | |
3330 | assert.deepStrictEqual( | |
3331 | linter.verify("/* eslint-disable */", { reportUnusedDisableDirectives: true }), | |
3332 | [ | |
3333 | { | |
3334 | ruleId: null, | |
3335 | message: "Unused eslint-disable directive (no problems were reported).", | |
3336 | line: 1, | |
3337 | column: 1, | |
3338 | severity: 1, | |
3339 | nodeType: null | |
3340 | } | |
3341 | ] | |
3342 | ); | |
3343 | }); | |
3344 | }); | |
3345 | ||
3346 | describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { | |
3347 | it("should not report a violation", () => { | |
3348 | const code = [ | |
3349 | "alert('test'); // eslint-disable-line no-alert" | |
3350 | ].join("\n"); | |
3351 | const config = { | |
3352 | rules: { | |
3353 | "no-alert": 1 | |
3354 | } | |
3355 | }; | |
3356 | ||
3357 | const messages = linter.verify(code, config, { | |
3358 | filename, | |
3359 | allowInlineConfig: true | |
3360 | }); | |
3361 | ||
3362 | assert.strictEqual(messages.length, 0); | |
3363 | }); | |
3364 | }); | |
3365 | ||
3366 | describe("when evaluating code with hashbang", () => { | |
3367 | it("should comment hashbang without breaking offset", () => { | |
3368 | const code = "#!/usr/bin/env node\n'123';"; | |
3369 | const config = { rules: { checker: "error" } }; | |
3370 | let spy; | |
3371 | ||
3372 | linter.defineRule("checker", context => { | |
3373 | spy = sinon.spy(node => { | |
3374 | assert.strictEqual(context.getSource(node), "'123';"); | |
3375 | }); | |
3376 | return { ExpressionStatement: spy }; | |
3377 | }); | |
3378 | ||
3379 | linter.verify(code, config); | |
3380 | assert(spy && spy.calledOnce); | |
3381 | }); | |
3382 | }); | |
3383 | ||
3384 | describe("verify()", () => { | |
3385 | describe("filenames", () => { | |
3386 | it("should allow filename to be passed on options object", () => { | |
3387 | const filenameChecker = sinon.spy(context => { | |
3388 | assert.strictEqual(context.getFilename(), "foo.js"); | |
3389 | return {}; | |
3390 | }); | |
3391 | ||
3392 | linter.defineRule("checker", filenameChecker); | |
3393 | linter.defineRule("checker", filenameChecker); | |
3394 | linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" }); | |
3395 | assert(filenameChecker.calledOnce); | |
3396 | }); | |
3397 | ||
3398 | it("should allow filename to be passed as third argument", () => { | |
3399 | const filenameChecker = sinon.spy(context => { | |
3400 | assert.strictEqual(context.getFilename(), "bar.js"); | |
3401 | return {}; | |
3402 | }); | |
3403 | ||
3404 | linter.defineRule("checker", filenameChecker); | |
3405 | linter.verify("foo;", { rules: { checker: "error" } }, "bar.js"); | |
3406 | assert(filenameChecker.calledOnce); | |
3407 | }); | |
3408 | ||
3409 | it("should default filename to <input> when options object doesn't have filename", () => { | |
3410 | const filenameChecker = sinon.spy(context => { | |
3411 | assert.strictEqual(context.getFilename(), "<input>"); | |
3412 | return {}; | |
3413 | }); | |
3414 | ||
3415 | linter.defineRule("checker", filenameChecker); | |
3416 | linter.verify("foo;", { rules: { checker: "error" } }, {}); | |
3417 | assert(filenameChecker.calledOnce); | |
3418 | }); | |
3419 | ||
3420 | it("should default filename to <input> when only two arguments are passed", () => { | |
3421 | const filenameChecker = sinon.spy(context => { | |
3422 | assert.strictEqual(context.getFilename(), "<input>"); | |
3423 | return {}; | |
3424 | }); | |
3425 | ||
3426 | linter.defineRule("checker", filenameChecker); | |
3427 | linter.verify("foo;", { rules: { checker: "error" } }); | |
3428 | assert(filenameChecker.calledOnce); | |
3429 | }); | |
3430 | }); | |
3431 | ||
3432 | it("should report warnings in order by line and column when called", () => { | |
3433 | ||
3434 | const code = "foo()\n alert('test')"; | |
3435 | const config = { rules: { "no-mixed-spaces-and-tabs": 1, "eol-last": 1, semi: [1, "always"] } }; | |
3436 | ||
3437 | const messages = linter.verify(code, config, filename); | |
3438 | ||
3439 | assert.strictEqual(messages.length, 3); | |
3440 | assert.strictEqual(messages[0].line, 1); | |
3441 | assert.strictEqual(messages[0].column, 6); | |
3442 | assert.strictEqual(messages[1].line, 2); | |
3443 | assert.strictEqual(messages[1].column, 18); | |
3444 | assert.strictEqual(messages[2].line, 2); | |
3445 | assert.strictEqual(messages[2].column, 18); | |
3446 | }); | |
3447 | ||
3448 | describe("ecmaVersion", () => { | |
3449 | describe("it should properly parse let declaration when", () => { | |
3450 | it("the ECMAScript version number is 6", () => { | |
3451 | const messages = linter.verify("let x = 5;", { | |
3452 | parserOptions: { | |
3453 | ecmaVersion: 6 | |
3454 | } | |
3455 | }); | |
3456 | ||
3457 | assert.strictEqual(messages.length, 0); | |
3458 | }); | |
3459 | ||
3460 | it("the ECMAScript version number is 2015", () => { | |
3461 | const messages = linter.verify("let x = 5;", { | |
3462 | parserOptions: { | |
3463 | ecmaVersion: 2015 | |
3464 | } | |
3465 | }); | |
3466 | ||
3467 | assert.strictEqual(messages.length, 0); | |
3468 | }); | |
3469 | }); | |
3470 | ||
3471 | it("should fail to parse exponentiation operator when the ECMAScript version number is 2015", () => { | |
3472 | const messages = linter.verify("x ** y;", { | |
3473 | parserOptions: { | |
3474 | ecmaVersion: 2015 | |
3475 | } | |
3476 | }); | |
3477 | ||
3478 | assert.strictEqual(messages.length, 1); | |
3479 | }); | |
3480 | ||
3481 | describe("should properly parse exponentiation operator when", () => { | |
3482 | it("the ECMAScript version number is 7", () => { | |
3483 | const messages = linter.verify("x ** y;", { | |
3484 | parserOptions: { | |
3485 | ecmaVersion: 7 | |
3486 | } | |
3487 | }); | |
3488 | ||
3489 | assert.strictEqual(messages.length, 0); | |
3490 | }); | |
3491 | ||
3492 | it("the ECMAScript version number is 2016", () => { | |
3493 | const messages = linter.verify("x ** y;", { | |
3494 | parserOptions: { | |
3495 | ecmaVersion: 2016 | |
3496 | } | |
3497 | }); | |
3498 | ||
3499 | assert.strictEqual(messages.length, 0); | |
3500 | }); | |
3501 | }); | |
3502 | }); | |
3503 | ||
3504 | it("should properly parse object spread when ecmaVersion is 2018", () => { | |
3505 | ||
3506 | const messages = linter.verify("var x = { ...y };", { | |
3507 | parserOptions: { | |
3508 | ecmaVersion: 2018 | |
3509 | } | |
3510 | }, filename); | |
3511 | ||
3512 | assert.strictEqual(messages.length, 0); | |
3513 | }); | |
3514 | ||
3515 | it("should properly parse global return when passed ecmaFeatures", () => { | |
3516 | ||
3517 | const messages = linter.verify("return;", { | |
3518 | parserOptions: { | |
3519 | ecmaFeatures: { | |
3520 | globalReturn: true | |
3521 | } | |
3522 | } | |
3523 | }, filename); | |
3524 | ||
3525 | assert.strictEqual(messages.length, 0); | |
3526 | }); | |
3527 | ||
3528 | it("should properly parse global return when in Node.js environment", () => { | |
3529 | ||
3530 | const messages = linter.verify("return;", { | |
3531 | env: { | |
3532 | node: true | |
3533 | } | |
3534 | }, filename); | |
3535 | ||
3536 | assert.strictEqual(messages.length, 0); | |
3537 | }); | |
3538 | ||
3539 | it("should not parse global return when in Node.js environment with globalReturn explicitly off", () => { | |
3540 | ||
3541 | const messages = linter.verify("return;", { | |
3542 | env: { | |
3543 | node: true | |
3544 | }, | |
3545 | parserOptions: { | |
3546 | ecmaFeatures: { | |
3547 | globalReturn: false | |
3548 | } | |
3549 | } | |
3550 | }, filename); | |
3551 | ||
3552 | assert.strictEqual(messages.length, 1); | |
3553 | assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function"); | |
3554 | }); | |
3555 | ||
3556 | it("should not parse global return when Node.js environment is false", () => { | |
3557 | ||
3558 | const messages = linter.verify("return;", {}, filename); | |
3559 | ||
3560 | assert.strictEqual(messages.length, 1); | |
3561 | assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function"); | |
3562 | }); | |
3563 | ||
3564 | it("should properly parse sloppy-mode code when impliedStrict is false", () => { | |
3565 | ||
3566 | const messages = linter.verify("var private;", {}, filename); | |
3567 | ||
3568 | assert.strictEqual(messages.length, 0); | |
3569 | }); | |
3570 | ||
3571 | it("should not parse sloppy-mode code when impliedStrict is true", () => { | |
3572 | ||
3573 | const messages = linter.verify("var private;", { | |
3574 | parserOptions: { | |
3575 | ecmaFeatures: { | |
3576 | impliedStrict: true | |
3577 | } | |
3578 | } | |
3579 | }, filename); | |
3580 | ||
3581 | assert.strictEqual(messages.length, 1); | |
3582 | assert.strictEqual(messages[0].message, "Parsing error: The keyword 'private' is reserved"); | |
3583 | }); | |
3584 | ||
3585 | it("should properly parse valid code when impliedStrict is true", () => { | |
3586 | ||
3587 | const messages = linter.verify("var foo;", { | |
3588 | parserOptions: { | |
3589 | ecmaFeatures: { | |
3590 | impliedStrict: true | |
3591 | } | |
3592 | } | |
3593 | }, filename); | |
3594 | ||
3595 | assert.strictEqual(messages.length, 0); | |
3596 | }); | |
3597 | ||
3598 | it("should properly parse JSX when passed ecmaFeatures", () => { | |
3599 | ||
3600 | const messages = linter.verify("var x = <div/>;", { | |
3601 | parserOptions: { | |
3602 | ecmaFeatures: { | |
3603 | jsx: true | |
3604 | } | |
3605 | } | |
3606 | }, filename); | |
3607 | ||
3608 | assert.strictEqual(messages.length, 0); | |
3609 | }); | |
3610 | ||
3611 | it("should report an error when JSX code is encountered and JSX is not enabled", () => { | |
3612 | const code = "var myDivElement = <div className=\"foo\" />;"; | |
3613 | const messages = linter.verify(code, {}, "filename"); | |
3614 | ||
3615 | assert.strictEqual(messages.length, 1); | |
3616 | assert.strictEqual(messages[0].line, 1); | |
3617 | assert.strictEqual(messages[0].column, 20); | |
3618 | assert.strictEqual(messages[0].message, "Parsing error: Unexpected token <"); | |
3619 | }); | |
3620 | ||
3621 | it("should not report an error when JSX code is encountered and JSX is enabled", () => { | |
3622 | const code = "var myDivElement = <div className=\"foo\" />;"; | |
3623 | const messages = linter.verify(code, { parserOptions: { ecmaFeatures: { jsx: true } } }, "filename"); | |
3624 | ||
3625 | assert.strictEqual(messages.length, 0); | |
3626 | }); | |
3627 | ||
3628 | it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { | |
3629 | const code = "var myDivElement = <div {...this.props} />;"; | |
3630 | const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, "filename"); | |
3631 | ||
3632 | assert.strictEqual(messages.length, 0); | |
3633 | }); | |
3634 | ||
3635 | it("should be able to use es6 features if there is a comment which has \"eslint-env es6\"", () => { | |
3636 | const code = [ | |
3637 | "/* eslint-env es6 */", | |
3638 | "var arrow = () => 0;", | |
3639 | "var binary = 0b1010;", | |
3640 | "{ let a = 0; const b = 1; }", | |
3641 | "class A {}", | |
3642 | "function defaultParams(a = 0) {}", | |
3643 | "var {a = 1, b = 2} = {};", | |
3644 | "for (var a of []) {}", | |
3645 | "function* generator() { yield 0; }", | |
3646 | "var computed = {[a]: 0};", | |
3647 | "var duplicate = {dup: 0, dup: 1};", | |
3648 | "var method = {foo() {}};", | |
3649 | "var property = {a, b};", | |
3650 | "var octal = 0o755;", | |
3651 | "var u = /^.$/u.test('ð ®·');", | |
3652 | "var y = /hello/y.test('hello');", | |
3653 | "function restParam(a, ...rest) {}", | |
3654 | "class B { superInFunc() { super.foo(); } }", | |
3655 | "var template = `hello, ${a}`;", | |
3656 | "var unicode = '\\u{20BB7}';" | |
3657 | ].join("\n"); | |
3658 | ||
3659 | const messages = linter.verify(code, null, "eslint-env es6"); | |
3660 | ||
3661 | assert.strictEqual(messages.length, 0); | |
3662 | }); | |
3663 | ||
3664 | it("should be able to return in global if there is a comment which enables the node environment with a comment", () => { | |
3665 | const messages = linter.verify(`/* ${ESLINT_ENV} node */ return;`, null, "node environment"); | |
3666 | ||
3667 | assert.strictEqual(messages.length, 0); | |
3668 | }); | |
3669 | ||
3670 | it("should attach a \"/*global\" comment node to declared variables", () => { | |
3671 | const code = "/* global foo */\n/* global bar, baz */"; | |
3672 | let ok = false; | |
3673 | ||
3674 | linter.defineRules({ | |
3675 | test(context) { | |
3676 | return { | |
3677 | Program() { | |
3678 | const scope = context.getScope(); | |
3679 | const sourceCode = context.getSourceCode(); | |
3680 | const comments = sourceCode.getAllComments(); | |
3681 | ||
3682 | assert.strictEqual(2, comments.length); | |
3683 | ||
3684 | const foo = getVariable(scope, "foo"); | |
3685 | ||
3686 | assert.strictEqual(foo.eslintExplicitGlobal, true); | |
3687 | assert.strictEqual(foo.eslintExplicitGlobalComments[0], comments[0]); | |
3688 | ||
3689 | const bar = getVariable(scope, "bar"); | |
3690 | ||
3691 | assert.strictEqual(bar.eslintExplicitGlobal, true); | |
3692 | assert.strictEqual(bar.eslintExplicitGlobalComments[0], comments[1]); | |
3693 | ||
3694 | const baz = getVariable(scope, "baz"); | |
3695 | ||
3696 | assert.strictEqual(baz.eslintExplicitGlobal, true); | |
3697 | assert.strictEqual(baz.eslintExplicitGlobalComments[0], comments[1]); | |
3698 | ||
3699 | ok = true; | |
3700 | } | |
3701 | }; | |
3702 | } | |
3703 | }); | |
3704 | ||
3705 | linter.verify(code, { rules: { test: 2 } }); | |
3706 | assert(ok); | |
3707 | }); | |
3708 | ||
3709 | it("should report a linting error when a global is set to an invalid value", () => { | |
3710 | const results = linter.verify("/* global foo: AAAAA, bar: readonly */\nfoo;\nbar;", { rules: { "no-undef": "error" } }); | |
3711 | ||
3712 | assert.deepStrictEqual(results, [ | |
3713 | { | |
3714 | ruleId: null, | |
3715 | severity: 2, | |
3716 | message: "'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')", | |
3717 | line: 1, | |
3718 | column: 1, | |
3719 | endLine: 1, | |
3720 | endColumn: 39, | |
3721 | nodeType: null | |
3722 | }, | |
3723 | { | |
3724 | ruleId: "no-undef", | |
3725 | messageId: "undef", | |
3726 | severity: 2, | |
3727 | message: "'foo' is not defined.", | |
3728 | line: 2, | |
3729 | column: 1, | |
3730 | endLine: 2, | |
3731 | endColumn: 4, | |
3732 | nodeType: "Identifier" | |
3733 | } | |
3734 | ]); | |
3735 | }); | |
3736 | ||
3737 | it("should not crash when we reuse the SourceCode object", () => { | |
3738 | linter.verify("function render() { return <div className='test'>{hello}</div> }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }); | |
3739 | linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }); | |
3740 | }); | |
3741 | ||
3742 | it("should reuse the SourceCode object", () => { | |
3743 | let ast1 = null, | |
3744 | ast2 = null; | |
3745 | ||
3746 | linter.defineRule("save-ast1", () => ({ | |
3747 | Program(node) { | |
3748 | ast1 = node; | |
3749 | } | |
3750 | })); | |
3751 | linter.defineRule("save-ast2", () => ({ | |
3752 | Program(node) { | |
3753 | ast2 = node; | |
3754 | } | |
3755 | })); | |
3756 | ||
3757 | linter.verify("function render() { return <div className='test'>{hello}</div> }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast1": 2 } }); | |
3758 | linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast2": 2 } }); | |
3759 | ||
3760 | assert(ast1 !== null); | |
3761 | assert(ast2 !== null); | |
3762 | assert(ast1 === ast2); | |
3763 | }); | |
3764 | ||
3765 | it("should allow 'await' as a property name in modules", () => { | |
3766 | const result = linter.verify( | |
3767 | "obj.await", | |
3768 | { parserOptions: { ecmaVersion: 6, sourceType: "module" } } | |
3769 | ); | |
3770 | ||
3771 | assert(result.length === 0); | |
3772 | }); | |
3773 | ||
3774 | ||
3775 | it("should not modify config object passed as argument", () => { | |
3776 | const config = {}; | |
3777 | ||
3778 | Object.freeze(config); | |
3779 | linter.verify("var", config); | |
3780 | }); | |
3781 | ||
3782 | it("should pass 'id' to rule contexts with the rule id", () => { | |
3783 | const spy = sinon.spy(context => { | |
3784 | assert.strictEqual(context.id, "foo-bar-baz"); | |
3785 | return {}; | |
3786 | }); | |
3787 | ||
3788 | linter.defineRule("foo-bar-baz", spy); | |
3789 | linter.verify("x", { rules: { "foo-bar-baz": "error" } }); | |
3790 | assert(spy.calledOnce); | |
3791 | }); | |
3792 | ||
3793 | describe("descriptions in directive comments", () => { | |
3794 | it("should ignore the part preceded by '--' in '/*eslint*/'.", () => { | |
3795 | const aaa = sinon.stub().returns({}); | |
3796 | const bbb = sinon.stub().returns({}); | |
3797 | ||
3798 | linter.defineRule("aaa", { create: aaa }); | |
3799 | linter.defineRule("bbb", { create: bbb }); | |
3800 | const messages = linter.verify(` | |
3801 | /*eslint aaa:error -- bbb:error */ | |
3802 | console.log("hello") | |
3803 | `, {}); | |
3804 | ||
3805 | // Don't include syntax error of the comment. | |
3806 | assert.deepStrictEqual(messages, []); | |
3807 | ||
3808 | // Use only `aaa`. | |
3809 | assert.strictEqual(aaa.callCount, 1); | |
3810 | assert.strictEqual(bbb.callCount, 0); | |
3811 | }); | |
3812 | ||
3813 | it("should ignore the part preceded by '--' in '/*eslint-env*/'.", () => { | |
3814 | const messages = linter.verify(` | |
3815 | /*eslint-env es2015 -- es2017 */ | |
3816 | var Promise = {} | |
3817 | var Atomics = {} | |
3818 | `, { rules: { "no-redeclare": "error" } }); | |
3819 | ||
3820 | // Don't include `Atomics` | |
3821 | assert.deepStrictEqual( | |
3822 | messages, | |
3823 | [{ | |
3824 | column: 25, | |
3825 | endColumn: 32, | |
3826 | endLine: 3, | |
3827 | line: 3, | |
3828 | message: "'Promise' is already defined as a built-in global variable.", | |
3829 | messageId: "redeclaredAsBuiltin", | |
3830 | nodeType: "Identifier", | |
3831 | ruleId: "no-redeclare", | |
3832 | severity: 2 | |
3833 | }] | |
3834 | ); | |
3835 | }); | |
3836 | ||
3837 | it("should ignore the part preceded by '--' in '/*global*/'.", () => { | |
3838 | const messages = linter.verify(` | |
3839 | /*global aaa -- bbb */ | |
3840 | var aaa = {} | |
3841 | var bbb = {} | |
3842 | `, { rules: { "no-redeclare": "error" } }); | |
3843 | ||
3844 | // Don't include `bbb` | |
3845 | assert.deepStrictEqual( | |
3846 | messages, | |
3847 | [{ | |
3848 | column: 30, | |
3849 | endColumn: 33, | |
3850 | line: 2, | |
3851 | endLine: 2, | |
3852 | message: "'aaa' is already defined by a variable declaration.", | |
3853 | messageId: "redeclaredBySyntax", | |
3854 | nodeType: "Block", | |
3855 | ruleId: "no-redeclare", | |
3856 | severity: 2 | |
3857 | }] | |
3858 | ); | |
3859 | }); | |
3860 | ||
3861 | it("should ignore the part preceded by '--' in '/*globals*/'.", () => { | |
3862 | const messages = linter.verify(` | |
3863 | /*globals aaa -- bbb */ | |
3864 | var aaa = {} | |
3865 | var bbb = {} | |
3866 | `, { rules: { "no-redeclare": "error" } }); | |
3867 | ||
3868 | // Don't include `bbb` | |
3869 | assert.deepStrictEqual( | |
3870 | messages, | |
3871 | [{ | |
3872 | column: 31, | |
3873 | endColumn: 34, | |
3874 | line: 2, | |
3875 | endLine: 2, | |
3876 | message: "'aaa' is already defined by a variable declaration.", | |
3877 | messageId: "redeclaredBySyntax", | |
3878 | nodeType: "Block", | |
3879 | ruleId: "no-redeclare", | |
3880 | severity: 2 | |
3881 | }] | |
3882 | ); | |
3883 | }); | |
3884 | ||
3885 | it("should ignore the part preceded by '--' in '/*exported*/'.", () => { | |
3886 | const messages = linter.verify(` | |
3887 | /*exported aaa -- bbb */ | |
3888 | var aaa = {} | |
3889 | var bbb = {} | |
3890 | `, { rules: { "no-unused-vars": "error" } }); | |
3891 | ||
3892 | // Don't include `aaa` | |
3893 | assert.deepStrictEqual( | |
3894 | messages, | |
3895 | [{ | |
3896 | column: 25, | |
3897 | endColumn: 28, | |
3898 | endLine: 4, | |
3899 | line: 4, | |
3900 | message: "'bbb' is assigned a value but never used.", | |
3901 | messageId: "unusedVar", | |
3902 | nodeType: "Identifier", | |
3903 | ruleId: "no-unused-vars", | |
3904 | severity: 2 | |
3905 | }] | |
3906 | ); | |
3907 | }); | |
3908 | ||
3909 | it("should ignore the part preceded by '--' in '/*eslint-disable*/'.", () => { | |
3910 | const messages = linter.verify(` | |
3911 | /*eslint-disable no-redeclare -- no-unused-vars */ | |
3912 | var aaa = {} | |
3913 | var aaa = {} | |
3914 | `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); | |
3915 | ||
3916 | // Do include `no-unused-vars` but not `no-redeclare` | |
3917 | assert.deepStrictEqual( | |
3918 | messages, | |
3919 | [{ | |
3920 | column: 25, | |
3921 | endLine: 3, | |
3922 | endColumn: 28, | |
3923 | line: 3, | |
3924 | message: "'aaa' is assigned a value but never used.", | |
3925 | messageId: "unusedVar", | |
3926 | nodeType: "Identifier", | |
3927 | ruleId: "no-unused-vars", | |
3928 | severity: 2 | |
3929 | }] | |
3930 | ); | |
3931 | }); | |
3932 | ||
3933 | it("should ignore the part preceded by '--' in '/*eslint-enable*/'.", () => { | |
3934 | const messages = linter.verify(` | |
3935 | /*eslint-disable no-redeclare, no-unused-vars */ | |
3936 | /*eslint-enable no-redeclare -- no-unused-vars */ | |
3937 | var aaa = {} | |
3938 | var aaa = {} | |
3939 | `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); | |
3940 | ||
3941 | // Do include `no-redeclare` but not `no-unused-vars` | |
3942 | assert.deepStrictEqual( | |
3943 | messages, | |
3944 | [{ | |
3945 | column: 25, | |
3946 | endLine: 5, | |
3947 | endColumn: 28, | |
3948 | line: 5, | |
3949 | message: "'aaa' is already defined.", | |
3950 | messageId: "redeclared", | |
3951 | nodeType: "Identifier", | |
3952 | ruleId: "no-redeclare", | |
3953 | severity: 2 | |
3954 | }] | |
3955 | ); | |
3956 | }); | |
3957 | ||
3958 | it("should ignore the part preceded by '--' in '//eslint-disable-line'.", () => { | |
3959 | const messages = linter.verify(` | |
3960 | var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars | |
3961 | var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars | |
3962 | `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); | |
3963 | ||
3964 | // Do include `no-unused-vars` but not `no-redeclare` | |
3965 | assert.deepStrictEqual( | |
3966 | messages, | |
3967 | [{ | |
3968 | column: 25, | |
3969 | endLine: 2, | |
3970 | endColumn: 28, | |
3971 | line: 2, | |
3972 | message: "'aaa' is assigned a value but never used.", | |
3973 | messageId: "unusedVar", | |
3974 | nodeType: "Identifier", | |
3975 | ruleId: "no-unused-vars", | |
3976 | severity: 2 | |
3977 | }] | |
3978 | ); | |
3979 | }); | |
3980 | ||
3981 | it("should ignore the part preceded by '--' in '/*eslint-disable-line*/'.", () => { | |
3982 | const messages = linter.verify(` | |
3983 | var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */ | |
3984 | var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */ | |
3985 | `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); | |
3986 | ||
3987 | // Do include `no-unused-vars` but not `no-redeclare` | |
3988 | assert.deepStrictEqual( | |
3989 | messages, | |
3990 | [{ | |
3991 | column: 25, | |
3992 | endLine: 2, | |
3993 | endColumn: 28, | |
3994 | line: 2, | |
3995 | message: "'aaa' is assigned a value but never used.", | |
3996 | messageId: "unusedVar", | |
3997 | nodeType: "Identifier", | |
3998 | ruleId: "no-unused-vars", | |
3999 | severity: 2 | |
4000 | }] | |
4001 | ); | |
4002 | }); | |
4003 | ||
4004 | it("should ignore the part preceded by '--' in '//eslint-disable-next-line'.", () => { | |
4005 | const messages = linter.verify(` | |
4006 | //eslint-disable-next-line no-redeclare -- no-unused-vars | |
4007 | var aaa = {} | |
4008 | //eslint-disable-next-line no-redeclare -- no-unused-vars | |
4009 | var aaa = {} | |
4010 | `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); | |
4011 | ||
4012 | // Do include `no-unused-vars` but not `no-redeclare` | |
4013 | assert.deepStrictEqual( | |
4014 | messages, | |
4015 | [{ | |
4016 | column: 25, | |
4017 | endLine: 3, | |
4018 | endColumn: 28, | |
4019 | line: 3, | |
4020 | message: "'aaa' is assigned a value but never used.", | |
4021 | messageId: "unusedVar", | |
4022 | nodeType: "Identifier", | |
4023 | ruleId: "no-unused-vars", | |
4024 | severity: 2 | |
4025 | }] | |
4026 | ); | |
4027 | }); | |
4028 | ||
4029 | it("should ignore the part preceded by '--' in '/*eslint-disable-next-line*/'.", () => { | |
4030 | const messages = linter.verify(` | |
4031 | /*eslint-disable-next-line no-redeclare -- no-unused-vars */ | |
4032 | var aaa = {} | |
4033 | /*eslint-disable-next-line no-redeclare -- no-unused-vars */ | |
4034 | var aaa = {} | |
4035 | `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); | |
4036 | ||
4037 | // Do include `no-unused-vars` but not `no-redeclare` | |
4038 | assert.deepStrictEqual( | |
4039 | messages, | |
4040 | [{ | |
4041 | column: 25, | |
4042 | endLine: 3, | |
4043 | endColumn: 28, | |
4044 | line: 3, | |
4045 | message: "'aaa' is assigned a value but never used.", | |
4046 | messageId: "unusedVar", | |
4047 | nodeType: "Identifier", | |
4048 | ruleId: "no-unused-vars", | |
4049 | severity: 2 | |
4050 | }] | |
4051 | ); | |
4052 | }); | |
4053 | ||
4054 | it("should not ignore the part preceded by '--' if the '--' is not surrounded by whitespaces.", () => { | |
4055 | const rule = sinon.stub().returns({}); | |
4056 | ||
4057 | linter.defineRule("a--rule", { create: rule }); | |
4058 | const messages = linter.verify(` | |
4059 | /*eslint a--rule:error */ | |
4060 | console.log("hello") | |
4061 | `, {}); | |
4062 | ||
4063 | // Don't include syntax error of the comment. | |
4064 | assert.deepStrictEqual(messages, []); | |
4065 | ||
4066 | // Use `a--rule`. | |
4067 | assert.strictEqual(rule.callCount, 1); | |
4068 | }); | |
4069 | ||
4070 | it("should ignore the part preceded by '--' even if the '--' is longer than 2.", () => { | |
4071 | const aaa = sinon.stub().returns({}); | |
4072 | const bbb = sinon.stub().returns({}); | |
4073 | ||
4074 | linter.defineRule("aaa", { create: aaa }); | |
4075 | linter.defineRule("bbb", { create: bbb }); | |
4076 | const messages = linter.verify(` | |
4077 | /*eslint aaa:error -------- bbb:error */ | |
4078 | console.log("hello") | |
4079 | `, {}); | |
4080 | ||
4081 | // Don't include syntax error of the comment. | |
4082 | assert.deepStrictEqual(messages, []); | |
4083 | ||
4084 | // Use only `aaa`. | |
4085 | assert.strictEqual(aaa.callCount, 1); | |
4086 | assert.strictEqual(bbb.callCount, 0); | |
4087 | }); | |
4088 | ||
4089 | it("should ignore the part preceded by '--' with line breaks.", () => { | |
4090 | const aaa = sinon.stub().returns({}); | |
4091 | const bbb = sinon.stub().returns({}); | |
4092 | ||
4093 | linter.defineRule("aaa", { create: aaa }); | |
4094 | linter.defineRule("bbb", { create: bbb }); | |
4095 | const messages = linter.verify(` | |
4096 | /*eslint aaa:error | |
4097 | -------- | |
4098 | bbb:error */ | |
4099 | console.log("hello") | |
4100 | `, {}); | |
4101 | ||
4102 | // Don't include syntax error of the comment. | |
4103 | assert.deepStrictEqual(messages, []); | |
4104 | ||
4105 | // Use only `aaa`. | |
4106 | assert.strictEqual(aaa.callCount, 1); | |
4107 | assert.strictEqual(bbb.callCount, 0); | |
4108 | }); | |
4109 | }); | |
4110 | }); | |
4111 | ||
4112 | describe("context.getScope()", () => { | |
4113 | ||
4114 | /** | |
4115 | * Get the scope on the node `astSelector` specified. | |
4116 | * @param {string} code The source code to verify. | |
4117 | * @param {string} astSelector The AST selector to get scope. | |
4118 | * @param {number} [ecmaVersion=5] The ECMAScript version. | |
4119 | * @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope. | |
4120 | */ | |
4121 | function getScope(code, astSelector, ecmaVersion = 5) { | |
4122 | let node, scope; | |
4123 | ||
4124 | linter.defineRule("get-scope", context => ({ | |
4125 | [astSelector](node0) { | |
4126 | node = node0; | |
4127 | scope = context.getScope(); | |
4128 | } | |
4129 | })); | |
4130 | linter.verify( | |
4131 | code, | |
4132 | { | |
4133 | parserOptions: { ecmaVersion }, | |
4134 | rules: { "get-scope": 2 } | |
4135 | } | |
4136 | ); | |
4137 | ||
4138 | return { node, scope }; | |
4139 | } | |
4140 | ||
4141 | it("should return 'function' scope on FunctionDeclaration (ES5)", () => { | |
4142 | const { node, scope } = getScope("function f() {}", "FunctionDeclaration"); | |
4143 | ||
4144 | assert.strictEqual(scope.type, "function"); | |
4145 | assert.strictEqual(scope.block, node); | |
4146 | }); | |
4147 | ||
4148 | it("should return 'function' scope on FunctionExpression (ES5)", () => { | |
4149 | const { node, scope } = getScope("!function f() {}", "FunctionExpression"); | |
4150 | ||
4151 | assert.strictEqual(scope.type, "function"); | |
4152 | assert.strictEqual(scope.block, node); | |
4153 | }); | |
4154 | ||
4155 | it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => { | |
4156 | const { node, scope } = getScope("function f() {}", "BlockStatement"); | |
4157 | ||
4158 | assert.strictEqual(scope.type, "function"); | |
4159 | assert.strictEqual(scope.block, node.parent); | |
4160 | }); | |
4161 | ||
4162 | it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => { | |
4163 | const { node, scope } = getScope("function f() {}", "BlockStatement", 2015); | |
4164 | ||
4165 | assert.strictEqual(scope.type, "function"); | |
4166 | assert.strictEqual(scope.block, node.parent); | |
4167 | }); | |
4168 | ||
4169 | it("should return 'function' scope on BlockStatement in functions (ES5)", () => { | |
4170 | const { node, scope } = getScope("function f() { { var b; } }", "BlockStatement > BlockStatement"); | |
4171 | ||
4172 | assert.strictEqual(scope.type, "function"); | |
4173 | assert.strictEqual(scope.block, node.parent.parent); | |
4174 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); | |
4175 | }); | |
4176 | ||
4177 | it("should return 'block' scope on BlockStatement in functions (ES2015)", () => { | |
4178 | const { node, scope } = getScope("function f() { { let a; var b; } }", "BlockStatement > BlockStatement", 2015); | |
4179 | ||
4180 | assert.strictEqual(scope.type, "block"); | |
4181 | assert.strictEqual(scope.upper.type, "function"); | |
4182 | assert.strictEqual(scope.block, node); | |
4183 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); | |
4184 | assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "b"]); | |
4185 | }); | |
4186 | ||
4187 | it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => { | |
4188 | const { node, scope } = getScope("function f() { { let a; { let b; var c; } } }", "BlockStatement > BlockStatement > BlockStatement", 2015); | |
4189 | ||
4190 | assert.strictEqual(scope.type, "block"); | |
4191 | assert.strictEqual(scope.upper.type, "block"); | |
4192 | assert.strictEqual(scope.upper.upper.type, "function"); | |
4193 | assert.strictEqual(scope.block, node); | |
4194 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); | |
4195 | assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["a"]); | |
4196 | assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "c"]); | |
4197 | }); | |
4198 | ||
4199 | it("should return 'function' scope on SwitchStatement in functions (ES5)", () => { | |
4200 | const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchStatement"); | |
4201 | ||
4202 | assert.strictEqual(scope.type, "function"); | |
4203 | assert.strictEqual(scope.block, node.parent.parent); | |
4204 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); | |
4205 | }); | |
4206 | ||
4207 | it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => { | |
4208 | const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchStatement", 2015); | |
4209 | ||
4210 | assert.strictEqual(scope.type, "switch"); | |
4211 | assert.strictEqual(scope.block, node); | |
4212 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); | |
4213 | }); | |
4214 | ||
4215 | it("should return 'function' scope on SwitchCase in functions (ES5)", () => { | |
4216 | const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchCase"); | |
4217 | ||
4218 | assert.strictEqual(scope.type, "function"); | |
4219 | assert.strictEqual(scope.block, node.parent.parent.parent); | |
4220 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); | |
4221 | }); | |
4222 | ||
4223 | it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => { | |
4224 | const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchCase", 2015); | |
4225 | ||
4226 | assert.strictEqual(scope.type, "switch"); | |
4227 | assert.strictEqual(scope.block, node.parent); | |
4228 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); | |
4229 | }); | |
4230 | ||
4231 | it("should return 'catch' scope on CatchClause in functions (ES5)", () => { | |
4232 | const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause"); | |
4233 | ||
4234 | assert.strictEqual(scope.type, "catch"); | |
4235 | assert.strictEqual(scope.block, node); | |
4236 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); | |
4237 | }); | |
4238 | ||
4239 | it("should return 'catch' scope on CatchClause in functions (ES2015)", () => { | |
4240 | const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause", 2015); | |
4241 | ||
4242 | assert.strictEqual(scope.type, "catch"); | |
4243 | assert.strictEqual(scope.block, node); | |
4244 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); | |
4245 | }); | |
4246 | ||
4247 | it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => { | |
4248 | const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause > BlockStatement"); | |
4249 | ||
4250 | assert.strictEqual(scope.type, "catch"); | |
4251 | assert.strictEqual(scope.block, node.parent); | |
4252 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); | |
4253 | }); | |
4254 | ||
4255 | it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => { | |
4256 | const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause > BlockStatement", 2015); | |
4257 | ||
4258 | assert.strictEqual(scope.type, "block"); | |
4259 | assert.strictEqual(scope.block, node); | |
4260 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); | |
4261 | }); | |
4262 | ||
4263 | it("should return 'function' scope on ForStatement in functions (ES5)", () => { | |
4264 | const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement"); | |
4265 | ||
4266 | assert.strictEqual(scope.type, "function"); | |
4267 | assert.strictEqual(scope.block, node.parent.parent); | |
4268 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); | |
4269 | }); | |
4270 | ||
4271 | it("should return 'for' scope on ForStatement in functions (ES2015)", () => { | |
4272 | const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement", 2015); | |
4273 | ||
4274 | assert.strictEqual(scope.type, "for"); | |
4275 | assert.strictEqual(scope.block, node); | |
4276 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["i"]); | |
4277 | }); | |
4278 | ||
4279 | it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => { | |
4280 | const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement"); | |
4281 | ||
4282 | assert.strictEqual(scope.type, "function"); | |
4283 | assert.strictEqual(scope.block, node.parent.parent.parent); | |
4284 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); | |
4285 | }); | |
4286 | ||
4287 | it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => { | |
4288 | const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement", 2015); | |
4289 | ||
4290 | assert.strictEqual(scope.type, "block"); | |
4291 | assert.strictEqual(scope.upper.type, "for"); | |
4292 | assert.strictEqual(scope.block, node); | |
4293 | assert.deepStrictEqual(scope.variables.map(v => v.name), []); | |
4294 | assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["i"]); | |
4295 | }); | |
4296 | ||
4297 | it("should return 'function' scope on ForInStatement in functions (ES5)", () => { | |
4298 | const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement"); | |
4299 | ||
4300 | assert.strictEqual(scope.type, "function"); | |
4301 | assert.strictEqual(scope.block, node.parent.parent); | |
4302 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); | |
4303 | }); | |
4304 | ||
4305 | it("should return 'for' scope on ForInStatement in functions (ES2015)", () => { | |
4306 | const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement", 2015); | |
4307 | ||
4308 | assert.strictEqual(scope.type, "for"); | |
4309 | assert.strictEqual(scope.block, node); | |
4310 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["key"]); | |
4311 | }); | |
4312 | ||
4313 | it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => { | |
4314 | const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement > BlockStatement"); | |
4315 | ||
4316 | assert.strictEqual(scope.type, "function"); | |
4317 | assert.strictEqual(scope.block, node.parent.parent.parent); | |
4318 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); | |
4319 | }); | |
4320 | ||
4321 | it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => { | |
4322 | const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement > BlockStatement", 2015); | |
4323 | ||
4324 | assert.strictEqual(scope.type, "block"); | |
4325 | assert.strictEqual(scope.upper.type, "for"); | |
4326 | assert.strictEqual(scope.block, node); | |
4327 | assert.deepStrictEqual(scope.variables.map(v => v.name), []); | |
4328 | assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["key"]); | |
4329 | }); | |
4330 | ||
4331 | it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => { | |
4332 | const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement", 2015); | |
4333 | ||
4334 | assert.strictEqual(scope.type, "for"); | |
4335 | assert.strictEqual(scope.block, node); | |
4336 | assert.deepStrictEqual(scope.variables.map(v => v.name), ["x"]); | |
4337 | }); | |
4338 | ||
4339 | it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => { | |
4340 | const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement > BlockStatement", 2015); | |
4341 | ||
4342 | assert.strictEqual(scope.type, "block"); | |
4343 | assert.strictEqual(scope.upper.type, "for"); | |
4344 | assert.strictEqual(scope.block, node); | |
4345 | assert.deepStrictEqual(scope.variables.map(v => v.name), []); | |
4346 | assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["x"]); | |
4347 | }); | |
4348 | ||
4349 | it("should shadow the same name variable by the iteration variable.", () => { | |
4350 | const { node, scope } = getScope("let x; for (let x of x) {}", "ForOfStatement", 2015); | |
4351 | ||
4352 | assert.strictEqual(scope.type, "for"); | |
4353 | assert.strictEqual(scope.upper.type, "global"); | |
4354 | assert.strictEqual(scope.block, node); | |
4355 | assert.strictEqual(scope.upper.variables[0].references.length, 0); | |
4356 | assert.strictEqual(scope.references[0].identifier, node.left.declarations[0].id); | |
4357 | assert.strictEqual(scope.references[1].identifier, node.right); | |
4358 | assert.strictEqual(scope.references[1].resolved, scope.variables[0]); | |
4359 | }); | |
4360 | }); | |
4361 | ||
4362 | describe("Variables and references", () => { | |
4363 | const code = [ | |
4364 | "a;", | |
4365 | "function foo() { b; }", | |
4366 | "Object;", | |
4367 | "foo;", | |
4368 | "var c;", | |
4369 | "c;", | |
4370 | "/* global d */", | |
4371 | "d;", | |
4372 | "e;", | |
4373 | "f;" | |
4374 | ].join("\n"); | |
4375 | let scope = null; | |
4376 | ||
4377 | beforeEach(() => { | |
4378 | let ok = false; | |
4379 | ||
4380 | linter.defineRules({ | |
4381 | test(context) { | |
4382 | return { | |
4383 | Program() { | |
4384 | scope = context.getScope(); | |
4385 | ok = true; | |
4386 | } | |
4387 | }; | |
4388 | } | |
4389 | }); | |
4390 | linter.verify(code, { rules: { test: 2 }, globals: { e: true, f: false } }); | |
4391 | assert(ok); | |
4392 | }); | |
4393 | ||
4394 | afterEach(() => { | |
4395 | scope = null; | |
4396 | }); | |
4397 | ||
4398 | it("Scope#through should contain references of undefined variables", () => { | |
4399 | assert.strictEqual(scope.through.length, 2); | |
4400 | assert.strictEqual(scope.through[0].identifier.name, "a"); | |
4401 | assert.strictEqual(scope.through[0].identifier.loc.start.line, 1); | |
4402 | assert.strictEqual(scope.through[0].resolved, null); | |
4403 | assert.strictEqual(scope.through[1].identifier.name, "b"); | |
4404 | assert.strictEqual(scope.through[1].identifier.loc.start.line, 2); | |
4405 | assert.strictEqual(scope.through[1].resolved, null); | |
4406 | }); | |
4407 | ||
4408 | it("Scope#variables should contain global variables", () => { | |
4409 | assert(scope.variables.some(v => v.name === "Object")); | |
4410 | assert(scope.variables.some(v => v.name === "foo")); | |
4411 | assert(scope.variables.some(v => v.name === "c")); | |
4412 | assert(scope.variables.some(v => v.name === "d")); | |
4413 | assert(scope.variables.some(v => v.name === "e")); | |
4414 | assert(scope.variables.some(v => v.name === "f")); | |
4415 | }); | |
4416 | ||
4417 | it("Scope#set should contain global variables", () => { | |
4418 | assert(scope.set.get("Object")); | |
4419 | assert(scope.set.get("foo")); | |
4420 | assert(scope.set.get("c")); | |
4421 | assert(scope.set.get("d")); | |
4422 | assert(scope.set.get("e")); | |
4423 | assert(scope.set.get("f")); | |
4424 | }); | |
4425 | ||
4426 | it("Variables#references should contain their references", () => { | |
4427 | assert.strictEqual(scope.set.get("Object").references.length, 1); | |
4428 | assert.strictEqual(scope.set.get("Object").references[0].identifier.name, "Object"); | |
4429 | assert.strictEqual(scope.set.get("Object").references[0].identifier.loc.start.line, 3); | |
4430 | assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object")); | |
4431 | assert.strictEqual(scope.set.get("foo").references.length, 1); | |
4432 | assert.strictEqual(scope.set.get("foo").references[0].identifier.name, "foo"); | |
4433 | assert.strictEqual(scope.set.get("foo").references[0].identifier.loc.start.line, 4); | |
4434 | assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo")); | |
4435 | assert.strictEqual(scope.set.get("c").references.length, 1); | |
4436 | assert.strictEqual(scope.set.get("c").references[0].identifier.name, "c"); | |
4437 | assert.strictEqual(scope.set.get("c").references[0].identifier.loc.start.line, 6); | |
4438 | assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c")); | |
4439 | assert.strictEqual(scope.set.get("d").references.length, 1); | |
4440 | assert.strictEqual(scope.set.get("d").references[0].identifier.name, "d"); | |
4441 | assert.strictEqual(scope.set.get("d").references[0].identifier.loc.start.line, 8); | |
4442 | assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d")); | |
4443 | assert.strictEqual(scope.set.get("e").references.length, 1); | |
4444 | assert.strictEqual(scope.set.get("e").references[0].identifier.name, "e"); | |
4445 | assert.strictEqual(scope.set.get("e").references[0].identifier.loc.start.line, 9); | |
4446 | assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e")); | |
4447 | assert.strictEqual(scope.set.get("f").references.length, 1); | |
4448 | assert.strictEqual(scope.set.get("f").references[0].identifier.name, "f"); | |
4449 | assert.strictEqual(scope.set.get("f").references[0].identifier.loc.start.line, 10); | |
4450 | assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f")); | |
4451 | }); | |
4452 | ||
4453 | it("Reference#resolved should be their variable", () => { | |
4454 | assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object")); | |
4455 | assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo")); | |
4456 | assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c")); | |
4457 | assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d")); | |
4458 | assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e")); | |
4459 | assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f")); | |
4460 | }); | |
4461 | }); | |
4462 | ||
4463 | describe("context.getDeclaredVariables(node)", () => { | |
4464 | ||
4465 | /** | |
4466 | * Assert `context.getDeclaredVariables(node)` is valid. | |
4467 | * @param {string} code A code to check. | |
4468 | * @param {string} type A type string of ASTNode. This method checks variables on the node of the type. | |
4469 | * @param {Array<Array<string>>} expectedNamesList An array of expected variable names. The expected variable names is an array of string. | |
4470 | * @returns {void} | |
4471 | */ | |
4472 | function verify(code, type, expectedNamesList) { | |
4473 | linter.defineRules({ | |
4474 | test(context) { | |
4475 | ||
4476 | /** | |
4477 | * Assert `context.getDeclaredVariables(node)` is empty. | |
4478 | * @param {ASTNode} node A node to check. | |
4479 | * @returns {void} | |
4480 | */ | |
4481 | function checkEmpty(node) { | |
4482 | assert.strictEqual(0, context.getDeclaredVariables(node).length); | |
4483 | } | |
4484 | const rule = { | |
4485 | Program: checkEmpty, | |
4486 | EmptyStatement: checkEmpty, | |
4487 | BlockStatement: checkEmpty, | |
4488 | ExpressionStatement: checkEmpty, | |
4489 | LabeledStatement: checkEmpty, | |
4490 | BreakStatement: checkEmpty, | |
4491 | ContinueStatement: checkEmpty, | |
4492 | WithStatement: checkEmpty, | |
4493 | SwitchStatement: checkEmpty, | |
4494 | ReturnStatement: checkEmpty, | |
4495 | ThrowStatement: checkEmpty, | |
4496 | TryStatement: checkEmpty, | |
4497 | WhileStatement: checkEmpty, | |
4498 | DoWhileStatement: checkEmpty, | |
4499 | ForStatement: checkEmpty, | |
4500 | ForInStatement: checkEmpty, | |
4501 | DebuggerStatement: checkEmpty, | |
4502 | ThisExpression: checkEmpty, | |
4503 | ArrayExpression: checkEmpty, | |
4504 | ObjectExpression: checkEmpty, | |
4505 | Property: checkEmpty, | |
4506 | SequenceExpression: checkEmpty, | |
4507 | UnaryExpression: checkEmpty, | |
4508 | BinaryExpression: checkEmpty, | |
4509 | AssignmentExpression: checkEmpty, | |
4510 | UpdateExpression: checkEmpty, | |
4511 | LogicalExpression: checkEmpty, | |
4512 | ConditionalExpression: checkEmpty, | |
4513 | CallExpression: checkEmpty, | |
4514 | NewExpression: checkEmpty, | |
4515 | MemberExpression: checkEmpty, | |
4516 | SwitchCase: checkEmpty, | |
4517 | Identifier: checkEmpty, | |
4518 | Literal: checkEmpty, | |
4519 | ForOfStatement: checkEmpty, | |
4520 | ArrowFunctionExpression: checkEmpty, | |
4521 | YieldExpression: checkEmpty, | |
4522 | TemplateLiteral: checkEmpty, | |
4523 | TaggedTemplateExpression: checkEmpty, | |
4524 | TemplateElement: checkEmpty, | |
4525 | ObjectPattern: checkEmpty, | |
4526 | ArrayPattern: checkEmpty, | |
4527 | RestElement: checkEmpty, | |
4528 | AssignmentPattern: checkEmpty, | |
4529 | ClassBody: checkEmpty, | |
4530 | MethodDefinition: checkEmpty, | |
4531 | MetaProperty: checkEmpty | |
4532 | }; | |
4533 | ||
4534 | rule[type] = function(node) { | |
4535 | const expectedNames = expectedNamesList.shift(); | |
4536 | const variables = context.getDeclaredVariables(node); | |
4537 | ||
4538 | assert(Array.isArray(expectedNames)); | |
4539 | assert(Array.isArray(variables)); | |
4540 | assert.strictEqual(expectedNames.length, variables.length); | |
4541 | for (let i = variables.length - 1; i >= 0; i--) { | |
4542 | assert.strictEqual(expectedNames[i], variables[i].name); | |
4543 | } | |
4544 | }; | |
4545 | return rule; | |
4546 | } | |
4547 | }); | |
4548 | linter.verify(code, { | |
4549 | rules: { test: 2 }, | |
4550 | parserOptions: { | |
4551 | ecmaVersion: 6, | |
4552 | sourceType: "module" | |
4553 | } | |
4554 | }); | |
4555 | ||
4556 | // Check all expected names are asserted. | |
4557 | assert.strictEqual(0, expectedNamesList.length); | |
4558 | } | |
4559 | ||
4560 | it("VariableDeclaration", () => { | |
4561 | const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; | |
4562 | const namesList = [ | |
4563 | ["a", "b", "c"], | |
4564 | ["d", "e", "f"], | |
4565 | ["g", "h", "i", "j", "k"], | |
4566 | ["l"] | |
4567 | ]; | |
4568 | ||
4569 | verify(code, "VariableDeclaration", namesList); | |
4570 | }); | |
4571 | ||
4572 | it("VariableDeclaration (on for-in/of loop)", () => { | |
4573 | ||
4574 | // TDZ scope is created here, so tests to exclude those. | |
4575 | const code = "\n for (var {a, x: [b], y: {c = 0}} in foo) {\n let g;\n }\n for (let {d, x: [e], y: {f = 0}} of foo) {\n let h;\n }\n "; | |
4576 | const namesList = [ | |
4577 | ["a", "b", "c"], | |
4578 | ["g"], | |
4579 | ["d", "e", "f"], | |
4580 | ["h"] | |
4581 | ]; | |
4582 | ||
4583 | verify(code, "VariableDeclaration", namesList); | |
4584 | }); | |
4585 | ||
4586 | it("VariableDeclarator", () => { | |
4587 | ||
4588 | // TDZ scope is created here, so tests to exclude those. | |
4589 | const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; | |
4590 | const namesList = [ | |
4591 | ["a", "b", "c"], | |
4592 | ["d", "e", "f"], | |
4593 | ["g", "h", "i"], | |
4594 | ["j", "k"], | |
4595 | ["l"] | |
4596 | ]; | |
4597 | ||
4598 | verify(code, "VariableDeclarator", namesList); | |
4599 | }); | |
4600 | ||
4601 | it("FunctionDeclaration", () => { | |
4602 | const code = "\n function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n }\n function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n }\n "; | |
4603 | const namesList = [ | |
4604 | ["foo", "a", "b", "c", "d", "e"], | |
4605 | ["bar", "f", "g", "h", "i", "j"] | |
4606 | ]; | |
4607 | ||
4608 | verify(code, "FunctionDeclaration", namesList); | |
4609 | }); | |
4610 | ||
4611 | it("FunctionExpression", () => { | |
4612 | const code = "\n (function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n });\n (function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n });\n "; | |
4613 | const namesList = [ | |
4614 | ["foo", "a", "b", "c", "d", "e"], | |
4615 | ["bar", "f", "g", "h", "i", "j"], | |
4616 | ["q"] | |
4617 | ]; | |
4618 | ||
4619 | verify(code, "FunctionExpression", namesList); | |
4620 | }); | |
4621 | ||
4622 | it("ArrowFunctionExpression", () => { | |
4623 | const code = "\n (({a, x: [b], y: {c = 0}}, [d, e]) => {\n let z;\n });\n (({f, x: [g], y: {h = 0}}, [i, j]) => {\n let z;\n });\n "; | |
4624 | const namesList = [ | |
4625 | ["a", "b", "c", "d", "e"], | |
4626 | ["f", "g", "h", "i", "j"] | |
4627 | ]; | |
4628 | ||
4629 | verify(code, "ArrowFunctionExpression", namesList); | |
4630 | }); | |
4631 | ||
4632 | it("ClassDeclaration", () => { | |
4633 | const code = "\n class A { foo(x) { let y; } }\n class B { foo(x) { let y; } }\n "; | |
4634 | const namesList = [ | |
4635 | ["A", "A"], // outer scope's and inner scope's. | |
4636 | ["B", "B"] | |
4637 | ]; | |
4638 | ||
4639 | verify(code, "ClassDeclaration", namesList); | |
4640 | }); | |
4641 | ||
4642 | it("ClassExpression", () => { | |
4643 | const code = "\n (class A { foo(x) { let y; } });\n (class B { foo(x) { let y; } });\n "; | |
4644 | const namesList = [ | |
4645 | ["A"], | |
4646 | ["B"] | |
4647 | ]; | |
4648 | ||
4649 | verify(code, "ClassExpression", namesList); | |
4650 | }); | |
4651 | ||
4652 | it("CatchClause", () => { | |
4653 | const code = "\n try {} catch ({a, b}) {\n let x;\n try {} catch ({c, d}) {\n let y;\n }\n }\n "; | |
4654 | const namesList = [ | |
4655 | ["a", "b"], | |
4656 | ["c", "d"] | |
4657 | ]; | |
4658 | ||
4659 | verify(code, "CatchClause", namesList); | |
4660 | }); | |
4661 | ||
4662 | it("ImportDeclaration", () => { | |
4663 | const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; | |
4664 | const namesList = [ | |
4665 | [], | |
4666 | ["a"], | |
4667 | ["b", "c", "d"] | |
4668 | ]; | |
4669 | ||
4670 | verify(code, "ImportDeclaration", namesList); | |
4671 | }); | |
4672 | ||
4673 | it("ImportSpecifier", () => { | |
4674 | const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; | |
4675 | const namesList = [ | |
4676 | ["c"], | |
4677 | ["d"] | |
4678 | ]; | |
4679 | ||
4680 | verify(code, "ImportSpecifier", namesList); | |
4681 | }); | |
4682 | ||
4683 | it("ImportDefaultSpecifier", () => { | |
4684 | const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; | |
4685 | const namesList = [ | |
4686 | ["b"] | |
4687 | ]; | |
4688 | ||
4689 | verify(code, "ImportDefaultSpecifier", namesList); | |
4690 | }); | |
4691 | ||
4692 | it("ImportNamespaceSpecifier", () => { | |
4693 | const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; | |
4694 | const namesList = [ | |
4695 | ["a"] | |
4696 | ]; | |
4697 | ||
4698 | verify(code, "ImportNamespaceSpecifier", namesList); | |
4699 | }); | |
4700 | }); | |
4701 | ||
4702 | describe("suggestions", () => { | |
4703 | it("provides suggestion information for tools to use", () => { | |
4704 | linter.defineRule("rule-with-suggestions", context => ({ | |
4705 | Program(node) { | |
4706 | context.report({ | |
4707 | node, | |
4708 | message: "Incorrect spacing", | |
4709 | suggest: [{ | |
4710 | desc: "Insert space at the beginning", | |
4711 | fix: fixer => fixer.insertTextBefore(node, " ") | |
4712 | }, { | |
4713 | desc: "Insert space at the end", | |
4714 | fix: fixer => fixer.insertTextAfter(node, " ") | |
4715 | }] | |
4716 | }); | |
4717 | } | |
4718 | })); | |
4719 | ||
4720 | const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } }); | |
4721 | ||
4722 | assert.deepStrictEqual(messages[0].suggestions, [{ | |
4723 | desc: "Insert space at the beginning", | |
4724 | fix: { | |
4725 | range: [0, 0], | |
4726 | text: " " | |
4727 | } | |
4728 | }, { | |
4729 | desc: "Insert space at the end", | |
4730 | fix: { | |
4731 | range: [10, 10], | |
4732 | text: " " | |
4733 | } | |
4734 | }]); | |
4735 | }); | |
4736 | ||
4737 | it("supports messageIds for suggestions", () => { | |
4738 | linter.defineRule("rule-with-suggestions", { | |
4739 | meta: { | |
4740 | messages: { | |
4741 | suggestion1: "Insert space at the beginning", | |
4742 | suggestion2: "Insert space at the end" | |
4743 | } | |
4744 | }, | |
4745 | create: context => ({ | |
4746 | Program(node) { | |
4747 | context.report({ | |
4748 | node, | |
4749 | message: "Incorrect spacing", | |
4750 | suggest: [{ | |
4751 | messageId: "suggestion1", | |
4752 | fix: fixer => fixer.insertTextBefore(node, " ") | |
4753 | }, { | |
4754 | messageId: "suggestion2", | |
4755 | fix: fixer => fixer.insertTextAfter(node, " ") | |
4756 | }] | |
4757 | }); | |
4758 | } | |
4759 | }) | |
4760 | }); | |
4761 | ||
4762 | const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } }); | |
4763 | ||
4764 | assert.deepStrictEqual(messages[0].suggestions, [{ | |
4765 | messageId: "suggestion1", | |
4766 | desc: "Insert space at the beginning", | |
4767 | fix: { | |
4768 | range: [0, 0], | |
4769 | text: " " | |
4770 | } | |
4771 | }, { | |
4772 | messageId: "suggestion2", | |
4773 | desc: "Insert space at the end", | |
4774 | fix: { | |
4775 | range: [10, 10], | |
4776 | text: " " | |
4777 | } | |
4778 | }]); | |
4779 | }); | |
4780 | }); | |
4781 | ||
4782 | describe("mutability", () => { | |
4783 | let linter1 = null; | |
4784 | let linter2 = null; | |
4785 | ||
4786 | beforeEach(() => { | |
4787 | linter1 = new Linter(); | |
4788 | linter2 = new Linter(); | |
4789 | }); | |
4790 | ||
4791 | describe("rules", () => { | |
4792 | it("with no changes, same rules are loaded", () => { | |
4793 | assert.sameDeepMembers(Array.from(linter1.getRules().keys()), Array.from(linter2.getRules().keys())); | |
4794 | }); | |
4795 | ||
4796 | it("loading rule in one doesn't change the other", () => { | |
4797 | linter1.defineRule("mock-rule", () => ({})); | |
4798 | ||
4799 | assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present"); | |
4800 | assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present"); | |
4801 | }); | |
4802 | }); | |
4803 | }); | |
4804 | ||
4805 | describe("processors", () => { | |
4806 | let receivedFilenames = []; | |
4807 | ||
4808 | beforeEach(() => { | |
4809 | receivedFilenames = []; | |
4810 | ||
4811 | // A rule that always reports the AST with a message equal to the source text | |
4812 | linter.defineRule("report-original-text", context => ({ | |
4813 | Program(ast) { | |
4814 | receivedFilenames.push(context.getFilename()); | |
4815 | context.report({ node: ast, message: context.getSourceCode().text }); | |
4816 | } | |
4817 | })); | |
4818 | }); | |
4819 | ||
4820 | describe("preprocessors", () => { | |
4821 | it("should receive text and filename.", () => { | |
4822 | const code = "foo bar baz"; | |
4823 | const preprocess = sinon.spy(text => text.split(" ")); | |
4824 | ||
4825 | linter.verify(code, {}, { filename, preprocess }); | |
4826 | ||
4827 | assert.strictEqual(preprocess.calledOnce, true); | |
4828 | assert.deepStrictEqual(preprocess.args[0], [code, filename]); | |
4829 | }); | |
4830 | ||
4831 | it("should apply a preprocessor to the code, and lint each code sample separately", () => { | |
4832 | const code = "foo bar baz"; | |
4833 | const problems = linter.verify( | |
4834 | code, | |
4835 | { rules: { "report-original-text": "error" } }, | |
4836 | { | |
4837 | ||
4838 | // Apply a preprocessor that splits the source text into spaces and lints each word individually | |
4839 | preprocess(input) { | |
4840 | return input.split(" "); | |
4841 | } | |
4842 | } | |
4843 | ); | |
4844 | ||
4845 | assert.strictEqual(problems.length, 3); | |
4846 | assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]); | |
4847 | }); | |
4848 | ||
4849 | it("should apply a preprocessor to the code even if the preprocessor returned code block objects.", () => { | |
4850 | const code = "foo bar baz"; | |
4851 | const problems = linter.verify( | |
4852 | code, | |
4853 | { rules: { "report-original-text": "error" } }, | |
4854 | { | |
4855 | filename, | |
4856 | ||
4857 | // Apply a preprocessor that splits the source text into spaces and lints each word individually | |
4858 | preprocess(input) { | |
4859 | return input.split(" ").map(text => ({ | |
4860 | filename: "block.js", | |
4861 | text | |
4862 | })); | |
4863 | } | |
4864 | } | |
4865 | ); | |
4866 | ||
4867 | assert.strictEqual(problems.length, 3); | |
4868 | assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]); | |
4869 | assert.strictEqual(receivedFilenames.length, 3); | |
4870 | assert(/^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0])); | |
4871 | assert(/^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1])); | |
4872 | assert(/^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2])); | |
4873 | }); | |
4874 | ||
4875 | it("should receive text even if a SourceCode object was given.", () => { | |
4876 | const code = "foo"; | |
4877 | const preprocess = sinon.spy(text => text.split(" ")); | |
4878 | ||
4879 | linter.verify(code, {}); | |
4880 | const sourceCode = linter.getSourceCode(); | |
4881 | ||
4882 | linter.verify(sourceCode, {}, { filename, preprocess }); | |
4883 | ||
4884 | assert.strictEqual(preprocess.calledOnce, true); | |
4885 | assert.deepStrictEqual(preprocess.args[0], [code, filename]); | |
4886 | }); | |
4887 | ||
4888 | it("should receive text even if a SourceCode object was given (with BOM).", () => { | |
4889 | const code = "\uFEFFfoo"; | |
4890 | const preprocess = sinon.spy(text => text.split(" ")); | |
4891 | ||
4892 | linter.verify(code, {}); | |
4893 | const sourceCode = linter.getSourceCode(); | |
4894 | ||
4895 | linter.verify(sourceCode, {}, { filename, preprocess }); | |
4896 | ||
4897 | assert.strictEqual(preprocess.calledOnce, true); | |
4898 | assert.deepStrictEqual(preprocess.args[0], [code, filename]); | |
4899 | }); | |
4900 | }); | |
4901 | ||
4902 | describe("postprocessors", () => { | |
4903 | it("should receive result and filename.", () => { | |
4904 | const code = "foo bar baz"; | |
4905 | const preprocess = sinon.spy(text => text.split(" ")); | |
4906 | const postprocess = sinon.spy(text => [text]); | |
4907 | ||
4908 | linter.verify(code, {}, { filename, postprocess, preprocess }); | |
4909 | ||
4910 | assert.strictEqual(postprocess.calledOnce, true); | |
4911 | assert.deepStrictEqual(postprocess.args[0], [[[], [], []], filename]); | |
4912 | }); | |
4913 | ||
4914 | it("should apply a postprocessor to the reported messages", () => { | |
4915 | const code = "foo bar baz"; | |
4916 | ||
4917 | const problems = linter.verify( | |
4918 | code, | |
4919 | { rules: { "report-original-text": "error" } }, | |
4920 | { | |
4921 | preprocess: input => input.split(" "), | |
4922 | ||
4923 | /* | |
4924 | * Apply a postprocessor that updates the locations of the reported problems | |
4925 | * to make sure they correspond to the locations in the original text. | |
4926 | */ | |
4927 | postprocess(problemLists) { | |
4928 | problemLists.forEach(problemList => assert.strictEqual(problemList.length, 1)); | |
4929 | return problemLists.reduce( | |
4930 | (combinedList, problemList, index) => | |
4931 | combinedList.concat( | |
4932 | problemList.map( | |
4933 | problem => | |
4934 | Object.assign( | |
4935 | {}, | |
4936 | problem, | |
4937 | { | |
4938 | message: problem.message.toUpperCase(), | |
4939 | column: problem.column + index * 4 | |
4940 | } | |
4941 | ) | |
4942 | ) | |
4943 | ), | |
4944 | [] | |
4945 | ); | |
4946 | } | |
4947 | } | |
4948 | ); | |
4949 | ||
4950 | assert.strictEqual(problems.length, 3); | |
4951 | assert.deepStrictEqual(problems.map(problem => problem.message), ["FOO", "BAR", "BAZ"]); | |
4952 | assert.deepStrictEqual(problems.map(problem => problem.column), [1, 5, 9]); | |
4953 | }); | |
4954 | ||
4955 | it("should use postprocessed problem ranges when applying autofixes", () => { | |
4956 | const code = "foo bar baz"; | |
4957 | ||
4958 | linter.defineRule("capitalize-identifiers", context => ({ | |
4959 | Identifier(node) { | |
4960 | if (node.name !== node.name.toUpperCase()) { | |
4961 | context.report({ | |
4962 | node, | |
4963 | message: "Capitalize this identifier", | |
4964 | fix: fixer => fixer.replaceText(node, node.name.toUpperCase()) | |
4965 | }); | |
4966 | } | |
4967 | } | |
4968 | })); | |
4969 | ||
4970 | const fixResult = linter.verifyAndFix( | |
4971 | code, | |
4972 | { rules: { "capitalize-identifiers": "error" } }, | |
4973 | { | |
4974 | ||
4975 | /* | |
4976 | * Apply a postprocessor that updates the locations of autofixes | |
4977 | * to make sure they correspond to locations in the original text. | |
4978 | */ | |
4979 | preprocess: input => input.split(" "), | |
4980 | postprocess(problemLists) { | |
4981 | return problemLists.reduce( | |
4982 | (combinedProblems, problemList, blockIndex) => | |
4983 | combinedProblems.concat( | |
4984 | problemList.map(problem => | |
4985 | Object.assign(problem, { | |
4986 | fix: { | |
4987 | text: problem.fix.text, | |
4988 | range: problem.fix.range.map( | |
4989 | rangeIndex => rangeIndex + blockIndex * 4 | |
4990 | ) | |
4991 | } | |
4992 | })) | |
4993 | ), | |
4994 | [] | |
4995 | ); | |
4996 | } | |
4997 | } | |
4998 | ); | |
4999 | ||
5000 | assert.strictEqual(fixResult.fixed, true); | |
5001 | assert.strictEqual(fixResult.messages.length, 0); | |
5002 | assert.strictEqual(fixResult.output, "FOO BAR BAZ"); | |
5003 | }); | |
5004 | }); | |
5005 | }); | |
5006 | ||
5007 | describe("verifyAndFix", () => { | |
5008 | it("Fixes the code", () => { | |
5009 | const messages = linter.verifyAndFix("var a", { | |
5010 | rules: { | |
5011 | semi: 2 | |
5012 | } | |
5013 | }, { filename: "test.js" }); | |
5014 | ||
5015 | assert.strictEqual(messages.output, "var a;", "Fixes were applied correctly"); | |
5016 | assert.isTrue(messages.fixed); | |
5017 | }); | |
5018 | ||
5019 | it("does not require a third argument", () => { | |
5020 | const fixResult = linter.verifyAndFix("var a", { | |
5021 | rules: { | |
5022 | semi: 2 | |
5023 | } | |
5024 | }); | |
5025 | ||
5026 | assert.deepStrictEqual(fixResult, { | |
5027 | fixed: true, | |
5028 | messages: [], | |
5029 | output: "var a;" | |
5030 | }); | |
5031 | }); | |
5032 | ||
5033 | it("does not include suggestions in autofix results", () => { | |
5034 | const fixResult = linter.verifyAndFix("var foo = /\\#/", { | |
5035 | rules: { | |
5036 | semi: 2, | |
5037 | "no-useless-escape": 2 | |
5038 | } | |
5039 | }); | |
5040 | ||
5041 | assert.strictEqual(fixResult.output, "var foo = /\\#/;"); | |
5042 | assert.strictEqual(fixResult.fixed, true); | |
5043 | assert.strictEqual(fixResult.messages[0].suggestions.length > 0, true); | |
5044 | }); | |
5045 | ||
5046 | it("does not apply autofixes when fix argument is `false`", () => { | |
5047 | const fixResult = linter.verifyAndFix("var a", { | |
5048 | rules: { | |
5049 | semi: 2 | |
5050 | } | |
5051 | }, { fix: false }); | |
5052 | ||
5053 | assert.strictEqual(fixResult.fixed, false); | |
5054 | }); | |
5055 | ||
5056 | it("stops fixing after 10 passes", () => { | |
5057 | linter.defineRule("add-spaces", context => ({ | |
5058 | Program(node) { | |
5059 | context.report({ | |
5060 | node, | |
5061 | message: "Add a space before this node.", | |
5062 | fix: fixer => fixer.insertTextBefore(node, " ") | |
5063 | }); | |
5064 | } | |
5065 | })); | |
5066 | ||
5067 | const fixResult = linter.verifyAndFix("a", { rules: { "add-spaces": "error" } }); | |
5068 | ||
5069 | assert.strictEqual(fixResult.fixed, true); | |
5070 | assert.strictEqual(fixResult.output, `${" ".repeat(10)}a`); | |
5071 | assert.strictEqual(fixResult.messages.length, 1); | |
5072 | }); | |
5073 | ||
5074 | it("should throw an error if fix is passed but meta has no `fixable` property", () => { | |
5075 | linter.defineRule("test-rule", { | |
5076 | meta: { | |
5077 | docs: {}, | |
5078 | schema: [] | |
5079 | }, | |
5080 | create: context => ({ | |
5081 | Program(node) { | |
5082 | context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); | |
5083 | } | |
5084 | }) | |
5085 | }); | |
5086 | ||
5087 | assert.throws(() => { | |
5088 | linter.verify("0", { rules: { "test-rule": "error" } }); | |
5089 | }, /Fixable rules should export a `meta\.fixable` property.\nOccurred while linting <input>:1$/u); | |
5090 | }); | |
5091 | ||
5092 | it("should not throw an error if fix is passed and there is no metadata", () => { | |
5093 | linter.defineRule("test-rule", { | |
5094 | create: context => ({ | |
5095 | Program(node) { | |
5096 | context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); | |
5097 | } | |
5098 | }) | |
5099 | }); | |
5100 | ||
5101 | linter.verify("0", { rules: { "test-rule": "error" } }); | |
5102 | }); | |
5103 | }); | |
5104 | ||
5105 | describe("Edge cases", () => { | |
5106 | ||
5107 | it("should properly parse import statements when sourceType is module", () => { | |
5108 | const code = "import foo from 'foo';"; | |
5109 | const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } }); | |
5110 | ||
5111 | assert.strictEqual(messages.length, 0); | |
5112 | }); | |
5113 | ||
5114 | it("should properly parse import all statements when sourceType is module", () => { | |
5115 | const code = "import * as foo from 'foo';"; | |
5116 | const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } }); | |
5117 | ||
5118 | assert.strictEqual(messages.length, 0); | |
5119 | }); | |
5120 | ||
5121 | it("should properly parse default export statements when sourceType is module", () => { | |
5122 | const code = "export default function initialize() {}"; | |
5123 | const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } }); | |
5124 | ||
5125 | assert.strictEqual(messages.length, 0); | |
5126 | }); | |
5127 | ||
5128 | // https://github.com/eslint/eslint/issues/9687 | |
5129 | it("should report an error when invalid parserOptions found", () => { | |
5130 | let messages = linter.verify("", { parserOptions: { ecmaVersion: 222 } }); | |
5131 | ||
5132 | assert.deepStrictEqual(messages.length, 1); | |
5133 | assert.ok(messages[0].message.includes("Invalid ecmaVersion")); | |
5134 | ||
5135 | messages = linter.verify("", { parserOptions: { sourceType: "foo" } }); | |
5136 | assert.deepStrictEqual(messages.length, 1); | |
5137 | assert.ok(messages[0].message.includes("Invalid sourceType")); | |
5138 | ||
5139 | messages = linter.verify("", { parserOptions: { ecmaVersion: 5, sourceType: "module" } }); | |
5140 | assert.deepStrictEqual(messages.length, 1); | |
5141 | assert.ok(messages[0].message.includes("sourceType 'module' is not supported when ecmaVersion < 2015")); | |
5142 | }); | |
5143 | ||
5144 | it("should not crash when invalid parentheses syntax is encountered", () => { | |
5145 | linter.verify("left = (aSize.width/2) - ()"); | |
5146 | }); | |
5147 | ||
5148 | it("should not crash when let is used inside of switch case", () => { | |
5149 | linter.verify("switch(foo) { case 1: let bar=2; }", { parserOptions: { ecmaVersion: 6 } }); | |
5150 | }); | |
5151 | ||
5152 | it("should not crash when parsing destructured assignment", () => { | |
5153 | linter.verify("var { a='a' } = {};", { parserOptions: { ecmaVersion: 6 } }); | |
5154 | }); | |
5155 | ||
5156 | it("should report syntax error when a keyword exists in object property shorthand", () => { | |
5157 | const messages = linter.verify("let a = {this}", { parserOptions: { ecmaVersion: 6 } }); | |
5158 | ||
5159 | assert.strictEqual(messages.length, 1); | |
5160 | assert.strictEqual(messages[0].fatal, true); | |
5161 | }); | |
5162 | ||
5163 | it("should not rewrite env setting in core (https://github.com/eslint/eslint/issues/4814)", () => { | |
5164 | ||
5165 | /* | |
5166 | * This test focuses on the instance of https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/conf/environments.js#L26-L28 | |
5167 | * This `verify()` takes the instance and runs https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/lib/eslint.js#L416 | |
5168 | */ | |
5169 | linter.defineRule("test", () => ({})); | |
5170 | linter.verify("var a = 0;", { | |
5171 | env: { node: true }, | |
5172 | parserOptions: { ecmaVersion: 6, sourceType: "module" }, | |
5173 | rules: { test: 2 } | |
5174 | }); | |
5175 | ||
5176 | // This `verify()` takes the instance and tests that the instance was not modified. | |
5177 | let ok = false; | |
5178 | ||
5179 | linter.defineRule("test", context => { | |
5180 | assert( | |
5181 | context.parserOptions.ecmaFeatures.globalReturn, | |
5182 | "`ecmaFeatures.globalReturn` of the node environment should not be modified." | |
5183 | ); | |
5184 | ok = true; | |
5185 | return {}; | |
5186 | }); | |
5187 | linter.verify("var a = 0;", { | |
5188 | env: { node: true }, | |
5189 | rules: { test: 2 } | |
5190 | }); | |
5191 | ||
5192 | assert(ok); | |
5193 | }); | |
5194 | }); | |
5195 | ||
5196 | describe("Custom parser", () => { | |
5197 | ||
5198 | const errorPrefix = "Parsing error: "; | |
5199 | ||
5200 | it("should have file path passed to it", () => { | |
5201 | const code = "/* this is code */"; | |
5202 | const parseSpy = sinon.spy(testParsers.stubParser, "parse"); | |
5203 | ||
5204 | linter.defineParser("stub-parser", testParsers.stubParser); | |
5205 | linter.verify(code, { parser: "stub-parser" }, filename, true); | |
5206 | ||
5207 | sinon.assert.calledWithMatch(parseSpy, "", { filePath: filename }); | |
5208 | }); | |
5209 | ||
5210 | it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { | |
5211 | const code = "var myDivElement = <div {...this.props} />;"; | |
5212 | ||
5213 | linter.defineParser("esprima", esprima); | |
5214 | const messages = linter.verify(code, { parser: "esprima", parserOptions: { jsx: true } }, "filename"); | |
5215 | ||
5216 | assert.strictEqual(messages.length, 0); | |
5217 | }); | |
5218 | ||
5219 | it("should return an error when the custom parser can't be found", () => { | |
5220 | const code = "var myDivElement = <div {...this.props} />;"; | |
5221 | const messages = linter.verify(code, { parser: "esprima-xyz" }, "filename"); | |
5222 | ||
5223 | assert.strictEqual(messages.length, 1); | |
5224 | assert.strictEqual(messages[0].severity, 2); | |
5225 | assert.strictEqual(messages[0].message, "Configured parser 'esprima-xyz' was not found."); | |
5226 | }); | |
5227 | ||
5228 | it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => { | |
5229 | const code = "null %% 'foo'"; | |
5230 | ||
5231 | linter.defineParser("unknown-logical-operator", testParsers.unknownLogicalOperator); | |
5232 | ||
5233 | // This shouldn't throw | |
5234 | const messages = linter.verify(code, { parser: "unknown-logical-operator" }, filename, true); | |
5235 | ||
5236 | assert.strictEqual(messages.length, 0); | |
5237 | }); | |
5238 | ||
5239 | it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => { | |
5240 | const code = "foo && bar %% baz"; | |
5241 | ||
5242 | linter.defineParser("unknown-logical-operator-nested", testParsers.unknownLogicalOperatorNested); | |
5243 | ||
5244 | // This shouldn't throw | |
5245 | const messages = linter.verify(code, { parser: "unknown-logical-operator-nested" }, filename, true); | |
5246 | ||
5247 | assert.strictEqual(messages.length, 0); | |
5248 | }); | |
5249 | ||
ebb53d86 TL |
5250 | it("should not throw or return errors when the custom parser returns unknown AST nodes", () => { |
5251 | const code = "foo && bar %% baz"; | |
5252 | ||
5253 | const nodes = []; | |
5254 | ||
5255 | linter.defineRule("collect-node-types", () => ({ | |
5256 | "*"(node) { | |
5257 | nodes.push(node.type); | |
5258 | } | |
5259 | })); | |
5260 | ||
5261 | linter.defineParser("non-js-parser", testParsers.nonJSParser); | |
5262 | ||
5263 | const messages = linter.verify(code, { | |
5264 | parser: "non-js-parser", | |
5265 | rules: { | |
5266 | "collect-node-types": "error" | |
5267 | } | |
5268 | }, filename, true); | |
5269 | ||
5270 | assert.strictEqual(messages.length, 0); | |
5271 | assert.isTrue(nodes.length > 0); | |
5272 | }); | |
5273 | ||
eb39fafa DC |
5274 | it("should strip leading line: prefix from parser error", () => { |
5275 | linter.defineParser("line-error", testParsers.lineError); | |
5276 | const messages = linter.verify(";", { parser: "line-error" }, "filename"); | |
5277 | ||
5278 | assert.strictEqual(messages.length, 1); | |
5279 | assert.strictEqual(messages[0].severity, 2); | |
5280 | assert.strictEqual(messages[0].message, errorPrefix + testParsers.lineError.expectedError); | |
5281 | }); | |
5282 | ||
5283 | it("should not modify a parser error message without a leading line: prefix", () => { | |
5284 | linter.defineParser("no-line-error", testParsers.noLineError); | |
5285 | const messages = linter.verify(";", { parser: "no-line-error" }, "filename"); | |
5286 | ||
5287 | assert.strictEqual(messages.length, 1); | |
5288 | assert.strictEqual(messages[0].severity, 2); | |
5289 | assert.strictEqual(messages[0].message, errorPrefix + testParsers.noLineError.expectedError); | |
5290 | }); | |
5291 | ||
5292 | describe("if a parser provides 'visitorKeys'", () => { | |
5293 | let types = []; | |
5294 | let sourceCode; | |
5295 | let scopeManager; | |
5296 | ||
5297 | beforeEach(() => { | |
5298 | types = []; | |
5299 | linter.defineRule("collect-node-types", () => ({ | |
5300 | "*"(node) { | |
5301 | types.push(node.type); | |
5302 | } | |
5303 | })); | |
5304 | linter.defineRule("save-scope-manager", context => { | |
5305 | scopeManager = context.getSourceCode().scopeManager; | |
5306 | ||
5307 | return {}; | |
5308 | }); | |
5309 | linter.defineParser("enhanced-parser2", testParsers.enhancedParser2); | |
5310 | linter.verify("@foo class A {}", { | |
5311 | parser: "enhanced-parser2", | |
5312 | rules: { | |
5313 | "collect-node-types": "error", | |
5314 | "save-scope-manager": "error" | |
5315 | } | |
5316 | }); | |
5317 | ||
5318 | sourceCode = linter.getSourceCode(); | |
5319 | }); | |
5320 | ||
5321 | it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => { | |
5322 | assert.deepStrictEqual( | |
5323 | types, | |
5324 | ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"] | |
5325 | ); | |
5326 | }); | |
5327 | ||
5328 | it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { | |
5329 | assert.deepStrictEqual( | |
5330 | scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle | |
5331 | ["experimentalDecorators", "id", "superClass", "body"] | |
5332 | ); | |
5333 | }); | |
5334 | ||
5335 | it("should use the same visitorKeys if the source code object is reused", () => { | |
5336 | const types2 = []; | |
5337 | ||
5338 | linter.defineRule("collect-node-types", () => ({ | |
5339 | "*"(node) { | |
5340 | types2.push(node.type); | |
5341 | } | |
5342 | })); | |
5343 | linter.verify(sourceCode, { | |
5344 | rules: { | |
5345 | "collect-node-types": "error" | |
5346 | } | |
5347 | }); | |
5348 | ||
5349 | assert.deepStrictEqual( | |
5350 | types2, | |
5351 | ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"] | |
5352 | ); | |
5353 | }); | |
5354 | }); | |
5355 | ||
5356 | describe("if a parser provides 'scope'", () => { | |
5357 | let scope = null; | |
5358 | let sourceCode = null; | |
5359 | ||
5360 | beforeEach(() => { | |
5361 | linter.defineParser("enhanced-parser3", testParsers.enhancedParser3); | |
5362 | linter.defineRule("save-scope1", context => ({ | |
5363 | Program() { | |
5364 | scope = context.getScope(); | |
5365 | } | |
5366 | })); | |
5367 | linter.verify("@foo class A {}", { parser: "enhanced-parser3", rules: { "save-scope1": 2 } }); | |
5368 | ||
5369 | sourceCode = linter.getSourceCode(); | |
5370 | }); | |
5371 | ||
5372 | it("should use the scope (so the global scope has the reference of '@foo')", () => { | |
5373 | assert.strictEqual(scope.references.length, 1); | |
5374 | assert.deepStrictEqual( | |
5375 | scope.references[0].identifier.name, | |
5376 | "foo" | |
5377 | ); | |
5378 | }); | |
5379 | ||
5380 | it("should use the same scope if the source code object is reused", () => { | |
5381 | let scope2 = null; | |
5382 | ||
5383 | linter.defineRule("save-scope2", context => ({ | |
5384 | Program() { | |
5385 | scope2 = context.getScope(); | |
5386 | } | |
5387 | })); | |
5388 | linter.verify(sourceCode, { rules: { "save-scope2": 2 } }, "test.js"); | |
5389 | ||
5390 | assert(scope2 !== null); | |
5391 | assert(scope2 === scope); | |
5392 | }); | |
5393 | }); | |
5394 | ||
5395 | it("should not pass any default parserOptions to the parser", () => { | |
5396 | linter.defineParser("throws-with-options", testParsers.throwsWithOptions); | |
5397 | const messages = linter.verify(";", { parser: "throws-with-options" }, "filename"); | |
5398 | ||
5399 | assert.strictEqual(messages.length, 0); | |
5400 | }); | |
5401 | }); | |
5402 | }); |