]> git.proxmox.com Git - pve-eslint.git/blob - eslint/tests/lib/linter/linter.js
093ebcfaf70f4016d963fc44fcbf648cc04bfafb
[pve-eslint.git] / eslint / tests / lib / linter / linter.js
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", () => {
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 `;
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");
2746 assert.strictEqual(messages[0].message, "This line has a length of 129. Maximum allowed is 100.");
2747 assert.include(messages[0].nodeType, "Program");
2748 });
2749 });
2750
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
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: 4,
3922 endColumn: 28,
3923 line: 4,
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: 3,
3970 endColumn: 28,
3971 line: 3,
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: 3,
3993 endColumn: 28,
3994 line: 3,
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: 5,
4018 endColumn: 28,
4019 line: 5,
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: 5,
4043 endColumn: 28,
4044 line: 5,
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
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
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 });