]> git.proxmox.com Git - pve-eslint.git/blob - eslint/tests/lib/linter/linter.js
3343461461013459bc4724dafc25f11895faa5bb
[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 // Requirements
10 //------------------------------------------------------------------------------
11
12 const assert = require("chai").assert,
13 sinon = require("sinon"),
14 espree = require("espree"),
15 esprima = require("esprima"),
16 testParsers = require("../../fixtures/parsers/linter-test-parsers");
17
18 const { Linter } = require("../../../lib/linter");
19 const { FlatConfigArray } = require("../../../lib/config/flat-config-array");
20
21 //------------------------------------------------------------------------------
22 // Constants
23 //------------------------------------------------------------------------------
24
25 const TEST_CODE = "var answer = 6 * 7;",
26 BROKEN_TEST_CODE = "var;";
27
28 //------------------------------------------------------------------------------
29 // Helpers
30 //------------------------------------------------------------------------------
31
32 /**
33 * Get variables in the current scope
34 * @param {Object} scope current scope
35 * @param {string} name name of the variable to look for
36 * @returns {ASTNode|null} The variable object
37 * @private
38 */
39 function getVariable(scope, name) {
40 return scope.variables.find(v => v.name === name) || null;
41 }
42
43 /**
44 * `eslint-env` comments are processed by doing a full source text match before parsing.
45 * As a result, if this source file contains `eslint- env` followed by an environment in a string,
46 * it will actually enable the given envs for this source file. This variable is used to avoid having a string
47 * like that appear in the code.
48 */
49 const ESLINT_ENV = "eslint-env";
50
51 //------------------------------------------------------------------------------
52 // Tests
53 //------------------------------------------------------------------------------
54
55 describe("Linter", () => {
56 const filename = "filename.js";
57
58 /** @type {InstanceType<import("../../../lib/linter/linter.js").Linter>} */
59 let linter;
60
61 beforeEach(() => {
62 linter = new Linter();
63 });
64
65 afterEach(() => {
66 sinon.verifyAndRestore();
67 });
68
69 describe("Static Members", () => {
70 describe("version", () => {
71 it("should return same version as instance property", () => {
72 assert.strictEqual(Linter.version, linter.version);
73 });
74 });
75 });
76
77 describe("when using events", () => {
78 const code = TEST_CODE;
79
80 it("an error should be thrown when an error occurs inside of an event handler", () => {
81 const config = { rules: { checker: "error" } };
82
83 linter.defineRule("checker", () => ({
84 Program() {
85 throw new Error("Intentional error.");
86 }
87 }));
88
89 assert.throws(() => {
90 linter.verify(code, config, filename);
91 }, `Intentional error.\nOccurred while linting ${filename}:1\nRule: "checker"`);
92 });
93
94 it("does not call rule listeners with a `this` value", () => {
95 const spy = sinon.spy();
96
97 linter.defineRule("checker", () => ({ Program: spy }));
98 linter.verify("foo", { rules: { checker: "error" } });
99 assert(spy.calledOnce, "Rule should have been called");
100 assert.strictEqual(spy.firstCall.thisValue, void 0, "this value should be undefined");
101 });
102
103 it("does not allow listeners to use special EventEmitter values", () => {
104 const spy = sinon.spy();
105
106 linter.defineRule("checker", () => ({ newListener: spy }));
107 linter.verify("foo", { rules: { checker: "error", "no-undef": "error" } });
108 assert(spy.notCalled);
109 });
110
111 it("has all the `parent` properties on nodes when the rule listeners are created", () => {
112 const spy = sinon.spy(context => {
113 const ast = context.getSourceCode().ast;
114
115 assert.strictEqual(ast.body[0].parent, ast);
116 assert.strictEqual(ast.body[0].expression.parent, ast.body[0]);
117 assert.strictEqual(ast.body[0].expression.left.parent, ast.body[0].expression);
118 assert.strictEqual(ast.body[0].expression.right.parent, ast.body[0].expression);
119
120 return {};
121 });
122
123 linter.defineRule("checker", spy);
124
125 linter.verify("foo + bar", { rules: { checker: "error" } });
126 assert(spy.calledOnce);
127 });
128 });
129
130 describe("context.getSourceLines()", () => {
131
132 it("should get proper lines when using \\n as a line break", () => {
133 const code = "a;\nb;";
134 const spy = sinon.spy(context => {
135 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
136 return {};
137 });
138
139 linter.defineRule("checker", spy);
140 linter.verify(code, { rules: { checker: "error" } });
141 assert(spy.calledOnce);
142 });
143
144 it("should get proper lines when using \\r\\n as a line break", () => {
145 const code = "a;\r\nb;";
146 const spy = sinon.spy(context => {
147 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
148 return {};
149 });
150
151 linter.defineRule("checker", spy);
152 linter.verify(code, { rules: { checker: "error" } });
153 assert(spy.calledOnce);
154 });
155
156 it("should get proper lines when using \\r as a line break", () => {
157 const code = "a;\rb;";
158 const spy = sinon.spy(context => {
159 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
160 return {};
161 });
162
163 linter.defineRule("checker", spy);
164 linter.verify(code, { rules: { checker: "error" } });
165 assert(spy.calledOnce);
166 });
167
168 it("should get proper lines when using \\u2028 as a line break", () => {
169 const code = "a;\u2028b;";
170 const spy = sinon.spy(context => {
171 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
172 return {};
173 });
174
175 linter.defineRule("checker", spy);
176 linter.verify(code, { rules: { checker: "error" } });
177 assert(spy.calledOnce);
178 });
179
180 it("should get proper lines when using \\u2029 as a line break", () => {
181 const code = "a;\u2029b;";
182 const spy = sinon.spy(context => {
183 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
184 return {};
185 });
186
187 linter.defineRule("checker", spy);
188 linter.verify(code, { rules: { checker: "error" } });
189 assert(spy.calledOnce);
190 });
191
192
193 });
194
195 describe("getSourceCode()", () => {
196 const code = TEST_CODE;
197
198 it("should retrieve SourceCode object after reset", () => {
199 linter.verify(code, {}, filename, true);
200
201 const sourceCode = linter.getSourceCode();
202
203 assert.isObject(sourceCode);
204 assert.strictEqual(sourceCode.text, code);
205 assert.isObject(sourceCode.ast);
206 });
207
208 it("should retrieve SourceCode object without reset", () => {
209 linter.verify(code, {}, filename);
210
211 const sourceCode = linter.getSourceCode();
212
213 assert.isObject(sourceCode);
214 assert.strictEqual(sourceCode.text, code);
215 assert.isObject(sourceCode.ast);
216 });
217
218 });
219
220 describe("context.getSource()", () => {
221 const code = TEST_CODE;
222
223 it("should retrieve all text when used without parameters", () => {
224
225 const config = { rules: { checker: "error" } };
226 let spy;
227
228 linter.defineRule("checker", context => {
229 spy = sinon.spy(() => {
230 assert.strictEqual(context.getSource(), TEST_CODE);
231 });
232 return { Program: spy };
233 });
234
235 linter.verify(code, config);
236 assert(spy && spy.calledOnce);
237 });
238
239 it("should retrieve all text for root node", () => {
240 const config = { rules: { checker: "error" } };
241 let spy;
242
243 linter.defineRule("checker", context => {
244 spy = sinon.spy(node => {
245 assert.strictEqual(context.getSource(node), TEST_CODE);
246 });
247 return { Program: spy };
248 });
249
250 linter.verify(code, config);
251 assert(spy && spy.calledOnce);
252 });
253
254 it("should clamp to valid range when retrieving characters before start of source", () => {
255 const config = { rules: { checker: "error" } };
256 let spy;
257
258 linter.defineRule("checker", context => {
259 spy = sinon.spy(node => {
260 assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE);
261 });
262 return { Program: spy };
263 });
264
265 linter.verify(code, config);
266 assert(spy && spy.calledOnce);
267 });
268
269 it("should retrieve all text for binary expression", () => {
270 const config = { rules: { checker: "error" } };
271 let spy;
272
273 linter.defineRule("checker", context => {
274 spy = sinon.spy(node => {
275 assert.strictEqual(context.getSource(node), "6 * 7");
276 });
277 return { BinaryExpression: spy };
278 });
279
280 linter.verify(code, config);
281 assert(spy && spy.calledOnce);
282 });
283
284 it("should retrieve all text plus two characters before for binary expression", () => {
285 const config = { rules: { checker: "error" } };
286 let spy;
287
288 linter.defineRule("checker", context => {
289 spy = sinon.spy(node => {
290 assert.strictEqual(context.getSource(node, 2), "= 6 * 7");
291 });
292 return { BinaryExpression: spy };
293 });
294
295 linter.verify(code, config);
296 assert(spy && spy.calledOnce);
297 });
298
299 it("should retrieve all text plus one character after for binary expression", () => {
300 const config = { rules: { checker: "error" } };
301 let spy;
302
303 linter.defineRule("checker", context => {
304 spy = sinon.spy(node => {
305 assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;");
306 });
307 return { BinaryExpression: spy };
308 });
309
310 linter.verify(code, config);
311 assert(spy && spy.calledOnce);
312 });
313
314 it("should retrieve all text plus two characters before and one character after for binary expression", () => {
315 const config = { rules: { checker: "error" } };
316 let spy;
317
318 linter.defineRule("checker", context => {
319 spy = sinon.spy(node => {
320 assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;");
321 });
322 return { BinaryExpression: spy };
323 });
324
325 linter.verify(code, config);
326 assert(spy && spy.calledOnce);
327 });
328
329 });
330
331 describe("when calling context.getAncestors", () => {
332 const code = TEST_CODE;
333
334 it("should retrieve all ancestors when used", () => {
335
336 const config = { rules: { checker: "error" } };
337 let spy;
338
339 linter.defineRule("checker", context => {
340 spy = sinon.spy(() => {
341 const ancestors = context.getAncestors();
342
343 assert.strictEqual(ancestors.length, 3);
344 });
345 return { BinaryExpression: spy };
346 });
347
348 linter.verify(code, config, filename, true);
349 assert(spy && spy.calledOnce);
350 });
351
352 it("should retrieve empty ancestors for root node", () => {
353 const config = { rules: { checker: "error" } };
354 let spy;
355
356 linter.defineRule("checker", context => {
357 spy = sinon.spy(() => {
358 const ancestors = context.getAncestors();
359
360 assert.strictEqual(ancestors.length, 0);
361 });
362
363 return { Program: spy };
364 });
365
366 linter.verify(code, config);
367 assert(spy && spy.calledOnce);
368 });
369 });
370
371 describe("when calling context.getNodeByRangeIndex", () => {
372 const code = TEST_CODE;
373
374 it("should retrieve a node starting at the given index", () => {
375 const config = { rules: { checker: "error" } };
376 const spy = sinon.spy(context => {
377 assert.strictEqual(context.getNodeByRangeIndex(4).type, "Identifier");
378 return {};
379 });
380
381 linter.defineRule("checker", spy);
382 linter.verify(code, config);
383 assert(spy.calledOnce);
384 });
385
386 it("should retrieve a node containing the given index", () => {
387 const config = { rules: { checker: "error" } };
388 const spy = sinon.spy(context => {
389 assert.strictEqual(context.getNodeByRangeIndex(6).type, "Identifier");
390 return {};
391 });
392
393 linter.defineRule("checker", spy);
394 linter.verify(code, config);
395 assert(spy.calledOnce);
396 });
397
398 it("should retrieve a node that is exactly the given index", () => {
399 const config = { rules: { checker: "error" } };
400 const spy = sinon.spy(context => {
401 const node = context.getNodeByRangeIndex(13);
402
403 assert.strictEqual(node.type, "Literal");
404 assert.strictEqual(node.value, 6);
405 return {};
406 });
407
408 linter.defineRule("checker", spy);
409 linter.verify(code, config);
410 assert(spy.calledOnce);
411 });
412
413 it("should retrieve a node ending with the given index", () => {
414 const config = { rules: { checker: "error" } };
415 const spy = sinon.spy(context => {
416 assert.strictEqual(context.getNodeByRangeIndex(9).type, "Identifier");
417 return {};
418 });
419
420 linter.defineRule("checker", spy);
421 linter.verify(code, config);
422 assert(spy.calledOnce);
423 });
424
425 it("should retrieve the deepest node containing the given index", () => {
426 const config = { rules: { checker: "error" } };
427 const spy = sinon.spy(context => {
428 const node1 = context.getNodeByRangeIndex(14);
429
430 assert.strictEqual(node1.type, "BinaryExpression");
431
432 const node2 = context.getNodeByRangeIndex(3);
433
434 assert.strictEqual(node2.type, "VariableDeclaration");
435 return {};
436 });
437
438 linter.defineRule("checker", spy);
439 linter.verify(code, config);
440 assert(spy.calledOnce);
441 });
442
443 it("should return null if the index is outside the range of any node", () => {
444 const config = { rules: { checker: "error" } };
445 const spy = sinon.spy(context => {
446 const node1 = context.getNodeByRangeIndex(-1);
447
448 assert.isNull(node1);
449
450 const node2 = context.getNodeByRangeIndex(-99);
451
452 assert.isNull(node2);
453 return {};
454 });
455
456 linter.defineRule("checker", spy);
457 linter.verify(code, config);
458 assert(spy.calledOnce);
459 });
460 });
461
462
463 describe("when calling context.getScope", () => {
464 const code = "function foo() { q: for(;;) { break q; } } function bar () { var q = t; } var baz = (() => { return 1; });";
465
466 it("should retrieve the global scope correctly from a Program", () => {
467 const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
468 let spy;
469
470 linter.defineRule("checker", context => {
471 spy = sinon.spy(() => {
472 const scope = context.getScope();
473
474 assert.strictEqual(scope.type, "global");
475 });
476 return { Program: spy };
477 });
478
479 linter.verify(code, config);
480 assert(spy && spy.calledOnce);
481 });
482
483 it("should retrieve the function scope correctly from a FunctionDeclaration", () => {
484 const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
485 let spy;
486
487 linter.defineRule("checker", context => {
488 spy = sinon.spy(() => {
489 const scope = context.getScope();
490
491 assert.strictEqual(scope.type, "function");
492 });
493 return { FunctionDeclaration: spy };
494 });
495
496 linter.verify(code, config);
497 assert(spy && spy.calledTwice);
498 });
499
500 it("should retrieve the function scope correctly from a LabeledStatement", () => {
501 const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
502 let spy;
503
504 linter.defineRule("checker", context => {
505 spy = sinon.spy(() => {
506 const scope = context.getScope();
507
508 assert.strictEqual(scope.type, "function");
509 assert.strictEqual(scope.block.id.name, "foo");
510 });
511 return { LabeledStatement: spy };
512 });
513
514 linter.verify(code, config);
515 assert(spy && spy.calledOnce);
516 });
517
518 it("should retrieve the function scope correctly from within an ArrowFunctionExpression", () => {
519 const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
520 let spy;
521
522 linter.defineRule("checker", context => {
523 spy = sinon.spy(() => {
524 const scope = context.getScope();
525
526 assert.strictEqual(scope.type, "function");
527 assert.strictEqual(scope.block.type, "ArrowFunctionExpression");
528 });
529
530 return { ReturnStatement: 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 SwitchStatement", () => {
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, "switch");
546 assert.strictEqual(scope.block.type, "SwitchStatement");
547 });
548
549 return { SwitchStatement: spy };
550 });
551
552 linter.verify("switch(foo){ case 'a': var b = 'foo'; }", config);
553 assert(spy && spy.calledOnce);
554 });
555
556 it("should retrieve the function scope correctly from within a BlockStatement", () => {
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, "block");
565 assert.strictEqual(scope.block.type, "BlockStatement");
566 });
567
568 return { BlockStatement: spy };
569 });
570
571 linter.verify("var x; {let y = 1}", config);
572 assert(spy && spy.calledOnce);
573 });
574
575 it("should retrieve the function scope correctly from within a nested block statement", () => {
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("if (true) { let x = 1 }", config);
591 assert(spy && spy.calledOnce);
592 });
593
594 it("should retrieve the function scope correctly from within a FunctionDeclaration", () => {
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, "function");
603 assert.strictEqual(scope.block.type, "FunctionDeclaration");
604 });
605
606 return { FunctionDeclaration: spy };
607 });
608
609 linter.verify("function foo() {}", config);
610 assert(spy && spy.calledOnce);
611 });
612
613 it("should retrieve the function scope correctly from within a FunctionExpression", () => {
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, "FunctionExpression");
623 });
624
625 return { FunctionExpression: spy };
626 });
627
628 linter.verify("(function foo() {})();", config);
629 assert(spy && spy.calledOnce);
630 });
631
632 it("should retrieve the catch scope correctly from within a CatchClause", () => {
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, "catch");
641 assert.strictEqual(scope.block.type, "CatchClause");
642 });
643
644 return { CatchClause: spy };
645 });
646
647 linter.verify("try {} catch (err) {}", config);
648 assert(spy && spy.calledOnce);
649 });
650
651 it("should retrieve module scope correctly from an ES6 module", () => {
652 const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } };
653 let spy;
654
655 linter.defineRule("checker", context => {
656 spy = sinon.spy(() => {
657 const scope = context.getScope();
658
659 assert.strictEqual(scope.type, "module");
660 });
661
662 return { AssignmentExpression: spy };
663 });
664
665 linter.verify("var foo = {}; foo.bar = 1;", config);
666 assert(spy && spy.calledOnce);
667 });
668
669 it("should retrieve function scope correctly when globalReturn is true", () => {
670 const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, ecmaFeatures: { globalReturn: true } } };
671 let spy;
672
673 linter.defineRule("checker", context => {
674 spy = sinon.spy(() => {
675 const scope = context.getScope();
676
677 assert.strictEqual(scope.type, "function");
678 });
679
680 return { AssignmentExpression: spy };
681 });
682
683 linter.verify("var foo = {}; foo.bar = 1;", config);
684 assert(spy && spy.calledOnce);
685 });
686 });
687
688 describe("marking variables as used", () => {
689 it("should mark variables in current scope as used", () => {
690 const code = "var a = 1, b = 2;";
691 let spy;
692
693 linter.defineRule("checker", context => {
694 spy = sinon.spy(() => {
695 assert.isTrue(context.markVariableAsUsed("a"));
696
697 const scope = context.getScope();
698
699 assert.isTrue(getVariable(scope, "a").eslintUsed);
700 assert.notOk(getVariable(scope, "b").eslintUsed);
701 });
702
703 return { "Program:exit": spy };
704 });
705
706 linter.verify(code, { rules: { checker: "error" } });
707 assert(spy && spy.calledOnce);
708 });
709 it("should mark variables in function args as used", () => {
710 const code = "function abc(a, b) { return 1; }";
711 let spy;
712
713 linter.defineRule("checker", context => {
714 spy = sinon.spy(() => {
715 assert.isTrue(context.markVariableAsUsed("a"));
716
717 const scope = context.getScope();
718
719 assert.isTrue(getVariable(scope, "a").eslintUsed);
720 assert.notOk(getVariable(scope, "b").eslintUsed);
721 });
722
723 return { ReturnStatement: spy };
724 });
725
726 linter.verify(code, { rules: { checker: "error" } });
727 assert(spy && spy.calledOnce);
728 });
729 it("should mark variables in higher scopes as used", () => {
730 const code = "var a, b; function abc() { return 1; }";
731 let returnSpy, exitSpy;
732
733 linter.defineRule("checker", context => {
734 returnSpy = sinon.spy(() => {
735 assert.isTrue(context.markVariableAsUsed("a"));
736 });
737 exitSpy = sinon.spy(() => {
738 const scope = context.getScope();
739
740 assert.isTrue(getVariable(scope, "a").eslintUsed);
741 assert.notOk(getVariable(scope, "b").eslintUsed);
742 });
743
744 return { ReturnStatement: returnSpy, "Program:exit": exitSpy };
745 });
746
747 linter.verify(code, { rules: { checker: "error" } });
748 assert(returnSpy && returnSpy.calledOnce);
749 assert(exitSpy && exitSpy.calledOnce);
750 });
751
752 it("should mark variables in Node.js environment as used", () => {
753 const code = "var a = 1, b = 2;";
754 let spy;
755
756 linter.defineRule("checker", context => {
757 spy = sinon.spy(() => {
758 const globalScope = context.getScope(),
759 childScope = globalScope.childScopes[0];
760
761 assert.isTrue(context.markVariableAsUsed("a"));
762
763 assert.isTrue(getVariable(childScope, "a").eslintUsed);
764 assert.isUndefined(getVariable(childScope, "b").eslintUsed);
765 });
766
767 return { "Program:exit": spy };
768 });
769
770 linter.verify(code, { rules: { checker: "error" }, env: { node: true } });
771 assert(spy && spy.calledOnce);
772 });
773
774 it("should mark variables in modules as used", () => {
775 const code = "var a = 1, b = 2;";
776 let spy;
777
778 linter.defineRule("checker", context => {
779 spy = sinon.spy(() => {
780 const globalScope = context.getScope(),
781 childScope = globalScope.childScopes[0];
782
783 assert.isTrue(context.markVariableAsUsed("a"));
784
785 assert.isTrue(getVariable(childScope, "a").eslintUsed);
786 assert.isUndefined(getVariable(childScope, "b").eslintUsed);
787 });
788
789 return { "Program:exit": spy };
790 });
791
792 linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }, filename, true);
793 assert(spy && spy.calledOnce);
794 });
795
796 it("should return false if the given variable is not found", () => {
797 const code = "var a = 1, b = 2;";
798 let spy;
799
800 linter.defineRule("checker", context => {
801 spy = sinon.spy(() => {
802 assert.isFalse(context.markVariableAsUsed("c"));
803 });
804
805 return { "Program:exit": spy };
806 });
807
808 linter.verify(code, { rules: { checker: "error" } });
809 assert(spy && spy.calledOnce);
810 });
811 });
812
813 describe("when evaluating code", () => {
814 const code = TEST_CODE;
815
816 it("events for each node type should fire", () => {
817 const config = { rules: { checker: "error" } };
818
819 // spies for various AST node types
820 const spyLiteral = sinon.spy(),
821 spyVariableDeclarator = sinon.spy(),
822 spyVariableDeclaration = sinon.spy(),
823 spyIdentifier = sinon.spy(),
824 spyBinaryExpression = sinon.spy();
825
826 linter.defineRule("checker", () => ({
827 Literal: spyLiteral,
828 VariableDeclarator: spyVariableDeclarator,
829 VariableDeclaration: spyVariableDeclaration,
830 Identifier: spyIdentifier,
831 BinaryExpression: spyBinaryExpression
832 }));
833
834 const messages = linter.verify(code, config, filename, true);
835
836 assert.strictEqual(messages.length, 0);
837 sinon.assert.calledOnce(spyVariableDeclaration);
838 sinon.assert.calledOnce(spyVariableDeclarator);
839 sinon.assert.calledOnce(spyIdentifier);
840 sinon.assert.calledTwice(spyLiteral);
841 sinon.assert.calledOnce(spyBinaryExpression);
842 });
843
844 it("should throw an error if a rule reports a problem without a message", () => {
845 linter.defineRule("invalid-report", context => ({
846 Program(node) {
847 context.report({ node });
848 }
849 }));
850
851 assert.throws(
852 () => linter.verify("foo", { rules: { "invalid-report": "error" } }),
853 TypeError,
854 "Missing `message` property in report() call; add a message that describes the linting problem."
855 );
856 });
857 });
858
859 describe("when config has shared settings for rules", () => {
860 const code = "test-rule";
861
862 it("should pass settings to all rules", () => {
863 linter.defineRule(code, context => ({
864 Literal(node) {
865 context.report(node, context.settings.info);
866 }
867 }));
868
869 const config = { rules: {}, settings: { info: "Hello" } };
870
871 config.rules[code] = 1;
872
873 const messages = linter.verify("0", config, filename);
874
875 assert.strictEqual(messages.length, 1);
876 assert.strictEqual(messages[0].message, "Hello");
877 });
878
879 it("should not have any settings if they were not passed in", () => {
880 linter.defineRule(code, context => ({
881 Literal(node) {
882 if (Object.getOwnPropertyNames(context.settings).length !== 0) {
883 context.report(node, "Settings should be empty");
884 }
885 }
886 }));
887
888 const config = { rules: {} };
889
890 config.rules[code] = 1;
891
892 const messages = linter.verify("0", config, filename);
893
894 assert.strictEqual(messages.length, 0);
895 });
896 });
897
898 describe("when config has parseOptions", () => {
899
900 it("should pass ecmaFeatures to all rules when provided on config", () => {
901
902 const parserOptions = {
903 ecmaFeatures: {
904 jsx: true,
905 globalReturn: true
906 }
907 };
908
909 linter.defineRule("test-rule", sinon.mock().withArgs(
910 sinon.match({ parserOptions })
911 ).returns({}));
912
913 const config = { rules: { "test-rule": 2 }, parserOptions };
914
915 linter.verify("0", config, filename);
916 });
917
918 it("should pass parserOptions to all rules when default parserOptions is used", () => {
919
920 const parserOptions = {};
921
922 linter.defineRule("test-rule", sinon.mock().withArgs(
923 sinon.match({ parserOptions })
924 ).returns({}));
925
926 const config = { rules: { "test-rule": 2 } };
927
928 linter.verify("0", config, filename);
929 });
930
931 });
932
933 describe("when a custom parser is defined using defineParser", () => {
934
935 it("should be able to define a custom parser", () => {
936 const parser = {
937 parseForESLint: function parse(code, options) {
938 return {
939 ast: esprima.parse(code, options),
940 services: {
941 test: {
942 getMessage() {
943 return "Hi!";
944 }
945 }
946 }
947 };
948 }
949 };
950
951 linter.defineParser("test-parser", parser);
952 const config = { rules: {}, parser: "test-parser" };
953 const messages = linter.verify("0", config, filename);
954
955 assert.strictEqual(messages.length, 0);
956 });
957
958 });
959
960 describe("when config has parser", () => {
961
962 it("should pass parser as parserPath to all rules when provided on config", () => {
963
964 const alternateParser = "esprima";
965
966 linter.defineParser("esprima", esprima);
967 linter.defineRule("test-rule", sinon.mock().withArgs(
968 sinon.match({ parserPath: alternateParser })
969 ).returns({}));
970
971 const config = { rules: { "test-rule": 2 }, parser: alternateParser };
972
973 linter.verify("0", config, filename);
974 });
975
976 it("should use parseForESLint() in custom parser when custom parser is specified", () => {
977 const config = { rules: {}, parser: "enhanced-parser" };
978
979 linter.defineParser("enhanced-parser", testParsers.enhancedParser);
980 const messages = linter.verify("0", config, filename);
981
982 assert.strictEqual(messages.length, 0);
983 });
984
985 it("should expose parser services when using parseForESLint() and services are specified", () => {
986 linter.defineParser("enhanced-parser", testParsers.enhancedParser);
987 linter.defineRule("test-service-rule", context => ({
988 Literal(node) {
989 context.report({
990 node,
991 message: context.parserServices.test.getMessage()
992 });
993 }
994 }));
995
996 const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" };
997 const messages = linter.verify("0", config, filename);
998
999 assert.strictEqual(messages.length, 1);
1000 assert.strictEqual(messages[0].message, "Hi!");
1001 });
1002
1003 it("should use the same parserServices if source code object is reused", () => {
1004 linter.defineParser("enhanced-parser", testParsers.enhancedParser);
1005 linter.defineRule("test-service-rule", context => ({
1006 Literal(node) {
1007 context.report({
1008 node,
1009 message: context.parserServices.test.getMessage()
1010 });
1011 }
1012 }));
1013
1014 const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" };
1015 const messages = linter.verify("0", config, filename);
1016
1017 assert.strictEqual(messages.length, 1);
1018 assert.strictEqual(messages[0].message, "Hi!");
1019
1020 const messages2 = linter.verify(linter.getSourceCode(), config, filename);
1021
1022 assert.strictEqual(messages2.length, 1);
1023 assert.strictEqual(messages2[0].message, "Hi!");
1024 });
1025
1026 it("should pass parser as parserPath to all rules when default parser is used", () => {
1027 linter.defineRule("test-rule", sinon.mock().withArgs(
1028 sinon.match({ parserPath: "espree" })
1029 ).returns({}));
1030
1031 const config = { rules: { "test-rule": 2 } };
1032
1033 linter.verify("0", config, filename);
1034 });
1035
1036 });
1037
1038
1039 describe("when passing in configuration values for rules", () => {
1040 const code = "var answer = 6 * 7";
1041
1042 it("should be configurable by only setting the integer value", () => {
1043 const rule = "semi",
1044 config = { rules: {} };
1045
1046 config.rules[rule] = 1;
1047
1048 const messages = linter.verify(code, config, filename, true);
1049
1050 assert.strictEqual(messages.length, 1);
1051 assert.strictEqual(messages[0].ruleId, rule);
1052 });
1053
1054 it("should be configurable by only setting the string value", () => {
1055 const rule = "semi",
1056 config = { rules: {} };
1057
1058 config.rules[rule] = "warn";
1059
1060 const messages = linter.verify(code, config, filename, true);
1061
1062 assert.strictEqual(messages.length, 1);
1063 assert.strictEqual(messages[0].severity, 1);
1064 assert.strictEqual(messages[0].ruleId, rule);
1065 });
1066
1067 it("should be configurable by passing in values as an array", () => {
1068 const rule = "semi",
1069 config = { rules: {} };
1070
1071 config.rules[rule] = [1];
1072
1073 const messages = linter.verify(code, config, filename, true);
1074
1075 assert.strictEqual(messages.length, 1);
1076 assert.strictEqual(messages[0].ruleId, rule);
1077 });
1078
1079 it("should be configurable by passing in string value as an array", () => {
1080 const rule = "semi",
1081 config = { rules: {} };
1082
1083 config.rules[rule] = ["warn"];
1084
1085 const messages = linter.verify(code, config, filename, true);
1086
1087 assert.strictEqual(messages.length, 1);
1088 assert.strictEqual(messages[0].severity, 1);
1089 assert.strictEqual(messages[0].ruleId, rule);
1090 });
1091
1092 it("should not be configurable by setting other value", () => {
1093 const rule = "semi",
1094 config = { rules: {} };
1095
1096 config.rules[rule] = "1";
1097
1098 const messages = linter.verify(code, config, filename, true);
1099
1100 assert.strictEqual(messages.length, 0);
1101 });
1102
1103 it("should process empty config", () => {
1104 const config = {};
1105 const messages = linter.verify(code, config, filename, true);
1106
1107 assert.strictEqual(messages.length, 0);
1108 });
1109 });
1110
1111 describe("when evaluating code containing /*global */ and /*globals */ blocks", () => {
1112
1113 it("variables should be available in global scope", () => {
1114 const config = { rules: { checker: "error" }, globals: { Array: "off", ConfigGlobal: "writeable" } };
1115 const code = `
1116 /*global a b:true c:false d:readable e:writeable Math:off */
1117 function foo() {}
1118 /*globals f:true*/
1119 /* global ConfigGlobal : readable */
1120 `;
1121 let spy;
1122
1123 linter.defineRule("checker", context => {
1124 spy = sinon.spy(() => {
1125 const scope = context.getScope();
1126 const a = getVariable(scope, "a"),
1127 b = getVariable(scope, "b"),
1128 c = getVariable(scope, "c"),
1129 d = getVariable(scope, "d"),
1130 e = getVariable(scope, "e"),
1131 f = getVariable(scope, "f"),
1132 mathGlobal = getVariable(scope, "Math"),
1133 arrayGlobal = getVariable(scope, "Array"),
1134 configGlobal = getVariable(scope, "ConfigGlobal");
1135
1136 assert.strictEqual(a.name, "a");
1137 assert.strictEqual(a.writeable, false);
1138 assert.strictEqual(b.name, "b");
1139 assert.strictEqual(b.writeable, true);
1140 assert.strictEqual(c.name, "c");
1141 assert.strictEqual(c.writeable, false);
1142 assert.strictEqual(d.name, "d");
1143 assert.strictEqual(d.writeable, false);
1144 assert.strictEqual(e.name, "e");
1145 assert.strictEqual(e.writeable, true);
1146 assert.strictEqual(f.name, "f");
1147 assert.strictEqual(f.writeable, true);
1148 assert.strictEqual(mathGlobal, null);
1149 assert.strictEqual(arrayGlobal, null);
1150 assert.strictEqual(configGlobal.name, "ConfigGlobal");
1151 assert.strictEqual(configGlobal.writeable, false);
1152 });
1153
1154 return { Program: spy };
1155 });
1156
1157 linter.verify(code, config);
1158 assert(spy && spy.calledOnce);
1159 });
1160 });
1161
1162 describe("when evaluating code containing a /*global */ block with sloppy whitespace", () => {
1163 const code = "/* global a b : true c: false*/";
1164
1165 it("variables should be available in global scope", () => {
1166 const config = { rules: { checker: "error" } };
1167 let spy;
1168
1169 linter.defineRule("checker", context => {
1170 spy = sinon.spy(() => {
1171 const scope = context.getScope(),
1172 a = getVariable(scope, "a"),
1173 b = getVariable(scope, "b"),
1174 c = getVariable(scope, "c");
1175
1176 assert.strictEqual(a.name, "a");
1177 assert.strictEqual(a.writeable, false);
1178 assert.strictEqual(b.name, "b");
1179 assert.strictEqual(b.writeable, true);
1180 assert.strictEqual(c.name, "c");
1181 assert.strictEqual(c.writeable, false);
1182 });
1183
1184 return { Program: spy };
1185 });
1186
1187 linter.verify(code, config);
1188 assert(spy && spy.calledOnce);
1189 });
1190 });
1191
1192 describe("when evaluating code containing a /*global */ block with specific variables", () => {
1193 const code = "/* global toString hasOwnProperty valueOf: true */";
1194
1195 it("should not throw an error if comment block has global variables which are Object.prototype contains", () => {
1196 const config = { rules: { checker: "error" } };
1197
1198 linter.verify(code, config);
1199 });
1200 });
1201
1202 describe("when evaluating code containing /*eslint-env */ block", () => {
1203 it("variables should be available in global scope", () => {
1204 const code = `/*${ESLINT_ENV} node*/ function f() {} /*${ESLINT_ENV} browser, foo*/`;
1205 const config = { rules: { checker: "error" } };
1206 let spy;
1207
1208 linter.defineRule("checker", context => {
1209 spy = sinon.spy(() => {
1210 const scope = context.getScope(),
1211 exports = getVariable(scope, "exports"),
1212 window = getVariable(scope, "window");
1213
1214 assert.strictEqual(exports.writeable, true);
1215 assert.strictEqual(window.writeable, false);
1216 });
1217
1218 return { Program: spy };
1219 });
1220
1221 linter.verify(code, config);
1222 assert(spy && spy.calledOnce);
1223 });
1224 });
1225
1226 describe("when evaluating code containing /*eslint-env */ block with sloppy whitespace", () => {
1227 const code = `/* ${ESLINT_ENV} ,, node , no-browser ,, */`;
1228
1229 it("variables should be available in global scope", () => {
1230 const config = { rules: { checker: "error" } };
1231 let spy;
1232
1233 linter.defineRule("checker", context => {
1234 spy = sinon.spy(() => {
1235 const scope = context.getScope(),
1236 exports = getVariable(scope, "exports"),
1237 window = getVariable(scope, "window");
1238
1239 assert.strictEqual(exports.writeable, true);
1240 assert.strictEqual(window, null);
1241 });
1242
1243 return { Program: spy };
1244 });
1245
1246 linter.verify(code, config);
1247 assert(spy && spy.calledOnce);
1248 });
1249 });
1250
1251 describe("when evaluating code containing /*exported */ block", () => {
1252
1253 it("we should behave nicely when no matching variable is found", () => {
1254 const code = "/* exported horse */";
1255 const config = { rules: {} };
1256
1257 linter.verify(code, config, filename, true);
1258 });
1259
1260 it("variables should be exported", () => {
1261 const code = "/* exported horse */\n\nvar horse = 'circus'";
1262 const config = { rules: { checker: "error" } };
1263 let spy;
1264
1265 linter.defineRule("checker", context => {
1266 spy = sinon.spy(() => {
1267 const scope = context.getScope(),
1268 horse = getVariable(scope, "horse");
1269
1270 assert.strictEqual(horse.eslintUsed, true);
1271 });
1272
1273 return { Program: spy };
1274 });
1275
1276 linter.verify(code, config);
1277 assert(spy && spy.calledOnce);
1278 });
1279
1280 it("undefined variables should not be exported", () => {
1281 const code = "/* exported horse */\n\nhorse = 'circus'";
1282 const config = { rules: { checker: "error" } };
1283 let spy;
1284
1285 linter.defineRule("checker", context => {
1286 spy = sinon.spy(() => {
1287 const scope = context.getScope(),
1288 horse = getVariable(scope, "horse");
1289
1290 assert.strictEqual(horse, null);
1291 });
1292
1293 return { Program: spy };
1294 });
1295
1296 linter.verify(code, config);
1297 assert(spy && spy.calledOnce);
1298 });
1299
1300 it("variables should be exported in strict mode", () => {
1301 const code = "/* exported horse */\n'use strict';\nvar horse = 'circus'";
1302 const config = { rules: { checker: "error" } };
1303 let spy;
1304
1305 linter.defineRule("checker", context => {
1306 spy = sinon.spy(() => {
1307 const scope = context.getScope(),
1308 horse = getVariable(scope, "horse");
1309
1310 assert.strictEqual(horse.eslintUsed, true);
1311 });
1312
1313 return { Program: spy };
1314 });
1315
1316 linter.verify(code, config);
1317 assert(spy && spy.calledOnce);
1318 });
1319
1320 it("variables should not be exported in the es6 module environment", () => {
1321 const code = "/* exported horse */\nvar horse = 'circus'";
1322 const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } };
1323 let spy;
1324
1325 linter.defineRule("checker", context => {
1326 spy = sinon.spy(() => {
1327 const scope = context.getScope(),
1328 horse = getVariable(scope, "horse");
1329
1330 assert.strictEqual(horse, null); // there is no global scope at all
1331 });
1332
1333 return { Program: spy };
1334 });
1335
1336 linter.verify(code, config);
1337 assert(spy && spy.calledOnce);
1338 });
1339
1340 it("variables should not be exported when in the node environment", () => {
1341 const code = "/* exported horse */\nvar horse = 'circus'";
1342 const config = { rules: { checker: "error" }, env: { node: true } };
1343 let spy;
1344
1345 linter.defineRule("checker", context => {
1346 spy = sinon.spy(() => {
1347 const scope = context.getScope(),
1348 horse = getVariable(scope, "horse");
1349
1350 assert.strictEqual(horse, null); // there is no global scope at all
1351 });
1352
1353 return { Program: spy };
1354 });
1355
1356 linter.verify(code, config);
1357 assert(spy && spy.calledOnce);
1358 });
1359 });
1360
1361 describe("when evaluating code containing a line comment", () => {
1362 const code = "//global a \n function f() {}";
1363
1364 it("should not introduce a global variable", () => {
1365 const config = { rules: { checker: "error" } };
1366 let spy;
1367
1368 linter.defineRule("checker", context => {
1369 spy = sinon.spy(() => {
1370 const scope = context.getScope();
1371
1372 assert.strictEqual(getVariable(scope, "a"), null);
1373 });
1374
1375 return { Program: spy };
1376 });
1377
1378 linter.verify(code, config);
1379 assert(spy && spy.calledOnce);
1380 });
1381 });
1382
1383 describe("when evaluating code containing normal block comments", () => {
1384 const code = "/**/ /*a*/ /*b:true*/ /*foo c:false*/";
1385
1386 it("should not introduce a global variable", () => {
1387 const config = { rules: { checker: "error" } };
1388 let spy;
1389
1390 linter.defineRule("checker", context => {
1391 spy = sinon.spy(() => {
1392 const scope = context.getScope();
1393
1394 assert.strictEqual(getVariable(scope, "a"), null);
1395 assert.strictEqual(getVariable(scope, "b"), null);
1396 assert.strictEqual(getVariable(scope, "foo"), null);
1397 assert.strictEqual(getVariable(scope, "c"), null);
1398 });
1399
1400 return { Program: spy };
1401 });
1402
1403 linter.verify(code, config);
1404 assert(spy && spy.calledOnce);
1405 });
1406 });
1407
1408 describe("when evaluating any code", () => {
1409 const code = "x";
1410
1411 it("builtin global variables should be available in the global scope", () => {
1412 const config = { rules: { checker: "error" } };
1413 let spy;
1414
1415 linter.defineRule("checker", context => {
1416 spy = sinon.spy(() => {
1417 const scope = context.getScope();
1418
1419 assert.notStrictEqual(getVariable(scope, "Object"), null);
1420 assert.notStrictEqual(getVariable(scope, "Array"), null);
1421 assert.notStrictEqual(getVariable(scope, "undefined"), null);
1422 });
1423
1424 return { Program: spy };
1425 });
1426
1427 linter.verify(code, config);
1428 assert(spy && spy.calledOnce);
1429 });
1430
1431 it("ES6 global variables should not be available by default", () => {
1432 const config = { rules: { checker: "error" } };
1433 let spy;
1434
1435 linter.defineRule("checker", context => {
1436 spy = sinon.spy(() => {
1437 const scope = context.getScope();
1438
1439 assert.strictEqual(getVariable(scope, "Promise"), null);
1440 assert.strictEqual(getVariable(scope, "Symbol"), null);
1441 assert.strictEqual(getVariable(scope, "WeakMap"), null);
1442 });
1443
1444 return { Program: spy };
1445 });
1446
1447 linter.verify(code, config);
1448 assert(spy && spy.calledOnce);
1449 });
1450
1451 it("ES6 global variables should be available in the es6 environment", () => {
1452 const config = { rules: { checker: "error" }, env: { es6: true } };
1453 let spy;
1454
1455 linter.defineRule("checker", context => {
1456 spy = sinon.spy(() => {
1457 const scope = context.getScope();
1458
1459 assert.notStrictEqual(getVariable(scope, "Promise"), null);
1460 assert.notStrictEqual(getVariable(scope, "Symbol"), null);
1461 assert.notStrictEqual(getVariable(scope, "WeakMap"), null);
1462 });
1463
1464 return { Program: spy };
1465 });
1466
1467 linter.verify(code, config);
1468 assert(spy && spy.calledOnce);
1469 });
1470
1471 it("ES6 global variables can be disabled when the es6 environment is enabled", () => {
1472 const config = { rules: { checker: "error" }, globals: { Promise: "off", Symbol: "off", WeakMap: "off" }, env: { es6: true } };
1473 let spy;
1474
1475 linter.defineRule("checker", context => {
1476 spy = sinon.spy(() => {
1477 const scope = context.getScope();
1478
1479 assert.strictEqual(getVariable(scope, "Promise"), null);
1480 assert.strictEqual(getVariable(scope, "Symbol"), null);
1481 assert.strictEqual(getVariable(scope, "WeakMap"), null);
1482 });
1483
1484 return { Program: spy };
1485 });
1486
1487 linter.verify(code, config);
1488 assert(spy && spy.calledOnce);
1489 });
1490 });
1491
1492 describe("at any time", () => {
1493 const code = "new-rule";
1494
1495 it("can add a rule dynamically", () => {
1496 linter.defineRule(code, context => ({
1497 Literal(node) {
1498 context.report(node, "message");
1499 }
1500 }));
1501
1502 const config = { rules: {} };
1503
1504 config.rules[code] = 1;
1505
1506 const messages = linter.verify("0", config, filename);
1507
1508 assert.strictEqual(messages.length, 1);
1509 assert.strictEqual(messages[0].ruleId, code);
1510 assert.strictEqual(messages[0].nodeType, "Literal");
1511 });
1512 });
1513
1514 describe("at any time", () => {
1515 const code = ["new-rule-0", "new-rule-1"];
1516
1517 it("can add multiple rules dynamically", () => {
1518 const config = { rules: {} };
1519 const newRules = {};
1520
1521 code.forEach(item => {
1522 config.rules[item] = 1;
1523 newRules[item] = function(context) {
1524 return {
1525 Literal(node) {
1526 context.report(node, "message");
1527 }
1528 };
1529 };
1530 });
1531 linter.defineRules(newRules);
1532
1533 const messages = linter.verify("0", config, filename);
1534
1535 assert.strictEqual(messages.length, code.length);
1536 code.forEach(item => {
1537 assert.ok(messages.some(message => message.ruleId === item));
1538 });
1539 messages.forEach(message => {
1540 assert.strictEqual(message.nodeType, "Literal");
1541 });
1542 });
1543 });
1544
1545 describe("at any time", () => {
1546 const code = "filename-rule";
1547
1548 it("has access to the filename", () => {
1549 linter.defineRule(code, context => ({
1550 Literal(node) {
1551 context.report(node, context.getFilename());
1552 }
1553 }));
1554
1555 const config = { rules: {} };
1556
1557 config.rules[code] = 1;
1558
1559 const messages = linter.verify("0", config, filename);
1560
1561 assert.strictEqual(messages[0].message, filename);
1562 });
1563
1564 it("has access to the physicalFilename", () => {
1565 linter.defineRule(code, context => ({
1566 Literal(node) {
1567 context.report(node, context.getPhysicalFilename());
1568 }
1569 }));
1570
1571 const config = { rules: {} };
1572
1573 config.rules[code] = 1;
1574
1575 const messages = linter.verify("0", config, filename);
1576
1577 assert.strictEqual(messages[0].message, filename);
1578 });
1579
1580 it("defaults filename to '<input>'", () => {
1581 linter.defineRule(code, context => ({
1582 Literal(node) {
1583 context.report(node, context.getFilename());
1584 }
1585 }));
1586
1587 const config = { rules: {} };
1588
1589 config.rules[code] = 1;
1590
1591 const messages = linter.verify("0", config);
1592
1593 assert.strictEqual(messages[0].message, "<input>");
1594 });
1595 });
1596
1597 describe("when evaluating code with comments to enable rules", () => {
1598
1599 it("should report a violation", () => {
1600 const code = "/*eslint no-alert:1*/ alert('test');";
1601 const config = { rules: {} };
1602
1603 const messages = linter.verify(code, config, filename);
1604
1605 assert.strictEqual(messages.length, 1);
1606 assert.strictEqual(messages[0].ruleId, "no-alert");
1607 assert.strictEqual(messages[0].message, "Unexpected alert.");
1608 assert.include(messages[0].nodeType, "CallExpression");
1609 });
1610
1611 it("rules should not change initial config", () => {
1612 const config = { rules: { strict: 2 } };
1613 const codeA = "/*eslint strict: 0*/ function bar() { return 2; }";
1614 const codeB = "function foo() { return 1; }";
1615 let messages = linter.verify(codeA, config, filename, false);
1616
1617 assert.strictEqual(messages.length, 0);
1618
1619 messages = linter.verify(codeB, config, filename, false);
1620 assert.strictEqual(messages.length, 1);
1621 });
1622
1623 it("rules should not change initial config", () => {
1624 const config = { rules: { quotes: [2, "double"] } };
1625 const codeA = "/*eslint quotes: 0*/ function bar() { return '2'; }";
1626 const codeB = "function foo() { return '1'; }";
1627 let messages = linter.verify(codeA, config, filename, false);
1628
1629 assert.strictEqual(messages.length, 0);
1630
1631 messages = linter.verify(codeB, config, filename, false);
1632 assert.strictEqual(messages.length, 1);
1633 });
1634
1635 it("rules should not change initial config", () => {
1636 const config = { rules: { quotes: [2, "double"] } };
1637 const codeA = "/*eslint quotes: [0, \"single\"]*/ function bar() { return '2'; }";
1638 const codeB = "function foo() { return '1'; }";
1639 let messages = linter.verify(codeA, config, filename, false);
1640
1641 assert.strictEqual(messages.length, 0);
1642
1643 messages = linter.verify(codeB, config, filename, false);
1644 assert.strictEqual(messages.length, 1);
1645 });
1646
1647 it("rules should not change initial config", () => {
1648 const config = { rules: { "no-unused-vars": [2, { vars: "all" }] } };
1649 const codeA = "/*eslint no-unused-vars: [0, {\"vars\": \"local\"}]*/ var a = 44;";
1650 const codeB = "var b = 55;";
1651 let messages = linter.verify(codeA, config, filename, false);
1652
1653 assert.strictEqual(messages.length, 0);
1654
1655 messages = linter.verify(codeB, config, filename, false);
1656 assert.strictEqual(messages.length, 1);
1657 });
1658 });
1659
1660 describe("when evaluating code with invalid comments to enable rules", () => {
1661 it("should report a violation when the config is not a valid rule configuration", () => {
1662 assert.deepStrictEqual(
1663 linter.verify("/*eslint no-alert:true*/ alert('test');", {}),
1664 [
1665 {
1666 severity: 2,
1667 ruleId: "no-alert",
1668 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",
1669 line: 1,
1670 column: 1,
1671 endLine: 1,
1672 endColumn: 25,
1673 nodeType: null
1674 }
1675 ]
1676 );
1677 });
1678
1679 it("should report a violation when the config violates a rule's schema", () => {
1680 assert.deepStrictEqual(
1681 linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {}),
1682 [
1683 {
1684 severity: 2,
1685 ruleId: "no-alert",
1686 message: "Configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n",
1687 line: 1,
1688 column: 1,
1689 endLine: 1,
1690 endColumn: 63,
1691 nodeType: null
1692 }
1693 ]
1694 );
1695 });
1696 });
1697
1698 describe("when evaluating code with comments to disable rules", () => {
1699 const code = "/*eslint no-alert:0*/ alert('test');";
1700
1701 it("should not report a violation", () => {
1702 const config = { rules: { "no-alert": 1 } };
1703
1704 const messages = linter.verify(code, config, filename);
1705
1706 assert.strictEqual(messages.length, 0);
1707 });
1708 });
1709
1710 describe("when evaluating code with comments to disable rules", () => {
1711 let code, messages;
1712
1713 it("should report an error when disabling a non-existent rule in inline comment", () => {
1714 code = "/*eslint foo:0*/ ;";
1715 messages = linter.verify(code, {}, filename);
1716 assert.strictEqual(messages.length, 1);
1717 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
1718
1719 code = "/*eslint-disable foo*/ ;";
1720 messages = linter.verify(code, {}, filename);
1721 assert.strictEqual(messages.length, 1);
1722 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
1723
1724 code = "/*eslint-disable-line foo*/ ;";
1725 messages = linter.verify(code, {}, filename);
1726 assert.strictEqual(messages.length, 1);
1727 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
1728
1729 code = "/*eslint-disable-next-line foo*/ ;";
1730 messages = linter.verify(code, {}, filename);
1731 assert.strictEqual(messages.length, 1);
1732 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
1733 });
1734
1735 it("should not report an error, when disabling a non-existent rule in config", () => {
1736 messages = linter.verify("", { rules: { foo: 0 } }, filename);
1737
1738 assert.strictEqual(messages.length, 0);
1739 });
1740
1741 it("should report an error, when config a non-existent rule in config", () => {
1742 messages = linter.verify("", { rules: { foo: 1 } }, filename);
1743 assert.strictEqual(messages.length, 1);
1744 assert.strictEqual(messages[0].severity, 2);
1745 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
1746
1747 messages = linter.verify("", { rules: { foo: 2 } }, filename);
1748 assert.strictEqual(messages.length, 1);
1749 assert.strictEqual(messages[0].severity, 2);
1750 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
1751 });
1752 });
1753
1754 describe("when evaluating code with comments to enable multiple rules", () => {
1755 const code = "/*eslint no-alert:1 no-console:1*/ alert('test'); console.log('test');";
1756
1757 it("should report a violation", () => {
1758 const config = { rules: {} };
1759
1760 const messages = linter.verify(code, config, filename);
1761
1762 assert.strictEqual(messages.length, 2);
1763 assert.strictEqual(messages[0].ruleId, "no-alert");
1764 assert.strictEqual(messages[0].message, "Unexpected alert.");
1765 assert.include(messages[0].nodeType, "CallExpression");
1766 assert.strictEqual(messages[1].ruleId, "no-console");
1767 });
1768 });
1769
1770 describe("when evaluating code with comments to enable and disable multiple rules", () => {
1771 const code = "/*eslint no-alert:1 no-console:0*/ alert('test'); console.log('test');";
1772
1773 it("should report a violation", () => {
1774 const config = { rules: { "no-console": 1, "no-alert": 0 } };
1775
1776 const messages = linter.verify(code, config, filename);
1777
1778 assert.strictEqual(messages.length, 1);
1779 assert.strictEqual(messages[0].ruleId, "no-alert");
1780 assert.strictEqual(messages[0].message, "Unexpected alert.");
1781 assert.include(messages[0].nodeType, "CallExpression");
1782 });
1783 });
1784
1785 describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => {
1786
1787 beforeEach(() => {
1788 linter.defineRule("test-plugin/test-rule", context => ({
1789 Literal(node) {
1790 if (node.value === "trigger violation") {
1791 context.report(node, "Reporting violation.");
1792 }
1793 }
1794 }));
1795 });
1796
1797 it("should not report a violation when inline comment enables plugin rule and there's no violation", () => {
1798 const config = { rules: {} };
1799 const code = "/*eslint test-plugin/test-rule: 2*/ var a = \"no violation\";";
1800
1801 const messages = linter.verify(code, config, filename);
1802
1803 assert.strictEqual(messages.length, 0);
1804 });
1805
1806 it("should not report a violation when inline comment disables plugin rule", () => {
1807 const code = "/*eslint test-plugin/test-rule:0*/ var a = \"trigger violation\"";
1808 const config = { rules: { "test-plugin/test-rule": 1 } };
1809
1810 const messages = linter.verify(code, config, filename);
1811
1812 assert.strictEqual(messages.length, 0);
1813 });
1814
1815 it("should report a violation when the report is right before the comment", () => {
1816 const code = " /* eslint-disable */ ";
1817
1818 linter.defineRule("checker", context => ({
1819 Program() {
1820 context.report({ loc: { line: 1, column: 0 }, message: "foo" });
1821 }
1822 }));
1823 const problems = linter.verify(code, { rules: { checker: "error" } });
1824
1825 assert.strictEqual(problems.length, 1);
1826 assert.strictEqual(problems[0].message, "foo");
1827 });
1828
1829 it("should not report a violation when the report is right at the start of the comment", () => {
1830 const code = " /* eslint-disable */ ";
1831
1832 linter.defineRule("checker", context => ({
1833 Program() {
1834 context.report({ loc: { line: 1, column: 1 }, message: "foo" });
1835 }
1836 }));
1837 const problems = linter.verify(code, { rules: { checker: "error" } });
1838
1839 assert.strictEqual(problems.length, 0);
1840 });
1841
1842 it("rules should not change initial config", () => {
1843 const config = { rules: { "test-plugin/test-rule": 2 } };
1844 const codeA = "/*eslint test-plugin/test-rule: 0*/ var a = \"trigger violation\";";
1845 const codeB = "var a = \"trigger violation\";";
1846 let messages = linter.verify(codeA, config, filename, false);
1847
1848 assert.strictEqual(messages.length, 0);
1849
1850 messages = linter.verify(codeB, config, filename, false);
1851 assert.strictEqual(messages.length, 1);
1852 });
1853 });
1854
1855 describe("when evaluating code with comments to enable and disable all reporting", () => {
1856 it("should report a violation", () => {
1857
1858 const code = [
1859 "/*eslint-disable */",
1860 "alert('test');",
1861 "/*eslint-enable */",
1862 "alert('test');"
1863 ].join("\n");
1864 const config = { rules: { "no-alert": 1 } };
1865
1866 const messages = linter.verify(code, config, filename);
1867
1868 assert.strictEqual(messages.length, 1);
1869 assert.strictEqual(messages[0].ruleId, "no-alert");
1870 assert.strictEqual(messages[0].message, "Unexpected alert.");
1871 assert.include(messages[0].nodeType, "CallExpression");
1872 assert.strictEqual(messages[0].line, 4);
1873 });
1874
1875 it("should not report a violation", () => {
1876 const code = [
1877 "/*eslint-disable */",
1878 "alert('test');",
1879 "alert('test');"
1880 ].join("\n");
1881 const config = { rules: { "no-alert": 1 } };
1882
1883 const messages = linter.verify(code, config, filename);
1884
1885 assert.strictEqual(messages.length, 0);
1886 });
1887
1888 it("should not report a violation", () => {
1889 const code = [
1890 " alert('test1');/*eslint-disable */\n",
1891 "alert('test');",
1892 " alert('test');\n",
1893 "/*eslint-enable */alert('test2');"
1894 ].join("");
1895 const config = { rules: { "no-alert": 1 } };
1896
1897 const messages = linter.verify(code, config, filename);
1898
1899 assert.strictEqual(messages.length, 2);
1900 assert.strictEqual(messages[0].column, 21);
1901 assert.strictEqual(messages[1].column, 19);
1902 });
1903
1904 it("should report a violation", () => {
1905
1906 const code = [
1907 "/*eslint-disable */",
1908 "alert('test');",
1909 "/*eslint-disable */",
1910 "alert('test');",
1911 "/*eslint-enable*/",
1912 "alert('test');",
1913 "/*eslint-enable*/"
1914 ].join("\n");
1915
1916 const config = { rules: { "no-alert": 1 } };
1917
1918 const messages = linter.verify(code, config, filename);
1919
1920 assert.strictEqual(messages.length, 1);
1921 });
1922
1923
1924 it("should not report a violation", () => {
1925 const code = [
1926 "/*eslint-disable */",
1927 "(function(){ var b = 44;})()",
1928 "/*eslint-enable */;any();"
1929 ].join("\n");
1930
1931 const config = { rules: { "no-unused-vars": 1 } };
1932
1933 const messages = linter.verify(code, config, filename);
1934
1935 assert.strictEqual(messages.length, 0);
1936 });
1937
1938 it("should not report a violation", () => {
1939 const code = [
1940 "(function(){ /*eslint-disable */ var b = 44;})()",
1941 "/*eslint-enable */;any();"
1942 ].join("\n");
1943
1944 const config = { rules: { "no-unused-vars": 1 } };
1945
1946 const messages = linter.verify(code, config, filename);
1947
1948 assert.strictEqual(messages.length, 0);
1949 });
1950 });
1951
1952 describe("when evaluating code with comments to ignore reporting on specific rules on a specific line", () => {
1953
1954 describe("eslint-disable-line", () => {
1955 it("should report a violation", () => {
1956 const code = [
1957 "alert('test'); // eslint-disable-line no-alert",
1958 "console.log('test');" // here
1959 ].join("\n");
1960 const config = {
1961 rules: {
1962 "no-alert": 1,
1963 "no-console": 1
1964 }
1965 };
1966
1967 const messages = linter.verify(code, config, filename);
1968
1969 assert.strictEqual(messages.length, 1);
1970
1971 assert.strictEqual(messages[0].ruleId, "no-console");
1972 });
1973
1974 it("should report a violation", () => {
1975 const code = [
1976 "alert('test'); // eslint-disable-line no-alert",
1977 "console.log('test'); // eslint-disable-line no-console",
1978 "alert('test');" // here
1979 ].join("\n");
1980 const config = {
1981 rules: {
1982 "no-alert": 1,
1983 "no-console": 1
1984 }
1985 };
1986
1987 const messages = linter.verify(code, config, filename);
1988
1989 assert.strictEqual(messages.length, 1);
1990
1991 assert.strictEqual(messages[0].ruleId, "no-alert");
1992 });
1993
1994 it("should report a violation if eslint-disable-line in a block comment is not on a single line", () => {
1995 const code = [
1996 "/* eslint-disable-line",
1997 "*",
1998 "*/ console.log('test');" // here
1999 ].join("\n");
2000 const config = {
2001 rules: {
2002 "no-console": 1
2003 }
2004 };
2005
2006 const messages = linter.verify(code, config, filename);
2007
2008 assert.strictEqual(messages.length, 2);
2009
2010 assert.strictEqual(messages[1].ruleId, "no-console");
2011 });
2012
2013 it("should not disable rule and add an extra report if eslint-disable-line in a block comment is not on a single line", () => {
2014 const code = [
2015 "alert('test'); /* eslint-disable-line ",
2016 "no-alert */"
2017 ].join("\n");
2018 const config = {
2019 rules: {
2020 "no-alert": 1
2021 }
2022 };
2023
2024 const messages = linter.verify(code, config);
2025
2026 assert.deepStrictEqual(messages, [
2027 {
2028 ruleId: "no-alert",
2029 severity: 1,
2030 line: 1,
2031 column: 1,
2032 endLine: 1,
2033 endColumn: 14,
2034 message: "Unexpected alert.",
2035 messageId: "unexpected",
2036 nodeType: "CallExpression"
2037 },
2038 {
2039 ruleId: null,
2040 severity: 2,
2041 message: "eslint-disable-line comment should not span multiple lines.",
2042 line: 1,
2043 column: 16,
2044 endLine: 2,
2045 endColumn: 12,
2046 nodeType: null
2047 }
2048 ]);
2049 });
2050
2051 it("should not report a violation for eslint-disable-line in block comment", () => {
2052 const code = [
2053 "alert('test'); // eslint-disable-line no-alert",
2054 "alert('test'); /*eslint-disable-line no-alert*/"
2055 ].join("\n");
2056 const config = {
2057 rules: {
2058 "no-alert": 1
2059 }
2060 };
2061
2062 const messages = linter.verify(code, config, filename);
2063
2064 assert.strictEqual(messages.length, 0);
2065 });
2066
2067 it("should not report a violation", () => {
2068 const code = [
2069 "alert('test'); // eslint-disable-line no-alert",
2070 "console.log('test'); // eslint-disable-line no-console"
2071 ].join("\n");
2072 const config = {
2073 rules: {
2074 "no-alert": 1,
2075 "no-console": 1
2076 }
2077 };
2078
2079 const messages = linter.verify(code, config, filename);
2080
2081 assert.strictEqual(messages.length, 0);
2082 });
2083
2084 it("should not report a violation", () => {
2085 const code = [
2086 "alert('test') // eslint-disable-line no-alert, quotes, semi",
2087 "console.log('test'); // eslint-disable-line"
2088 ].join("\n");
2089 const config = {
2090 rules: {
2091 "no-alert": 1,
2092 quotes: [1, "double"],
2093 semi: [1, "always"],
2094 "no-console": 1
2095 }
2096 };
2097
2098 const messages = linter.verify(code, config, filename);
2099
2100 assert.strictEqual(messages.length, 0);
2101 });
2102
2103 it("should not report a violation", () => {
2104 const code = [
2105 "alert('test') /* eslint-disable-line no-alert, quotes, semi */",
2106 "console.log('test'); /* eslint-disable-line */"
2107 ].join("\n");
2108 const config = {
2109 rules: {
2110 "no-alert": 1,
2111 quotes: [1, "double"],
2112 semi: [1, "always"],
2113 "no-console": 1
2114 }
2115 };
2116
2117 const messages = linter.verify(code, config, filename);
2118
2119 assert.strictEqual(messages.length, 0);
2120 });
2121
2122 it("should ignore violations of multiple rules when specified in mixed comments", () => {
2123 const code = [
2124 " alert(\"test\"); /* eslint-disable-line no-alert */ // eslint-disable-line quotes"
2125 ].join("\n");
2126 const config = {
2127 rules: {
2128 "no-alert": 1,
2129 quotes: [1, "single"]
2130 }
2131 };
2132 const messages = linter.verify(code, config, filename);
2133
2134 assert.strictEqual(messages.length, 0);
2135 });
2136 });
2137
2138 describe("eslint-disable-next-line", () => {
2139 it("should ignore violation of specified rule on next line", () => {
2140 const code = [
2141 "// eslint-disable-next-line no-alert",
2142 "alert('test');",
2143 "console.log('test');"
2144 ].join("\n");
2145 const config = {
2146 rules: {
2147 "no-alert": 1,
2148 "no-console": 1
2149 }
2150 };
2151 const messages = linter.verify(code, config, filename);
2152
2153 assert.strictEqual(messages.length, 1);
2154 assert.strictEqual(messages[0].ruleId, "no-console");
2155 });
2156
2157 it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => {
2158 const code = [
2159 "/* eslint-disable-next-line no-alert */",
2160 "alert('test');",
2161 "console.log('test');"
2162 ].join("\n");
2163 const config = {
2164 rules: {
2165 "no-alert": 1,
2166 "no-console": 1
2167 }
2168 };
2169 const messages = linter.verify(code, config, filename);
2170
2171 assert.strictEqual(messages.length, 1);
2172 assert.strictEqual(messages[0].ruleId, "no-console");
2173 });
2174 it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => {
2175 const code = [
2176 "/* eslint-disable-next-line no-alert */",
2177 "alert('test');"
2178 ].join("\n");
2179 const config = {
2180 rules: {
2181 "no-alert": 1
2182 }
2183 };
2184 const messages = linter.verify(code, config, filename);
2185
2186 assert.strictEqual(messages.length, 0);
2187 });
2188
2189 it("should not ignore violation if block comment is not on a single line", () => {
2190 const code = [
2191 "/* eslint-disable-next-line",
2192 "no-alert */alert('test');"
2193 ].join("\n");
2194 const config = {
2195 rules: {
2196 "no-alert": 1
2197 }
2198 };
2199 const messages = linter.verify(code, config, filename);
2200
2201 assert.strictEqual(messages.length, 2);
2202 assert.strictEqual(messages[1].ruleId, "no-alert");
2203 });
2204
2205 it("should ignore violations only of specified rule", () => {
2206 const code = [
2207 "// eslint-disable-next-line no-console",
2208 "alert('test');",
2209 "console.log('test');"
2210 ].join("\n");
2211 const config = {
2212 rules: {
2213 "no-alert": 1,
2214 "no-console": 1
2215 }
2216 };
2217 const messages = linter.verify(code, config, filename);
2218
2219 assert.strictEqual(messages.length, 2);
2220 assert.strictEqual(messages[0].ruleId, "no-alert");
2221 assert.strictEqual(messages[1].ruleId, "no-console");
2222 });
2223
2224 it("should ignore violations of multiple rules when specified", () => {
2225 const code = [
2226 "// eslint-disable-next-line no-alert, quotes",
2227 "alert(\"test\");",
2228 "console.log('test');"
2229 ].join("\n");
2230 const config = {
2231 rules: {
2232 "no-alert": 1,
2233 quotes: [1, "single"],
2234 "no-console": 1
2235 }
2236 };
2237 const messages = linter.verify(code, config, filename);
2238
2239 assert.strictEqual(messages.length, 1);
2240 assert.strictEqual(messages[0].ruleId, "no-console");
2241 });
2242
2243 it("should ignore violations of multiple rules when specified in mixed comments", () => {
2244 const code = [
2245 "/* eslint-disable-next-line no-alert */ // eslint-disable-next-line quotes",
2246 "alert(\"test\");"
2247 ].join("\n");
2248 const config = {
2249 rules: {
2250 "no-alert": 1,
2251 quotes: [1, "single"]
2252 }
2253 };
2254 const messages = linter.verify(code, config, filename);
2255
2256 assert.strictEqual(messages.length, 0);
2257 });
2258
2259 it("should ignore violations of only the specified rule on next line", () => {
2260 const code = [
2261 "// eslint-disable-next-line quotes",
2262 "alert(\"test\");",
2263 "console.log('test');"
2264 ].join("\n");
2265 const config = {
2266 rules: {
2267 "no-alert": 1,
2268 quotes: [1, "single"],
2269 "no-console": 1
2270 }
2271 };
2272 const messages = linter.verify(code, config, filename);
2273
2274 assert.strictEqual(messages.length, 2);
2275 assert.strictEqual(messages[0].ruleId, "no-alert");
2276 assert.strictEqual(messages[1].ruleId, "no-console");
2277 });
2278
2279 it("should ignore violations of specified rule on next line only", () => {
2280 const code = [
2281 "alert('test');",
2282 "// eslint-disable-next-line no-alert",
2283 "alert('test');",
2284 "console.log('test');"
2285 ].join("\n");
2286 const config = {
2287 rules: {
2288 "no-alert": 1,
2289 "no-console": 1
2290 }
2291 };
2292 const messages = linter.verify(code, config, filename);
2293
2294 assert.strictEqual(messages.length, 2);
2295 assert.strictEqual(messages[0].ruleId, "no-alert");
2296 assert.strictEqual(messages[1].ruleId, "no-console");
2297 });
2298
2299 it("should ignore all rule violations on next line if none specified", () => {
2300 const code = [
2301 "// eslint-disable-next-line",
2302 "alert(\"test\");",
2303 "console.log('test')"
2304 ].join("\n");
2305 const config = {
2306 rules: {
2307 semi: [1, "never"],
2308 quotes: [1, "single"],
2309 "no-alert": 1,
2310 "no-console": 1
2311 }
2312 };
2313 const messages = linter.verify(code, config, filename);
2314
2315 assert.strictEqual(messages.length, 1);
2316 assert.strictEqual(messages[0].ruleId, "no-console");
2317 });
2318
2319 it("should ignore violations if eslint-disable-next-line is a block comment", () => {
2320 const code = [
2321 "alert('test');",
2322 "/* eslint-disable-next-line no-alert */",
2323 "alert('test');",
2324 "console.log('test');"
2325 ].join("\n");
2326 const config = {
2327 rules: {
2328 "no-alert": 1,
2329 "no-console": 1
2330 }
2331 };
2332 const messages = linter.verify(code, config, filename);
2333
2334 assert.strictEqual(messages.length, 2);
2335 assert.strictEqual(messages[0].ruleId, "no-alert");
2336 assert.strictEqual(messages[1].ruleId, "no-console");
2337 });
2338
2339 it("should report a violation", () => {
2340 const code = [
2341 "/* eslint-disable-next-line",
2342 "*",
2343 "*/",
2344 "console.log('test');" // here
2345 ].join("\n");
2346 const config = {
2347 rules: {
2348 "no-alert": 1,
2349 "no-console": 1
2350 }
2351 };
2352
2353 const messages = linter.verify(code, config, filename);
2354
2355 assert.strictEqual(messages.length, 2);
2356
2357 assert.strictEqual(messages[1].ruleId, "no-console");
2358 });
2359
2360 it("should not ignore violations if comment is of the type hashbang", () => {
2361 const code = [
2362 "#! eslint-disable-next-line no-alert",
2363 "alert('test');",
2364 "console.log('test');"
2365 ].join("\n");
2366 const config = {
2367 rules: {
2368 "no-alert": 1,
2369 "no-console": 1
2370 }
2371 };
2372 const messages = linter.verify(code, config, filename);
2373
2374 assert.strictEqual(messages.length, 2);
2375 assert.strictEqual(messages[0].ruleId, "no-alert");
2376 assert.strictEqual(messages[1].ruleId, "no-console");
2377 });
2378 });
2379 });
2380
2381 describe("when evaluating code with comments to enable and disable reporting of specific rules", () => {
2382
2383 it("should report a violation", () => {
2384 const code = [
2385 "/*eslint-disable no-alert */",
2386 "alert('test');",
2387 "console.log('test');" // here
2388 ].join("\n");
2389 const config = { rules: { "no-alert": 1, "no-console": 1 } };
2390
2391 const messages = linter.verify(code, config, filename);
2392
2393 assert.strictEqual(messages.length, 1);
2394
2395 assert.strictEqual(messages[0].ruleId, "no-console");
2396 });
2397
2398 it("should report no violation", () => {
2399 const code = [
2400 "/*eslint-disable no-unused-vars */",
2401 "var foo; // eslint-disable-line no-unused-vars",
2402 "var bar;",
2403 "/* eslint-enable no-unused-vars */" // here
2404 ].join("\n");
2405 const config = { rules: { "no-unused-vars": 2 } };
2406
2407 const messages = linter.verify(code, config, filename);
2408
2409 assert.strictEqual(messages.length, 0);
2410 });
2411
2412 it("should report no violation", () => {
2413 const code = [
2414 "var foo1; // eslint-disable-line no-unused-vars",
2415 "var foo2; // eslint-disable-line no-unused-vars",
2416 "var foo3; // eslint-disable-line no-unused-vars",
2417 "var foo4; // eslint-disable-line no-unused-vars",
2418 "var foo5; // eslint-disable-line no-unused-vars"
2419 ].join("\n");
2420 const config = { rules: { "no-unused-vars": 2 } };
2421
2422 const messages = linter.verify(code, config, filename);
2423
2424 assert.strictEqual(messages.length, 0);
2425 });
2426
2427 it("should report no violation", () => {
2428 const code = [
2429 "/* eslint-disable quotes */",
2430 "console.log(\"foo\");",
2431 "/* eslint-enable quotes */"
2432 ].join("\n");
2433 const config = { rules: { quotes: 2 } };
2434
2435 const messages = linter.verify(code, config, filename);
2436
2437 assert.strictEqual(messages.length, 0);
2438 });
2439
2440 it("should report a violation", () => {
2441 const code = [
2442 "/*eslint-disable no-alert, no-console */",
2443 "alert('test');",
2444 "console.log('test');",
2445 "/*eslint-enable*/",
2446
2447 "alert('test');", // here
2448 "console.log('test');" // here
2449 ].join("\n");
2450 const config = { rules: { "no-alert": 1, "no-console": 1 } };
2451
2452 const messages = linter.verify(code, config, filename);
2453
2454 assert.strictEqual(messages.length, 2);
2455
2456 assert.strictEqual(messages[0].ruleId, "no-alert");
2457 assert.strictEqual(messages[0].line, 5);
2458 assert.strictEqual(messages[1].ruleId, "no-console");
2459 assert.strictEqual(messages[1].line, 6);
2460 });
2461
2462 it("should report a violation", () => {
2463 const code = [
2464 "/*eslint-disable no-alert */",
2465 "alert('test');",
2466 "console.log('test');",
2467 "/*eslint-enable no-console */",
2468
2469 "alert('test');" // here
2470 ].join("\n");
2471 const config = { rules: { "no-alert": 1, "no-console": 1 } };
2472
2473 const messages = linter.verify(code, config, filename);
2474
2475 assert.strictEqual(messages.length, 1);
2476
2477 assert.strictEqual(messages[0].ruleId, "no-console");
2478 });
2479
2480
2481 it("should report a violation", () => {
2482 const code = [
2483 "/*eslint-disable no-alert, no-console */",
2484 "alert('test');",
2485 "console.log('test');",
2486 "/*eslint-enable no-alert*/",
2487
2488 "alert('test');", // here
2489 "console.log('test');"
2490 ].join("\n");
2491 const config = { rules: { "no-alert": 1, "no-console": 1 } };
2492
2493 const messages = linter.verify(code, config, filename);
2494
2495 assert.strictEqual(messages.length, 1);
2496
2497 assert.strictEqual(messages[0].ruleId, "no-alert");
2498 assert.strictEqual(messages[0].line, 5);
2499 });
2500
2501
2502 it("should report a violation", () => {
2503 const code = [
2504 "/*eslint-disable no-alert */",
2505
2506 "/*eslint-disable no-console */",
2507 "alert('test');",
2508 "console.log('test');",
2509 "/*eslint-enable */",
2510
2511 "alert('test');", // here
2512 "console.log('test');", // here
2513
2514 "/*eslint-enable */",
2515
2516 "alert('test');", // here
2517 "console.log('test');", // here
2518
2519 "/*eslint-enable*/"
2520 ].join("\n");
2521 const config = { rules: { "no-alert": 1, "no-console": 1 } };
2522
2523 const messages = linter.verify(code, config, filename);
2524
2525 assert.strictEqual(messages.length, 4);
2526
2527 assert.strictEqual(messages[0].ruleId, "no-alert");
2528 assert.strictEqual(messages[0].line, 6);
2529
2530 assert.strictEqual(messages[1].ruleId, "no-console");
2531 assert.strictEqual(messages[1].line, 7);
2532
2533 assert.strictEqual(messages[2].ruleId, "no-alert");
2534 assert.strictEqual(messages[2].line, 9);
2535
2536 assert.strictEqual(messages[3].ruleId, "no-console");
2537 assert.strictEqual(messages[3].line, 10);
2538
2539 });
2540
2541 it("should report a violation", () => {
2542 const code = [
2543 "/*eslint-disable no-alert, no-console */",
2544 "alert('test');",
2545 "console.log('test');",
2546
2547 "/*eslint-enable no-alert */",
2548
2549 "alert('test');", // here
2550 "console.log('test');",
2551
2552 "/*eslint-enable no-console */",
2553
2554 "alert('test');", // here
2555 "console.log('test');", // here
2556 "/*eslint-enable no-console */"
2557 ].join("\n");
2558 const config = { rules: { "no-alert": 1, "no-console": 1 } };
2559
2560 const messages = linter.verify(code, config, filename);
2561
2562 assert.strictEqual(messages.length, 3);
2563
2564 assert.strictEqual(messages[0].ruleId, "no-alert");
2565 assert.strictEqual(messages[0].line, 5);
2566
2567 assert.strictEqual(messages[1].ruleId, "no-alert");
2568 assert.strictEqual(messages[1].line, 8);
2569
2570 assert.strictEqual(messages[2].ruleId, "no-console");
2571 assert.strictEqual(messages[2].line, 9);
2572
2573 });
2574
2575 it("should report a violation when severity is warn", () => {
2576 const code = [
2577 "/*eslint-disable no-alert, no-console */",
2578 "alert('test');",
2579 "console.log('test');",
2580
2581 "/*eslint-enable no-alert */",
2582
2583 "alert('test');", // here
2584 "console.log('test');",
2585
2586 "/*eslint-enable no-console */",
2587
2588 "alert('test');", // here
2589 "console.log('test');", // here
2590 "/*eslint-enable no-console */"
2591 ].join("\n");
2592 const config = { rules: { "no-alert": "warn", "no-console": "warn" } };
2593
2594 const messages = linter.verify(code, config, filename);
2595
2596 assert.strictEqual(messages.length, 3);
2597
2598 assert.strictEqual(messages[0].ruleId, "no-alert");
2599 assert.strictEqual(messages[0].line, 5);
2600
2601 assert.strictEqual(messages[1].ruleId, "no-alert");
2602 assert.strictEqual(messages[1].line, 8);
2603
2604 assert.strictEqual(messages[2].ruleId, "no-console");
2605 assert.strictEqual(messages[2].line, 9);
2606
2607 });
2608 });
2609
2610 describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => {
2611 const code = "/*eslint no-alert:1, no-console:0*/ alert('test'); console.log('test');";
2612
2613 it("should report a violation", () => {
2614 const config = { rules: { "no-console": 1, "no-alert": 0 } };
2615
2616 const messages = linter.verify(code, config, filename);
2617
2618 assert.strictEqual(messages.length, 1);
2619 assert.strictEqual(messages[0].ruleId, "no-alert");
2620 assert.strictEqual(messages[0].message, "Unexpected alert.");
2621 assert.include(messages[0].nodeType, "CallExpression");
2622 });
2623 });
2624
2625 describe("when evaluating code with comments to enable configurable rule", () => {
2626 const code = "/*eslint quotes:[2, \"double\"]*/ alert('test');";
2627
2628 it("should report a violation", () => {
2629 const config = { rules: { quotes: [2, "single"] } };
2630
2631 const messages = linter.verify(code, config, filename);
2632
2633 assert.strictEqual(messages.length, 1);
2634 assert.strictEqual(messages[0].ruleId, "quotes");
2635 assert.strictEqual(messages[0].message, "Strings must use doublequote.");
2636 assert.include(messages[0].nodeType, "Literal");
2637 });
2638 });
2639
2640 describe("when evaluating code with comments to enable configurable rule using string severity", () => {
2641 const code = "/*eslint quotes:[\"error\", \"double\"]*/ alert('test');";
2642
2643 it("should report a violation", () => {
2644 const config = { rules: { quotes: [2, "single"] } };
2645
2646 const messages = linter.verify(code, config, filename);
2647
2648 assert.strictEqual(messages.length, 1);
2649 assert.strictEqual(messages[0].ruleId, "quotes");
2650 assert.strictEqual(messages[0].message, "Strings must use doublequote.");
2651 assert.include(messages[0].nodeType, "Literal");
2652 });
2653 });
2654
2655 describe("when evaluating code with incorrectly formatted comments to disable rule", () => {
2656 it("should report a violation", () => {
2657 const code = "/*eslint no-alert:'1'*/ alert('test');";
2658
2659 const config = { rules: { "no-alert": 1 } };
2660
2661 const messages = linter.verify(code, config, filename);
2662
2663 assert.strictEqual(messages.length, 2);
2664
2665 /*
2666 * Incorrectly formatted comment threw error;
2667 * message from caught exception
2668 * may differ amongst UAs, so verifying
2669 * first part only as defined in the
2670 * parseJsonConfig function in lib/eslint.js
2671 */
2672 assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":'1'':/u);
2673 assert.strictEqual(messages[0].line, 1);
2674 assert.strictEqual(messages[0].column, 1);
2675
2676 assert.strictEqual(messages[1].ruleId, "no-alert");
2677 assert.strictEqual(messages[1].message, "Unexpected alert.");
2678 assert.include(messages[1].nodeType, "CallExpression");
2679 });
2680
2681 it("should report a violation", () => {
2682 const code = "/*eslint no-alert:abc*/ alert('test');";
2683
2684 const config = { rules: { "no-alert": 1 } };
2685
2686 const messages = linter.verify(code, config, filename);
2687
2688 assert.strictEqual(messages.length, 2);
2689
2690 /*
2691 * Incorrectly formatted comment threw error;
2692 * message from caught exception
2693 * may differ amongst UAs, so verifying
2694 * first part only as defined in the
2695 * parseJsonConfig function in lib/eslint.js
2696 */
2697 assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":abc':/u);
2698 assert.strictEqual(messages[0].line, 1);
2699 assert.strictEqual(messages[0].column, 1);
2700
2701 assert.strictEqual(messages[1].ruleId, "no-alert");
2702 assert.strictEqual(messages[1].message, "Unexpected alert.");
2703 assert.include(messages[1].nodeType, "CallExpression");
2704 });
2705
2706 it("should report a violation", () => {
2707 const code = "/*eslint no-alert:0 2*/ alert('test');";
2708
2709 const config = { rules: { "no-alert": 1 } };
2710
2711 const messages = linter.verify(code, config, filename);
2712
2713 assert.strictEqual(messages.length, 2);
2714
2715 /*
2716 * Incorrectly formatted comment threw error;
2717 * message from caught exception
2718 * may differ amongst UAs, so verifying
2719 * first part only as defined in the
2720 * parseJsonConfig function in lib/eslint.js
2721 */
2722 assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":0 2':/u);
2723 assert.strictEqual(messages[0].line, 1);
2724 assert.strictEqual(messages[0].column, 1);
2725
2726 assert.strictEqual(messages[1].ruleId, "no-alert");
2727 assert.strictEqual(messages[1].message, "Unexpected alert.");
2728 assert.include(messages[1].nodeType, "CallExpression");
2729 });
2730 });
2731
2732 describe("when evaluating code with comments which have colon in its value", () => {
2733 const code = String.raw`
2734 /* eslint max-len: [2, 100, 2, {ignoreUrls: true, ignorePattern: "data:image\\/|\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}] */
2735 alert('test');
2736 `;
2737
2738 it("should not parse errors, should report a violation", () => {
2739 const messages = linter.verify(code, {}, filename);
2740
2741 assert.strictEqual(messages.length, 1);
2742 assert.strictEqual(messages[0].ruleId, "max-len");
2743 assert.strictEqual(messages[0].message, "This line has a length of 129. Maximum allowed is 100.");
2744 assert.include(messages[0].nodeType, "Program");
2745 });
2746 });
2747
2748 describe("when evaluating code with comments that contain escape sequences", () => {
2749 const code = String.raw`
2750 /* eslint max-len: ["error", 1, { ignoreComments: true, ignorePattern: "console\\.log\\(" }] */
2751 console.log("test");
2752 consolexlog("test2");
2753 var a = "test2";
2754 `;
2755
2756 it("should validate correctly", () => {
2757 const config = { rules: {} };
2758 const messages = linter.verify(code, config, filename);
2759 const [message1, message2] = messages;
2760
2761 assert.strictEqual(messages.length, 2);
2762 assert.strictEqual(message1.ruleId, "max-len");
2763 assert.strictEqual(message1.message, "This line has a length of 21. Maximum allowed is 1.");
2764 assert.strictEqual(message1.line, 4);
2765 assert.strictEqual(message1.column, 1);
2766 assert.include(message1.nodeType, "Program");
2767 assert.strictEqual(message2.ruleId, "max-len");
2768 assert.strictEqual(message2.message, "This line has a length of 16. Maximum allowed is 1.");
2769 assert.strictEqual(message2.line, 5);
2770 assert.strictEqual(message2.column, 1);
2771 assert.include(message2.nodeType, "Program");
2772 });
2773 });
2774
2775 describe("when evaluating a file with a hashbang", () => {
2776
2777 it("should preserve line numbers", () => {
2778 const code = "#!bin/program\n\nvar foo;;";
2779 const config = { rules: { "no-extra-semi": 1 } };
2780 const messages = linter.verify(code, config);
2781
2782 assert.strictEqual(messages.length, 1);
2783 assert.strictEqual(messages[0].ruleId, "no-extra-semi");
2784 assert.strictEqual(messages[0].nodeType, "EmptyStatement");
2785 assert.strictEqual(messages[0].line, 3);
2786 });
2787
2788 it("should have a comment with the hashbang in it", () => {
2789 const code = "#!bin/program\n\nvar foo;;";
2790 const config = { rules: { checker: "error" } };
2791 const spy = sinon.spy(context => {
2792 const comments = context.getAllComments();
2793
2794 assert.strictEqual(comments.length, 1);
2795 assert.strictEqual(comments[0].type, "Shebang");
2796 return {};
2797 });
2798
2799 linter.defineRule("checker", spy);
2800 linter.verify(code, config);
2801 assert(spy.calledOnce);
2802 });
2803
2804 it("should comment hashbang without breaking offset", () => {
2805 const code = "#!/usr/bin/env node\n'123';";
2806 const config = { rules: { checker: "error" } };
2807 let spy;
2808
2809 linter.defineRule("checker", context => {
2810 spy = sinon.spy(node => {
2811 assert.strictEqual(context.getSource(node), "'123';");
2812 });
2813 return { ExpressionStatement: spy };
2814 });
2815
2816 linter.verify(code, config);
2817 assert(spy && spy.calledOnce);
2818 });
2819
2820 });
2821
2822 describe("when evaluating broken code", () => {
2823 const code = BROKEN_TEST_CODE;
2824
2825 it("should report a violation with a useful parse error prefix", () => {
2826 const messages = linter.verify(code);
2827
2828 assert.strictEqual(messages.length, 1);
2829 assert.strictEqual(messages[0].severity, 2);
2830 assert.isNull(messages[0].ruleId);
2831 assert.strictEqual(messages[0].line, 1);
2832 assert.strictEqual(messages[0].column, 4);
2833 assert.isTrue(messages[0].fatal);
2834 assert.match(messages[0].message, /^Parsing error:/u);
2835 });
2836
2837 it("should report source code where the issue is present", () => {
2838 const inValidCode = [
2839 "var x = 20;",
2840 "if (x ==4 {",
2841 " x++;",
2842 "}"
2843 ];
2844 const messages = linter.verify(inValidCode.join("\n"));
2845
2846 assert.strictEqual(messages.length, 1);
2847 assert.strictEqual(messages[0].severity, 2);
2848 assert.isTrue(messages[0].fatal);
2849 assert.match(messages[0].message, /^Parsing error:/u);
2850 });
2851 });
2852
2853 describe("when using an invalid (undefined) rule", () => {
2854 linter = new Linter();
2855
2856 const code = TEST_CODE;
2857 let results, result, warningResult, arrayOptionResults, objectOptionResults, resultsMultiple;
2858
2859 beforeEach(() => {
2860 results = linter.verify(code, { rules: { foobar: 2 } });
2861 result = results[0];
2862 warningResult = linter.verify(code, { rules: { foobar: 1 } })[0];
2863 arrayOptionResults = linter.verify(code, { rules: { foobar: [2, "always"] } });
2864 objectOptionResults = linter.verify(code, { rules: { foobar: [1, { bar: false }] } });
2865 resultsMultiple = linter.verify(code, { rules: { foobar: 2, barfoo: 1 } });
2866 });
2867
2868 it("should report a problem", () => {
2869 assert.isNotNull(result);
2870 assert.isArray(results);
2871 assert.isObject(result);
2872 assert.property(result, "ruleId");
2873 assert.strictEqual(result.ruleId, "foobar");
2874 });
2875
2876 it("should report that the rule does not exist", () => {
2877 assert.property(result, "message");
2878 assert.strictEqual(result.message, "Definition for rule 'foobar' was not found.");
2879 });
2880
2881 it("should report at the correct severity", () => {
2882 assert.property(result, "severity");
2883 assert.strictEqual(result.severity, 2);
2884 assert.strictEqual(warningResult.severity, 2); // this is 2, since the rulename is very likely to be wrong
2885 });
2886
2887 it("should accept any valid rule configuration", () => {
2888 assert.isObject(arrayOptionResults[0]);
2889 assert.isObject(objectOptionResults[0]);
2890 });
2891
2892 it("should report multiple missing rules", () => {
2893 assert.isArray(resultsMultiple);
2894
2895 assert.deepStrictEqual(
2896 resultsMultiple[1],
2897 {
2898 ruleId: "barfoo",
2899 message: "Definition for rule 'barfoo' was not found.",
2900 line: 1,
2901 column: 1,
2902 endLine: 1,
2903 endColumn: 2,
2904 severity: 2,
2905 nodeType: null
2906 }
2907 );
2908 });
2909 });
2910
2911 describe("when using a rule which has been replaced", () => {
2912 const code = TEST_CODE;
2913
2914 it("should report the new rule", () => {
2915 const results = linter.verify(code, { rules: { "no-comma-dangle": 2 } });
2916
2917 assert.strictEqual(results[0].ruleId, "no-comma-dangle");
2918 assert.strictEqual(results[0].message, "Rule 'no-comma-dangle' was removed and replaced by: comma-dangle");
2919 });
2920 });
2921
2922 describe("when calling getRules", () => {
2923 it("should return all loaded rules", () => {
2924 const rules = linter.getRules();
2925
2926 assert.isAbove(rules.size, 230);
2927 assert.isObject(rules.get("no-alert"));
2928 });
2929 });
2930
2931 describe("when calling version", () => {
2932 it("should return current version number", () => {
2933 const version = linter.version;
2934
2935 assert.isString(version);
2936 assert.isTrue(parseInt(version[0], 10) >= 3);
2937 });
2938 });
2939
2940 describe("when evaluating an empty string", () => {
2941 it("runs rules", () => {
2942 linter.defineRule("no-programs", context => ({
2943 Program(node) {
2944 context.report({ node, message: "No programs allowed." });
2945 }
2946 }));
2947
2948 assert.strictEqual(
2949 linter.verify("", { rules: { "no-programs": "error" } }).length,
2950 1
2951 );
2952 });
2953 });
2954
2955 describe("when evaluating code without comments to environment", () => {
2956 it("should report a violation when using typed array", () => {
2957 const code = "var array = new Uint8Array();";
2958
2959 const config = { rules: { "no-undef": 1 } };
2960
2961 const messages = linter.verify(code, config, filename);
2962
2963 assert.strictEqual(messages.length, 1);
2964 });
2965
2966 it("should report a violation when using Promise", () => {
2967 const code = "new Promise();";
2968
2969 const config = { rules: { "no-undef": 1 } };
2970
2971 const messages = linter.verify(code, config, filename);
2972
2973 assert.strictEqual(messages.length, 1);
2974 });
2975 });
2976
2977 describe("when evaluating code with comments to environment", () => {
2978 it("should not support legacy config", () => {
2979 const code = "/*jshint mocha:true */ describe();";
2980
2981 const config = { rules: { "no-undef": 1 } };
2982
2983 const messages = linter.verify(code, config, filename);
2984
2985 assert.strictEqual(messages.length, 1);
2986 assert.strictEqual(messages[0].ruleId, "no-undef");
2987 assert.strictEqual(messages[0].nodeType, "Identifier");
2988 assert.strictEqual(messages[0].line, 1);
2989 });
2990
2991 it("should not report a violation", () => {
2992 const code = "/*eslint-env es6 */ new Promise();";
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 // https://github.com/eslint/eslint/issues/14652
3002 it("should not report a violation", () => {
3003 const codes = [
3004 "/*eslint-env es6\n */ new Promise();",
3005 "/*eslint-env browser,\nes6 */ window;Promise;",
3006 "/*eslint-env\nbrowser,es6 */ window;Promise;"
3007 ];
3008 const config = { rules: { "no-undef": 1 } };
3009
3010 for (const code of codes) {
3011 const messages = linter.verify(code, config, filename);
3012
3013 assert.strictEqual(messages.length, 0);
3014 }
3015
3016 });
3017
3018 it("should not report a violation", () => {
3019 const code = `/*${ESLINT_ENV} mocha,node */ require();describe();`;
3020
3021 const config = { rules: { "no-undef": 1 } };
3022
3023 const messages = linter.verify(code, config, filename);
3024
3025 assert.strictEqual(messages.length, 0);
3026 });
3027
3028 it("should not report a violation", () => {
3029 const code = "/*eslint-env mocha */ suite();test();";
3030
3031 const config = { rules: { "no-undef": 1 } };
3032
3033 const messages = linter.verify(code, config, filename);
3034
3035 assert.strictEqual(messages.length, 0);
3036 });
3037
3038 it("should not report a violation", () => {
3039 const code = `/*${ESLINT_ENV} amd */ define();require();`;
3040
3041 const config = { rules: { "no-undef": 1 } };
3042
3043 const messages = linter.verify(code, config, filename);
3044
3045 assert.strictEqual(messages.length, 0);
3046 });
3047
3048 it("should not report a violation", () => {
3049 const code = `/*${ESLINT_ENV} jasmine */ expect();spyOn();`;
3050
3051 const config = { rules: { "no-undef": 1 } };
3052
3053 const messages = linter.verify(code, config, filename);
3054
3055 assert.strictEqual(messages.length, 0);
3056 });
3057
3058 it("should not report a violation", () => {
3059 const code = `/*globals require: true */ /*${ESLINT_ENV} node */ require = 1;`;
3060
3061 const config = { rules: { "no-undef": 1 } };
3062
3063 const messages = linter.verify(code, config, filename);
3064
3065 assert.strictEqual(messages.length, 0);
3066 });
3067
3068 it("should not report a violation", () => {
3069 const code = `/*${ESLINT_ENV} node */ process.exit();`;
3070
3071 const config = { rules: {} };
3072
3073 const messages = linter.verify(code, config, filename);
3074
3075 assert.strictEqual(messages.length, 0);
3076 });
3077
3078 it("should not report a violation", () => {
3079 const code = `/*eslint no-process-exit: 0 */ /*${ESLINT_ENV} node */ process.exit();`;
3080
3081 const config = { rules: { "no-undef": 1 } };
3082
3083 const messages = linter.verify(code, config, filename);
3084
3085 assert.strictEqual(messages.length, 0);
3086 });
3087 });
3088
3089 describe("when evaluating code with comments to change config when allowInlineConfig is enabled", () => {
3090 it("should report a violation for disabling rules", () => {
3091 const code = [
3092 "alert('test'); // eslint-disable-line no-alert"
3093 ].join("\n");
3094 const config = {
3095 rules: {
3096 "no-alert": 1
3097 }
3098 };
3099
3100 const messages = linter.verify(code, config, {
3101 filename,
3102 allowInlineConfig: false
3103 });
3104
3105 assert.strictEqual(messages.length, 1);
3106 assert.strictEqual(messages[0].ruleId, "no-alert");
3107 });
3108
3109 it("should report a violation for global variable declarations", () => {
3110 const code = [
3111 "/* global foo */"
3112 ].join("\n");
3113 const config = {
3114 rules: {
3115 test: 2
3116 }
3117 };
3118 let ok = false;
3119
3120 linter.defineRules({
3121 test(context) {
3122 return {
3123 Program() {
3124 const scope = context.getScope();
3125 const sourceCode = context.getSourceCode();
3126 const comments = sourceCode.getAllComments();
3127
3128 assert.strictEqual(1, comments.length);
3129
3130 const foo = getVariable(scope, "foo");
3131
3132 assert.notOk(foo);
3133
3134 ok = true;
3135 }
3136 };
3137 }
3138 });
3139
3140 linter.verify(code, config, { allowInlineConfig: false });
3141 assert(ok);
3142 });
3143
3144 it("should report a violation for eslint-disable", () => {
3145 const code = [
3146 "/* eslint-disable */",
3147 "alert('test');"
3148 ].join("\n");
3149 const config = {
3150 rules: {
3151 "no-alert": 1
3152 }
3153 };
3154
3155 const messages = linter.verify(code, config, {
3156 filename,
3157 allowInlineConfig: false
3158 });
3159
3160 assert.strictEqual(messages.length, 1);
3161 assert.strictEqual(messages[0].ruleId, "no-alert");
3162 });
3163
3164 it("should not report a violation for rule changes", () => {
3165 const code = [
3166 "/*eslint no-alert:2*/",
3167 "alert('test');"
3168 ].join("\n");
3169 const config = {
3170 rules: {
3171 "no-alert": 0
3172 }
3173 };
3174
3175 const messages = linter.verify(code, config, {
3176 filename,
3177 allowInlineConfig: false
3178 });
3179
3180 assert.strictEqual(messages.length, 0);
3181 });
3182
3183 it("should report a violation for disable-line", () => {
3184 const code = [
3185 "alert('test'); // eslint-disable-line"
3186 ].join("\n");
3187 const config = {
3188 rules: {
3189 "no-alert": 2
3190 }
3191 };
3192
3193 const messages = linter.verify(code, config, {
3194 filename,
3195 allowInlineConfig: false
3196 });
3197
3198 assert.strictEqual(messages.length, 1);
3199 assert.strictEqual(messages[0].ruleId, "no-alert");
3200 });
3201
3202 it("should report a violation for env changes", () => {
3203 const code = [
3204 `/*${ESLINT_ENV} browser*/ window`
3205 ].join("\n");
3206 const config = {
3207 rules: {
3208 "no-undef": 2
3209 }
3210 };
3211 const messages = linter.verify(code, config, { allowInlineConfig: false });
3212
3213 assert.strictEqual(messages.length, 1);
3214 assert.strictEqual(messages[0].ruleId, "no-undef");
3215 });
3216 });
3217
3218 describe("when evaluating code with 'noInlineComment'", () => {
3219 for (const directive of [
3220 "globals foo",
3221 "global foo",
3222 "exported foo",
3223 "eslint eqeqeq: error",
3224 "eslint-disable eqeqeq",
3225 "eslint-disable-line eqeqeq",
3226 "eslint-disable-next-line eqeqeq",
3227 "eslint-enable eqeqeq",
3228 "eslint-env es6"
3229 ]) {
3230 // eslint-disable-next-line no-loop-func -- No closures
3231 it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => {
3232 const messages = linter.verify(`/* ${directive} */`, { noInlineConfig: true });
3233
3234 assert.deepStrictEqual(messages.length, 1);
3235 assert.deepStrictEqual(messages[0].fatal, void 0);
3236 assert.deepStrictEqual(messages[0].ruleId, null);
3237 assert.deepStrictEqual(messages[0].severity, 1);
3238 assert.deepStrictEqual(messages[0].message, `'/*${directive.split(" ")[0]}*/' has no effect because you have 'noInlineConfig' setting in your config.`);
3239 });
3240 }
3241
3242 for (const directive of [
3243 "eslint-disable-line eqeqeq",
3244 "eslint-disable-next-line eqeqeq"
3245 ]) {
3246 // eslint-disable-next-line no-loop-func -- No closures
3247 it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => {
3248 const messages = linter.verify(`// ${directive}`, { noInlineConfig: true });
3249
3250 assert.deepStrictEqual(messages.length, 1);
3251 assert.deepStrictEqual(messages[0].fatal, void 0);
3252 assert.deepStrictEqual(messages[0].ruleId, null);
3253 assert.deepStrictEqual(messages[0].severity, 1);
3254 assert.deepStrictEqual(messages[0].message, `'//${directive.split(" ")[0]}' has no effect because you have 'noInlineConfig' setting in your config.`);
3255 });
3256 }
3257
3258 it("should not warn if 'noInlineConfig' and '--no-inline-config' were given.", () => {
3259 const messages = linter.verify("/* globals foo */", { noInlineConfig: true }, { allowInlineConfig: false });
3260
3261 assert.deepStrictEqual(messages.length, 0);
3262 });
3263 });
3264
3265 describe("when receiving cwd in options during instantiation", () => {
3266 const code = "a;\nb;";
3267 const config = { rules: { checker: "error" } };
3268
3269 it("should get cwd correctly in the context", () => {
3270 const cwd = "cwd";
3271 const linterWithOption = new Linter({ cwd });
3272 let spy;
3273
3274 linterWithOption.defineRule("checker", context => {
3275 spy = sinon.spy(() => {
3276 assert.strictEqual(context.getCwd(), cwd);
3277 });
3278 return { Program: spy };
3279 });
3280
3281 linterWithOption.verify(code, config);
3282 assert(spy && spy.calledOnce);
3283 });
3284
3285 it("should assign process.cwd() to it if cwd is undefined", () => {
3286 let spy;
3287 const linterWithOption = new Linter({ });
3288
3289 linterWithOption.defineRule("checker", context => {
3290
3291 spy = sinon.spy(() => {
3292 assert.strictEqual(context.getCwd(), process.cwd());
3293 });
3294 return { Program: spy };
3295 });
3296
3297 linterWithOption.verify(code, config);
3298 assert(spy && spy.calledOnce);
3299 });
3300
3301 it("should assign process.cwd() to it if the option is undefined", () => {
3302 let spy;
3303
3304 linter.defineRule("checker", context => {
3305
3306 spy = sinon.spy(() => {
3307 assert.strictEqual(context.getCwd(), process.cwd());
3308 });
3309 return { Program: spy };
3310 });
3311
3312 linter.verify(code, config);
3313 assert(spy && spy.calledOnce);
3314 });
3315 });
3316
3317 describe("reportUnusedDisable option", () => {
3318 it("reports problems for unused eslint-disable comments", () => {
3319 assert.deepStrictEqual(
3320 linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: true }),
3321 [
3322 {
3323 ruleId: null,
3324 message: "Unused eslint-disable directive (no problems were reported).",
3325 line: 1,
3326 column: 1,
3327 fix: {
3328 range: [0, 20],
3329 text: " "
3330 },
3331 severity: 2,
3332 nodeType: null
3333 }
3334 ]
3335 );
3336 });
3337
3338 it("reports problems for unused eslint-disable comments (error)", () => {
3339 assert.deepStrictEqual(
3340 linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "error" }),
3341 [
3342 {
3343 ruleId: null,
3344 message: "Unused eslint-disable directive (no problems were reported).",
3345 line: 1,
3346 column: 1,
3347 fix: {
3348 range: [0, 20],
3349 text: " "
3350 },
3351 severity: 2,
3352 nodeType: null
3353 }
3354 ]
3355 );
3356 });
3357
3358 it("reports problems for unused eslint-disable comments (warn)", () => {
3359 assert.deepStrictEqual(
3360 linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "warn" }),
3361 [
3362 {
3363 ruleId: null,
3364 message: "Unused eslint-disable directive (no problems were reported).",
3365 line: 1,
3366 column: 1,
3367 fix: {
3368 range: [0, 20],
3369 text: " "
3370 },
3371 severity: 1,
3372 nodeType: null
3373 }
3374 ]
3375 );
3376 });
3377
3378 it("reports problems for unused eslint-disable comments (in config)", () => {
3379 assert.deepStrictEqual(
3380 linter.verify("/* eslint-disable */", { reportUnusedDisableDirectives: true }),
3381 [
3382 {
3383 ruleId: null,
3384 message: "Unused eslint-disable directive (no problems were reported).",
3385 line: 1,
3386 column: 1,
3387 fix: {
3388 range: [0, 20],
3389 text: " "
3390 },
3391 severity: 1,
3392 nodeType: null
3393 }
3394 ]
3395 );
3396 });
3397
3398 it("reports problems for partially unused eslint-disable comments (in config)", () => {
3399 const code = "alert('test'); // eslint-disable-line no-alert, no-redeclare";
3400 const config = {
3401 reportUnusedDisableDirectives: true,
3402 rules: {
3403 "no-alert": 1,
3404 "no-redeclare": 1
3405 }
3406 };
3407
3408 const messages = linter.verify(code, config, {
3409 filename,
3410 allowInlineConfig: true
3411 });
3412
3413 assert.deepStrictEqual(
3414 messages,
3415 [
3416 {
3417 ruleId: null,
3418 message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').",
3419 line: 1,
3420 column: 16,
3421 fix: {
3422 range: [46, 60],
3423 text: ""
3424 },
3425 severity: 1,
3426 nodeType: null
3427 }
3428 ]
3429 );
3430 });
3431
3432 describe("autofix", () => {
3433 const alwaysReportsRule = {
3434 create(context) {
3435 return {
3436 Program(node) {
3437 context.report({ message: "bad code", loc: node.loc.end });
3438 }
3439 };
3440 }
3441 };
3442
3443 const neverReportsRule = {
3444 create() {
3445 return {};
3446 }
3447 };
3448
3449 const ruleCount = 3;
3450 const usedRules = Array.from(
3451 { length: ruleCount },
3452 (_, index) => `used${index ? `-${index}` : ""}` // "used", "used-1", "used-2"
3453 );
3454 const unusedRules = usedRules.map(name => `un${name}`); // "unused", "unused-1", "unused-2"
3455
3456 const config = {
3457 reportUnusedDisableDirectives: true,
3458 rules: {
3459 ...Object.fromEntries(usedRules.map(name => [name, "error"])),
3460 ...Object.fromEntries(unusedRules.map(name => [name, "error"]))
3461 }
3462 };
3463
3464 beforeEach(() => {
3465 linter.defineRules(Object.fromEntries(usedRules.map(name => [name, alwaysReportsRule])));
3466 linter.defineRules(Object.fromEntries(unusedRules.map(name => [name, neverReportsRule])));
3467 });
3468
3469 const tests = [
3470
3471 //-----------------------------------------------
3472 // Removing the entire comment
3473 //-----------------------------------------------
3474
3475 {
3476 code: "// eslint-disable-line unused",
3477 output: " "
3478 },
3479 {
3480 code: "foo// eslint-disable-line unused",
3481 output: "foo "
3482 },
3483 {
3484 code: "// eslint-disable-line ,unused,",
3485 output: " "
3486 },
3487 {
3488 code: "// eslint-disable-line unused-1, unused-2",
3489 output: " "
3490 },
3491 {
3492 code: "// eslint-disable-line ,unused-1,, unused-2,, -- comment",
3493 output: " "
3494 },
3495 {
3496 code: "// eslint-disable-next-line unused\n",
3497 output: " \n"
3498 },
3499 {
3500 code: "// eslint-disable-next-line unused\nfoo",
3501 output: " \nfoo"
3502 },
3503 {
3504 code: "/* eslint-disable \nunused\n*/",
3505 output: " "
3506 },
3507
3508 //-----------------------------------------------
3509 // Removing only individual rules
3510 //-----------------------------------------------
3511
3512 // content before the first rule should not be changed
3513 {
3514 code: "//eslint-disable-line unused, used",
3515 output: "//eslint-disable-line used"
3516 },
3517 {
3518 code: "// eslint-disable-line unused, used",
3519 output: "// eslint-disable-line used"
3520 },
3521 {
3522 code: "// eslint-disable-line unused, used",
3523 output: "// eslint-disable-line used"
3524 },
3525 {
3526 code: "/*\neslint-disable unused, used*/",
3527 output: "/*\neslint-disable used*/"
3528 },
3529 {
3530 code: "/*\n eslint-disable unused, used*/",
3531 output: "/*\n eslint-disable used*/"
3532 },
3533 {
3534 code: "/*\r\neslint-disable unused, used*/",
3535 output: "/*\r\neslint-disable used*/"
3536 },
3537 {
3538 code: "/*\u2028eslint-disable unused, used*/",
3539 output: "/*\u2028eslint-disable used*/"
3540 },
3541 {
3542 code: "/*\u00A0eslint-disable unused, used*/",
3543 output: "/*\u00A0eslint-disable used*/"
3544 },
3545 {
3546 code: "// eslint-disable-line unused, used",
3547 output: "// eslint-disable-line used"
3548 },
3549 {
3550 code: "/* eslint-disable\nunused, used*/",
3551 output: "/* eslint-disable\nused*/"
3552 },
3553 {
3554 code: "/* eslint-disable\n unused, used*/",
3555 output: "/* eslint-disable\n used*/"
3556 },
3557 {
3558 code: "/* eslint-disable\r\nunused, used*/",
3559 output: "/* eslint-disable\r\nused*/"
3560 },
3561 {
3562 code: "/* eslint-disable\u2028unused, used*/",
3563 output: "/* eslint-disable\u2028used*/"
3564 },
3565 {
3566 code: "/* eslint-disable\u00A0unused, used*/",
3567 output: "/* eslint-disable\u00A0used*/"
3568 },
3569
3570 // when removing the first rule, the comma and all whitespace up to the next rule (or next lone comma) should also be removed
3571 {
3572 code: "// eslint-disable-line unused,used",
3573 output: "// eslint-disable-line used"
3574 },
3575 {
3576 code: "// eslint-disable-line unused, used",
3577 output: "// eslint-disable-line used"
3578 },
3579 {
3580 code: "// eslint-disable-line unused , used",
3581 output: "// eslint-disable-line used"
3582 },
3583 {
3584 code: "// eslint-disable-line unused, used",
3585 output: "// eslint-disable-line used"
3586 },
3587 {
3588 code: "// eslint-disable-line unused ,used",
3589 output: "// eslint-disable-line used"
3590 },
3591 {
3592 code: "/* eslint-disable unused\n,\nused */",
3593 output: "/* eslint-disable used */"
3594 },
3595 {
3596 code: "/* eslint-disable unused \n \n,\n\n used */",
3597 output: "/* eslint-disable used */"
3598 },
3599 {
3600 code: "/* eslint-disable unused\u2028,\u2028used */",
3601 output: "/* eslint-disable used */"
3602 },
3603 {
3604 code: "// eslint-disable-line unused\u00A0,\u00A0used",
3605 output: "// eslint-disable-line used"
3606 },
3607 {
3608 code: "// eslint-disable-line unused,,used",
3609 output: "// eslint-disable-line ,used"
3610 },
3611 {
3612 code: "// eslint-disable-line unused, ,used",
3613 output: "// eslint-disable-line ,used"
3614 },
3615 {
3616 code: "// eslint-disable-line unused,, used",
3617 output: "// eslint-disable-line , used"
3618 },
3619 {
3620 code: "// eslint-disable-line unused,used ",
3621 output: "// eslint-disable-line used "
3622 },
3623 {
3624 code: "// eslint-disable-next-line unused,used\n",
3625 output: "// eslint-disable-next-line used\n"
3626 },
3627
3628 // when removing a rule in the middle, one comma and all whitespace between commas should also be removed
3629 {
3630 code: "// eslint-disable-line used-1,unused,used-2",
3631 output: "// eslint-disable-line used-1,used-2"
3632 },
3633 {
3634 code: "// eslint-disable-line used-1, unused,used-2",
3635 output: "// eslint-disable-line used-1,used-2"
3636 },
3637 {
3638 code: "// eslint-disable-line used-1,unused ,used-2",
3639 output: "// eslint-disable-line used-1,used-2"
3640 },
3641 {
3642 code: "// eslint-disable-line used-1, unused ,used-2",
3643 output: "// eslint-disable-line used-1,used-2"
3644 },
3645 {
3646 code: "/* eslint-disable used-1,\nunused\n,used-2 */",
3647 output: "/* eslint-disable used-1,used-2 */"
3648 },
3649 {
3650 code: "/* eslint-disable used-1,\n\n unused \n \n ,used-2 */",
3651 output: "/* eslint-disable used-1,used-2 */"
3652 },
3653 {
3654 code: "/* eslint-disable used-1,\u2028unused\u2028,used-2 */",
3655 output: "/* eslint-disable used-1,used-2 */"
3656 },
3657 {
3658 code: "// eslint-disable-line used-1,\u00A0unused\u00A0,used-2",
3659 output: "// eslint-disable-line used-1,used-2"
3660 },
3661
3662 // when removing a rule in the middle, content around commas should not be changed
3663 {
3664 code: "// eslint-disable-line used-1, unused ,used-2",
3665 output: "// eslint-disable-line used-1,used-2"
3666 },
3667 {
3668 code: "// eslint-disable-line used-1,unused, used-2",
3669 output: "// eslint-disable-line used-1, used-2"
3670 },
3671 {
3672 code: "// eslint-disable-line used-1 ,unused,used-2",
3673 output: "// eslint-disable-line used-1 ,used-2"
3674 },
3675 {
3676 code: "// eslint-disable-line used-1 ,unused, used-2",
3677 output: "// eslint-disable-line used-1 , used-2"
3678 },
3679 {
3680 code: "// eslint-disable-line used-1 , unused , used-2",
3681 output: "// eslint-disable-line used-1 , used-2"
3682 },
3683 {
3684 code: "/* eslint-disable used-1\n,unused,\nused-2 */",
3685 output: "/* eslint-disable used-1\n,\nused-2 */"
3686 },
3687 {
3688 code: "/* eslint-disable used-1\u2028,unused,\u2028used-2 */",
3689 output: "/* eslint-disable used-1\u2028,\u2028used-2 */"
3690 },
3691 {
3692 code: "// eslint-disable-line used-1\u00A0,unused,\u00A0used-2",
3693 output: "// eslint-disable-line used-1\u00A0,\u00A0used-2"
3694 },
3695 {
3696 code: "// eslint-disable-line , unused ,used",
3697 output: "// eslint-disable-line ,used"
3698 },
3699 {
3700 code: "/* eslint-disable\n, unused ,used */",
3701 output: "/* eslint-disable\n,used */"
3702 },
3703 {
3704 code: "/* eslint-disable used-1,\n,unused,used-2 */",
3705 output: "/* eslint-disable used-1,\n,used-2 */"
3706 },
3707 {
3708 code: "/* eslint-disable used-1,unused,\n,used-2 */",
3709 output: "/* eslint-disable used-1,\n,used-2 */"
3710 },
3711 {
3712 code: "/* eslint-disable used-1,\n,unused,\n,used-2 */",
3713 output: "/* eslint-disable used-1,\n,\n,used-2 */"
3714 },
3715 {
3716 code: "// eslint-disable-line used, unused,",
3717 output: "// eslint-disable-line used,"
3718 },
3719 {
3720 code: "// eslint-disable-next-line used, unused,\n",
3721 output: "// eslint-disable-next-line used,\n"
3722 },
3723 {
3724 code: "// eslint-disable-line used, unused, ",
3725 output: "// eslint-disable-line used, "
3726 },
3727 {
3728 code: "// eslint-disable-line used, unused, -- comment",
3729 output: "// eslint-disable-line used, -- comment"
3730 },
3731 {
3732 code: "/* eslint-disable used, unused,\n*/",
3733 output: "/* eslint-disable used,\n*/"
3734 },
3735
3736 // when removing the last rule, the comma and all whitespace up to the previous rule (or previous lone comma) should also be removed
3737 {
3738 code: "// eslint-disable-line used,unused",
3739 output: "// eslint-disable-line used"
3740 },
3741 {
3742 code: "// eslint-disable-line used, unused",
3743 output: "// eslint-disable-line used"
3744 },
3745 {
3746 code: "// eslint-disable-line used ,unused",
3747 output: "// eslint-disable-line used"
3748 },
3749 {
3750 code: "// eslint-disable-line used , unused",
3751 output: "// eslint-disable-line used"
3752 },
3753 {
3754 code: "// eslint-disable-line used, unused",
3755 output: "// eslint-disable-line used"
3756 },
3757 {
3758 code: "// eslint-disable-line used ,unused",
3759 output: "// eslint-disable-line used"
3760 },
3761 {
3762 code: "/* eslint-disable used\n,\nunused */",
3763 output: "/* eslint-disable used */"
3764 },
3765 {
3766 code: "/* eslint-disable used \n \n,\n\n unused */",
3767 output: "/* eslint-disable used */"
3768 },
3769 {
3770 code: "/* eslint-disable used\u2028,\u2028unused */",
3771 output: "/* eslint-disable used */"
3772 },
3773 {
3774 code: "// eslint-disable-line used\u00A0,\u00A0unused",
3775 output: "// eslint-disable-line used"
3776 },
3777 {
3778 code: "// eslint-disable-line used,,unused",
3779 output: "// eslint-disable-line used,"
3780 },
3781 {
3782 code: "// eslint-disable-line used, ,unused",
3783 output: "// eslint-disable-line used,"
3784 },
3785 {
3786 code: "/* eslint-disable used,\n,unused */",
3787 output: "/* eslint-disable used, */"
3788 },
3789 {
3790 code: "/* eslint-disable used\n, ,unused */",
3791 output: "/* eslint-disable used\n, */"
3792 },
3793
3794 // content after the last rule should not be changed
3795 {
3796 code: "// eslint-disable-line used,unused",
3797 output: "// eslint-disable-line used"
3798 },
3799 {
3800 code: "// eslint-disable-line used,unused ",
3801 output: "// eslint-disable-line used "
3802 },
3803 {
3804 code: "// eslint-disable-line used,unused ",
3805 output: "// eslint-disable-line used "
3806 },
3807 {
3808 code: "// eslint-disable-line used,unused -- comment",
3809 output: "// eslint-disable-line used -- comment"
3810 },
3811 {
3812 code: "// eslint-disable-next-line used,unused\n",
3813 output: "// eslint-disable-next-line used\n"
3814 },
3815 {
3816 code: "// eslint-disable-next-line used,unused \n",
3817 output: "// eslint-disable-next-line used \n"
3818 },
3819 {
3820 code: "/* eslint-disable used,unused\u2028*/",
3821 output: "/* eslint-disable used\u2028*/"
3822 },
3823 {
3824 code: "// eslint-disable-line used,unused\u00A0",
3825 output: "// eslint-disable-line used\u00A0"
3826 },
3827
3828 // multiply rules to remove
3829 {
3830 code: "// eslint-disable-line used, unused-1, unused-2",
3831 output: "// eslint-disable-line used"
3832 },
3833 {
3834 code: "// eslint-disable-line unused-1, used, unused-2",
3835 output: "// eslint-disable-line used"
3836 },
3837 {
3838 code: "// eslint-disable-line unused-1, unused-2, used",
3839 output: "// eslint-disable-line used"
3840 },
3841 {
3842 code: "// eslint-disable-line used-1, unused-1, used-2, unused-2",
3843 output: "// eslint-disable-line used-1, used-2"
3844 },
3845 {
3846 code: "// eslint-disable-line unused-1, used-1, unused-2, used-2",
3847 output: "// eslint-disable-line used-1, used-2"
3848 },
3849 {
3850 code: `
3851 /* eslint-disable unused-1,
3852 used-1,
3853 unused-2,
3854 used-2
3855 */
3856 `,
3857 output: `
3858 /* eslint-disable used-1,
3859 used-2
3860 */
3861 `
3862 },
3863 {
3864 code: `
3865 /* eslint-disable
3866 unused-1,
3867 used-1,
3868 unused-2,
3869 used-2
3870 */
3871 `,
3872 output: `
3873 /* eslint-disable
3874 used-1,
3875 used-2
3876 */
3877 `
3878 },
3879 {
3880 code: `
3881 /* eslint-disable
3882 used-1,
3883 unused-1,
3884 used-2,
3885 unused-2
3886 */
3887 `,
3888 output: `
3889 /* eslint-disable
3890 used-1,
3891 used-2
3892 */
3893 `
3894 },
3895 {
3896 code: `
3897 /* eslint-disable
3898 used-1,
3899 unused-1,
3900 used-2,
3901 unused-2,
3902 */
3903 `,
3904 output: `
3905 /* eslint-disable
3906 used-1,
3907 used-2,
3908 */
3909 `
3910 },
3911 {
3912 code: `
3913 /* eslint-disable
3914 ,unused-1
3915 ,used-1
3916 ,unused-2
3917 ,used-2
3918 */
3919 `,
3920 output: `
3921 /* eslint-disable
3922 ,used-1
3923 ,used-2
3924 */
3925 `
3926 },
3927 {
3928 code: `
3929 /* eslint-disable
3930 ,used-1
3931 ,unused-1
3932 ,used-2
3933 ,unused-2
3934 */
3935 `,
3936 output: `
3937 /* eslint-disable
3938 ,used-1
3939 ,used-2
3940 */
3941 `
3942 },
3943 {
3944 code: `
3945 /* eslint-disable
3946 used-1,
3947 unused-1,
3948 used-2,
3949 unused-2
3950
3951 -- comment
3952 */
3953 `,
3954 output: `
3955 /* eslint-disable
3956 used-1,
3957 used-2
3958
3959 -- comment
3960 */
3961 `
3962 },
3963
3964 // duplicates in the list
3965 {
3966 code: "// eslint-disable-line unused, unused, used",
3967 output: "// eslint-disable-line used"
3968 },
3969 {
3970 code: "// eslint-disable-line unused, used, unused",
3971 output: "// eslint-disable-line used"
3972 },
3973 {
3974 code: "// eslint-disable-line used, unused, unused, used",
3975 output: "// eslint-disable-line used, used"
3976 }
3977 ];
3978
3979 for (const { code, output } of tests) {
3980 // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach()
3981 it(code, () => {
3982 assert.strictEqual(
3983 linter.verifyAndFix(code, config).output,
3984 output
3985 );
3986 });
3987 }
3988 });
3989 });
3990
3991 describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => {
3992 it("should not report a violation", () => {
3993 const code = [
3994 "alert('test'); // eslint-disable-line no-alert"
3995 ].join("\n");
3996 const config = {
3997 rules: {
3998 "no-alert": 1
3999 }
4000 };
4001
4002 const messages = linter.verify(code, config, {
4003 filename,
4004 allowInlineConfig: true
4005 });
4006
4007 assert.strictEqual(messages.length, 0);
4008 });
4009 });
4010
4011 describe("when evaluating code with hashbang", () => {
4012 it("should comment hashbang without breaking offset", () => {
4013 const code = "#!/usr/bin/env node\n'123';";
4014 const config = { rules: { checker: "error" } };
4015 let spy;
4016
4017 linter.defineRule("checker", context => {
4018 spy = sinon.spy(node => {
4019 assert.strictEqual(context.getSource(node), "'123';");
4020 });
4021 return { ExpressionStatement: spy };
4022 });
4023
4024 linter.verify(code, config);
4025 assert(spy && spy.calledOnce);
4026 });
4027 });
4028
4029 describe("verify()", () => {
4030 describe("filenames", () => {
4031 it("should allow filename to be passed on options object", () => {
4032 const filenameChecker = sinon.spy(context => {
4033 assert.strictEqual(context.getFilename(), "foo.js");
4034 return {};
4035 });
4036
4037 linter.defineRule("checker", filenameChecker);
4038 linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" });
4039 assert(filenameChecker.calledOnce);
4040 });
4041
4042 it("should allow filename to be passed as third argument", () => {
4043 const filenameChecker = sinon.spy(context => {
4044 assert.strictEqual(context.getFilename(), "bar.js");
4045 return {};
4046 });
4047
4048 linter.defineRule("checker", filenameChecker);
4049 linter.verify("foo;", { rules: { checker: "error" } }, "bar.js");
4050 assert(filenameChecker.calledOnce);
4051 });
4052
4053 it("should default filename to <input> when options object doesn't have filename", () => {
4054 const filenameChecker = sinon.spy(context => {
4055 assert.strictEqual(context.getFilename(), "<input>");
4056 return {};
4057 });
4058
4059 linter.defineRule("checker", filenameChecker);
4060 linter.verify("foo;", { rules: { checker: "error" } }, {});
4061 assert(filenameChecker.calledOnce);
4062 });
4063
4064 it("should default filename to <input> when only two arguments are passed", () => {
4065 const filenameChecker = sinon.spy(context => {
4066 assert.strictEqual(context.getFilename(), "<input>");
4067 return {};
4068 });
4069
4070 linter.defineRule("checker", filenameChecker);
4071 linter.verify("foo;", { rules: { checker: "error" } });
4072 assert(filenameChecker.calledOnce);
4073 });
4074 });
4075
4076 describe("physicalFilenames", () => {
4077 it("should be same as `filename` passed on options object, if no processors are used", () => {
4078 const physicalFilenameChecker = sinon.spy(context => {
4079 assert.strictEqual(context.getPhysicalFilename(), "foo.js");
4080 return {};
4081 });
4082
4083 linter.defineRule("checker", physicalFilenameChecker);
4084 linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" });
4085 assert(physicalFilenameChecker.calledOnce);
4086 });
4087
4088 it("should default physicalFilename to <input> when options object doesn't have filename", () => {
4089 const physicalFilenameChecker = sinon.spy(context => {
4090 assert.strictEqual(context.getPhysicalFilename(), "<input>");
4091 return {};
4092 });
4093
4094 linter.defineRule("checker", physicalFilenameChecker);
4095 linter.verify("foo;", { rules: { checker: "error" } }, {});
4096 assert(physicalFilenameChecker.calledOnce);
4097 });
4098
4099 it("should default physicalFilename to <input> when only two arguments are passed", () => {
4100 const physicalFilenameChecker = sinon.spy(context => {
4101 assert.strictEqual(context.getPhysicalFilename(), "<input>");
4102 return {};
4103 });
4104
4105 linter.defineRule("checker", physicalFilenameChecker);
4106 linter.verify("foo;", { rules: { checker: "error" } });
4107 assert(physicalFilenameChecker.calledOnce);
4108 });
4109 });
4110
4111 it("should report warnings in order by line and column when called", () => {
4112
4113 const code = "foo()\n alert('test')";
4114 const config = { rules: { "no-mixed-spaces-and-tabs": 1, "eol-last": 1, semi: [1, "always"] } };
4115
4116 const messages = linter.verify(code, config, filename);
4117
4118 assert.strictEqual(messages.length, 3);
4119 assert.strictEqual(messages[0].line, 1);
4120 assert.strictEqual(messages[0].column, 6);
4121 assert.strictEqual(messages[1].line, 2);
4122 assert.strictEqual(messages[1].column, 18);
4123 assert.strictEqual(messages[2].line, 2);
4124 assert.strictEqual(messages[2].column, 18);
4125 });
4126
4127 describe("ecmaVersion", () => {
4128
4129 it("should not support ES6 when no ecmaVersion provided", () => {
4130 const messages = linter.verify("let x = 0;");
4131
4132 assert.strictEqual(messages.length, 1);
4133 });
4134
4135 it("supports ECMAScript version 'latest'", () => {
4136 const messages = linter.verify("let x = 5 ** 7;", {
4137 parserOptions: { ecmaVersion: "latest" }
4138 });
4139
4140 assert.strictEqual(messages.length, 0);
4141 });
4142
4143 it("the 'latest' is equal to espree.latestEcmaVersion", () => {
4144 let ecmaVersion = null;
4145 const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } };
4146
4147 linter.defineRule("ecma-version", context => ({
4148 Program() {
4149 ecmaVersion = context.parserOptions.ecmaVersion;
4150 }
4151 }));
4152 linter.verify("", config);
4153 assert.strictEqual(ecmaVersion, espree.latestEcmaVersion, "ecmaVersion should be 13");
4154 });
4155
4156 it("the 'latest' is not normalized for custom parsers", () => {
4157 let ecmaVersion = null;
4158 const config = { rules: { "ecma-version": 2 }, parser: "custom-parser", parserOptions: { ecmaVersion: "latest" } };
4159
4160 linter.defineParser("custom-parser", testParsers.enhancedParser);
4161 linter.defineRule("ecma-version", context => ({
4162 Program() {
4163 ecmaVersion = context.parserOptions.ecmaVersion;
4164 }
4165 }));
4166 linter.verify("", config);
4167 assert.strictEqual(ecmaVersion, "latest", "ecmaVersion should be latest");
4168 });
4169
4170 it("the 'latest' is equal to espree.latestEcmaVersion on languageOptions", () => {
4171 let ecmaVersion = null;
4172 const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } };
4173
4174 linter.defineRule("ecma-version", context => ({
4175 Program() {
4176 ecmaVersion = context.languageOptions.ecmaVersion;
4177 }
4178 }));
4179 linter.verify("", config);
4180 assert.strictEqual(ecmaVersion, espree.latestEcmaVersion + 2009, "ecmaVersion should be 2022");
4181 });
4182
4183 it("the 'next' is equal to espree.latestEcmaVersion on languageOptions with custom parser", () => {
4184 let ecmaVersion = null;
4185 const config = { rules: { "ecma-version": 2 }, parser: "custom-parser", parserOptions: { ecmaVersion: "next" } };
4186
4187 linter.defineParser("custom-parser", testParsers.stubParser);
4188 linter.defineRule("ecma-version", context => ({
4189 Program() {
4190 ecmaVersion = context.languageOptions.ecmaVersion;
4191 }
4192 }));
4193 linter.verify("", config);
4194 assert.strictEqual(ecmaVersion, espree.latestEcmaVersion + 2009, "ecmaVersion should be 2022");
4195 });
4196
4197 it("missing ecmaVersion is equal to 5 on languageOptions with custom parser", () => {
4198 let ecmaVersion = null;
4199 const config = { rules: { "ecma-version": 2 }, parser: "custom-parser" };
4200
4201 linter.defineParser("custom-parser", testParsers.enhancedParser);
4202 linter.defineRule("ecma-version", context => ({
4203 Program() {
4204 ecmaVersion = context.languageOptions.ecmaVersion;
4205 }
4206 }));
4207 linter.verify("", config);
4208 assert.strictEqual(ecmaVersion, 5, "ecmaVersion should be 5");
4209 });
4210
4211 it("should pass normalized ecmaVersion to eslint-scope", () => {
4212 let blockScope = null;
4213
4214 linter.defineRule("block-scope", context => ({
4215 BlockStatement() {
4216 blockScope = context.getScope();
4217 }
4218 }));
4219 linter.defineParser("custom-parser", {
4220 parse: (...args) => espree.parse(...args)
4221 });
4222
4223 // Use standard parser
4224 linter.verify("{}", {
4225 rules: { "block-scope": 2 },
4226 parserOptions: { ecmaVersion: "latest" }
4227 });
4228
4229 assert.strictEqual(blockScope.type, "block");
4230
4231 linter.verify("{}", {
4232 rules: { "block-scope": 2 },
4233 parserOptions: {} // ecmaVersion defaults to 5
4234 });
4235 assert.strictEqual(blockScope.type, "global");
4236
4237 // Use custom parser
4238 linter.verify("{}", {
4239 rules: { "block-scope": 2 },
4240 parser: "custom-parser",
4241 parserOptions: { ecmaVersion: "latest" }
4242 });
4243
4244 assert.strictEqual(blockScope.type, "block");
4245
4246 linter.verify("{}", {
4247 rules: { "block-scope": 2 },
4248 parser: "custom-parser",
4249 parserOptions: {} // ecmaVersion defaults to 5
4250 });
4251 assert.strictEqual(blockScope.type, "global");
4252 });
4253
4254 describe("it should properly parse let declaration when", () => {
4255 it("the ECMAScript version number is 6", () => {
4256 const messages = linter.verify("let x = 5;", {
4257 parserOptions: {
4258 ecmaVersion: 6
4259 }
4260 });
4261
4262 assert.strictEqual(messages.length, 0);
4263 });
4264
4265 it("the ECMAScript version number is 2015", () => {
4266 const messages = linter.verify("let x = 5;", {
4267 parserOptions: {
4268 ecmaVersion: 2015
4269 }
4270 });
4271
4272 assert.strictEqual(messages.length, 0);
4273 });
4274 });
4275
4276 it("should fail to parse exponentiation operator when the ECMAScript version number is 2015", () => {
4277 const messages = linter.verify("x ** y;", {
4278 parserOptions: {
4279 ecmaVersion: 2015
4280 }
4281 });
4282
4283 assert.strictEqual(messages.length, 1);
4284 });
4285
4286 describe("should properly parse exponentiation operator when", () => {
4287 it("the ECMAScript version number is 7", () => {
4288 const messages = linter.verify("x ** y;", {
4289 parserOptions: {
4290 ecmaVersion: 7
4291 }
4292 });
4293
4294 assert.strictEqual(messages.length, 0);
4295 });
4296
4297 it("the ECMAScript version number is 2016", () => {
4298 const messages = linter.verify("x ** y;", {
4299 parserOptions: {
4300 ecmaVersion: 2016
4301 }
4302 });
4303
4304 assert.strictEqual(messages.length, 0);
4305 });
4306 });
4307 });
4308
4309 it("should properly parse object spread when ecmaVersion is 2018", () => {
4310
4311 const messages = linter.verify("var x = { ...y };", {
4312 parserOptions: {
4313 ecmaVersion: 2018
4314 }
4315 }, filename);
4316
4317 assert.strictEqual(messages.length, 0);
4318 });
4319
4320 it("should properly parse global return when passed ecmaFeatures", () => {
4321
4322 const messages = linter.verify("return;", {
4323 parserOptions: {
4324 ecmaFeatures: {
4325 globalReturn: true
4326 }
4327 }
4328 }, filename);
4329
4330 assert.strictEqual(messages.length, 0);
4331 });
4332
4333 it("should properly parse global return when in Node.js environment", () => {
4334
4335 const messages = linter.verify("return;", {
4336 env: {
4337 node: true
4338 }
4339 }, filename);
4340
4341 assert.strictEqual(messages.length, 0);
4342 });
4343
4344 it("should not parse global return when in Node.js environment with globalReturn explicitly off", () => {
4345
4346 const messages = linter.verify("return;", {
4347 env: {
4348 node: true
4349 },
4350 parserOptions: {
4351 ecmaFeatures: {
4352 globalReturn: false
4353 }
4354 }
4355 }, filename);
4356
4357 assert.strictEqual(messages.length, 1);
4358 assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function");
4359 });
4360
4361 it("should not parse global return when Node.js environment is false", () => {
4362
4363 const messages = linter.verify("return;", {}, filename);
4364
4365 assert.strictEqual(messages.length, 1);
4366 assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function");
4367 });
4368
4369 it("should properly parse sloppy-mode code when impliedStrict is false", () => {
4370
4371 const messages = linter.verify("var private;", {}, filename);
4372
4373 assert.strictEqual(messages.length, 0);
4374 });
4375
4376 it("should not parse sloppy-mode code when impliedStrict is true", () => {
4377
4378 const messages = linter.verify("var private;", {
4379 parserOptions: {
4380 ecmaFeatures: {
4381 impliedStrict: true
4382 }
4383 }
4384 }, filename);
4385
4386 assert.strictEqual(messages.length, 1);
4387 assert.strictEqual(messages[0].message, "Parsing error: The keyword 'private' is reserved");
4388 });
4389
4390 it("should properly parse valid code when impliedStrict is true", () => {
4391
4392 const messages = linter.verify("var foo;", {
4393 parserOptions: {
4394 ecmaFeatures: {
4395 impliedStrict: true
4396 }
4397 }
4398 }, filename);
4399
4400 assert.strictEqual(messages.length, 0);
4401 });
4402
4403 it("should properly parse JSX when passed ecmaFeatures", () => {
4404
4405 const messages = linter.verify("var x = <div/>;", {
4406 parserOptions: {
4407 ecmaFeatures: {
4408 jsx: true
4409 }
4410 }
4411 }, filename);
4412
4413 assert.strictEqual(messages.length, 0);
4414 });
4415
4416 it("should report an error when JSX code is encountered and JSX is not enabled", () => {
4417 const code = "var myDivElement = <div className=\"foo\" />;";
4418 const messages = linter.verify(code, {}, "filename");
4419
4420 assert.strictEqual(messages.length, 1);
4421 assert.strictEqual(messages[0].line, 1);
4422 assert.strictEqual(messages[0].column, 20);
4423 assert.strictEqual(messages[0].message, "Parsing error: Unexpected token <");
4424 });
4425
4426 it("should not report an error when JSX code is encountered and JSX is enabled", () => {
4427 const code = "var myDivElement = <div className=\"foo\" />;";
4428 const messages = linter.verify(code, { parserOptions: { ecmaFeatures: { jsx: true } } }, "filename");
4429
4430 assert.strictEqual(messages.length, 0);
4431 });
4432
4433 it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => {
4434 const code = "var myDivElement = <div {...this.props} />;";
4435 const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, "filename");
4436
4437 assert.strictEqual(messages.length, 0);
4438 });
4439
4440 it("should not allow the use of reserved words as variable names in ES3", () => {
4441 const code = "var char;";
4442 const messages = linter.verify(code, { parserOptions: { ecmaVersion: 3 } }, filename);
4443
4444 assert.strictEqual(messages.length, 1);
4445 assert.strictEqual(messages[0].severity, 2);
4446 assert.isTrue(messages[0].fatal);
4447 assert.match(messages[0].message, /^Parsing error:.*'char'/u);
4448 });
4449
4450 it("should not allow the use of reserved words as property names in member expressions in ES3", () => {
4451 const code = "obj.char;";
4452 const messages = linter.verify(code, { parserOptions: { ecmaVersion: 3 } }, filename);
4453
4454 assert.strictEqual(messages.length, 1);
4455 assert.strictEqual(messages[0].severity, 2);
4456 assert.isTrue(messages[0].fatal);
4457 assert.match(messages[0].message, /^Parsing error:.*'char'/u);
4458 });
4459
4460 it("should not allow the use of reserved words as property names in object literals in ES3", () => {
4461 const code = "var obj = { char: 1 };";
4462 const messages = linter.verify(code, { parserOptions: { ecmaVersion: 3 } }, filename);
4463
4464 assert.strictEqual(messages.length, 1);
4465 assert.strictEqual(messages[0].severity, 2);
4466 assert.isTrue(messages[0].fatal);
4467 assert.match(messages[0].message, /^Parsing error:.*'char'/u);
4468 });
4469
4470 it("should allow the use of reserved words as variable and property names in ES3 when allowReserved is true", () => {
4471 const code = "var char; obj.char; var obj = { char: 1 };";
4472 const messages = linter.verify(code, { parserOptions: { ecmaVersion: 3, allowReserved: true } }, filename);
4473
4474 assert.strictEqual(messages.length, 0);
4475 });
4476
4477 it("should not allow the use of reserved words as variable names in ES > 3", () => {
4478 const ecmaVersions = [void 0, ...espree.supportedEcmaVersions.filter(ecmaVersion => ecmaVersion > 3)];
4479
4480 ecmaVersions.forEach(ecmaVersion => {
4481 const code = "var enum;";
4482 const messages = linter.verify(code, { parserOptions: { ecmaVersion } }, filename);
4483
4484 assert.strictEqual(messages.length, 1);
4485 assert.strictEqual(messages[0].severity, 2);
4486 assert.isTrue(messages[0].fatal);
4487 assert.match(messages[0].message, /^Parsing error:.*'enum'/u);
4488 });
4489 });
4490
4491 it("should allow the use of reserved words as property names in ES > 3", () => {
4492 const ecmaVersions = [void 0, ...espree.supportedEcmaVersions.filter(ecmaVersion => ecmaVersion > 3)];
4493
4494 ecmaVersions.forEach(ecmaVersion => {
4495 const code = "obj.enum; obj.function; var obj = { enum: 1, function: 2 };";
4496 const messages = linter.verify(code, { parserOptions: { ecmaVersion } }, filename);
4497
4498 assert.strictEqual(messages.length, 0);
4499 });
4500 });
4501
4502 it("should not allow `allowReserved: true` in ES > 3", () => {
4503 const ecmaVersions = [void 0, ...espree.supportedEcmaVersions.filter(ecmaVersion => ecmaVersion > 3)];
4504
4505 ecmaVersions.forEach(ecmaVersion => {
4506 const code = "";
4507 const messages = linter.verify(code, { parserOptions: { ecmaVersion, allowReserved: true } }, filename);
4508
4509 assert.strictEqual(messages.length, 1);
4510 assert.strictEqual(messages[0].severity, 2);
4511 assert.isTrue(messages[0].fatal);
4512 assert.match(messages[0].message, /^Parsing error:.*allowReserved/u);
4513 });
4514 });
4515
4516 it("should be able to use es6 features if there is a comment which has \"eslint-env es6\"", () => {
4517 const code = [
4518 "/* eslint-env es6 */",
4519 "var arrow = () => 0;",
4520 "var binary = 0b1010;",
4521 "{ let a = 0; const b = 1; }",
4522 "class A {}",
4523 "function defaultParams(a = 0) {}",
4524 "var {a = 1, b = 2} = {};",
4525 "for (var a of []) {}",
4526 "function* generator() { yield 0; }",
4527 "var computed = {[a]: 0};",
4528 "var duplicate = {dup: 0, dup: 1};",
4529 "var method = {foo() {}};",
4530 "var property = {a, b};",
4531 "var octal = 0o755;",
4532 "var u = /^.$/u.test('ð ®·');",
4533 "var y = /hello/y.test('hello');",
4534 "function restParam(a, ...rest) {}",
4535 "class B { superInFunc() { super.foo(); } }",
4536 "var template = `hello, ${a}`;",
4537 "var unicode = '\\u{20BB7}';"
4538 ].join("\n");
4539
4540 const messages = linter.verify(code, null, "eslint-env es6");
4541
4542 assert.strictEqual(messages.length, 0);
4543 });
4544
4545 it("should be able to return in global if there is a comment which enables the node environment with a comment", () => {
4546 const messages = linter.verify(`/* ${ESLINT_ENV} node */ return;`, null, "node environment");
4547
4548 assert.strictEqual(messages.length, 0);
4549 });
4550
4551 it("should attach a \"/*global\" comment node to declared variables", () => {
4552 const code = "/* global foo */\n/* global bar, baz */";
4553 let ok = false;
4554
4555 linter.defineRules({
4556 test(context) {
4557 return {
4558 Program() {
4559 const scope = context.getScope();
4560 const sourceCode = context.getSourceCode();
4561 const comments = sourceCode.getAllComments();
4562
4563 assert.strictEqual(2, comments.length);
4564
4565 const foo = getVariable(scope, "foo");
4566
4567 assert.strictEqual(foo.eslintExplicitGlobal, true);
4568 assert.strictEqual(foo.eslintExplicitGlobalComments[0], comments[0]);
4569
4570 const bar = getVariable(scope, "bar");
4571
4572 assert.strictEqual(bar.eslintExplicitGlobal, true);
4573 assert.strictEqual(bar.eslintExplicitGlobalComments[0], comments[1]);
4574
4575 const baz = getVariable(scope, "baz");
4576
4577 assert.strictEqual(baz.eslintExplicitGlobal, true);
4578 assert.strictEqual(baz.eslintExplicitGlobalComments[0], comments[1]);
4579
4580 ok = true;
4581 }
4582 };
4583 }
4584 });
4585
4586 linter.verify(code, { rules: { test: 2 } });
4587 assert(ok);
4588 });
4589
4590 it("should report a linting error when a global is set to an invalid value", () => {
4591 const results = linter.verify("/* global foo: AAAAA, bar: readonly */\nfoo;\nbar;", { rules: { "no-undef": "error" } });
4592
4593 assert.deepStrictEqual(results, [
4594 {
4595 ruleId: null,
4596 severity: 2,
4597 message: "'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')",
4598 line: 1,
4599 column: 1,
4600 endLine: 1,
4601 endColumn: 39,
4602 nodeType: null
4603 },
4604 {
4605 ruleId: "no-undef",
4606 messageId: "undef",
4607 severity: 2,
4608 message: "'foo' is not defined.",
4609 line: 2,
4610 column: 1,
4611 endLine: 2,
4612 endColumn: 4,
4613 nodeType: "Identifier"
4614 }
4615 ]);
4616 });
4617
4618 it("should not crash when we reuse the SourceCode object", () => {
4619 linter.verify("function render() { return <div className='test'>{hello}</div> }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
4620 linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
4621 });
4622
4623 it("should reuse the SourceCode object", () => {
4624 let ast1 = null,
4625 ast2 = null;
4626
4627 linter.defineRule("save-ast1", () => ({
4628 Program(node) {
4629 ast1 = node;
4630 }
4631 }));
4632 linter.defineRule("save-ast2", () => ({
4633 Program(node) {
4634 ast2 = node;
4635 }
4636 }));
4637
4638 linter.verify("function render() { return <div className='test'>{hello}</div> }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast1": 2 } });
4639 linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast2": 2 } });
4640
4641 assert(ast1 !== null);
4642 assert(ast2 !== null);
4643 assert(ast1 === ast2);
4644 });
4645
4646 it("should allow 'await' as a property name in modules", () => {
4647 const result = linter.verify(
4648 "obj.await",
4649 { parserOptions: { ecmaVersion: 6, sourceType: "module" } }
4650 );
4651
4652 assert(result.length === 0);
4653 });
4654
4655
4656 it("should not modify config object passed as argument", () => {
4657 const config = {};
4658
4659 Object.freeze(config);
4660 linter.verify("var", config);
4661 });
4662
4663 it("should pass 'id' to rule contexts with the rule id", () => {
4664 const spy = sinon.spy(context => {
4665 assert.strictEqual(context.id, "foo-bar-baz");
4666 return {};
4667 });
4668
4669 linter.defineRule("foo-bar-baz", spy);
4670 linter.verify("x", { rules: { "foo-bar-baz": "error" } });
4671 assert(spy.calledOnce);
4672 });
4673
4674 describe("descriptions in directive comments", () => {
4675 it("should ignore the part preceded by '--' in '/*eslint*/'.", () => {
4676 const aaa = sinon.stub().returns({});
4677 const bbb = sinon.stub().returns({});
4678
4679 linter.defineRule("aaa", { create: aaa });
4680 linter.defineRule("bbb", { create: bbb });
4681 const messages = linter.verify(`
4682 /*eslint aaa:error -- bbb:error */
4683 console.log("hello")
4684 `, {});
4685
4686 // Don't include syntax error of the comment.
4687 assert.deepStrictEqual(messages, []);
4688
4689 // Use only `aaa`.
4690 assert.strictEqual(aaa.callCount, 1);
4691 assert.strictEqual(bbb.callCount, 0);
4692 });
4693
4694 it("should ignore the part preceded by '--' in '/*eslint-env*/'.", () => {
4695 const messages = linter.verify(`
4696 /*eslint-env es2015 -- es2017 */
4697 var Promise = {}
4698 var Atomics = {}
4699 `, { rules: { "no-redeclare": "error" } });
4700
4701 // Don't include `Atomics`
4702 assert.deepStrictEqual(
4703 messages,
4704 [{
4705 column: 25,
4706 endColumn: 32,
4707 endLine: 3,
4708 line: 3,
4709 message: "'Promise' is already defined as a built-in global variable.",
4710 messageId: "redeclaredAsBuiltin",
4711 nodeType: "Identifier",
4712 ruleId: "no-redeclare",
4713 severity: 2
4714 }]
4715 );
4716 });
4717
4718 it("should ignore the part preceded by '--' in '/*global*/'.", () => {
4719 const messages = linter.verify(`
4720 /*global aaa -- bbb */
4721 var aaa = {}
4722 var bbb = {}
4723 `, { rules: { "no-redeclare": "error" } });
4724
4725 // Don't include `bbb`
4726 assert.deepStrictEqual(
4727 messages,
4728 [{
4729 column: 30,
4730 endColumn: 33,
4731 line: 2,
4732 endLine: 2,
4733 message: "'aaa' is already defined by a variable declaration.",
4734 messageId: "redeclaredBySyntax",
4735 nodeType: "Block",
4736 ruleId: "no-redeclare",
4737 severity: 2
4738 }]
4739 );
4740 });
4741
4742 it("should ignore the part preceded by '--' in '/*globals*/'.", () => {
4743 const messages = linter.verify(`
4744 /*globals aaa -- bbb */
4745 var aaa = {}
4746 var bbb = {}
4747 `, { rules: { "no-redeclare": "error" } });
4748
4749 // Don't include `bbb`
4750 assert.deepStrictEqual(
4751 messages,
4752 [{
4753 column: 31,
4754 endColumn: 34,
4755 line: 2,
4756 endLine: 2,
4757 message: "'aaa' is already defined by a variable declaration.",
4758 messageId: "redeclaredBySyntax",
4759 nodeType: "Block",
4760 ruleId: "no-redeclare",
4761 severity: 2
4762 }]
4763 );
4764 });
4765
4766 it("should ignore the part preceded by '--' in '/*exported*/'.", () => {
4767 const messages = linter.verify(`
4768 /*exported aaa -- bbb */
4769 var aaa = {}
4770 var bbb = {}
4771 `, { rules: { "no-unused-vars": "error" } });
4772
4773 // Don't include `aaa`
4774 assert.deepStrictEqual(
4775 messages,
4776 [{
4777 column: 25,
4778 endColumn: 28,
4779 endLine: 4,
4780 line: 4,
4781 message: "'bbb' is assigned a value but never used.",
4782 messageId: "unusedVar",
4783 nodeType: "Identifier",
4784 ruleId: "no-unused-vars",
4785 severity: 2
4786 }]
4787 );
4788 });
4789
4790 it("should ignore the part preceded by '--' in '/*eslint-disable*/'.", () => {
4791 const messages = linter.verify(`
4792 /*eslint-disable no-redeclare -- no-unused-vars */
4793 var aaa = {}
4794 var aaa = {}
4795 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
4796
4797 // Do include `no-unused-vars` but not `no-redeclare`
4798 assert.deepStrictEqual(
4799 messages,
4800 [{
4801 column: 25,
4802 endLine: 4,
4803 endColumn: 28,
4804 line: 4,
4805 message: "'aaa' is assigned a value but never used.",
4806 messageId: "unusedVar",
4807 nodeType: "Identifier",
4808 ruleId: "no-unused-vars",
4809 severity: 2
4810 }]
4811 );
4812 });
4813
4814 it("should ignore the part preceded by '--' in '/*eslint-enable*/'.", () => {
4815 const messages = linter.verify(`
4816 /*eslint-disable no-redeclare, no-unused-vars */
4817 /*eslint-enable no-redeclare -- no-unused-vars */
4818 var aaa = {}
4819 var aaa = {}
4820 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
4821
4822 // Do include `no-redeclare` but not `no-unused-vars`
4823 assert.deepStrictEqual(
4824 messages,
4825 [{
4826 column: 25,
4827 endLine: 5,
4828 endColumn: 28,
4829 line: 5,
4830 message: "'aaa' is already defined.",
4831 messageId: "redeclared",
4832 nodeType: "Identifier",
4833 ruleId: "no-redeclare",
4834 severity: 2
4835 }]
4836 );
4837 });
4838
4839 it("should ignore the part preceded by '--' in '//eslint-disable-line'.", () => {
4840 const messages = linter.verify(`
4841 var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars
4842 var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars
4843 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
4844
4845 // Do include `no-unused-vars` but not `no-redeclare`
4846 assert.deepStrictEqual(
4847 messages,
4848 [{
4849 column: 25,
4850 endLine: 3,
4851 endColumn: 28,
4852 line: 3,
4853 message: "'aaa' is assigned a value but never used.",
4854 messageId: "unusedVar",
4855 nodeType: "Identifier",
4856 ruleId: "no-unused-vars",
4857 severity: 2
4858 }]
4859 );
4860 });
4861
4862 it("should ignore the part preceded by '--' in '/*eslint-disable-line*/'.", () => {
4863 const messages = linter.verify(`
4864 var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */
4865 var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */
4866 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
4867
4868 // Do include `no-unused-vars` but not `no-redeclare`
4869 assert.deepStrictEqual(
4870 messages,
4871 [{
4872 column: 25,
4873 endLine: 3,
4874 endColumn: 28,
4875 line: 3,
4876 message: "'aaa' is assigned a value but never used.",
4877 messageId: "unusedVar",
4878 nodeType: "Identifier",
4879 ruleId: "no-unused-vars",
4880 severity: 2
4881 }]
4882 );
4883 });
4884
4885 it("should ignore the part preceded by '--' in '//eslint-disable-next-line'.", () => {
4886 const messages = linter.verify(`
4887 //eslint-disable-next-line no-redeclare -- no-unused-vars
4888 var aaa = {}
4889 //eslint-disable-next-line no-redeclare -- no-unused-vars
4890 var aaa = {}
4891 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
4892
4893 // Do include `no-unused-vars` but not `no-redeclare`
4894 assert.deepStrictEqual(
4895 messages,
4896 [{
4897 column: 25,
4898 endLine: 5,
4899 endColumn: 28,
4900 line: 5,
4901 message: "'aaa' is assigned a value but never used.",
4902 messageId: "unusedVar",
4903 nodeType: "Identifier",
4904 ruleId: "no-unused-vars",
4905 severity: 2
4906 }]
4907 );
4908 });
4909
4910 it("should ignore the part preceded by '--' in '/*eslint-disable-next-line*/'.", () => {
4911 const messages = linter.verify(`
4912 /*eslint-disable-next-line no-redeclare -- no-unused-vars */
4913 var aaa = {}
4914 /*eslint-disable-next-line no-redeclare -- no-unused-vars */
4915 var aaa = {}
4916 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
4917
4918 // Do include `no-unused-vars` but not `no-redeclare`
4919 assert.deepStrictEqual(
4920 messages,
4921 [{
4922 column: 25,
4923 endLine: 5,
4924 endColumn: 28,
4925 line: 5,
4926 message: "'aaa' is assigned a value but never used.",
4927 messageId: "unusedVar",
4928 nodeType: "Identifier",
4929 ruleId: "no-unused-vars",
4930 severity: 2
4931 }]
4932 );
4933 });
4934
4935 it("should not ignore the part preceded by '--' if the '--' is not surrounded by whitespaces.", () => {
4936 const rule = sinon.stub().returns({});
4937
4938 linter.defineRule("a--rule", { create: rule });
4939 const messages = linter.verify(`
4940 /*eslint a--rule:error */
4941 console.log("hello")
4942 `, {});
4943
4944 // Don't include syntax error of the comment.
4945 assert.deepStrictEqual(messages, []);
4946
4947 // Use `a--rule`.
4948 assert.strictEqual(rule.callCount, 1);
4949 });
4950
4951 it("should ignore the part preceded by '--' even if the '--' is longer than 2.", () => {
4952 const aaa = sinon.stub().returns({});
4953 const bbb = sinon.stub().returns({});
4954
4955 linter.defineRule("aaa", { create: aaa });
4956 linter.defineRule("bbb", { create: bbb });
4957 const messages = linter.verify(`
4958 /*eslint aaa:error -------- bbb:error */
4959 console.log("hello")
4960 `, {});
4961
4962 // Don't include syntax error of the comment.
4963 assert.deepStrictEqual(messages, []);
4964
4965 // Use only `aaa`.
4966 assert.strictEqual(aaa.callCount, 1);
4967 assert.strictEqual(bbb.callCount, 0);
4968 });
4969
4970 it("should ignore the part preceded by '--' with line breaks.", () => {
4971 const aaa = sinon.stub().returns({});
4972 const bbb = sinon.stub().returns({});
4973
4974 linter.defineRule("aaa", { create: aaa });
4975 linter.defineRule("bbb", { create: bbb });
4976 const messages = linter.verify(`
4977 /*eslint aaa:error
4978 --------
4979 bbb:error */
4980 console.log("hello")
4981 `, {});
4982
4983 // Don't include syntax error of the comment.
4984 assert.deepStrictEqual(messages, []);
4985
4986 // Use only `aaa`.
4987 assert.strictEqual(aaa.callCount, 1);
4988 assert.strictEqual(bbb.callCount, 0);
4989 });
4990 });
4991 });
4992
4993 describe("context.getScope()", () => {
4994
4995 /**
4996 * Get the scope on the node `astSelector` specified.
4997 * @param {string} code The source code to verify.
4998 * @param {string} astSelector The AST selector to get scope.
4999 * @param {number} [ecmaVersion=5] The ECMAScript version.
5000 * @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope.
5001 */
5002 function getScope(code, astSelector, ecmaVersion = 5) {
5003 let node, scope;
5004
5005 linter.defineRule("get-scope", context => ({
5006 [astSelector](node0) {
5007 node = node0;
5008 scope = context.getScope();
5009 }
5010 }));
5011 linter.verify(
5012 code,
5013 {
5014 parserOptions: { ecmaVersion },
5015 rules: { "get-scope": 2 }
5016 }
5017 );
5018
5019 return { node, scope };
5020 }
5021
5022 it("should return 'function' scope on FunctionDeclaration (ES5)", () => {
5023 const { node, scope } = getScope("function f() {}", "FunctionDeclaration");
5024
5025 assert.strictEqual(scope.type, "function");
5026 assert.strictEqual(scope.block, node);
5027 });
5028
5029 it("should return 'function' scope on FunctionExpression (ES5)", () => {
5030 const { node, scope } = getScope("!function f() {}", "FunctionExpression");
5031
5032 assert.strictEqual(scope.type, "function");
5033 assert.strictEqual(scope.block, node);
5034 });
5035
5036 it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => {
5037 const { node, scope } = getScope("function f() {}", "BlockStatement");
5038
5039 assert.strictEqual(scope.type, "function");
5040 assert.strictEqual(scope.block, node.parent);
5041 });
5042
5043 it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => {
5044 const { node, scope } = getScope("function f() {}", "BlockStatement", 2015);
5045
5046 assert.strictEqual(scope.type, "function");
5047 assert.strictEqual(scope.block, node.parent);
5048 });
5049
5050 it("should return 'function' scope on BlockStatement in functions (ES5)", () => {
5051 const { node, scope } = getScope("function f() { { var b; } }", "BlockStatement > BlockStatement");
5052
5053 assert.strictEqual(scope.type, "function");
5054 assert.strictEqual(scope.block, node.parent.parent);
5055 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]);
5056 });
5057
5058 it("should return 'block' scope on BlockStatement in functions (ES2015)", () => {
5059 const { node, scope } = getScope("function f() { { let a; var b; } }", "BlockStatement > BlockStatement", 2015);
5060
5061 assert.strictEqual(scope.type, "block");
5062 assert.strictEqual(scope.upper.type, "function");
5063 assert.strictEqual(scope.block, node);
5064 assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]);
5065 assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "b"]);
5066 });
5067
5068 it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => {
5069 const { node, scope } = getScope("function f() { { let a; { let b; var c; } } }", "BlockStatement > BlockStatement > BlockStatement", 2015);
5070
5071 assert.strictEqual(scope.type, "block");
5072 assert.strictEqual(scope.upper.type, "block");
5073 assert.strictEqual(scope.upper.upper.type, "function");
5074 assert.strictEqual(scope.block, node);
5075 assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]);
5076 assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["a"]);
5077 assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "c"]);
5078 });
5079
5080 it("should return 'function' scope on SwitchStatement in functions (ES5)", () => {
5081 const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchStatement");
5082
5083 assert.strictEqual(scope.type, "function");
5084 assert.strictEqual(scope.block, node.parent.parent);
5085 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]);
5086 });
5087
5088 it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => {
5089 const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchStatement", 2015);
5090
5091 assert.strictEqual(scope.type, "switch");
5092 assert.strictEqual(scope.block, node);
5093 assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]);
5094 });
5095
5096 it("should return 'function' scope on SwitchCase in functions (ES5)", () => {
5097 const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchCase");
5098
5099 assert.strictEqual(scope.type, "function");
5100 assert.strictEqual(scope.block, node.parent.parent.parent);
5101 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]);
5102 });
5103
5104 it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => {
5105 const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchCase", 2015);
5106
5107 assert.strictEqual(scope.type, "switch");
5108 assert.strictEqual(scope.block, node.parent);
5109 assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]);
5110 });
5111
5112 it("should return 'catch' scope on CatchClause in functions (ES5)", () => {
5113 const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause");
5114
5115 assert.strictEqual(scope.type, "catch");
5116 assert.strictEqual(scope.block, node);
5117 assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]);
5118 });
5119
5120 it("should return 'catch' scope on CatchClause in functions (ES2015)", () => {
5121 const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause", 2015);
5122
5123 assert.strictEqual(scope.type, "catch");
5124 assert.strictEqual(scope.block, node);
5125 assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]);
5126 });
5127
5128 it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => {
5129 const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause > BlockStatement");
5130
5131 assert.strictEqual(scope.type, "catch");
5132 assert.strictEqual(scope.block, node.parent);
5133 assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]);
5134 });
5135
5136 it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => {
5137 const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause > BlockStatement", 2015);
5138
5139 assert.strictEqual(scope.type, "block");
5140 assert.strictEqual(scope.block, node);
5141 assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]);
5142 });
5143
5144 it("should return 'function' scope on ForStatement in functions (ES5)", () => {
5145 const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement");
5146
5147 assert.strictEqual(scope.type, "function");
5148 assert.strictEqual(scope.block, node.parent.parent);
5149 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]);
5150 });
5151
5152 it("should return 'for' scope on ForStatement in functions (ES2015)", () => {
5153 const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement", 2015);
5154
5155 assert.strictEqual(scope.type, "for");
5156 assert.strictEqual(scope.block, node);
5157 assert.deepStrictEqual(scope.variables.map(v => v.name), ["i"]);
5158 });
5159
5160 it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => {
5161 const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement");
5162
5163 assert.strictEqual(scope.type, "function");
5164 assert.strictEqual(scope.block, node.parent.parent.parent);
5165 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]);
5166 });
5167
5168 it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => {
5169 const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement", 2015);
5170
5171 assert.strictEqual(scope.type, "block");
5172 assert.strictEqual(scope.upper.type, "for");
5173 assert.strictEqual(scope.block, node);
5174 assert.deepStrictEqual(scope.variables.map(v => v.name), []);
5175 assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["i"]);
5176 });
5177
5178 it("should return 'function' scope on ForInStatement in functions (ES5)", () => {
5179 const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement");
5180
5181 assert.strictEqual(scope.type, "function");
5182 assert.strictEqual(scope.block, node.parent.parent);
5183 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]);
5184 });
5185
5186 it("should return 'for' scope on ForInStatement in functions (ES2015)", () => {
5187 const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement", 2015);
5188
5189 assert.strictEqual(scope.type, "for");
5190 assert.strictEqual(scope.block, node);
5191 assert.deepStrictEqual(scope.variables.map(v => v.name), ["key"]);
5192 });
5193
5194 it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => {
5195 const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement > BlockStatement");
5196
5197 assert.strictEqual(scope.type, "function");
5198 assert.strictEqual(scope.block, node.parent.parent.parent);
5199 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]);
5200 });
5201
5202 it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => {
5203 const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement > BlockStatement", 2015);
5204
5205 assert.strictEqual(scope.type, "block");
5206 assert.strictEqual(scope.upper.type, "for");
5207 assert.strictEqual(scope.block, node);
5208 assert.deepStrictEqual(scope.variables.map(v => v.name), []);
5209 assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["key"]);
5210 });
5211
5212 it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => {
5213 const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement", 2015);
5214
5215 assert.strictEqual(scope.type, "for");
5216 assert.strictEqual(scope.block, node);
5217 assert.deepStrictEqual(scope.variables.map(v => v.name), ["x"]);
5218 });
5219
5220 it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => {
5221 const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement > BlockStatement", 2015);
5222
5223 assert.strictEqual(scope.type, "block");
5224 assert.strictEqual(scope.upper.type, "for");
5225 assert.strictEqual(scope.block, node);
5226 assert.deepStrictEqual(scope.variables.map(v => v.name), []);
5227 assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["x"]);
5228 });
5229
5230 it("should shadow the same name variable by the iteration variable.", () => {
5231 const { node, scope } = getScope("let x; for (let x of x) {}", "ForOfStatement", 2015);
5232
5233 assert.strictEqual(scope.type, "for");
5234 assert.strictEqual(scope.upper.type, "global");
5235 assert.strictEqual(scope.block, node);
5236 assert.strictEqual(scope.upper.variables[0].references.length, 0);
5237 assert.strictEqual(scope.references[0].identifier, node.left.declarations[0].id);
5238 assert.strictEqual(scope.references[1].identifier, node.right);
5239 assert.strictEqual(scope.references[1].resolved, scope.variables[0]);
5240 });
5241 });
5242
5243 describe("Variables and references", () => {
5244 const code = [
5245 "a;",
5246 "function foo() { b; }",
5247 "Object;",
5248 "foo;",
5249 "var c;",
5250 "c;",
5251 "/* global d */",
5252 "d;",
5253 "e;",
5254 "f;"
5255 ].join("\n");
5256 let scope = null;
5257
5258 beforeEach(() => {
5259 let ok = false;
5260
5261 linter.defineRules({
5262 test(context) {
5263 return {
5264 Program() {
5265 scope = context.getScope();
5266 ok = true;
5267 }
5268 };
5269 }
5270 });
5271 linter.verify(code, { rules: { test: 2 }, globals: { e: true, f: false } });
5272 assert(ok);
5273 });
5274
5275 afterEach(() => {
5276 scope = null;
5277 });
5278
5279 it("Scope#through should contain references of undefined variables", () => {
5280 assert.strictEqual(scope.through.length, 2);
5281 assert.strictEqual(scope.through[0].identifier.name, "a");
5282 assert.strictEqual(scope.through[0].identifier.loc.start.line, 1);
5283 assert.strictEqual(scope.through[0].resolved, null);
5284 assert.strictEqual(scope.through[1].identifier.name, "b");
5285 assert.strictEqual(scope.through[1].identifier.loc.start.line, 2);
5286 assert.strictEqual(scope.through[1].resolved, null);
5287 });
5288
5289 it("Scope#variables should contain global variables", () => {
5290 assert(scope.variables.some(v => v.name === "Object"));
5291 assert(scope.variables.some(v => v.name === "foo"));
5292 assert(scope.variables.some(v => v.name === "c"));
5293 assert(scope.variables.some(v => v.name === "d"));
5294 assert(scope.variables.some(v => v.name === "e"));
5295 assert(scope.variables.some(v => v.name === "f"));
5296 });
5297
5298 it("Scope#set should contain global variables", () => {
5299 assert(scope.set.get("Object"));
5300 assert(scope.set.get("foo"));
5301 assert(scope.set.get("c"));
5302 assert(scope.set.get("d"));
5303 assert(scope.set.get("e"));
5304 assert(scope.set.get("f"));
5305 });
5306
5307 it("Variables#references should contain their references", () => {
5308 assert.strictEqual(scope.set.get("Object").references.length, 1);
5309 assert.strictEqual(scope.set.get("Object").references[0].identifier.name, "Object");
5310 assert.strictEqual(scope.set.get("Object").references[0].identifier.loc.start.line, 3);
5311 assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object"));
5312 assert.strictEqual(scope.set.get("foo").references.length, 1);
5313 assert.strictEqual(scope.set.get("foo").references[0].identifier.name, "foo");
5314 assert.strictEqual(scope.set.get("foo").references[0].identifier.loc.start.line, 4);
5315 assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo"));
5316 assert.strictEqual(scope.set.get("c").references.length, 1);
5317 assert.strictEqual(scope.set.get("c").references[0].identifier.name, "c");
5318 assert.strictEqual(scope.set.get("c").references[0].identifier.loc.start.line, 6);
5319 assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c"));
5320 assert.strictEqual(scope.set.get("d").references.length, 1);
5321 assert.strictEqual(scope.set.get("d").references[0].identifier.name, "d");
5322 assert.strictEqual(scope.set.get("d").references[0].identifier.loc.start.line, 8);
5323 assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d"));
5324 assert.strictEqual(scope.set.get("e").references.length, 1);
5325 assert.strictEqual(scope.set.get("e").references[0].identifier.name, "e");
5326 assert.strictEqual(scope.set.get("e").references[0].identifier.loc.start.line, 9);
5327 assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e"));
5328 assert.strictEqual(scope.set.get("f").references.length, 1);
5329 assert.strictEqual(scope.set.get("f").references[0].identifier.name, "f");
5330 assert.strictEqual(scope.set.get("f").references[0].identifier.loc.start.line, 10);
5331 assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f"));
5332 });
5333
5334 it("Reference#resolved should be their variable", () => {
5335 assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object"));
5336 assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo"));
5337 assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c"));
5338 assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d"));
5339 assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e"));
5340 assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f"));
5341 });
5342 });
5343
5344 describe("context.getDeclaredVariables(node)", () => {
5345
5346 /**
5347 * Assert `context.getDeclaredVariables(node)` is valid.
5348 * @param {string} code A code to check.
5349 * @param {string} type A type string of ASTNode. This method checks variables on the node of the type.
5350 * @param {Array<Array<string>>} expectedNamesList An array of expected variable names. The expected variable names is an array of string.
5351 * @returns {void}
5352 */
5353 function verify(code, type, expectedNamesList) {
5354 linter.defineRules({
5355 test(context) {
5356
5357 /**
5358 * Assert `context.getDeclaredVariables(node)` is empty.
5359 * @param {ASTNode} node A node to check.
5360 * @returns {void}
5361 */
5362 function checkEmpty(node) {
5363 assert.strictEqual(0, context.getDeclaredVariables(node).length);
5364 }
5365 const rule = {
5366 Program: checkEmpty,
5367 EmptyStatement: checkEmpty,
5368 BlockStatement: checkEmpty,
5369 ExpressionStatement: checkEmpty,
5370 LabeledStatement: checkEmpty,
5371 BreakStatement: checkEmpty,
5372 ContinueStatement: checkEmpty,
5373 WithStatement: checkEmpty,
5374 SwitchStatement: checkEmpty,
5375 ReturnStatement: checkEmpty,
5376 ThrowStatement: checkEmpty,
5377 TryStatement: checkEmpty,
5378 WhileStatement: checkEmpty,
5379 DoWhileStatement: checkEmpty,
5380 ForStatement: checkEmpty,
5381 ForInStatement: checkEmpty,
5382 DebuggerStatement: checkEmpty,
5383 ThisExpression: checkEmpty,
5384 ArrayExpression: checkEmpty,
5385 ObjectExpression: checkEmpty,
5386 Property: checkEmpty,
5387 SequenceExpression: checkEmpty,
5388 UnaryExpression: checkEmpty,
5389 BinaryExpression: checkEmpty,
5390 AssignmentExpression: checkEmpty,
5391 UpdateExpression: checkEmpty,
5392 LogicalExpression: checkEmpty,
5393 ConditionalExpression: checkEmpty,
5394 CallExpression: checkEmpty,
5395 NewExpression: checkEmpty,
5396 MemberExpression: checkEmpty,
5397 SwitchCase: checkEmpty,
5398 Identifier: checkEmpty,
5399 Literal: checkEmpty,
5400 ForOfStatement: checkEmpty,
5401 ArrowFunctionExpression: checkEmpty,
5402 YieldExpression: checkEmpty,
5403 TemplateLiteral: checkEmpty,
5404 TaggedTemplateExpression: checkEmpty,
5405 TemplateElement: checkEmpty,
5406 ObjectPattern: checkEmpty,
5407 ArrayPattern: checkEmpty,
5408 RestElement: checkEmpty,
5409 AssignmentPattern: checkEmpty,
5410 ClassBody: checkEmpty,
5411 MethodDefinition: checkEmpty,
5412 MetaProperty: checkEmpty
5413 };
5414
5415 rule[type] = function(node) {
5416 const expectedNames = expectedNamesList.shift();
5417 const variables = context.getDeclaredVariables(node);
5418
5419 assert(Array.isArray(expectedNames));
5420 assert(Array.isArray(variables));
5421 assert.strictEqual(expectedNames.length, variables.length);
5422 for (let i = variables.length - 1; i >= 0; i--) {
5423 assert.strictEqual(expectedNames[i], variables[i].name);
5424 }
5425 };
5426 return rule;
5427 }
5428 });
5429 linter.verify(code, {
5430 rules: { test: 2 },
5431 parserOptions: {
5432 ecmaVersion: 6,
5433 sourceType: "module"
5434 }
5435 });
5436
5437 // Check all expected names are asserted.
5438 assert.strictEqual(0, expectedNamesList.length);
5439 }
5440
5441 it("VariableDeclaration", () => {
5442 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 ";
5443 const namesList = [
5444 ["a", "b", "c"],
5445 ["d", "e", "f"],
5446 ["g", "h", "i", "j", "k"],
5447 ["l"]
5448 ];
5449
5450 verify(code, "VariableDeclaration", namesList);
5451 });
5452
5453 it("VariableDeclaration (on for-in/of loop)", () => {
5454
5455 // TDZ scope is created here, so tests to exclude those.
5456 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 ";
5457 const namesList = [
5458 ["a", "b", "c"],
5459 ["g"],
5460 ["d", "e", "f"],
5461 ["h"]
5462 ];
5463
5464 verify(code, "VariableDeclaration", namesList);
5465 });
5466
5467 it("VariableDeclarator", () => {
5468
5469 // TDZ scope is created here, so tests to exclude those.
5470 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 ";
5471 const namesList = [
5472 ["a", "b", "c"],
5473 ["d", "e", "f"],
5474 ["g", "h", "i"],
5475 ["j", "k"],
5476 ["l"]
5477 ];
5478
5479 verify(code, "VariableDeclarator", namesList);
5480 });
5481
5482 it("FunctionDeclaration", () => {
5483 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 ";
5484 const namesList = [
5485 ["foo", "a", "b", "c", "d", "e"],
5486 ["bar", "f", "g", "h", "i", "j"]
5487 ];
5488
5489 verify(code, "FunctionDeclaration", namesList);
5490 });
5491
5492 it("FunctionExpression", () => {
5493 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 ";
5494 const namesList = [
5495 ["foo", "a", "b", "c", "d", "e"],
5496 ["bar", "f", "g", "h", "i", "j"],
5497 ["q"]
5498 ];
5499
5500 verify(code, "FunctionExpression", namesList);
5501 });
5502
5503 it("ArrowFunctionExpression", () => {
5504 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 ";
5505 const namesList = [
5506 ["a", "b", "c", "d", "e"],
5507 ["f", "g", "h", "i", "j"]
5508 ];
5509
5510 verify(code, "ArrowFunctionExpression", namesList);
5511 });
5512
5513 it("ClassDeclaration", () => {
5514 const code = "\n class A { foo(x) { let y; } }\n class B { foo(x) { let y; } }\n ";
5515 const namesList = [
5516 ["A", "A"], // outer scope's and inner scope's.
5517 ["B", "B"]
5518 ];
5519
5520 verify(code, "ClassDeclaration", namesList);
5521 });
5522
5523 it("ClassExpression", () => {
5524 const code = "\n (class A { foo(x) { let y; } });\n (class B { foo(x) { let y; } });\n ";
5525 const namesList = [
5526 ["A"],
5527 ["B"]
5528 ];
5529
5530 verify(code, "ClassExpression", namesList);
5531 });
5532
5533 it("CatchClause", () => {
5534 const code = "\n try {} catch ({a, b}) {\n let x;\n try {} catch ({c, d}) {\n let y;\n }\n }\n ";
5535 const namesList = [
5536 ["a", "b"],
5537 ["c", "d"]
5538 ];
5539
5540 verify(code, "CatchClause", namesList);
5541 });
5542
5543 it("ImportDeclaration", () => {
5544 const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
5545 const namesList = [
5546 [],
5547 ["a"],
5548 ["b", "c", "d"]
5549 ];
5550
5551 verify(code, "ImportDeclaration", namesList);
5552 });
5553
5554 it("ImportSpecifier", () => {
5555 const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
5556 const namesList = [
5557 ["c"],
5558 ["d"]
5559 ];
5560
5561 verify(code, "ImportSpecifier", namesList);
5562 });
5563
5564 it("ImportDefaultSpecifier", () => {
5565 const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
5566 const namesList = [
5567 ["b"]
5568 ];
5569
5570 verify(code, "ImportDefaultSpecifier", namesList);
5571 });
5572
5573 it("ImportNamespaceSpecifier", () => {
5574 const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
5575 const namesList = [
5576 ["a"]
5577 ];
5578
5579 verify(code, "ImportNamespaceSpecifier", namesList);
5580 });
5581 });
5582
5583 describe("suggestions", () => {
5584 it("provides suggestion information for tools to use", () => {
5585 linter.defineRule("rule-with-suggestions", {
5586 meta: { hasSuggestions: true },
5587 create: context => ({
5588 Program(node) {
5589 context.report({
5590 node,
5591 message: "Incorrect spacing",
5592 suggest: [{
5593 desc: "Insert space at the beginning",
5594 fix: fixer => fixer.insertTextBefore(node, " ")
5595 }, {
5596 desc: "Insert space at the end",
5597 fix: fixer => fixer.insertTextAfter(node, " ")
5598 }]
5599 });
5600 }
5601 })
5602 });
5603
5604 const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } });
5605
5606 assert.deepStrictEqual(messages[0].suggestions, [{
5607 desc: "Insert space at the beginning",
5608 fix: {
5609 range: [0, 0],
5610 text: " "
5611 }
5612 }, {
5613 desc: "Insert space at the end",
5614 fix: {
5615 range: [10, 10],
5616 text: " "
5617 }
5618 }]);
5619 });
5620
5621 it("supports messageIds for suggestions", () => {
5622 linter.defineRule("rule-with-suggestions", {
5623 meta: {
5624 messages: {
5625 suggestion1: "Insert space at the beginning",
5626 suggestion2: "Insert space at the end"
5627 },
5628 hasSuggestions: true
5629 },
5630 create: context => ({
5631 Program(node) {
5632 context.report({
5633 node,
5634 message: "Incorrect spacing",
5635 suggest: [{
5636 messageId: "suggestion1",
5637 fix: fixer => fixer.insertTextBefore(node, " ")
5638 }, {
5639 messageId: "suggestion2",
5640 fix: fixer => fixer.insertTextAfter(node, " ")
5641 }]
5642 });
5643 }
5644 })
5645 });
5646
5647 const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } });
5648
5649 assert.deepStrictEqual(messages[0].suggestions, [{
5650 messageId: "suggestion1",
5651 desc: "Insert space at the beginning",
5652 fix: {
5653 range: [0, 0],
5654 text: " "
5655 }
5656 }, {
5657 messageId: "suggestion2",
5658 desc: "Insert space at the end",
5659 fix: {
5660 range: [10, 10],
5661 text: " "
5662 }
5663 }]);
5664 });
5665
5666 it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled", () => {
5667 linter.defineRule("rule-with-suggestions", {
5668 meta: { docs: {}, schema: [] },
5669 create: context => ({
5670 Program(node) {
5671 context.report({
5672 node,
5673 message: "hello world",
5674 suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }]
5675 });
5676 }
5677 })
5678 });
5679
5680 assert.throws(() => {
5681 linter.verify("0", { rules: { "rule-with-suggestions": "error" } });
5682 }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
5683 });
5684
5685 it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled and the rule has the obsolete `meta.docs.suggestion` property", () => {
5686 linter.defineRule("rule-with-meta-docs-suggestion", {
5687 meta: { docs: { suggestion: true }, schema: [] },
5688 create: context => ({
5689 Program(node) {
5690 context.report({
5691 node,
5692 message: "hello world",
5693 suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }]
5694 });
5695 }
5696 })
5697 });
5698
5699 assert.throws(() => {
5700 linter.verify("0", { rules: { "rule-with-meta-docs-suggestion": "error" } });
5701 }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint.");
5702 });
5703 });
5704
5705 describe("mutability", () => {
5706 let linter1 = null;
5707 let linter2 = null;
5708
5709 beforeEach(() => {
5710 linter1 = new Linter();
5711 linter2 = new Linter();
5712 });
5713
5714 describe("rules", () => {
5715 it("with no changes, same rules are loaded", () => {
5716 assert.sameDeepMembers(Array.from(linter1.getRules().keys()), Array.from(linter2.getRules().keys()));
5717 });
5718
5719 it("loading rule in one doesn't change the other", () => {
5720 linter1.defineRule("mock-rule", () => ({}));
5721
5722 assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present");
5723 assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present");
5724 });
5725 });
5726 });
5727
5728 describe("processors", () => {
5729 let receivedFilenames = [];
5730 let receivedPhysicalFilenames = [];
5731
5732 beforeEach(() => {
5733 receivedFilenames = [];
5734 receivedPhysicalFilenames = [];
5735
5736 // A rule that always reports the AST with a message equal to the source text
5737 linter.defineRule("report-original-text", context => ({
5738 Program(ast) {
5739 receivedFilenames.push(context.getFilename());
5740 receivedPhysicalFilenames.push(context.getPhysicalFilename());
5741 context.report({ node: ast, message: context.getSourceCode().text });
5742 }
5743 }));
5744 });
5745
5746 describe("preprocessors", () => {
5747 it("should receive text and filename.", () => {
5748 const code = "foo bar baz";
5749 const preprocess = sinon.spy(text => text.split(" "));
5750
5751 linter.verify(code, {}, { filename, preprocess });
5752
5753 assert.strictEqual(preprocess.calledOnce, true);
5754 assert.deepStrictEqual(preprocess.args[0], [code, filename]);
5755 });
5756
5757 it("should apply a preprocessor to the code, and lint each code sample separately", () => {
5758 const code = "foo bar baz";
5759 const problems = linter.verify(
5760 code,
5761 { rules: { "report-original-text": "error" } },
5762 {
5763
5764 // Apply a preprocessor that splits the source text into spaces and lints each word individually
5765 preprocess(input) {
5766 return input.split(" ");
5767 }
5768 }
5769 );
5770
5771 assert.strictEqual(problems.length, 3);
5772 assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
5773 });
5774
5775 it("should apply a preprocessor to the code even if the preprocessor returned code block objects.", () => {
5776 const code = "foo bar baz";
5777 const problems = linter.verify(
5778 code,
5779 { rules: { "report-original-text": "error" } },
5780 {
5781 filename,
5782
5783 // Apply a preprocessor that splits the source text into spaces and lints each word individually
5784 preprocess(input) {
5785 return input.split(" ").map(text => ({
5786 filename: "block.js",
5787 text
5788 }));
5789 }
5790 }
5791 );
5792
5793 assert.strictEqual(problems.length, 3);
5794 assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
5795
5796 // filename
5797 assert.strictEqual(receivedFilenames.length, 3);
5798 assert(/^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0]));
5799 assert(/^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1]));
5800 assert(/^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2]));
5801
5802 // physical filename
5803 assert.strictEqual(receivedPhysicalFilenames.length, 3);
5804 assert.strictEqual(receivedPhysicalFilenames.every(name => name === filename), true);
5805 });
5806
5807 it("should receive text even if a SourceCode object was given.", () => {
5808 const code = "foo";
5809 const preprocess = sinon.spy(text => text.split(" "));
5810
5811 linter.verify(code, {});
5812 const sourceCode = linter.getSourceCode();
5813
5814 linter.verify(sourceCode, {}, { filename, preprocess });
5815
5816 assert.strictEqual(preprocess.calledOnce, true);
5817 assert.deepStrictEqual(preprocess.args[0], [code, filename]);
5818 });
5819
5820 it("should receive text even if a SourceCode object was given (with BOM).", () => {
5821 const code = "\uFEFFfoo";
5822 const preprocess = sinon.spy(text => text.split(" "));
5823
5824 linter.verify(code, {});
5825 const sourceCode = linter.getSourceCode();
5826
5827 linter.verify(sourceCode, {}, { filename, preprocess });
5828
5829 assert.strictEqual(preprocess.calledOnce, true);
5830 assert.deepStrictEqual(preprocess.args[0], [code, filename]);
5831 });
5832 });
5833
5834 describe("postprocessors", () => {
5835 it("should receive result and filename.", () => {
5836 const code = "foo bar baz";
5837 const preprocess = sinon.spy(text => text.split(" "));
5838 const postprocess = sinon.spy(text => [text]);
5839
5840 linter.verify(code, {}, { filename, postprocess, preprocess });
5841
5842 assert.strictEqual(postprocess.calledOnce, true);
5843 assert.deepStrictEqual(postprocess.args[0], [[[], [], []], filename]);
5844 });
5845
5846 it("should apply a postprocessor to the reported messages", () => {
5847 const code = "foo bar baz";
5848
5849 const problems = linter.verify(
5850 code,
5851 { rules: { "report-original-text": "error" } },
5852 {
5853 preprocess: input => input.split(" "),
5854
5855 /*
5856 * Apply a postprocessor that updates the locations of the reported problems
5857 * to make sure they correspond to the locations in the original text.
5858 */
5859 postprocess(problemLists) {
5860 problemLists.forEach(problemList => assert.strictEqual(problemList.length, 1));
5861 return problemLists.reduce(
5862 (combinedList, problemList, index) =>
5863 combinedList.concat(
5864 problemList.map(
5865 problem =>
5866 Object.assign(
5867 {},
5868 problem,
5869 {
5870 message: problem.message.toUpperCase(),
5871 column: problem.column + index * 4
5872 }
5873 )
5874 )
5875 ),
5876 []
5877 );
5878 }
5879 }
5880 );
5881
5882 assert.strictEqual(problems.length, 3);
5883 assert.deepStrictEqual(problems.map(problem => problem.message), ["FOO", "BAR", "BAZ"]);
5884 assert.deepStrictEqual(problems.map(problem => problem.column), [1, 5, 9]);
5885 });
5886
5887 it("should use postprocessed problem ranges when applying autofixes", () => {
5888 const code = "foo bar baz";
5889
5890 linter.defineRule("capitalize-identifiers", {
5891 meta: {
5892 fixable: "code"
5893 },
5894 create(context) {
5895 return {
5896 Identifier(node) {
5897 if (node.name !== node.name.toUpperCase()) {
5898 context.report({
5899 node,
5900 message: "Capitalize this identifier",
5901 fix: fixer => fixer.replaceText(node, node.name.toUpperCase())
5902 });
5903 }
5904 }
5905 };
5906 }
5907 });
5908
5909 const fixResult = linter.verifyAndFix(
5910 code,
5911 { rules: { "capitalize-identifiers": "error" } },
5912 {
5913
5914 /*
5915 * Apply a postprocessor that updates the locations of autofixes
5916 * to make sure they correspond to locations in the original text.
5917 */
5918 preprocess: input => input.split(" "),
5919 postprocess(problemLists) {
5920 return problemLists.reduce(
5921 (combinedProblems, problemList, blockIndex) =>
5922 combinedProblems.concat(
5923 problemList.map(problem =>
5924 Object.assign(problem, {
5925 fix: {
5926 text: problem.fix.text,
5927 range: problem.fix.range.map(
5928 rangeIndex => rangeIndex + blockIndex * 4
5929 )
5930 }
5931 }))
5932 ),
5933 []
5934 );
5935 }
5936 }
5937 );
5938
5939 assert.strictEqual(fixResult.fixed, true);
5940 assert.strictEqual(fixResult.messages.length, 0);
5941 assert.strictEqual(fixResult.output, "FOO BAR BAZ");
5942 });
5943 });
5944 });
5945
5946 describe("verifyAndFix", () => {
5947 it("Fixes the code", () => {
5948 const messages = linter.verifyAndFix("var a", {
5949 rules: {
5950 semi: 2
5951 }
5952 }, { filename: "test.js" });
5953
5954 assert.strictEqual(messages.output, "var a;", "Fixes were applied correctly");
5955 assert.isTrue(messages.fixed);
5956 });
5957
5958 it("does not require a third argument", () => {
5959 const fixResult = linter.verifyAndFix("var a", {
5960 rules: {
5961 semi: 2
5962 }
5963 });
5964
5965 assert.deepStrictEqual(fixResult, {
5966 fixed: true,
5967 messages: [],
5968 output: "var a;"
5969 });
5970 });
5971
5972 it("does not include suggestions in autofix results", () => {
5973 const fixResult = linter.verifyAndFix("var foo = /\\#/", {
5974 rules: {
5975 semi: 2,
5976 "no-useless-escape": 2
5977 }
5978 });
5979
5980 assert.strictEqual(fixResult.output, "var foo = /\\#/;");
5981 assert.strictEqual(fixResult.fixed, true);
5982 assert.strictEqual(fixResult.messages[0].suggestions.length > 0, true);
5983 });
5984
5985 it("does not apply autofixes when fix argument is `false`", () => {
5986 const fixResult = linter.verifyAndFix("var a", {
5987 rules: {
5988 semi: 2
5989 }
5990 }, { fix: false });
5991
5992 assert.strictEqual(fixResult.fixed, false);
5993 });
5994
5995 it("stops fixing after 10 passes", () => {
5996
5997 linter.defineRule("add-spaces", {
5998 meta: {
5999 fixable: "whitespace"
6000 },
6001 create(context) {
6002 return {
6003 Program(node) {
6004 context.report({
6005 node,
6006 message: "Add a space before this node.",
6007 fix: fixer => fixer.insertTextBefore(node, " ")
6008 });
6009 }
6010 };
6011 }
6012 });
6013
6014 const fixResult = linter.verifyAndFix("a", { rules: { "add-spaces": "error" } });
6015
6016 assert.strictEqual(fixResult.fixed, true);
6017 assert.strictEqual(fixResult.output, `${" ".repeat(10)}a`);
6018 assert.strictEqual(fixResult.messages.length, 1);
6019 });
6020
6021 it("should throw an error if fix is passed but meta has no `fixable` property", () => {
6022 linter.defineRule("test-rule", {
6023 meta: {
6024 docs: {},
6025 schema: []
6026 },
6027 create: context => ({
6028 Program(node) {
6029 context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" }));
6030 }
6031 })
6032 });
6033
6034 assert.throws(() => {
6035 linter.verify("0", { rules: { "test-rule": "error" } });
6036 }, /Fixable rules must set the `meta\.fixable` property to "code" or "whitespace".\nOccurred while linting <input>:1\nRule: "test-rule"$/u);
6037 });
6038
6039 it("should throw an error if fix is passed and there is no metadata", () => {
6040 linter.defineRule("test-rule", {
6041 create: context => ({
6042 Program(node) {
6043 context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" }));
6044 }
6045 })
6046 });
6047
6048 assert.throws(() => {
6049 linter.verify("0", { rules: { "test-rule": "error" } });
6050 }, /Fixable rules must set the `meta\.fixable` property/u);
6051 });
6052
6053 it("should throw an error if fix is passed from a legacy-format rule", () => {
6054 linter.defineRule("test-rule", context => ({
6055 Program(node) {
6056 context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" }));
6057 }
6058 }));
6059
6060 assert.throws(() => {
6061 linter.verify("0", { rules: { "test-rule": "error" } });
6062 }, /Fixable rules must set the `meta\.fixable` property/u);
6063 });
6064 });
6065
6066 describe("Edge cases", () => {
6067
6068 it("should properly parse import statements when sourceType is module", () => {
6069 const code = "import foo from 'foo';";
6070 const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } });
6071
6072 assert.strictEqual(messages.length, 0);
6073 });
6074
6075 it("should properly parse import all statements when sourceType is module", () => {
6076 const code = "import * as foo from 'foo';";
6077 const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } });
6078
6079 assert.strictEqual(messages.length, 0);
6080 });
6081
6082 it("should properly parse default export statements when sourceType is module", () => {
6083 const code = "export default function initialize() {}";
6084 const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } });
6085
6086 assert.strictEqual(messages.length, 0);
6087 });
6088
6089 // https://github.com/eslint/eslint/issues/9687
6090 it("should report an error when invalid parserOptions found", () => {
6091 let messages = linter.verify("", { parserOptions: { ecmaVersion: 222 } });
6092
6093 assert.deepStrictEqual(messages.length, 1);
6094 assert.ok(messages[0].message.includes("Invalid ecmaVersion"));
6095
6096 messages = linter.verify("", { parserOptions: { sourceType: "foo" } });
6097 assert.deepStrictEqual(messages.length, 1);
6098 assert.ok(messages[0].message.includes("Invalid sourceType"));
6099
6100 messages = linter.verify("", { parserOptions: { ecmaVersion: 5, sourceType: "module" } });
6101 assert.deepStrictEqual(messages.length, 1);
6102 assert.ok(messages[0].message.includes("sourceType 'module' is not supported when ecmaVersion < 2015"));
6103 });
6104
6105 it("should not crash when invalid parentheses syntax is encountered", () => {
6106 linter.verify("left = (aSize.width/2) - ()");
6107 });
6108
6109 it("should not crash when let is used inside of switch case", () => {
6110 linter.verify("switch(foo) { case 1: let bar=2; }", { parserOptions: { ecmaVersion: 6 } });
6111 });
6112
6113 it("should not crash when parsing destructured assignment", () => {
6114 linter.verify("var { a='a' } = {};", { parserOptions: { ecmaVersion: 6 } });
6115 });
6116
6117 it("should report syntax error when a keyword exists in object property shorthand", () => {
6118 const messages = linter.verify("let a = {this}", { parserOptions: { ecmaVersion: 6 } });
6119
6120 assert.strictEqual(messages.length, 1);
6121 assert.strictEqual(messages[0].fatal, true);
6122 });
6123
6124 it("should not rewrite env setting in core (https://github.com/eslint/eslint/issues/4814)", () => {
6125
6126 /*
6127 * This test focuses on the instance of https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/conf/environments.js#L26-L28
6128 * This `verify()` takes the instance and runs https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/lib/eslint.js#L416
6129 */
6130 linter.defineRule("test", () => ({}));
6131 linter.verify("var a = 0;", {
6132 env: { node: true },
6133 parserOptions: { ecmaVersion: 6, sourceType: "module" },
6134 rules: { test: 2 }
6135 });
6136
6137 // This `verify()` takes the instance and tests that the instance was not modified.
6138 let ok = false;
6139
6140 linter.defineRule("test", context => {
6141 assert(
6142 context.parserOptions.ecmaFeatures.globalReturn,
6143 "`ecmaFeatures.globalReturn` of the node environment should not be modified."
6144 );
6145 ok = true;
6146 return {};
6147 });
6148 linter.verify("var a = 0;", {
6149 env: { node: true },
6150 rules: { test: 2 }
6151 });
6152
6153 assert(ok);
6154 });
6155 });
6156
6157 describe("Custom parser", () => {
6158
6159 const errorPrefix = "Parsing error: ";
6160
6161 it("should have file path passed to it", () => {
6162 const code = "/* this is code */";
6163 const parseSpy = sinon.spy(testParsers.stubParser, "parse");
6164
6165 linter.defineParser("stub-parser", testParsers.stubParser);
6166 linter.verify(code, { parser: "stub-parser" }, filename, true);
6167
6168 sinon.assert.calledWithMatch(parseSpy, "", { filePath: filename });
6169 });
6170
6171 it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => {
6172 const code = "var myDivElement = <div {...this.props} />;";
6173
6174 linter.defineParser("esprima", esprima);
6175 const messages = linter.verify(code, { parser: "esprima", parserOptions: { jsx: true } }, "filename");
6176
6177 assert.strictEqual(messages.length, 0);
6178 });
6179
6180 it("should return an error when the custom parser can't be found", () => {
6181 const code = "var myDivElement = <div {...this.props} />;";
6182 const messages = linter.verify(code, { parser: "esprima-xyz" }, "filename");
6183
6184 assert.strictEqual(messages.length, 1);
6185 assert.strictEqual(messages[0].severity, 2);
6186 assert.strictEqual(messages[0].message, "Configured parser 'esprima-xyz' was not found.");
6187 });
6188
6189 it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => {
6190 const code = "null %% 'foo'";
6191
6192 linter.defineParser("unknown-logical-operator", testParsers.unknownLogicalOperator);
6193
6194 // This shouldn't throw
6195 const messages = linter.verify(code, { parser: "unknown-logical-operator" }, filename, true);
6196
6197 assert.strictEqual(messages.length, 0);
6198 });
6199
6200 it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => {
6201 const code = "foo && bar %% baz";
6202
6203 linter.defineParser("unknown-logical-operator-nested", testParsers.unknownLogicalOperatorNested);
6204
6205 // This shouldn't throw
6206 const messages = linter.verify(code, { parser: "unknown-logical-operator-nested" }, filename, true);
6207
6208 assert.strictEqual(messages.length, 0);
6209 });
6210
6211 it("should not throw or return errors when the custom parser returns unknown AST nodes", () => {
6212 const code = "foo && bar %% baz";
6213
6214 const nodes = [];
6215
6216 linter.defineRule("collect-node-types", () => ({
6217 "*"(node) {
6218 nodes.push(node.type);
6219 }
6220 }));
6221
6222 linter.defineParser("non-js-parser", testParsers.nonJSParser);
6223
6224 const messages = linter.verify(code, {
6225 parser: "non-js-parser",
6226 rules: {
6227 "collect-node-types": "error"
6228 }
6229 }, filename, true);
6230
6231 assert.strictEqual(messages.length, 0);
6232 assert.isTrue(nodes.length > 0);
6233 });
6234
6235 it("should strip leading line: prefix from parser error", () => {
6236 linter.defineParser("line-error", testParsers.lineError);
6237 const messages = linter.verify(";", { parser: "line-error" }, "filename");
6238
6239 assert.strictEqual(messages.length, 1);
6240 assert.strictEqual(messages[0].severity, 2);
6241 assert.strictEqual(messages[0].message, errorPrefix + testParsers.lineError.expectedError);
6242 });
6243
6244 it("should not modify a parser error message without a leading line: prefix", () => {
6245 linter.defineParser("no-line-error", testParsers.noLineError);
6246 const messages = linter.verify(";", { parser: "no-line-error" }, "filename");
6247
6248 assert.strictEqual(messages.length, 1);
6249 assert.strictEqual(messages[0].severity, 2);
6250 assert.strictEqual(messages[0].message, errorPrefix + testParsers.noLineError.expectedError);
6251 });
6252
6253 describe("if a parser provides 'visitorKeys'", () => {
6254 let types = [];
6255 let sourceCode;
6256 let scopeManager;
6257 let firstChildNodes = [];
6258
6259 beforeEach(() => {
6260 types = [];
6261 firstChildNodes = [];
6262 linter.defineRule("collect-node-types", () => ({
6263 "*"(node) {
6264 types.push(node.type);
6265 }
6266 }));
6267 linter.defineRule("save-scope-manager", context => {
6268 scopeManager = context.getSourceCode().scopeManager;
6269
6270 return {};
6271 });
6272 linter.defineRule("esquery-option", () => ({
6273 ":first-child"(node) {
6274 firstChildNodes.push(node);
6275 }
6276 }));
6277 linter.defineParser("enhanced-parser2", testParsers.enhancedParser2);
6278 linter.verify("@foo class A {}", {
6279 parser: "enhanced-parser2",
6280 rules: {
6281 "collect-node-types": "error",
6282 "save-scope-manager": "error",
6283 "esquery-option": "error"
6284 }
6285 });
6286
6287 sourceCode = linter.getSourceCode();
6288 });
6289
6290 it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => {
6291 assert.deepStrictEqual(
6292 types,
6293 ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
6294 );
6295 });
6296
6297 it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => {
6298 assert.deepStrictEqual(
6299 scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle -- ScopeManager API
6300 ["experimentalDecorators", "id", "superClass", "body"]
6301 );
6302 });
6303
6304 it("should use the same visitorKeys if the source code object is reused", () => {
6305 const types2 = [];
6306
6307 linter.defineRule("collect-node-types", () => ({
6308 "*"(node) {
6309 types2.push(node.type);
6310 }
6311 }));
6312 linter.verify(sourceCode, {
6313 rules: {
6314 "collect-node-types": "error"
6315 }
6316 });
6317
6318 assert.deepStrictEqual(
6319 types2,
6320 ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
6321 );
6322 });
6323
6324 it("esquery should use the visitorKeys (so 'visitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => {
6325 assert.deepStrictEqual(
6326 firstChildNodes,
6327 [sourceCode.ast.body[0], sourceCode.ast.body[0].experimentalDecorators[0]]
6328 );
6329 });
6330 });
6331
6332 describe("if a parser provides 'scope'", () => {
6333 let scope = null;
6334 let sourceCode = null;
6335
6336 beforeEach(() => {
6337 linter.defineParser("enhanced-parser3", testParsers.enhancedParser3);
6338 linter.defineRule("save-scope1", context => ({
6339 Program() {
6340 scope = context.getScope();
6341 }
6342 }));
6343 linter.verify("@foo class A {}", { parser: "enhanced-parser3", rules: { "save-scope1": 2 } });
6344
6345 sourceCode = linter.getSourceCode();
6346 });
6347
6348 it("should use the scope (so the global scope has the reference of '@foo')", () => {
6349 assert.strictEqual(scope.references.length, 1);
6350 assert.deepStrictEqual(
6351 scope.references[0].identifier.name,
6352 "foo"
6353 );
6354 });
6355
6356 it("should use the same scope if the source code object is reused", () => {
6357 let scope2 = null;
6358
6359 linter.defineRule("save-scope2", context => ({
6360 Program() {
6361 scope2 = context.getScope();
6362 }
6363 }));
6364 linter.verify(sourceCode, { rules: { "save-scope2": 2 } }, "test.js");
6365
6366 assert(scope2 !== null);
6367 assert(scope2 === scope);
6368 });
6369 });
6370
6371 it("should not pass any default parserOptions to the parser", () => {
6372 linter.defineParser("throws-with-options", testParsers.throwsWithOptions);
6373 const messages = linter.verify(";", { parser: "throws-with-options" }, "filename");
6374
6375 assert.strictEqual(messages.length, 0);
6376 });
6377 });
6378
6379 describe("merging 'parserOptions'", () => {
6380 it("should deeply merge 'parserOptions' from an environment with 'parserOptions' from the provided config", () => {
6381 const code = "return <div/>";
6382 const config = {
6383 env: {
6384 node: true // ecmaFeatures: { globalReturn: true }
6385 },
6386 parserOptions: {
6387 ecmaFeatures: {
6388 jsx: true
6389 }
6390 }
6391 };
6392
6393 const messages = linter.verify(code, config);
6394
6395 // no parsing errors
6396 assert.strictEqual(messages.length, 0);
6397 });
6398 });
6399 });
6400
6401 describe("Linter with FlatConfigArray", () => {
6402
6403 let linter;
6404 const filename = "filename.js";
6405
6406 /**
6407 * Creates a config array with some default properties.
6408 * @param {FlatConfig|FlatConfig[]} value The value to base the
6409 * config array on.
6410 * @returns {FlatConfigArray} The created config array.
6411 */
6412 function createFlatConfigArray(value) {
6413 return new FlatConfigArray(value, { basePath: "" });
6414 }
6415
6416 beforeEach(() => {
6417 linter = new Linter({ configType: "flat" });
6418 });
6419
6420 describe("Static Members", () => {
6421 describe("version", () => {
6422 it("should return same version as instance property", () => {
6423 assert.strictEqual(Linter.version, linter.version);
6424 });
6425 });
6426 });
6427
6428 describe("Config Options", () => {
6429
6430 describe("languageOptions", () => {
6431
6432 describe("ecmaVersion", () => {
6433
6434 it("should error when accessing a global that isn't available in ecmaVersion 5", () => {
6435 const messages = linter.verify("new Map()", {
6436 languageOptions: {
6437 ecmaVersion: 5,
6438 sourceType: "script"
6439 },
6440 rules: {
6441 "no-undef": "error"
6442 }
6443 });
6444
6445 assert.strictEqual(messages.length, 1, "There should be one linting error.");
6446 assert.strictEqual(messages[0].ruleId, "no-undef", "The linting error should be no-undef.");
6447 });
6448
6449 it("should error when accessing a global that isn't available in ecmaVersion 3", () => {
6450 const messages = linter.verify("JSON.stringify({})", {
6451 languageOptions: {
6452 ecmaVersion: 3,
6453 sourceType: "script"
6454 },
6455 rules: {
6456 "no-undef": "error"
6457 }
6458 });
6459
6460 assert.strictEqual(messages.length, 1, "There should be one linting error.");
6461 assert.strictEqual(messages[0].ruleId, "no-undef", "The linting error should be no-undef.");
6462 });
6463
6464 it("should add globals for ES6 when ecmaVersion is 6", () => {
6465 const messages = linter.verify("new Map()", {
6466 languageOptions: {
6467 ecmaVersion: 6
6468 },
6469 rules: {
6470 "no-undef": "error"
6471 }
6472 });
6473
6474 assert.strictEqual(messages.length, 0, "There should be no linting errors.");
6475 });
6476
6477 it("should allow destructuring when ecmaVersion is 6", () => {
6478 const messages = linter.verify("let {a} = b", {
6479 languageOptions: {
6480 ecmaVersion: 6
6481 }
6482 });
6483
6484 assert.strictEqual(messages.length, 0, "There should be no linting errors.");
6485 });
6486
6487 it("ecmaVersion should be normalized to year name for ES 6", () => {
6488 const config = {
6489 plugins: {
6490 test: {
6491 rules: {
6492 checker(context) {
6493 return {
6494 Program() {
6495 assert.strictEqual(context.languageOptions.ecmaVersion, 2015);
6496 }
6497 };
6498 }
6499 }
6500 }
6501 },
6502 languageOptions: {
6503 ecmaVersion: 6
6504 },
6505 rules: { "test/checker": "error" }
6506 };
6507
6508 linter.verify("foo", config, filename);
6509 });
6510
6511 it("ecmaVersion should be normalized to latest year by default", () => {
6512 const config = {
6513 plugins: {
6514 test: {
6515 rules: {
6516 checker(context) {
6517 return {
6518 Program() {
6519 assert.strictEqual(context.languageOptions.ecmaVersion, espree.latestEcmaVersion + 2009);
6520 }
6521 };
6522 }
6523 }
6524 }
6525 },
6526 rules: { "test/checker": "error" }
6527 };
6528
6529 linter.verify("foo", config, filename);
6530 });
6531
6532 it("ecmaVersion should not be normalized to year name for ES 5", () => {
6533 const config = {
6534 plugins: {
6535 test: {
6536 rules: {
6537 checker(context) {
6538 return {
6539 Program() {
6540 assert.strictEqual(context.languageOptions.ecmaVersion, 5);
6541 }
6542 };
6543 }
6544 }
6545 }
6546 },
6547 languageOptions: {
6548 ecmaVersion: 5
6549 },
6550 rules: { "test/checker": "error" }
6551 };
6552
6553 linter.verify("foo", config, filename);
6554 });
6555
6556 it("ecmaVersion should be normalized to year name for 'latest'", () => {
6557 const config = {
6558 plugins: {
6559 test: {
6560 rules: {
6561 checker(context) {
6562 return {
6563 Program() {
6564 assert.strictEqual(context.languageOptions.ecmaVersion, espree.latestEcmaVersion + 2009);
6565 }
6566 };
6567 }
6568 }
6569 }
6570 },
6571 languageOptions: {
6572 ecmaVersion: "latest"
6573 },
6574 rules: { "test/checker": "error" }
6575 };
6576
6577 linter.verify("foo", config, filename);
6578 });
6579
6580
6581 });
6582
6583 describe("sourceType", () => {
6584
6585 it("should be module by default", () => {
6586 const config = {
6587 plugins: {
6588 test: {
6589 rules: {
6590 checker(context) {
6591 return {
6592 Program() {
6593 assert.strictEqual(context.languageOptions.sourceType, "module");
6594 }
6595 };
6596 }
6597 }
6598 }
6599 },
6600 rules: { "test/checker": "error" }
6601 };
6602
6603 linter.verify("import foo from 'bar'", config, filename);
6604 });
6605
6606 it("should default to commonjs when passed a .cjs filename", () => {
6607 const config = {
6608 plugins: {
6609 test: {
6610 rules: {
6611 checker(context) {
6612 return {
6613 Program() {
6614 assert.strictEqual(context.languageOptions.sourceType, "commonjs");
6615 }
6616 };
6617 }
6618 }
6619 }
6620 },
6621 rules: { "test/checker": "error" }
6622 };
6623
6624 linter.verify("import foo from 'bar'", config, `${filename}.cjs`);
6625 });
6626
6627
6628 it("should error when import is used in a script", () => {
6629 const messages = linter.verify("import foo from 'bar';", {
6630 languageOptions: {
6631 ecmaVersion: 6,
6632 sourceType: "script"
6633 }
6634 });
6635
6636 assert.strictEqual(messages.length, 1, "There should be one parsing error.");
6637 assert.strictEqual(messages[0].message, "Parsing error: 'import' and 'export' may appear only with 'sourceType: module'");
6638 });
6639
6640 it("should not error when import is used in a module", () => {
6641 const messages = linter.verify("import foo from 'bar';", {
6642 languageOptions: {
6643 ecmaVersion: 6,
6644 sourceType: "module"
6645 }
6646 });
6647
6648 assert.strictEqual(messages.length, 0, "There should no linting errors.");
6649 });
6650
6651 it("should error when return is used at the top-level outside of commonjs", () => {
6652 const messages = linter.verify("return", {
6653 languageOptions: {
6654 ecmaVersion: 6,
6655 sourceType: "script"
6656 }
6657 });
6658
6659 assert.strictEqual(messages.length, 1, "There should be one parsing error.");
6660 assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function");
6661 });
6662
6663 it("should not error when top-level return is used in commonjs", () => {
6664 const messages = linter.verify("return", {
6665 languageOptions: {
6666 ecmaVersion: 6,
6667 sourceType: "commonjs"
6668 }
6669 });
6670
6671 assert.strictEqual(messages.length, 0, "There should no linting errors.");
6672 });
6673
6674 it("should error when accessing a Node.js global outside of commonjs", () => {
6675 const messages = linter.verify("require()", {
6676 languageOptions: {
6677 ecmaVersion: 6
6678 },
6679 rules: {
6680 "no-undef": "error"
6681 }
6682 });
6683
6684 assert.strictEqual(messages.length, 1, "There should be one linting error.");
6685 assert.strictEqual(messages[0].ruleId, "no-undef", "The linting error should be no-undef.");
6686 });
6687
6688 it("should add globals for Node.js when sourceType is commonjs", () => {
6689 const messages = linter.verify("require()", {
6690 languageOptions: {
6691 ecmaVersion: 6,
6692 sourceType: "commonjs"
6693 },
6694 rules: {
6695 "no-undef": "error"
6696 }
6697 });
6698
6699 assert.strictEqual(messages.length, 0, "There should be no linting errors.");
6700 });
6701
6702 it("should allow 'await' as a property name in modules", () => {
6703 const result = linter.verify(
6704 "obj.await",
6705 {
6706 languageOptions: {
6707 ecmaVersion: 6,
6708 sourceType: "module"
6709 }
6710 }
6711 );
6712
6713 assert(result.length === 0);
6714 });
6715
6716 });
6717
6718 describe("parser", () => {
6719
6720 it("should be able to define a custom parser", () => {
6721 const parser = {
6722 parseForESLint: function parse(code, options) {
6723 return {
6724 ast: esprima.parse(code, options),
6725 services: {
6726 test: {
6727 getMessage() {
6728 return "Hi!";
6729 }
6730 }
6731 }
6732 };
6733 }
6734 };
6735
6736 const config = {
6737 plugins: {
6738 test: {
6739 parsers: {
6740 "test-parser": parser
6741 }
6742 }
6743 },
6744 languageOptions: {
6745 parser: "test/test-parser"
6746 }
6747 };
6748
6749
6750 const messages = linter.verify("0", config, filename);
6751
6752 assert.strictEqual(messages.length, 0);
6753 });
6754
6755 it("should pass parser as context.languageOptions.parser to all rules when provided on config", () => {
6756
6757 const config = {
6758 plugins: {
6759 test: {
6760 rules: {
6761 "test-rule": sinon.mock().withArgs(
6762 sinon.match({ languageOptions: { parser: esprima } })
6763 ).returns({})
6764 }
6765 }
6766 },
6767 languageOptions: {
6768 parser: esprima
6769 },
6770 rules: {
6771 "test/test-rule": 2
6772 }
6773 };
6774
6775 linter.verify("0", config, filename);
6776 });
6777
6778 it("should use parseForESLint() in custom parser when custom parser is specified", () => {
6779 const config = {
6780 plugins: {
6781 test: {
6782 parsers: {
6783 "enhanced-parser": testParsers.enhancedParser
6784 }
6785 }
6786 },
6787 languageOptions: {
6788 parser: "test/enhanced-parser"
6789 }
6790 };
6791
6792 const messages = linter.verify("0", config, filename);
6793
6794 assert.strictEqual(messages.length, 0);
6795 });
6796
6797 it("should expose parser services when using parseForESLint() and services are specified", () => {
6798
6799 const config = {
6800 plugins: {
6801 test: {
6802 parsers: {
6803 "enhanced-parser": testParsers.enhancedParser
6804 },
6805 rules: {
6806 "test-service-rule": context => ({
6807 Literal(node) {
6808 context.report({
6809 node,
6810 message: context.parserServices.test.getMessage()
6811 });
6812 }
6813 })
6814 }
6815 }
6816 },
6817 languageOptions: {
6818 parser: "test/enhanced-parser"
6819 },
6820 rules: {
6821 "test/test-service-rule": 2
6822 }
6823 };
6824
6825 const messages = linter.verify("0", config, filename);
6826
6827 assert.strictEqual(messages.length, 1);
6828 assert.strictEqual(messages[0].message, "Hi!");
6829 });
6830
6831 it("should use the same parserServices if source code object is reused", () => {
6832
6833 const config = {
6834 plugins: {
6835 test: {
6836 parsers: {
6837 "enhanced-parser": testParsers.enhancedParser
6838 },
6839 rules: {
6840 "test-service-rule": context => ({
6841 Literal(node) {
6842 context.report({
6843 node,
6844 message: context.parserServices.test.getMessage()
6845 });
6846 }
6847 })
6848 }
6849 }
6850 },
6851 languageOptions: {
6852 parser: "test/enhanced-parser"
6853 },
6854 rules: {
6855 "test/test-service-rule": 2
6856 }
6857 };
6858
6859 const messages = linter.verify("0", config, filename);
6860
6861 assert.strictEqual(messages.length, 1);
6862 assert.strictEqual(messages[0].message, "Hi!");
6863
6864 const messages2 = linter.verify(linter.getSourceCode(), config, filename);
6865
6866 assert.strictEqual(messages2.length, 1);
6867 assert.strictEqual(messages2[0].message, "Hi!");
6868 });
6869
6870 it("should pass parser as context.languageOptions.parser to all rules when default parser is used", () => {
6871
6872 // references to Espree get messed up in a browser context, so wrap it
6873 const fakeParser = {
6874 parse: espree.parse
6875 };
6876
6877 const spy = sinon.spy(context => {
6878 assert.strictEqual(context.languageOptions.parser, fakeParser);
6879 return {};
6880 });
6881
6882 const config = {
6883 plugins: {
6884 test: {
6885 rules: {
6886 "test-rule": spy
6887 }
6888 }
6889 },
6890 languageOptions: {
6891 parser: fakeParser
6892 },
6893 rules: {
6894 "test/test-rule": 2
6895 }
6896 };
6897
6898 linter.verify("0", config, filename);
6899 assert.isTrue(spy.calledOnce);
6900 });
6901
6902
6903 describe("Custom Parsers", () => {
6904
6905 const errorPrefix = "Parsing error: ";
6906
6907 it("should have file path passed to it", () => {
6908 const code = "/* this is code */";
6909 const parseSpy = sinon.spy(testParsers.stubParser, "parse");
6910 const config = {
6911 languageOptions: {
6912 parser: testParsers.stubParser
6913 }
6914 };
6915
6916 linter.verify(code, config, filename, true);
6917
6918 sinon.assert.calledWithMatch(parseSpy, "", { filePath: filename });
6919 });
6920
6921 it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => {
6922 const code = "var myDivElement = <div {...this.props} />;";
6923 const config = {
6924 languageOptions: {
6925 parser: esprima,
6926 parserOptions: {
6927 jsx: true
6928 }
6929 }
6930 };
6931
6932 const messages = linter.verify(code, config, filename);
6933
6934 assert.strictEqual(messages.length, 0);
6935 });
6936
6937 it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => {
6938 const code = "null %% 'foo'";
6939 const config = {
6940 languageOptions: {
6941 parser: testParsers.unknownLogicalOperator
6942 }
6943 };
6944
6945 // This shouldn't throw
6946 const messages = linter.verify(code, config, filename);
6947
6948 assert.strictEqual(messages.length, 0);
6949 });
6950
6951 it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => {
6952 const code = "foo && bar %% baz";
6953 const config = {
6954 languageOptions: {
6955 parser: testParsers.unknownLogicalOperatorNested
6956 }
6957 };
6958
6959 // This shouldn't throw
6960 const messages = linter.verify(code, config, filename);
6961
6962 assert.strictEqual(messages.length, 0);
6963 });
6964
6965 it("should not throw or return errors when the custom parser returns unknown AST nodes", () => {
6966 const code = "foo && bar %% baz";
6967 const nodes = [];
6968 const config = {
6969 plugins: {
6970 test: {
6971 rules: {
6972 "collect-node-types": () => ({
6973 "*"(node) {
6974 nodes.push(node.type);
6975 }
6976 })
6977 }
6978 }
6979 },
6980 languageOptions: {
6981 parser: testParsers.nonJSParser
6982 },
6983 rules: {
6984 "test/collect-node-types": "error"
6985 }
6986 };
6987
6988 const messages = linter.verify(code, config, filename, true);
6989
6990 assert.strictEqual(messages.length, 0);
6991 assert.isTrue(nodes.length > 0);
6992 });
6993
6994 it("should strip leading line: prefix from parser error", () => {
6995 const messages = linter.verify(";", {
6996 languageOptions: {
6997 parser: testParsers.lineError
6998 }
6999 }, "filename");
7000
7001 assert.strictEqual(messages.length, 1);
7002 assert.strictEqual(messages[0].severity, 2);
7003 assert.strictEqual(messages[0].message, errorPrefix + testParsers.lineError.expectedError);
7004 });
7005
7006 it("should not modify a parser error message without a leading line: prefix", () => {
7007 const messages = linter.verify(";", {
7008 languageOptions: {
7009 parser: testParsers.noLineError
7010 }
7011 }, "filename");
7012
7013 assert.strictEqual(messages.length, 1);
7014 assert.strictEqual(messages[0].severity, 2);
7015 assert.strictEqual(messages[0].message, errorPrefix + testParsers.noLineError.expectedError);
7016 });
7017
7018 describe("if a parser provides 'visitorKeys'", () => {
7019 let types = [];
7020 let sourceCode;
7021 let scopeManager;
7022 let firstChildNodes = [];
7023
7024 beforeEach(() => {
7025 types = [];
7026 firstChildNodes = [];
7027 const config = {
7028 plugins: {
7029 test: {
7030 rules: {
7031 "collect-node-types": () => ({
7032 "*"(node) {
7033 types.push(node.type);
7034 }
7035 }),
7036 "save-scope-manager": context => {
7037 scopeManager = context.getSourceCode().scopeManager;
7038
7039 return {};
7040 },
7041 "esquery-option": () => ({
7042 ":first-child"(node) {
7043 firstChildNodes.push(node);
7044 }
7045 })
7046 }
7047 }
7048 },
7049 languageOptions: {
7050 parser: testParsers.enhancedParser2
7051 },
7052 rules: {
7053 "test/collect-node-types": "error",
7054 "test/save-scope-manager": "error",
7055 "test/esquery-option": "error"
7056 }
7057 };
7058
7059 linter.verify("@foo class A {}", config);
7060
7061 sourceCode = linter.getSourceCode();
7062 });
7063
7064 it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => {
7065 assert.deepStrictEqual(
7066 types,
7067 ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
7068 );
7069 });
7070
7071 it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => {
7072 assert.deepStrictEqual(
7073 scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle -- ScopeManager API
7074 ["experimentalDecorators", "id", "superClass", "body"]
7075 );
7076 });
7077
7078 it("should use the same visitorKeys if the source code object is reused", () => {
7079 const types2 = [];
7080 const config = {
7081 plugins: {
7082 test: {
7083 rules: {
7084 "collect-node-types": () => ({
7085 "*"(node) {
7086 types2.push(node.type);
7087 }
7088 })
7089 }
7090 }
7091 },
7092 rules: {
7093 "test/collect-node-types": "error"
7094 }
7095 };
7096
7097 linter.verify(sourceCode, config);
7098
7099 assert.deepStrictEqual(
7100 types2,
7101 ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
7102 );
7103 });
7104
7105 it("esquery should use the visitorKeys (so 'visitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => {
7106 assert.deepStrictEqual(
7107 firstChildNodes,
7108 [sourceCode.ast.body[0], sourceCode.ast.body[0].experimentalDecorators[0]]
7109 );
7110 });
7111 });
7112
7113 describe("if a parser provides 'scope'", () => {
7114 let scope = null;
7115 let sourceCode = null;
7116
7117 beforeEach(() => {
7118 const config = {
7119 plugins: {
7120 test: {
7121 rules: {
7122 "save-scope1": context => ({
7123 Program() {
7124 scope = context.getScope();
7125 }
7126 })
7127 }
7128 }
7129 },
7130 languageOptions: {
7131 parser: testParsers.enhancedParser3
7132 },
7133 rules: {
7134 "test/save-scope1": "error"
7135 }
7136 };
7137
7138 linter.verify("@foo class A {}", config);
7139
7140 sourceCode = linter.getSourceCode();
7141 });
7142
7143 it("should use the scope (so the global scope has the reference of '@foo')", () => {
7144 assert.strictEqual(scope.references.length, 1);
7145 assert.deepStrictEqual(
7146 scope.references[0].identifier.name,
7147 "foo"
7148 );
7149 });
7150
7151 it("should use the same scope if the source code object is reused", () => {
7152 let scope2 = null;
7153 const config = {
7154 plugins: {
7155 test: {
7156 rules: {
7157 "save-scope2": context => ({
7158 Program() {
7159 scope2 = context.getScope();
7160 }
7161 })
7162 }
7163 }
7164 },
7165 rules: {
7166 "test/save-scope2": "error"
7167 }
7168 };
7169
7170 linter.verify(sourceCode, config, "test.js");
7171
7172 assert(scope2 !== null);
7173 assert(scope2 === scope);
7174 });
7175 });
7176
7177 it("should pass default languageOptions to the parser", () => {
7178
7179 const spy = sinon.spy((code, options) => espree.parse(code, options));
7180
7181 linter.verify(";", {
7182 languageOptions: {
7183 parser: {
7184 parse: spy
7185 }
7186 }
7187 }, "filename.js");
7188
7189 assert(spy.calledWithMatch(";", {
7190 ecmaVersion: espree.latestEcmaVersion + 2009,
7191 sourceType: "module"
7192 }));
7193 });
7194 });
7195
7196
7197 });
7198
7199 describe("parseOptions", () => {
7200
7201 it("should pass ecmaFeatures to all rules when provided on config", () => {
7202
7203 const parserOptions = {
7204 ecmaFeatures: {
7205 jsx: true
7206 }
7207 };
7208
7209 const config = {
7210 plugins: {
7211 test: {
7212 rules: {
7213 "test-rule": sinon.mock().withArgs(
7214 sinon.match({ languageOptions: { parserOptions } })
7215 ).returns({})
7216 }
7217 }
7218 },
7219 languageOptions: {
7220 parserOptions
7221 },
7222 rules: {
7223 "test/test-rule": 2
7224 }
7225 };
7226
7227 linter.verify("0", config, filename);
7228 });
7229
7230 it("should switch globalReturn to false if sourceType is module", () => {
7231
7232 const config = {
7233 plugins: {
7234 test: {
7235 rules: {
7236 "test-rule": sinon.mock().withArgs(
7237 sinon.match({
7238 languageOptions: {
7239 parserOptions: {
7240 ecmaFeatures: {
7241 globalReturn: false
7242 }
7243 }
7244 }
7245 })
7246 ).returns({})
7247 }
7248 }
7249 },
7250 languageOptions: {
7251 sourceType: "module",
7252 parserOptions: {
7253 ecmaFeatures: {
7254 globalReturn: true
7255 }
7256 }
7257 },
7258 rules: {
7259 "test/test-rule": 2
7260 }
7261 };
7262
7263 linter.verify("0", config, filename);
7264 });
7265
7266 it("should not parse sloppy-mode code when impliedStrict is true", () => {
7267
7268 const messages = linter.verify("var private;", {
7269 languageOptions: {
7270 parserOptions: {
7271 ecmaFeatures: {
7272 impliedStrict: true
7273 }
7274 }
7275 }
7276 }, filename);
7277
7278 assert.strictEqual(messages.length, 1);
7279 assert.strictEqual(messages[0].message, "Parsing error: The keyword 'private' is reserved");
7280 });
7281
7282 it("should properly parse valid code when impliedStrict is true", () => {
7283
7284 const messages = linter.verify("var foo;", {
7285 languageOptions: {
7286 parserOptions: {
7287 ecmaFeatures: {
7288 impliedStrict: true
7289 }
7290 }
7291 }
7292 }, filename);
7293
7294 assert.strictEqual(messages.length, 0);
7295 });
7296
7297 it("should properly parse JSX when passed ecmaFeatures", () => {
7298
7299 const messages = linter.verify("var x = <div/>;", {
7300 languageOptions: {
7301 parserOptions: {
7302 ecmaFeatures: {
7303 jsx: true
7304 }
7305 }
7306 }
7307 }, filename);
7308
7309 assert.strictEqual(messages.length, 0);
7310 });
7311
7312 it("should report an error when JSX code is encountered and JSX is not enabled", () => {
7313 const code = "var myDivElement = <div className=\"foo\" />;";
7314 const messages = linter.verify(code, {}, "filename");
7315
7316 assert.strictEqual(messages.length, 1);
7317 assert.strictEqual(messages[0].line, 1);
7318 assert.strictEqual(messages[0].column, 20);
7319 assert.strictEqual(messages[0].message, "Parsing error: Unexpected token <");
7320 });
7321
7322 it("should not report an error when JSX code is encountered and JSX is enabled", () => {
7323 const code = "var myDivElement = <div className=\"foo\" />;";
7324 const messages = linter.verify(code, {
7325 languageOptions: {
7326 parserOptions: {
7327 ecmaFeatures: {
7328 jsx: true
7329 }
7330 }
7331 }
7332
7333 }, "filename");
7334
7335 assert.strictEqual(messages.length, 0);
7336 });
7337
7338 it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => {
7339 const code = "var myDivElement = <div {...this.props} />;";
7340 const messages = linter.verify(code, {
7341 languageOptions: {
7342 ecmaVersion: 6,
7343 parserOptions: {
7344 ecmaFeatures: {
7345 jsx: true
7346 }
7347 }
7348 }
7349
7350 }, "filename");
7351
7352 assert.strictEqual(messages.length, 0);
7353 });
7354
7355 });
7356
7357 });
7358
7359 describe("settings", () => {
7360 const ruleId = "test-rule";
7361
7362 it("should pass settings to all rules", () => {
7363
7364 const config = {
7365 plugins: {
7366 test: {
7367 rules: {
7368 [ruleId]: context => ({
7369 Literal(node) {
7370 context.report(node, context.settings.info);
7371 }
7372 })
7373 }
7374 }
7375 },
7376 settings: {
7377 info: "Hello"
7378 },
7379 rules: {
7380 [`test/${ruleId}`]: 1
7381 }
7382 };
7383
7384 const messages = linter.verify("0", config, filename);
7385
7386 assert.strictEqual(messages.length, 1);
7387 assert.strictEqual(messages[0].message, "Hello");
7388 });
7389
7390 it("should not have any settings if they were not passed in", () => {
7391
7392 const config = {
7393 plugins: {
7394 test: {
7395 rules: {
7396 [ruleId]: context => ({
7397 Literal(node) {
7398 if (Object.getOwnPropertyNames(context.settings).length !== 0) {
7399 context.report(node, "Settings should be empty");
7400 }
7401 }
7402 })
7403 }
7404 }
7405 },
7406 settings: {
7407 },
7408 rules: {
7409 [`test/${ruleId}`]: 1
7410 }
7411 };
7412
7413 const messages = linter.verify("0", config, filename);
7414
7415 assert.strictEqual(messages.length, 0);
7416 });
7417 });
7418
7419 describe("rules", () => {
7420 const code = "var answer = 6 * 7";
7421
7422 it("should be configurable by only setting the integer value", () => {
7423 const rule = "semi",
7424 config = { rules: {} };
7425
7426 config.rules[rule] = 1;
7427
7428 const messages = linter.verify(code, config, filename, true);
7429
7430 assert.strictEqual(messages.length, 1);
7431 assert.strictEqual(messages[0].ruleId, rule);
7432 });
7433
7434 it("should be configurable by only setting the string value", () => {
7435 const rule = "semi",
7436 config = { rules: {} };
7437
7438 config.rules[rule] = "warn";
7439
7440 const messages = linter.verify(code, config, filename, true);
7441
7442 assert.strictEqual(messages.length, 1);
7443 assert.strictEqual(messages[0].severity, 1);
7444 assert.strictEqual(messages[0].ruleId, rule);
7445 });
7446
7447 it("should be configurable by passing in values as an array", () => {
7448 const rule = "semi",
7449 config = { rules: {} };
7450
7451 config.rules[rule] = [1];
7452
7453 const messages = linter.verify(code, config, filename, true);
7454
7455 assert.strictEqual(messages.length, 1);
7456 assert.strictEqual(messages[0].ruleId, rule);
7457 });
7458
7459 it("should be configurable by passing in string value as an array", () => {
7460 const rule = "semi",
7461 config = { rules: {} };
7462
7463 config.rules[rule] = ["warn"];
7464
7465 const messages = linter.verify(code, config, filename, true);
7466
7467 assert.strictEqual(messages.length, 1);
7468 assert.strictEqual(messages[0].severity, 1);
7469 assert.strictEqual(messages[0].ruleId, rule);
7470 });
7471
7472 it("should not be configurable by setting other value", () => {
7473 const rule = "semi",
7474 config = { rules: {} };
7475
7476 config.rules[rule] = "1";
7477
7478 assert.throws(() => {
7479 linter.verify(code, config, filename, true);
7480 }, /Key "rules": Key "semi"/u);
7481 });
7482
7483 it("should process empty config", () => {
7484 const config = {};
7485 const messages = linter.verify(code, config, filename, true);
7486
7487 assert.strictEqual(messages.length, 0);
7488 });
7489 });
7490
7491 });
7492
7493 describe("verify()", () => {
7494
7495 it("should report warnings in order by line and column when called", () => {
7496
7497 const code = "foo()\n alert('test')";
7498 const config = { rules: { "no-mixed-spaces-and-tabs": 1, "eol-last": 1, semi: [1, "always"] } };
7499
7500 const messages = linter.verify(code, config, filename);
7501
7502 assert.strictEqual(messages.length, 3);
7503 assert.strictEqual(messages[0].line, 1);
7504 assert.strictEqual(messages[0].column, 6);
7505 assert.strictEqual(messages[1].line, 2);
7506 assert.strictEqual(messages[1].column, 18);
7507 assert.strictEqual(messages[2].line, 2);
7508 assert.strictEqual(messages[2].column, 18);
7509 });
7510
7511 describe("Plugins", () => {
7512
7513 it("should not load rule definition when rule isn't used", () => {
7514
7515 const spy = sinon.spy();
7516
7517 const config = {
7518 plugins: {
7519 test: {
7520 rules: {
7521 checker: spy
7522 }
7523 }
7524 }
7525 };
7526
7527 linter.verify("code", config, filename);
7528 assert.isTrue(spy.notCalled, "Rule should not have been called");
7529 });
7530 });
7531
7532 describe("Rule Internals", () => {
7533
7534 const code = TEST_CODE;
7535
7536 it("should throw an error when an error occurs inside of a rule visitor", () => {
7537 const config = {
7538 plugins: {
7539 test: {
7540 rules: {
7541 checker: () => ({
7542 Program() {
7543 throw new Error("Intentional error.");
7544 }
7545 })
7546 }
7547 }
7548 },
7549 rules: { "test/checker": "error" }
7550 };
7551
7552 assert.throws(() => {
7553 linter.verify(code, config, filename);
7554 }, `Intentional error.\nOccurred while linting ${filename}:1\nRule: "test/checker"`);
7555 });
7556
7557 it("should not call rule visitor with a `this` value", () => {
7558 const spy = sinon.spy();
7559 const config = {
7560 plugins: {
7561 test: {
7562 rules: {
7563 checker: () => ({
7564 Program: spy
7565 })
7566 }
7567 }
7568 },
7569 rules: { "test/checker": "error" }
7570 };
7571
7572 linter.verify("foo", config);
7573 assert(spy.calledOnce);
7574 assert.strictEqual(spy.firstCall.thisValue, void 0);
7575 });
7576
7577 it("should not call unrecognized rule visitor when present in a rule", () => {
7578 const spy = sinon.spy();
7579 const config = {
7580 plugins: {
7581 test: {
7582 rules: {
7583 checker: () => ({
7584 newListener: spy
7585 })
7586 }
7587 }
7588 },
7589 rules: {
7590 "test/checker": "error",
7591 "no-undef": "error"
7592 }
7593 };
7594
7595 linter.verify("foo", config);
7596 assert(spy.notCalled);
7597 });
7598
7599 it("should have all the `parent` properties on nodes when the rule visitors are created", () => {
7600 const spy = sinon.spy(context => {
7601 const ast = context.getSourceCode().ast;
7602
7603 assert.strictEqual(ast.body[0].parent, ast);
7604 assert.strictEqual(ast.body[0].expression.parent, ast.body[0]);
7605 assert.strictEqual(ast.body[0].expression.left.parent, ast.body[0].expression);
7606 assert.strictEqual(ast.body[0].expression.right.parent, ast.body[0].expression);
7607
7608 return {};
7609 });
7610
7611 const config = {
7612 plugins: {
7613 test: {
7614 rules: {
7615 checker: spy
7616 }
7617 }
7618 },
7619 rules: { "test/checker": "error" }
7620 };
7621
7622 linter.verify("foo + bar", config);
7623 assert(spy.calledOnce);
7624 });
7625
7626 it("events for each node type should fire", () => {
7627
7628 // spies for various AST node types
7629 const spyLiteral = sinon.spy(),
7630 spyVariableDeclarator = sinon.spy(),
7631 spyVariableDeclaration = sinon.spy(),
7632 spyIdentifier = sinon.spy(),
7633 spyBinaryExpression = sinon.spy();
7634
7635 const config = {
7636 plugins: {
7637 test: {
7638 rules: {
7639 checker() {
7640 return {
7641 Literal: spyLiteral,
7642 VariableDeclarator: spyVariableDeclarator,
7643 VariableDeclaration: spyVariableDeclaration,
7644 Identifier: spyIdentifier,
7645 BinaryExpression: spyBinaryExpression
7646 };
7647 }
7648 }
7649 }
7650 },
7651 rules: { "test/checker": "error" }
7652 };
7653
7654 const messages = linter.verify(code, config, filename, true);
7655
7656 assert.strictEqual(messages.length, 0);
7657 sinon.assert.calledOnce(spyVariableDeclaration);
7658 sinon.assert.calledOnce(spyVariableDeclarator);
7659 sinon.assert.calledOnce(spyIdentifier);
7660 sinon.assert.calledTwice(spyLiteral);
7661 sinon.assert.calledOnce(spyBinaryExpression);
7662 });
7663
7664 it("should throw an error if a rule reports a problem without a message", () => {
7665
7666 const config = {
7667 plugins: {
7668 test: {
7669 rules: {
7670 "invalid-report"(context) {
7671 return {
7672 Program(node) {
7673 context.report({ node });
7674 }
7675 };
7676 }
7677 }
7678 }
7679 },
7680 rules: { "test/invalid-report": "error" }
7681 };
7682
7683 assert.throws(
7684 () => linter.verify("foo", config),
7685 TypeError,
7686 "Missing `message` property in report() call; add a message that describes the linting problem."
7687 );
7688 });
7689
7690
7691 });
7692
7693 describe("Rule Context", () => {
7694
7695 describe("context.getFilename()", () => {
7696 const ruleId = "filename-rule";
7697
7698 it("has access to the filename", () => {
7699
7700 const config = {
7701 plugins: {
7702 test: {
7703 rules: {
7704 [ruleId]: context => ({
7705 Literal(node) {
7706 context.report(node, context.getFilename());
7707 }
7708 })
7709 }
7710 }
7711 },
7712 rules: {
7713 [`test/${ruleId}`]: 1
7714 }
7715 };
7716
7717 const messages = linter.verify("0", config, filename);
7718
7719 assert.strictEqual(messages[0].message, filename);
7720 });
7721
7722 it("defaults filename to '<input>'", () => {
7723
7724 const config = {
7725 plugins: {
7726 test: {
7727 rules: {
7728 [ruleId]: context => ({
7729 Literal(node) {
7730 context.report(node, context.getFilename());
7731 }
7732 })
7733 }
7734 }
7735 },
7736 rules: {
7737 [`test/${ruleId}`]: 1
7738 }
7739 };
7740
7741
7742 const messages = linter.verify("0", config);
7743
7744 assert.strictEqual(messages[0].message, "<input>");
7745 });
7746 });
7747
7748 describe("context.getPhysicalFilename()", () => {
7749
7750 const ruleId = "filename-rule";
7751
7752 it("has access to the physicalFilename", () => {
7753
7754 const config = {
7755 plugins: {
7756 test: {
7757 rules: {
7758 [ruleId]: context => ({
7759 Literal(node) {
7760 context.report(node, context.getPhysicalFilename());
7761 }
7762 })
7763 }
7764 }
7765 },
7766 rules: {
7767 [`test/${ruleId}`]: 1
7768 }
7769 };
7770
7771 const messages = linter.verify("0", config, filename);
7772
7773 assert.strictEqual(messages[0].message, filename);
7774 });
7775
7776 });
7777
7778 describe("context.getSourceLines()", () => {
7779
7780 it("should get proper lines when using \\n as a line break", () => {
7781 const code = "a;\nb;";
7782 const spy = sinon.spy(context => {
7783 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
7784 return {};
7785 });
7786
7787 const config = {
7788 plugins: {
7789 test: {
7790 rules: {
7791 checker: spy
7792 }
7793 }
7794 },
7795 rules: { "test/checker": "error" }
7796 };
7797
7798 linter.verify(code, config);
7799 assert(spy.calledOnce);
7800 });
7801
7802 it("should get proper lines when using \\r\\n as a line break", () => {
7803 const code = "a;\r\nb;";
7804 const spy = sinon.spy(context => {
7805 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
7806 return {};
7807 });
7808
7809 const config = {
7810 plugins: {
7811 test: {
7812 rules: {
7813 checker: spy
7814 }
7815 }
7816 },
7817 rules: { "test/checker": "error" }
7818 };
7819
7820 linter.verify(code, config);
7821 assert(spy.calledOnce);
7822 });
7823
7824 it("should get proper lines when using \\r as a line break", () => {
7825 const code = "a;\rb;";
7826 const spy = sinon.spy(context => {
7827 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
7828 return {};
7829 });
7830
7831 const config = {
7832 plugins: {
7833 test: {
7834 rules: {
7835 checker: spy
7836 }
7837 }
7838 },
7839 rules: { "test/checker": "error" }
7840 };
7841
7842 linter.verify(code, config);
7843 assert(spy.calledOnce);
7844 });
7845
7846 it("should get proper lines when using \\u2028 as a line break", () => {
7847 const code = "a;\u2028b;";
7848 const spy = sinon.spy(context => {
7849 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
7850 return {};
7851 });
7852
7853 const config = {
7854 plugins: {
7855 test: {
7856 rules: {
7857 checker: spy
7858 }
7859 }
7860 },
7861 rules: { "test/checker": "error" }
7862 };
7863
7864 linter.verify(code, config);
7865 assert(spy.calledOnce);
7866 });
7867
7868 it("should get proper lines when using \\u2029 as a line break", () => {
7869 const code = "a;\u2029b;";
7870 const spy = sinon.spy(context => {
7871 assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
7872 return {};
7873 });
7874
7875 const config = {
7876 plugins: {
7877 test: {
7878 rules: {
7879 checker: spy
7880 }
7881 }
7882 },
7883 rules: { "test/checker": "error" }
7884 };
7885
7886 linter.verify(code, config);
7887 assert(spy.calledOnce);
7888 });
7889
7890 });
7891
7892 describe("context.getSource()", () => {
7893 const code = TEST_CODE;
7894
7895 it("should retrieve all text when used without parameters", () => {
7896
7897 let spy;
7898
7899 const config = {
7900 plugins: {
7901 test: {
7902 rules: {
7903 checker: context => {
7904 spy = sinon.spy(() => {
7905 assert.strictEqual(context.getSource(), TEST_CODE);
7906 });
7907 return { Program: spy };
7908 }
7909 }
7910 }
7911 },
7912 rules: { "test/checker": "error" }
7913 };
7914
7915 linter.verify(code, config);
7916 assert(spy && spy.calledOnce);
7917 });
7918
7919 it("should retrieve all text for root node", () => {
7920
7921 let spy;
7922
7923 const config = {
7924 plugins: {
7925 test: {
7926 rules: {
7927 checker: context => {
7928 spy = sinon.spy(node => {
7929 assert.strictEqual(context.getSource(node), TEST_CODE);
7930 });
7931 return { Program: spy };
7932 }
7933 }
7934 }
7935 },
7936 rules: { "test/checker": "error" }
7937 };
7938
7939 linter.verify(code, config);
7940 assert(spy && spy.calledOnce);
7941 });
7942
7943 it("should clamp to valid range when retrieving characters before start of source", () => {
7944 let spy;
7945
7946 const config = {
7947 plugins: {
7948 test: {
7949 rules: {
7950 checker: context => {
7951 spy = sinon.spy(node => {
7952 assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE);
7953 });
7954 return { Program: spy };
7955 }
7956 }
7957 }
7958 },
7959 rules: { "test/checker": "error" }
7960 };
7961
7962 linter.verify(code, config);
7963 assert(spy && spy.calledOnce);
7964 });
7965
7966 it("should retrieve all text for binary expression", () => {
7967 let spy;
7968
7969 const config = {
7970 plugins: {
7971 test: {
7972 rules: {
7973 checker: context => {
7974 spy = sinon.spy(node => {
7975 assert.strictEqual(context.getSource(node), "6 * 7");
7976 });
7977 return { BinaryExpression: spy };
7978 }
7979 }
7980 }
7981 },
7982 rules: { "test/checker": "error" }
7983 };
7984
7985 linter.verify(code, config);
7986 assert(spy && spy.calledOnce);
7987 });
7988
7989 it("should retrieve all text plus two characters before for binary expression", () => {
7990 let spy;
7991
7992 const config = {
7993 plugins: {
7994 test: {
7995 rules: {
7996 checker: context => {
7997 spy = sinon.spy(node => {
7998 assert.strictEqual(context.getSource(node, 2), "= 6 * 7");
7999 });
8000 return { BinaryExpression: spy };
8001 }
8002 }
8003 }
8004 },
8005 rules: { "test/checker": "error" }
8006 };
8007
8008 linter.verify(code, config);
8009 assert(spy && spy.calledOnce);
8010 });
8011
8012 it("should retrieve all text plus one character after for binary expression", () => {
8013 let spy;
8014
8015 const config = {
8016 plugins: {
8017 test: {
8018 rules: {
8019 checker: context => {
8020 spy = sinon.spy(node => {
8021 assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;");
8022 });
8023 return { BinaryExpression: spy };
8024 }
8025 }
8026 }
8027 },
8028 rules: { "test/checker": "error" }
8029 };
8030
8031 linter.verify(code, config);
8032 assert(spy && spy.calledOnce);
8033 });
8034
8035 it("should retrieve all text plus two characters before and one character after for binary expression", () => {
8036 let spy;
8037
8038 const config = {
8039 plugins: {
8040 test: {
8041 rules: {
8042 checker: context => {
8043 spy = sinon.spy(node => {
8044 assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;");
8045 });
8046 return { BinaryExpression: spy };
8047 }
8048 }
8049 }
8050 },
8051 rules: { "test/checker": "error" }
8052 };
8053
8054 linter.verify(code, config);
8055 assert(spy && spy.calledOnce);
8056 });
8057
8058 });
8059
8060 describe("context.getAncestors()", () => {
8061 const code = TEST_CODE;
8062
8063 it("should retrieve all ancestors when used", () => {
8064
8065 let spy;
8066
8067 const config = {
8068 plugins: {
8069 test: {
8070 rules: {
8071 checker: context => {
8072 spy = sinon.spy(() => {
8073 const ancestors = context.getAncestors();
8074
8075 assert.strictEqual(ancestors.length, 3);
8076 });
8077 return { BinaryExpression: spy };
8078 }
8079 }
8080 }
8081 },
8082 rules: { "test/checker": "error" }
8083 };
8084
8085 linter.verify(code, config, filename, true);
8086 assert(spy && spy.calledOnce);
8087 });
8088
8089 it("should retrieve empty ancestors for root node", () => {
8090 let spy;
8091
8092 const config = {
8093 plugins: {
8094 test: {
8095 rules: {
8096 checker: context => {
8097 spy = sinon.spy(() => {
8098 const ancestors = context.getAncestors();
8099
8100 assert.strictEqual(ancestors.length, 0);
8101 });
8102
8103 return { Program: spy };
8104 }
8105 }
8106 }
8107 },
8108 rules: { "test/checker": "error" }
8109 };
8110
8111 linter.verify(code, config);
8112 assert(spy && spy.calledOnce);
8113 });
8114 });
8115
8116 describe("context.getNodeByRangeIndex()", () => {
8117 const code = TEST_CODE;
8118
8119 it("should retrieve a node starting at the given index", () => {
8120 const spy = sinon.spy(context => {
8121 assert.strictEqual(context.getNodeByRangeIndex(4).type, "Identifier");
8122 return {};
8123 });
8124
8125 const config = {
8126 plugins: {
8127 test: {
8128 rules: {
8129 checker: spy
8130 }
8131 }
8132 },
8133 rules: { "test/checker": "error" }
8134 };
8135
8136 linter.verify(code, config);
8137 assert(spy.calledOnce);
8138 });
8139
8140 it("should retrieve a node containing the given index", () => {
8141 const spy = sinon.spy(context => {
8142 assert.strictEqual(context.getNodeByRangeIndex(6).type, "Identifier");
8143 return {};
8144 });
8145
8146 const config = {
8147 plugins: {
8148 test: {
8149 rules: {
8150 checker: spy
8151 }
8152 }
8153 },
8154 rules: { "test/checker": "error" }
8155 };
8156
8157 linter.verify(code, config);
8158 assert(spy.calledOnce);
8159 });
8160
8161 it("should retrieve a node that is exactly the given index", () => {
8162 const spy = sinon.spy(context => {
8163 const node = context.getNodeByRangeIndex(13);
8164
8165 assert.strictEqual(node.type, "Literal");
8166 assert.strictEqual(node.value, 6);
8167 return {};
8168 });
8169
8170 const config = {
8171 plugins: {
8172 test: {
8173 rules: {
8174 checker: spy
8175 }
8176 }
8177 },
8178 rules: { "test/checker": "error" }
8179 };
8180
8181 linter.verify(code, config);
8182 assert(spy.calledOnce);
8183 });
8184
8185 it("should retrieve a node ending with the given index", () => {
8186 const spy = sinon.spy(context => {
8187 assert.strictEqual(context.getNodeByRangeIndex(9).type, "Identifier");
8188 return {};
8189 });
8190
8191 const config = {
8192 plugins: {
8193 test: {
8194 rules: {
8195 checker: spy
8196 }
8197 }
8198 },
8199 rules: { "test/checker": "error" }
8200 };
8201
8202 linter.verify(code, config);
8203 assert(spy.calledOnce);
8204 });
8205
8206 it("should retrieve the deepest node containing the given index", () => {
8207 const spy = sinon.spy(context => {
8208 const node1 = context.getNodeByRangeIndex(14);
8209
8210 assert.strictEqual(node1.type, "BinaryExpression");
8211
8212 const node2 = context.getNodeByRangeIndex(3);
8213
8214 assert.strictEqual(node2.type, "VariableDeclaration");
8215 return {};
8216 });
8217
8218 const config = {
8219 plugins: {
8220 test: {
8221 rules: {
8222 checker: spy
8223 }
8224 }
8225 },
8226 rules: { "test/checker": "error" }
8227 };
8228
8229 linter.verify(code, config);
8230 assert(spy.calledOnce);
8231 });
8232
8233 it("should return null if the index is outside the range of any node", () => {
8234 const spy = sinon.spy(context => {
8235 const node1 = context.getNodeByRangeIndex(-1);
8236
8237 assert.isNull(node1);
8238
8239 const node2 = context.getNodeByRangeIndex(-99);
8240
8241 assert.isNull(node2);
8242 return {};
8243 });
8244
8245 const config = {
8246 plugins: {
8247 test: {
8248 rules: {
8249 checker: spy
8250 }
8251 }
8252 },
8253 rules: { "test/checker": "error" }
8254 };
8255
8256 linter.verify(code, config);
8257 assert(spy.calledOnce);
8258 });
8259 });
8260
8261 describe("context.getScope()", () => {
8262 const codeToTestScope = "function foo() { q: for(;;) { break q; } } function bar () { var q = t; } var baz = (() => { return 1; });";
8263
8264 it("should retrieve the global scope correctly from a Program", () => {
8265 let spy;
8266
8267 const config = {
8268 plugins: {
8269 test: {
8270 rules: {
8271 checker: context => {
8272 spy = sinon.spy(() => {
8273 const scope = context.getScope();
8274
8275 assert.strictEqual(scope.type, "global");
8276 });
8277 return { Program: spy };
8278 }
8279 }
8280 }
8281 },
8282 languageOptions: {
8283 ecmaVersion: 6
8284 },
8285 rules: { "test/checker": "error" }
8286 };
8287
8288 linter.verify(codeToTestScope, config);
8289 assert(spy && spy.calledOnce);
8290 });
8291
8292 it("should retrieve the function scope correctly from a FunctionDeclaration", () => {
8293 let spy;
8294
8295 const config = {
8296 plugins: {
8297 test: {
8298 rules: {
8299 checker: context => {
8300 spy = sinon.spy(() => {
8301 const scope = context.getScope();
8302
8303 assert.strictEqual(scope.type, "function");
8304 });
8305 return { FunctionDeclaration: spy };
8306 }
8307 }
8308 }
8309 },
8310 languageOptions: {
8311 ecmaVersion: 6
8312 },
8313 rules: { "test/checker": "error" }
8314 };
8315
8316 linter.verify(codeToTestScope, config);
8317 assert(spy && spy.calledTwice);
8318 });
8319
8320 it("should retrieve the function scope correctly from a LabeledStatement", () => {
8321 let spy;
8322
8323 const config = {
8324 plugins: {
8325 test: {
8326 rules: {
8327 checker: context => {
8328 spy = sinon.spy(() => {
8329 const scope = context.getScope();
8330
8331 assert.strictEqual(scope.type, "function");
8332 assert.strictEqual(scope.block.id.name, "foo");
8333 });
8334 return { LabeledStatement: spy };
8335 }
8336 }
8337 }
8338 },
8339 languageOptions: {
8340 ecmaVersion: 6
8341 },
8342 rules: { "test/checker": "error" }
8343 };
8344
8345
8346 linter.verify(codeToTestScope, config);
8347 assert(spy && spy.calledOnce);
8348 });
8349
8350 it("should retrieve the function scope correctly from within an ArrowFunctionExpression", () => {
8351 let spy;
8352
8353 const config = {
8354 plugins: {
8355 test: {
8356 rules: {
8357 checker: context => {
8358 spy = sinon.spy(() => {
8359 const scope = context.getScope();
8360
8361 assert.strictEqual(scope.type, "function");
8362 assert.strictEqual(scope.block.type, "ArrowFunctionExpression");
8363 });
8364
8365 return { ReturnStatement: spy };
8366 }
8367 }
8368 }
8369 },
8370 languageOptions: {
8371 ecmaVersion: 6
8372 },
8373 rules: { "test/checker": "error" }
8374 };
8375
8376
8377 linter.verify(codeToTestScope, config);
8378 assert(spy && spy.calledOnce);
8379 });
8380
8381 it("should retrieve the function scope correctly from within an SwitchStatement", () => {
8382 let spy;
8383
8384 const config = {
8385 plugins: {
8386 test: {
8387 rules: {
8388 checker: context => {
8389 spy = sinon.spy(() => {
8390 const scope = context.getScope();
8391
8392 assert.strictEqual(scope.type, "switch");
8393 assert.strictEqual(scope.block.type, "SwitchStatement");
8394 });
8395
8396 return { SwitchStatement: spy };
8397 }
8398 }
8399 }
8400 },
8401 languageOptions: {
8402 ecmaVersion: 6
8403 },
8404 rules: { "test/checker": "error" }
8405 };
8406
8407 linter.verify("switch(foo){ case 'a': var b = 'foo'; }", config);
8408 assert(spy && spy.calledOnce);
8409 });
8410
8411 it("should retrieve the function scope correctly from within a BlockStatement", () => {
8412 let spy;
8413
8414 const config = {
8415 plugins: {
8416 test: {
8417 rules: {
8418 checker: context => {
8419 spy = sinon.spy(() => {
8420 const scope = context.getScope();
8421
8422 assert.strictEqual(scope.type, "block");
8423 assert.strictEqual(scope.block.type, "BlockStatement");
8424 });
8425
8426 return { BlockStatement: spy };
8427 }
8428 }
8429 }
8430 },
8431 languageOptions: {
8432 ecmaVersion: 6
8433 },
8434 rules: { "test/checker": "error" }
8435 };
8436
8437
8438 linter.verify("var x; {let y = 1}", config);
8439 assert(spy && spy.calledOnce);
8440 });
8441
8442 it("should retrieve the function scope correctly from within a nested block statement", () => {
8443 let spy;
8444
8445 const config = {
8446 plugins: {
8447 test: {
8448 rules: {
8449 checker: context => {
8450 spy = sinon.spy(() => {
8451 const scope = context.getScope();
8452
8453 assert.strictEqual(scope.type, "block");
8454 assert.strictEqual(scope.block.type, "BlockStatement");
8455 });
8456
8457 return { BlockStatement: spy };
8458 }
8459 }
8460 }
8461 },
8462 languageOptions: {
8463 ecmaVersion: 6
8464 },
8465 rules: { "test/checker": "error" }
8466 };
8467
8468
8469 linter.verify("if (true) { let x = 1 }", config);
8470 assert(spy && spy.calledOnce);
8471 });
8472
8473 it("should retrieve the function scope correctly from within a FunctionDeclaration", () => {
8474 let spy;
8475
8476 const config = {
8477 plugins: {
8478 test: {
8479 rules: {
8480 checker: context => {
8481 spy = sinon.spy(() => {
8482 const scope = context.getScope();
8483
8484 assert.strictEqual(scope.type, "function");
8485 assert.strictEqual(scope.block.type, "FunctionDeclaration");
8486 });
8487
8488 return { FunctionDeclaration: spy };
8489 }
8490 }
8491 }
8492 },
8493 languageOptions: {
8494 ecmaVersion: 6
8495 },
8496 rules: { "test/checker": "error" }
8497 };
8498
8499
8500 linter.verify("function foo() {}", config);
8501 assert(spy && spy.calledOnce);
8502 });
8503
8504 it("should retrieve the function scope correctly from within a FunctionExpression", () => {
8505 let spy;
8506
8507 const config = {
8508 plugins: {
8509 test: {
8510 rules: {
8511 checker: context => {
8512 spy = sinon.spy(() => {
8513 const scope = context.getScope();
8514
8515 assert.strictEqual(scope.type, "function");
8516 assert.strictEqual(scope.block.type, "FunctionExpression");
8517 });
8518
8519 return { FunctionExpression: spy };
8520 }
8521 }
8522 }
8523 },
8524 languageOptions: {
8525 ecmaVersion: 6
8526 },
8527 rules: { "test/checker": "error" }
8528 };
8529
8530
8531 linter.verify("(function foo() {})();", config);
8532 assert(spy && spy.calledOnce);
8533 });
8534
8535 it("should retrieve the catch scope correctly from within a CatchClause", () => {
8536 let spy;
8537
8538 const config = {
8539 plugins: {
8540 test: {
8541 rules: {
8542 checker: context => {
8543 spy = sinon.spy(() => {
8544 const scope = context.getScope();
8545
8546 assert.strictEqual(scope.type, "catch");
8547 assert.strictEqual(scope.block.type, "CatchClause");
8548 });
8549
8550 return { CatchClause: spy };
8551 }
8552 }
8553 }
8554 },
8555 languageOptions: {
8556 ecmaVersion: 6
8557 },
8558 rules: { "test/checker": "error" }
8559 };
8560
8561 linter.verify("try {} catch (err) {}", config);
8562 assert(spy && spy.calledOnce);
8563 });
8564
8565 it("should retrieve module scope correctly from an ES6 module", () => {
8566 let spy;
8567
8568 const config = {
8569 plugins: {
8570 test: {
8571 rules: {
8572 checker: context => {
8573 spy = sinon.spy(() => {
8574 const scope = context.getScope();
8575
8576 assert.strictEqual(scope.type, "module");
8577 });
8578
8579 return { AssignmentExpression: spy };
8580 }
8581 }
8582 }
8583 },
8584 languageOptions: {
8585 ecmaVersion: 6,
8586 sourceType: "module"
8587 },
8588 rules: { "test/checker": "error" }
8589 };
8590
8591
8592 linter.verify("var foo = {}; foo.bar = 1;", config);
8593 assert(spy && spy.calledOnce);
8594 });
8595
8596 it("should retrieve function scope correctly when sourceType is commonjs", () => {
8597 let spy;
8598
8599 const config = {
8600 plugins: {
8601 test: {
8602 rules: {
8603 checker: context => {
8604 spy = sinon.spy(() => {
8605 const scope = context.getScope();
8606
8607 assert.strictEqual(scope.type, "function");
8608 });
8609
8610 return { AssignmentExpression: spy };
8611 }
8612 }
8613 }
8614 },
8615 languageOptions: {
8616 ecmaVersion: 6,
8617 sourceType: "commonjs"
8618 },
8619 rules: { "test/checker": "error" }
8620 };
8621
8622 linter.verify("var foo = {}; foo.bar = 1;", config);
8623 assert(spy && spy.calledOnce);
8624 });
8625
8626 describe("Scope Internals", () => {
8627
8628 /**
8629 * Get the scope on the node `astSelector` specified.
8630 * @param {string} codeToEvaluate The source code to verify.
8631 * @param {string} astSelector The AST selector to get scope.
8632 * @param {number} [ecmaVersion=5] The ECMAScript version.
8633 * @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope.
8634 */
8635 function getScope(codeToEvaluate, astSelector, ecmaVersion = 5) {
8636 let node, scope;
8637
8638 const config = {
8639 plugins: {
8640 test: {
8641 rules: {
8642 "get-scope": context => ({
8643 [astSelector](node0) {
8644 node = node0;
8645 scope = context.getScope();
8646 }
8647 })
8648 }
8649 }
8650 },
8651 languageOptions: {
8652 ecmaVersion,
8653 sourceType: "script"
8654 },
8655 rules: { "test/get-scope": "error" }
8656 };
8657
8658 linter.verify(
8659 codeToEvaluate,
8660 config
8661 );
8662
8663 return { node, scope };
8664 }
8665
8666 it("should return 'function' scope on FunctionDeclaration (ES5)", () => {
8667 const { node, scope } = getScope("function f() {}", "FunctionDeclaration");
8668
8669 assert.strictEqual(scope.type, "function");
8670 assert.strictEqual(scope.block, node);
8671 });
8672
8673 it("should return 'function' scope on FunctionExpression (ES5)", () => {
8674 const { node, scope } = getScope("!function f() {}", "FunctionExpression");
8675
8676 assert.strictEqual(scope.type, "function");
8677 assert.strictEqual(scope.block, node);
8678 });
8679
8680 it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => {
8681 const { node, scope } = getScope("function f() {}", "BlockStatement");
8682
8683 assert.strictEqual(scope.type, "function");
8684 assert.strictEqual(scope.block, node.parent);
8685 });
8686
8687 it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => {
8688 const { node, scope } = getScope("function f() {}", "BlockStatement", 2015);
8689
8690 assert.strictEqual(scope.type, "function");
8691 assert.strictEqual(scope.block, node.parent);
8692 });
8693
8694 it("should return 'function' scope on BlockStatement in functions (ES5)", () => {
8695 const { node, scope } = getScope("function f() { { var b; } }", "BlockStatement > BlockStatement");
8696
8697 assert.strictEqual(scope.type, "function");
8698 assert.strictEqual(scope.block, node.parent.parent);
8699 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]);
8700 });
8701
8702 it("should return 'block' scope on BlockStatement in functions (ES2015)", () => {
8703 const { node, scope } = getScope("function f() { { let a; var b; } }", "BlockStatement > BlockStatement", 2015);
8704
8705 assert.strictEqual(scope.type, "block");
8706 assert.strictEqual(scope.upper.type, "function");
8707 assert.strictEqual(scope.block, node);
8708 assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]);
8709 assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "b"]);
8710 });
8711
8712 it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => {
8713 const { node, scope } = getScope("function f() { { let a; { let b; var c; } } }", "BlockStatement > BlockStatement > BlockStatement", 2015);
8714
8715 assert.strictEqual(scope.type, "block");
8716 assert.strictEqual(scope.upper.type, "block");
8717 assert.strictEqual(scope.upper.upper.type, "function");
8718 assert.strictEqual(scope.block, node);
8719 assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]);
8720 assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["a"]);
8721 assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "c"]);
8722 });
8723
8724 it("should return 'function' scope on SwitchStatement in functions (ES5)", () => {
8725 const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchStatement");
8726
8727 assert.strictEqual(scope.type, "function");
8728 assert.strictEqual(scope.block, node.parent.parent);
8729 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]);
8730 });
8731
8732 it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => {
8733 const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchStatement", 2015);
8734
8735 assert.strictEqual(scope.type, "switch");
8736 assert.strictEqual(scope.block, node);
8737 assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]);
8738 });
8739
8740 it("should return 'function' scope on SwitchCase in functions (ES5)", () => {
8741 const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchCase");
8742
8743 assert.strictEqual(scope.type, "function");
8744 assert.strictEqual(scope.block, node.parent.parent.parent);
8745 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]);
8746 });
8747
8748 it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => {
8749 const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchCase", 2015);
8750
8751 assert.strictEqual(scope.type, "switch");
8752 assert.strictEqual(scope.block, node.parent);
8753 assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]);
8754 });
8755
8756 it("should return 'catch' scope on CatchClause in functions (ES5)", () => {
8757 const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause");
8758
8759 assert.strictEqual(scope.type, "catch");
8760 assert.strictEqual(scope.block, node);
8761 assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]);
8762 });
8763
8764 it("should return 'catch' scope on CatchClause in functions (ES2015)", () => {
8765 const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause", 2015);
8766
8767 assert.strictEqual(scope.type, "catch");
8768 assert.strictEqual(scope.block, node);
8769 assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]);
8770 });
8771
8772 it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => {
8773 const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause > BlockStatement");
8774
8775 assert.strictEqual(scope.type, "catch");
8776 assert.strictEqual(scope.block, node.parent);
8777 assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]);
8778 });
8779
8780 it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => {
8781 const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause > BlockStatement", 2015);
8782
8783 assert.strictEqual(scope.type, "block");
8784 assert.strictEqual(scope.block, node);
8785 assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]);
8786 });
8787
8788 it("should return 'function' scope on ForStatement in functions (ES5)", () => {
8789 const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement");
8790
8791 assert.strictEqual(scope.type, "function");
8792 assert.strictEqual(scope.block, node.parent.parent);
8793 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]);
8794 });
8795
8796 it("should return 'for' scope on ForStatement in functions (ES2015)", () => {
8797 const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement", 2015);
8798
8799 assert.strictEqual(scope.type, "for");
8800 assert.strictEqual(scope.block, node);
8801 assert.deepStrictEqual(scope.variables.map(v => v.name), ["i"]);
8802 });
8803
8804 it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => {
8805 const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement");
8806
8807 assert.strictEqual(scope.type, "function");
8808 assert.strictEqual(scope.block, node.parent.parent.parent);
8809 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]);
8810 });
8811
8812 it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => {
8813 const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement", 2015);
8814
8815 assert.strictEqual(scope.type, "block");
8816 assert.strictEqual(scope.upper.type, "for");
8817 assert.strictEqual(scope.block, node);
8818 assert.deepStrictEqual(scope.variables.map(v => v.name), []);
8819 assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["i"]);
8820 });
8821
8822 it("should return 'function' scope on ForInStatement in functions (ES5)", () => {
8823 const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement");
8824
8825 assert.strictEqual(scope.type, "function");
8826 assert.strictEqual(scope.block, node.parent.parent);
8827 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]);
8828 });
8829
8830 it("should return 'for' scope on ForInStatement in functions (ES2015)", () => {
8831 const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement", 2015);
8832
8833 assert.strictEqual(scope.type, "for");
8834 assert.strictEqual(scope.block, node);
8835 assert.deepStrictEqual(scope.variables.map(v => v.name), ["key"]);
8836 });
8837
8838 it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => {
8839 const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement > BlockStatement");
8840
8841 assert.strictEqual(scope.type, "function");
8842 assert.strictEqual(scope.block, node.parent.parent.parent);
8843 assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]);
8844 });
8845
8846 it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => {
8847 const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement > BlockStatement", 2015);
8848
8849 assert.strictEqual(scope.type, "block");
8850 assert.strictEqual(scope.upper.type, "for");
8851 assert.strictEqual(scope.block, node);
8852 assert.deepStrictEqual(scope.variables.map(v => v.name), []);
8853 assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["key"]);
8854 });
8855
8856 it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => {
8857 const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement", 2015);
8858
8859 assert.strictEqual(scope.type, "for");
8860 assert.strictEqual(scope.block, node);
8861 assert.deepStrictEqual(scope.variables.map(v => v.name), ["x"]);
8862 });
8863
8864 it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => {
8865 const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement > BlockStatement", 2015);
8866
8867 assert.strictEqual(scope.type, "block");
8868 assert.strictEqual(scope.upper.type, "for");
8869 assert.strictEqual(scope.block, node);
8870 assert.deepStrictEqual(scope.variables.map(v => v.name), []);
8871 assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["x"]);
8872 });
8873
8874 it("should shadow the same name variable by the iteration variable.", () => {
8875 const { node, scope } = getScope("let x; for (let x of x) {}", "ForOfStatement", 2015);
8876
8877 assert.strictEqual(scope.type, "for");
8878 assert.strictEqual(scope.upper.type, "global");
8879 assert.strictEqual(scope.block, node);
8880 assert.strictEqual(scope.upper.variables[0].references.length, 0);
8881 assert.strictEqual(scope.references[0].identifier, node.left.declarations[0].id);
8882 assert.strictEqual(scope.references[1].identifier, node.right);
8883 assert.strictEqual(scope.references[1].resolved, scope.variables[0]);
8884 });
8885 });
8886
8887 describe("Variables and references", () => {
8888 const code = [
8889 "a;",
8890 "function foo() { b; }",
8891 "Object;",
8892 "foo;",
8893 "var c;",
8894 "c;",
8895 "/* global d */",
8896 "d;",
8897 "e;",
8898 "f;"
8899 ].join("\n");
8900 let scope = null;
8901
8902 beforeEach(() => {
8903 let ok = false;
8904
8905 const config = {
8906 plugins: {
8907 test: {
8908 rules: {
8909 test(context) {
8910 return {
8911 Program() {
8912 scope = context.getScope();
8913 ok = true;
8914 }
8915 };
8916 }
8917 }
8918 }
8919 },
8920 languageOptions: {
8921 globals: { e: true, f: false },
8922 sourceType: "script",
8923 ecmaVersion: 5
8924 },
8925 rules: {
8926 "test/test": 2
8927 }
8928 };
8929
8930 linter.verify(code, config);
8931 assert(ok);
8932 });
8933
8934 afterEach(() => {
8935 scope = null;
8936 });
8937
8938 it("Scope#through should contain references of undefined variables", () => {
8939 assert.strictEqual(scope.through.length, 2);
8940 assert.strictEqual(scope.through[0].identifier.name, "a");
8941 assert.strictEqual(scope.through[0].identifier.loc.start.line, 1);
8942 assert.strictEqual(scope.through[0].resolved, null);
8943 assert.strictEqual(scope.through[1].identifier.name, "b");
8944 assert.strictEqual(scope.through[1].identifier.loc.start.line, 2);
8945 assert.strictEqual(scope.through[1].resolved, null);
8946 });
8947
8948 it("Scope#variables should contain global variables", () => {
8949 assert(scope.variables.some(v => v.name === "Object"));
8950 assert(scope.variables.some(v => v.name === "foo"));
8951 assert(scope.variables.some(v => v.name === "c"));
8952 assert(scope.variables.some(v => v.name === "d"));
8953 assert(scope.variables.some(v => v.name === "e"));
8954 assert(scope.variables.some(v => v.name === "f"));
8955 });
8956
8957 it("Scope#set should contain global variables", () => {
8958 assert(scope.set.get("Object"));
8959 assert(scope.set.get("foo"));
8960 assert(scope.set.get("c"));
8961 assert(scope.set.get("d"));
8962 assert(scope.set.get("e"));
8963 assert(scope.set.get("f"));
8964 });
8965
8966 it("Variables#references should contain their references", () => {
8967 assert.strictEqual(scope.set.get("Object").references.length, 1);
8968 assert.strictEqual(scope.set.get("Object").references[0].identifier.name, "Object");
8969 assert.strictEqual(scope.set.get("Object").references[0].identifier.loc.start.line, 3);
8970 assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object"));
8971 assert.strictEqual(scope.set.get("foo").references.length, 1);
8972 assert.strictEqual(scope.set.get("foo").references[0].identifier.name, "foo");
8973 assert.strictEqual(scope.set.get("foo").references[0].identifier.loc.start.line, 4);
8974 assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo"));
8975 assert.strictEqual(scope.set.get("c").references.length, 1);
8976 assert.strictEqual(scope.set.get("c").references[0].identifier.name, "c");
8977 assert.strictEqual(scope.set.get("c").references[0].identifier.loc.start.line, 6);
8978 assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c"));
8979 assert.strictEqual(scope.set.get("d").references.length, 1);
8980 assert.strictEqual(scope.set.get("d").references[0].identifier.name, "d");
8981 assert.strictEqual(scope.set.get("d").references[0].identifier.loc.start.line, 8);
8982 assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d"));
8983 assert.strictEqual(scope.set.get("e").references.length, 1);
8984 assert.strictEqual(scope.set.get("e").references[0].identifier.name, "e");
8985 assert.strictEqual(scope.set.get("e").references[0].identifier.loc.start.line, 9);
8986 assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e"));
8987 assert.strictEqual(scope.set.get("f").references.length, 1);
8988 assert.strictEqual(scope.set.get("f").references[0].identifier.name, "f");
8989 assert.strictEqual(scope.set.get("f").references[0].identifier.loc.start.line, 10);
8990 assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f"));
8991 });
8992
8993 it("Reference#resolved should be their variable", () => {
8994 assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object"));
8995 assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo"));
8996 assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c"));
8997 assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d"));
8998 assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e"));
8999 assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f"));
9000 });
9001 });
9002 });
9003
9004 describe("context.getDeclaredVariables(node)", () => {
9005
9006 /**
9007 * Assert `context.getDeclaredVariables(node)` is valid.
9008 * @param {string} code A code to check.
9009 * @param {string} type A type string of ASTNode. This method checks variables on the node of the type.
9010 * @param {Array<Array<string>>} expectedNamesList An array of expected variable names. The expected variable names is an array of string.
9011 * @returns {void}
9012 */
9013 function verify(code, type, expectedNamesList) {
9014 const config = {
9015 plugins: {
9016 test: {
9017
9018 rules: {
9019 test(context) {
9020
9021 /**
9022 * Assert `context.getDeclaredVariables(node)` is empty.
9023 * @param {ASTNode} node A node to check.
9024 * @returns {void}
9025 */
9026 function checkEmpty(node) {
9027 assert.strictEqual(0, context.getDeclaredVariables(node).length);
9028 }
9029 const rule = {
9030 Program: checkEmpty,
9031 EmptyStatement: checkEmpty,
9032 BlockStatement: checkEmpty,
9033 ExpressionStatement: checkEmpty,
9034 LabeledStatement: checkEmpty,
9035 BreakStatement: checkEmpty,
9036 ContinueStatement: checkEmpty,
9037 WithStatement: checkEmpty,
9038 SwitchStatement: checkEmpty,
9039 ReturnStatement: checkEmpty,
9040 ThrowStatement: checkEmpty,
9041 TryStatement: checkEmpty,
9042 WhileStatement: checkEmpty,
9043 DoWhileStatement: checkEmpty,
9044 ForStatement: checkEmpty,
9045 ForInStatement: checkEmpty,
9046 DebuggerStatement: checkEmpty,
9047 ThisExpression: checkEmpty,
9048 ArrayExpression: checkEmpty,
9049 ObjectExpression: checkEmpty,
9050 Property: checkEmpty,
9051 SequenceExpression: checkEmpty,
9052 UnaryExpression: checkEmpty,
9053 BinaryExpression: checkEmpty,
9054 AssignmentExpression: checkEmpty,
9055 UpdateExpression: checkEmpty,
9056 LogicalExpression: checkEmpty,
9057 ConditionalExpression: checkEmpty,
9058 CallExpression: checkEmpty,
9059 NewExpression: checkEmpty,
9060 MemberExpression: checkEmpty,
9061 SwitchCase: checkEmpty,
9062 Identifier: checkEmpty,
9063 Literal: checkEmpty,
9064 ForOfStatement: checkEmpty,
9065 ArrowFunctionExpression: checkEmpty,
9066 YieldExpression: checkEmpty,
9067 TemplateLiteral: checkEmpty,
9068 TaggedTemplateExpression: checkEmpty,
9069 TemplateElement: checkEmpty,
9070 ObjectPattern: checkEmpty,
9071 ArrayPattern: checkEmpty,
9072 RestElement: checkEmpty,
9073 AssignmentPattern: checkEmpty,
9074 ClassBody: checkEmpty,
9075 MethodDefinition: checkEmpty,
9076 MetaProperty: checkEmpty
9077 };
9078
9079 rule[type] = function(node) {
9080 const expectedNames = expectedNamesList.shift();
9081 const variables = context.getDeclaredVariables(node);
9082
9083 assert(Array.isArray(expectedNames));
9084 assert(Array.isArray(variables));
9085 assert.strictEqual(expectedNames.length, variables.length);
9086 for (let i = variables.length - 1; i >= 0; i--) {
9087 assert.strictEqual(expectedNames[i], variables[i].name);
9088 }
9089 };
9090 return rule;
9091 }
9092 }
9093
9094 }
9095 },
9096 languageOptions: {
9097 ecmaVersion: 6,
9098 sourceType: "module"
9099 },
9100 rules: {
9101 "test/test": 2
9102 }
9103 };
9104
9105 linter.verify(code, config);
9106
9107 // Check all expected names are asserted.
9108 assert.strictEqual(0, expectedNamesList.length);
9109 }
9110
9111 it("VariableDeclaration", () => {
9112 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 ";
9113 const namesList = [
9114 ["a", "b", "c"],
9115 ["d", "e", "f"],
9116 ["g", "h", "i", "j", "k"],
9117 ["l"]
9118 ];
9119
9120 verify(code, "VariableDeclaration", namesList);
9121 });
9122
9123 it("VariableDeclaration (on for-in/of loop)", () => {
9124
9125 // TDZ scope is created here, so tests to exclude those.
9126 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 ";
9127 const namesList = [
9128 ["a", "b", "c"],
9129 ["g"],
9130 ["d", "e", "f"],
9131 ["h"]
9132 ];
9133
9134 verify(code, "VariableDeclaration", namesList);
9135 });
9136
9137 it("VariableDeclarator", () => {
9138
9139 // TDZ scope is created here, so tests to exclude those.
9140 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 ";
9141 const namesList = [
9142 ["a", "b", "c"],
9143 ["d", "e", "f"],
9144 ["g", "h", "i"],
9145 ["j", "k"],
9146 ["l"]
9147 ];
9148
9149 verify(code, "VariableDeclarator", namesList);
9150 });
9151
9152 it("FunctionDeclaration", () => {
9153 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 ";
9154 const namesList = [
9155 ["foo", "a", "b", "c", "d", "e"],
9156 ["bar", "f", "g", "h", "i", "j"]
9157 ];
9158
9159 verify(code, "FunctionDeclaration", namesList);
9160 });
9161
9162 it("FunctionExpression", () => {
9163 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 ";
9164 const namesList = [
9165 ["foo", "a", "b", "c", "d", "e"],
9166 ["bar", "f", "g", "h", "i", "j"],
9167 ["q"]
9168 ];
9169
9170 verify(code, "FunctionExpression", namesList);
9171 });
9172
9173 it("ArrowFunctionExpression", () => {
9174 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 ";
9175 const namesList = [
9176 ["a", "b", "c", "d", "e"],
9177 ["f", "g", "h", "i", "j"]
9178 ];
9179
9180 verify(code, "ArrowFunctionExpression", namesList);
9181 });
9182
9183 it("ClassDeclaration", () => {
9184 const code = "\n class A { foo(x) { let y; } }\n class B { foo(x) { let y; } }\n ";
9185 const namesList = [
9186 ["A", "A"], // outer scope's and inner scope's.
9187 ["B", "B"]
9188 ];
9189
9190 verify(code, "ClassDeclaration", namesList);
9191 });
9192
9193 it("ClassExpression", () => {
9194 const code = "\n (class A { foo(x) { let y; } });\n (class B { foo(x) { let y; } });\n ";
9195 const namesList = [
9196 ["A"],
9197 ["B"]
9198 ];
9199
9200 verify(code, "ClassExpression", namesList);
9201 });
9202
9203 it("CatchClause", () => {
9204 const code = "\n try {} catch ({a, b}) {\n let x;\n try {} catch ({c, d}) {\n let y;\n }\n }\n ";
9205 const namesList = [
9206 ["a", "b"],
9207 ["c", "d"]
9208 ];
9209
9210 verify(code, "CatchClause", namesList);
9211 });
9212
9213 it("ImportDeclaration", () => {
9214 const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
9215 const namesList = [
9216 [],
9217 ["a"],
9218 ["b", "c", "d"]
9219 ];
9220
9221 verify(code, "ImportDeclaration", namesList);
9222 });
9223
9224 it("ImportSpecifier", () => {
9225 const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
9226 const namesList = [
9227 ["c"],
9228 ["d"]
9229 ];
9230
9231 verify(code, "ImportSpecifier", namesList);
9232 });
9233
9234 it("ImportDefaultSpecifier", () => {
9235 const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
9236 const namesList = [
9237 ["b"]
9238 ];
9239
9240 verify(code, "ImportDefaultSpecifier", namesList);
9241 });
9242
9243 it("ImportNamespaceSpecifier", () => {
9244 const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
9245 const namesList = [
9246 ["a"]
9247 ];
9248
9249 verify(code, "ImportNamespaceSpecifier", namesList);
9250 });
9251 });
9252
9253 describe("context.markVariableAsUsed()", () => {
9254
9255 it("should mark variables in current scope as used", () => {
9256 const code = "var a = 1, b = 2;";
9257 let spy;
9258
9259 const config = {
9260 plugins: {
9261 test: {
9262 rules: {
9263 checker: context => {
9264 spy = sinon.spy(() => {
9265 assert.isTrue(context.markVariableAsUsed("a"));
9266
9267 const scope = context.getScope();
9268
9269 assert.isTrue(getVariable(scope, "a").eslintUsed);
9270 assert.notOk(getVariable(scope, "b").eslintUsed);
9271 });
9272
9273 return { "Program:exit": spy };
9274 }
9275 }
9276 }
9277 },
9278 languageOptions: {
9279 sourceType: "script"
9280 },
9281 rules: { "test/checker": "error" }
9282 };
9283
9284 linter.verify(code, config);
9285 assert(spy && spy.calledOnce);
9286 });
9287
9288 it("should mark variables in function args as used", () => {
9289 const code = "function abc(a, b) { return 1; }";
9290 let spy;
9291
9292 const config = {
9293 plugins: {
9294 test: {
9295 rules: {
9296 checker: context => {
9297 spy = sinon.spy(() => {
9298 assert.isTrue(context.markVariableAsUsed("a"));
9299
9300 const scope = context.getScope();
9301
9302 assert.isTrue(getVariable(scope, "a").eslintUsed);
9303 assert.notOk(getVariable(scope, "b").eslintUsed);
9304 });
9305
9306 return { ReturnStatement: spy };
9307 }
9308 }
9309 }
9310 },
9311 rules: { "test/checker": "error" }
9312 };
9313
9314 linter.verify(code, config);
9315 assert(spy && spy.calledOnce);
9316 });
9317
9318 it("should mark variables in higher scopes as used", () => {
9319 const code = "var a, b; function abc() { return 1; }";
9320 let returnSpy, exitSpy;
9321
9322 const config = {
9323 plugins: {
9324 test: {
9325 rules: {
9326 checker: context => {
9327 returnSpy = sinon.spy(() => {
9328 assert.isTrue(context.markVariableAsUsed("a"));
9329 });
9330 exitSpy = sinon.spy(() => {
9331 const scope = context.getScope();
9332
9333 assert.isTrue(getVariable(scope, "a").eslintUsed);
9334 assert.notOk(getVariable(scope, "b").eslintUsed);
9335 });
9336
9337 return { ReturnStatement: returnSpy, "Program:exit": exitSpy };
9338 }
9339 }
9340 }
9341 },
9342 languageOptions: {
9343 sourceType: "script"
9344 },
9345 rules: { "test/checker": "error" }
9346 };
9347
9348 linter.verify(code, config);
9349 assert(returnSpy && returnSpy.calledOnce);
9350 assert(exitSpy && exitSpy.calledOnce);
9351 });
9352
9353 it("should mark variables as used when sourceType is commonjs", () => {
9354 const code = "var a = 1, b = 2;";
9355 let spy;
9356
9357 const config = {
9358 plugins: {
9359 test: {
9360 rules: {
9361 checker: context => {
9362 spy = sinon.spy(() => {
9363 const globalScope = context.getScope(),
9364 childScope = globalScope.childScopes[0];
9365
9366 assert.isTrue(context.markVariableAsUsed("a"), "Call to markVariableAsUsed should return true");
9367
9368 assert.isTrue(getVariable(childScope, "a").eslintUsed, "'a' should be marked as used.");
9369 assert.isUndefined(getVariable(childScope, "b").eslintUsed, "'b' should be marked as used.");
9370 });
9371
9372 return { "Program:exit": spy };
9373 }
9374 }
9375 }
9376 },
9377 languageOptions: {
9378 sourceType: "commonjs"
9379 },
9380 rules: { "test/checker": "error" }
9381 };
9382
9383 linter.verify(code, config);
9384 assert(spy && spy.calledOnce, "Spy wasn't called.");
9385 });
9386
9387 it("should mark variables in modules as used", () => {
9388 const code = "var a = 1, b = 2;";
9389 let spy;
9390
9391 const config = {
9392 plugins: {
9393 test: {
9394 rules: {
9395 checker: context => {
9396 spy = sinon.spy(() => {
9397 const globalScope = context.getScope(),
9398 childScope = globalScope.childScopes[0];
9399
9400 assert.isTrue(context.markVariableAsUsed("a"));
9401
9402 assert.isTrue(getVariable(childScope, "a").eslintUsed);
9403 assert.isUndefined(getVariable(childScope, "b").eslintUsed);
9404 });
9405
9406 return { "Program:exit": spy };
9407 }
9408 }
9409 }
9410 },
9411 languageOptions: {
9412 ecmaVersion: 6,
9413 sourceType: "module"
9414 },
9415 rules: { "test/checker": "error" }
9416 };
9417
9418 linter.verify(code, config);
9419 assert(spy && spy.calledOnce);
9420 });
9421
9422 it("should return false if the given variable is not found", () => {
9423 const code = "var a = 1, b = 2;";
9424 let spy;
9425
9426 const config = {
9427 plugins: {
9428 test: {
9429 rules: {
9430 checker: context => {
9431 spy = sinon.spy(() => {
9432 assert.isFalse(context.markVariableAsUsed("c"));
9433 });
9434
9435 return { "Program:exit": spy };
9436 }
9437 }
9438 }
9439 },
9440 rules: { "test/checker": "error" }
9441 };
9442
9443 linter.verify(code, config);
9444 assert(spy && spy.calledOnce);
9445 });
9446 });
9447
9448 describe("context.getCwd()", () => {
9449 const code = "a;\nb;";
9450 const baseConfig = { rules: { "test/checker": "error" } };
9451
9452 it("should get cwd correctly in the context", () => {
9453 const cwd = "cwd";
9454 const linterWithOption = new Linter({ cwd, configType: "flat" });
9455 let spy;
9456 const config = {
9457 plugins: {
9458 test: {
9459 rules: {
9460 checker: context => {
9461 spy = sinon.spy(() => {
9462 assert.strictEqual(context.getCwd(), cwd);
9463 });
9464 return { Program: spy };
9465 }
9466 }
9467 }
9468 },
9469 ...baseConfig
9470 };
9471
9472 linterWithOption.verify(code, config);
9473 assert(spy && spy.calledOnce);
9474 });
9475
9476 it("should assign process.cwd() to it if cwd is undefined", () => {
9477
9478 const linterWithOption = new Linter({ configType: "flat" });
9479 let spy;
9480 const config = {
9481 plugins: {
9482 test: {
9483 rules: {
9484 checker: context => {
9485
9486 spy = sinon.spy(() => {
9487 assert.strictEqual(context.getCwd(), process.cwd());
9488 });
9489 return { Program: spy };
9490 }
9491 }
9492 }
9493 },
9494 ...baseConfig
9495 };
9496
9497 linterWithOption.verify(code, config);
9498 assert(spy && spy.calledOnce);
9499 });
9500
9501 it("should assign process.cwd() to it if the option is undefined", () => {
9502 let spy;
9503 const config = {
9504 plugins: {
9505 test: {
9506 rules: {
9507 checker: context => {
9508
9509 spy = sinon.spy(() => {
9510 assert.strictEqual(context.getCwd(), process.cwd());
9511 });
9512 return { Program: spy };
9513 }
9514 }
9515 }
9516 },
9517 ...baseConfig
9518 };
9519
9520 linter.verify(code, config);
9521 assert(spy && spy.calledOnce);
9522 });
9523 });
9524
9525 });
9526
9527 describe("Rule Severity", () => {
9528
9529 it("rule should run as warning when set to 1 with a config array", () => {
9530 const ruleId = "semi",
9531 configs = createFlatConfigArray({
9532 files: ["**/*.js"],
9533 rules: {
9534 [ruleId]: 1
9535 }
9536 });
9537
9538 configs.normalizeSync();
9539 const messages = linter.verify("foo", configs, filename, true);
9540
9541 assert.strictEqual(messages.length, 1, "Message length is wrong");
9542 assert.strictEqual(messages[0].ruleId, ruleId);
9543 });
9544
9545 it("rule should run as warning when set to 1 with a plain array", () => {
9546 const ruleId = "semi",
9547 configs = [{
9548 files: ["**/*.js"],
9549 rules: {
9550 [ruleId]: 1
9551 }
9552 }];
9553
9554 const messages = linter.verify("foo", configs, filename, true);
9555
9556 assert.strictEqual(messages.length, 1, "Message length is wrong");
9557 assert.strictEqual(messages[0].ruleId, ruleId);
9558 });
9559
9560 it("rule should run as warning when set to 1 with an object", () => {
9561 const ruleId = "semi",
9562 config = {
9563 files: ["**/*.js"],
9564 rules: {
9565 [ruleId]: 1
9566 }
9567 };
9568
9569 const messages = linter.verify("foo", config, filename, true);
9570
9571 assert.strictEqual(messages.length, 1, "Message length is wrong");
9572 assert.strictEqual(messages[0].ruleId, ruleId);
9573 });
9574 });
9575
9576 describe("Code with a hashbang comment", () => {
9577 const code = "#!bin/program\n\nvar foo;;";
9578
9579 it("should preserve line numbers", () => {
9580 const config = { rules: { "no-extra-semi": 1 } };
9581 const messages = linter.verify(code, config);
9582
9583 assert.strictEqual(messages.length, 1);
9584 assert.strictEqual(messages[0].ruleId, "no-extra-semi");
9585 assert.strictEqual(messages[0].nodeType, "EmptyStatement");
9586 assert.strictEqual(messages[0].line, 3);
9587 });
9588
9589 it("should have a comment with the hashbang in it", () => {
9590 const spy = sinon.spy(context => {
9591 const comments = context.getAllComments();
9592
9593 assert.strictEqual(comments.length, 1);
9594 assert.strictEqual(comments[0].type, "Shebang");
9595 return {};
9596 });
9597
9598 const config = {
9599 plugins: {
9600 test: {
9601 rules: {
9602 checker: spy
9603 }
9604 }
9605 },
9606 rules: {
9607 "test/checker": "error"
9608 }
9609 };
9610
9611 linter.verify(code, config);
9612 assert(spy.calledOnce);
9613 });
9614 });
9615
9616 describe("Options", () => {
9617
9618 describe("filename", () => {
9619 it("should allow filename to be passed on options object", () => {
9620 const filenameChecker = sinon.spy(context => {
9621 assert.strictEqual(context.getFilename(), "foo.js");
9622 return {};
9623 });
9624
9625 const config = {
9626 plugins: {
9627 test: {
9628 rules: {
9629 checker: filenameChecker
9630 }
9631 }
9632 },
9633 rules: {
9634 "test/checker": "error"
9635 }
9636 };
9637
9638 linter.verify("foo;", config, { filename: "foo.js" });
9639 assert(filenameChecker.calledOnce);
9640 });
9641
9642 it("should allow filename to be passed as third argument", () => {
9643 const filenameChecker = sinon.spy(context => {
9644 assert.strictEqual(context.getFilename(), "bar.js");
9645 return {};
9646 });
9647
9648 const config = {
9649 plugins: {
9650 test: {
9651 rules: {
9652 checker: filenameChecker
9653 }
9654 }
9655 },
9656 rules: {
9657 "test/checker": "error"
9658 }
9659 };
9660
9661 linter.verify("foo;", config, "bar.js");
9662 assert(filenameChecker.calledOnce);
9663 });
9664
9665 it("should default filename to <input> when options object doesn't have filename", () => {
9666 const filenameChecker = sinon.spy(context => {
9667 assert.strictEqual(context.getFilename(), "<input>");
9668 return {};
9669 });
9670
9671 const config = {
9672 plugins: {
9673 test: {
9674 rules: {
9675 checker: filenameChecker
9676 }
9677 }
9678 },
9679 rules: {
9680 "test/checker": "error"
9681 }
9682 };
9683
9684 linter.verify("foo;", config, {});
9685 assert(filenameChecker.calledOnce);
9686 });
9687
9688 it("should default filename to <input> when only two arguments are passed", () => {
9689 const filenameChecker = sinon.spy(context => {
9690 assert.strictEqual(context.getFilename(), "<input>");
9691 return {};
9692 });
9693
9694 const config = {
9695 plugins: {
9696 test: {
9697 rules: {
9698 checker: filenameChecker
9699 }
9700 }
9701 },
9702 rules: {
9703 "test/checker": "error"
9704 }
9705 };
9706
9707 linter.verify("foo;", config);
9708 assert(filenameChecker.calledOnce);
9709 });
9710 });
9711
9712 describe("physicalFilename", () => {
9713 it("should be same as `filename` passed on options object, if no processors are used", () => {
9714 const physicalFilenameChecker = sinon.spy(context => {
9715 assert.strictEqual(context.getPhysicalFilename(), "foo.js");
9716 return {};
9717 });
9718
9719 const config = {
9720 plugins: {
9721 test: {
9722 rules: {
9723 checker: physicalFilenameChecker
9724 }
9725 }
9726 },
9727 rules: {
9728 "test/checker": "error"
9729 }
9730 };
9731
9732 linter.verify("foo;", config, { filename: "foo.js" });
9733 assert(physicalFilenameChecker.calledOnce);
9734 });
9735
9736 it("should default physicalFilename to <input> when options object doesn't have filename", () => {
9737 const physicalFilenameChecker = sinon.spy(context => {
9738 assert.strictEqual(context.getPhysicalFilename(), "<input>");
9739 return {};
9740 });
9741
9742 const config = {
9743 plugins: {
9744 test: {
9745 rules: {
9746 checker: physicalFilenameChecker
9747 }
9748 }
9749 },
9750 rules: {
9751 "test/checker": "error"
9752 }
9753 };
9754
9755 linter.verify("foo;", config, {});
9756 assert(physicalFilenameChecker.calledOnce);
9757 });
9758
9759 it("should default physicalFilename to <input> when only two arguments are passed", () => {
9760 const physicalFilenameChecker = sinon.spy(context => {
9761 assert.strictEqual(context.getPhysicalFilename(), "<input>");
9762 return {};
9763 });
9764
9765 const config = {
9766 plugins: {
9767 test: {
9768 rules: {
9769 checker: physicalFilenameChecker
9770 }
9771 }
9772 },
9773 rules: {
9774 "test/checker": "error"
9775 }
9776 };
9777
9778 linter.verify("foo;", config);
9779 assert(physicalFilenameChecker.calledOnce);
9780 });
9781 });
9782
9783 });
9784
9785 describe("Inline Directives", () => {
9786
9787 describe("/*global*/ Comments", () => {
9788
9789 describe("when evaluating code containing /*global */ and /*globals */ blocks", () => {
9790
9791 it("variables should be available in global scope", () => {
9792 const code = `
9793 /*global a b:true c:false d:readable e:writeable Math:off */
9794 function foo() {}
9795 /*globals f:true*/
9796 /* global ConfigGlobal : readable */
9797 `;
9798 let spy;
9799
9800 const config = {
9801 plugins: {
9802 test: {
9803 rules: {
9804 checker: context => {
9805 spy = sinon.spy(() => {
9806 const scope = context.getScope();
9807 const a = getVariable(scope, "a"),
9808 b = getVariable(scope, "b"),
9809 c = getVariable(scope, "c"),
9810 d = getVariable(scope, "d"),
9811 e = getVariable(scope, "e"),
9812 f = getVariable(scope, "f"),
9813 mathGlobal = getVariable(scope, "Math"),
9814 arrayGlobal = getVariable(scope, "Array"),
9815 configGlobal = getVariable(scope, "ConfigGlobal");
9816
9817 assert.strictEqual(a.name, "a");
9818 assert.strictEqual(a.writeable, false);
9819 assert.strictEqual(b.name, "b");
9820 assert.strictEqual(b.writeable, true);
9821 assert.strictEqual(c.name, "c");
9822 assert.strictEqual(c.writeable, false);
9823 assert.strictEqual(d.name, "d");
9824 assert.strictEqual(d.writeable, false);
9825 assert.strictEqual(e.name, "e");
9826 assert.strictEqual(e.writeable, true);
9827 assert.strictEqual(f.name, "f");
9828 assert.strictEqual(f.writeable, true);
9829 assert.strictEqual(mathGlobal, null);
9830 assert.strictEqual(arrayGlobal, null);
9831 assert.strictEqual(configGlobal.name, "ConfigGlobal");
9832 assert.strictEqual(configGlobal.writeable, false);
9833 });
9834
9835 return { Program: spy };
9836 }
9837 }
9838 }
9839 },
9840 rules: { "test/checker": "error" },
9841 languageOptions: {
9842 globals: { Array: "off", ConfigGlobal: "writeable" }
9843 }
9844 };
9845
9846 linter.verify(code, config);
9847 assert(spy && spy.calledOnce);
9848 });
9849 });
9850
9851 describe("when evaluating code containing a /*global */ block with sloppy whitespace", () => {
9852 const code = "/* global a b : true c: false*/";
9853
9854 it("variables should be available in global scope", () => {
9855
9856 let spy;
9857 const config = {
9858 plugins: {
9859 test: {
9860 rules: {
9861 checker: context => {
9862 spy = sinon.spy(() => {
9863 const scope = context.getScope(),
9864 a = getVariable(scope, "a"),
9865 b = getVariable(scope, "b"),
9866 c = getVariable(scope, "c");
9867
9868 assert.strictEqual(a.name, "a");
9869 assert.strictEqual(a.writeable, false);
9870 assert.strictEqual(b.name, "b");
9871 assert.strictEqual(b.writeable, true);
9872 assert.strictEqual(c.name, "c");
9873 assert.strictEqual(c.writeable, false);
9874 });
9875
9876 return { Program: spy };
9877 }
9878 }
9879 }
9880 },
9881 rules: { "test/checker": "error" }
9882 };
9883
9884 linter.verify(code, config);
9885 assert(spy && spy.calledOnce);
9886 });
9887 });
9888
9889 describe("when evaluating code containing a line comment", () => {
9890 const code = "//global a \n function f() {}";
9891
9892 it("should not introduce a global variable", () => {
9893 let spy;
9894
9895 const config = {
9896 plugins: {
9897 test: {
9898 rules: {
9899 checker: context => {
9900 spy = sinon.spy(() => {
9901 const scope = context.getScope();
9902
9903 assert.strictEqual(getVariable(scope, "a"), null);
9904 });
9905
9906 return { Program: spy };
9907 }
9908 }
9909 }
9910 },
9911 rules: { "test/checker": "error" }
9912 };
9913
9914
9915 linter.verify(code, config);
9916 assert(spy && spy.calledOnce);
9917 });
9918 });
9919
9920 describe("when evaluating code containing normal block comments", () => {
9921 const code = "/**/ /*a*/ /*b:true*/ /*foo c:false*/";
9922
9923 it("should not introduce a global variable", () => {
9924 let spy;
9925
9926 const config = {
9927 plugins: {
9928 test: {
9929 rules: {
9930 checker: context => {
9931 spy = sinon.spy(() => {
9932 const scope = context.getScope();
9933
9934 assert.strictEqual(getVariable(scope, "a"), null);
9935 assert.strictEqual(getVariable(scope, "b"), null);
9936 assert.strictEqual(getVariable(scope, "foo"), null);
9937 assert.strictEqual(getVariable(scope, "c"), null);
9938 });
9939
9940 return { Program: spy };
9941 }
9942 }
9943 }
9944 },
9945 rules: { "test/checker": "error" }
9946 };
9947
9948
9949 linter.verify(code, config);
9950 assert(spy && spy.calledOnce);
9951 });
9952 });
9953
9954 it("should attach a \"/*global\" comment node to declared variables", () => {
9955 const code = "/* global foo */\n/* global bar, baz */";
9956 let ok = false;
9957 const config = {
9958 plugins: {
9959 test: {
9960 rules: {
9961 test(context) {
9962 return {
9963 Program() {
9964 const scope = context.getScope();
9965 const sourceCode = context.getSourceCode();
9966 const comments = sourceCode.getAllComments();
9967
9968 assert.strictEqual(2, comments.length);
9969
9970 const foo = getVariable(scope, "foo");
9971
9972 assert.strictEqual(foo.eslintExplicitGlobal, true);
9973 assert.strictEqual(foo.eslintExplicitGlobalComments[0], comments[0]);
9974
9975 const bar = getVariable(scope, "bar");
9976
9977 assert.strictEqual(bar.eslintExplicitGlobal, true);
9978 assert.strictEqual(bar.eslintExplicitGlobalComments[0], comments[1]);
9979
9980 const baz = getVariable(scope, "baz");
9981
9982 assert.strictEqual(baz.eslintExplicitGlobal, true);
9983 assert.strictEqual(baz.eslintExplicitGlobalComments[0], comments[1]);
9984
9985 ok = true;
9986 }
9987 };
9988 }
9989 }
9990 }
9991 },
9992 rules: { "test/test": "error" }
9993 };
9994
9995
9996 linter.verify(code, config);
9997 assert(ok);
9998 });
9999
10000 it("should report a linting error when a global is set to an invalid value", () => {
10001 const results = linter.verify("/* global foo: AAAAA, bar: readonly */\nfoo;\nbar;", { rules: { "no-undef": "error" } });
10002
10003 assert.deepStrictEqual(results, [
10004 {
10005 ruleId: null,
10006 severity: 2,
10007 message: "'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')",
10008 line: 1,
10009 column: 1,
10010 endLine: 1,
10011 endColumn: 39,
10012 nodeType: null
10013 },
10014 {
10015 ruleId: "no-undef",
10016 messageId: "undef",
10017 severity: 2,
10018 message: "'foo' is not defined.",
10019 line: 2,
10020 column: 1,
10021 endLine: 2,
10022 endColumn: 4,
10023 nodeType: "Identifier"
10024 }
10025 ]);
10026 });
10027
10028 });
10029
10030 describe("/*exported*/ Comments", () => {
10031
10032 it("we should behave nicely when no matching variable is found", () => {
10033 const code = "/* exported horse */";
10034 const config = { rules: {} };
10035
10036 linter.verify(code, config, filename, true);
10037 });
10038
10039 it("variables should be exported", () => {
10040 const code = "/* exported horse */\n\nvar horse = 'circus'";
10041 let spy;
10042
10043 const config = {
10044 plugins: {
10045 test: {
10046 rules: {
10047 checker: context => {
10048 spy = sinon.spy(() => {
10049 const scope = context.getScope(),
10050 horse = getVariable(scope, "horse");
10051
10052 assert.strictEqual(horse.eslintUsed, true);
10053 });
10054
10055 return { Program: spy };
10056 }
10057 }
10058 }
10059 },
10060 languageOptions: {
10061 sourceType: "script"
10062 },
10063 rules: { "test/checker": "error" }
10064 };
10065
10066 linter.verify(code, config);
10067 assert(spy && spy.calledOnce);
10068 });
10069
10070 it("undefined variables should not be exported", () => {
10071 const code = "/* exported horse */\n\nhorse = 'circus'";
10072 let spy;
10073 const config = {
10074 plugins: {
10075 test: {
10076 rules: {
10077 checker: context => {
10078 spy = sinon.spy(() => {
10079 const scope = context.getScope(),
10080 horse = getVariable(scope, "horse");
10081
10082 assert.strictEqual(horse, null);
10083 });
10084
10085 return { Program: spy };
10086 }
10087 }
10088 }
10089 },
10090 languageOptions: {
10091 sourceType: "script"
10092 },
10093 rules: { "test/checker": "error" }
10094 };
10095
10096 linter.verify(code, config);
10097 assert(spy && spy.calledOnce);
10098 });
10099
10100 it("variables should be exported in strict mode", () => {
10101 const code = "/* exported horse */\n'use strict';\nvar horse = 'circus'";
10102 let spy;
10103 const config = {
10104 plugins: {
10105 test: {
10106 rules: {
10107 checker: context => {
10108 spy = sinon.spy(() => {
10109 const scope = context.getScope(),
10110 horse = getVariable(scope, "horse");
10111
10112 assert.strictEqual(horse.eslintUsed, true);
10113 });
10114
10115 return { Program: spy };
10116 }
10117 }
10118 }
10119 },
10120 languageOptions: {
10121 sourceType: "script"
10122 },
10123 rules: { "test/checker": "error" }
10124 };
10125
10126 linter.verify(code, config);
10127 assert(spy && spy.calledOnce);
10128 });
10129
10130 it("variables should not be exported in the es6 module environment", () => {
10131 const code = "/* exported horse */\nvar horse = 'circus'";
10132 let spy;
10133 const config = {
10134 plugins: {
10135 test: {
10136 rules: {
10137 checker: context => {
10138 spy = sinon.spy(() => {
10139 const scope = context.getScope(),
10140 horse = getVariable(scope, "horse");
10141
10142 assert.strictEqual(horse, null); // there is no global scope at all
10143 });
10144
10145 return { Program: spy };
10146 }
10147 }
10148 }
10149 },
10150 languageOptions: {
10151 ecmaVersion: 6,
10152 sourceType: "module"
10153 },
10154 rules: { "test/checker": "error" }
10155 };
10156
10157 linter.verify(code, config);
10158 assert(spy && spy.calledOnce);
10159 });
10160
10161 it("variables should not be exported when in a commonjs file", () => {
10162 const code = "/* exported horse */\nvar horse = 'circus'";
10163 let spy;
10164 const config = {
10165 plugins: {
10166 test: {
10167 rules: {
10168 checker: context => {
10169 spy = sinon.spy(() => {
10170 const scope = context.getScope(),
10171 horse = getVariable(scope, "horse");
10172
10173 assert.strictEqual(horse, null); // there is no global scope at all
10174 });
10175
10176 return { Program: spy };
10177 }
10178 }
10179 }
10180 },
10181 languageOptions: {
10182 sourceType: "commonjs"
10183 },
10184 rules: { "test/checker": "error" }
10185 };
10186
10187 linter.verify(code, config);
10188 assert(spy && spy.calledOnce);
10189 });
10190 });
10191
10192 describe("/*eslint*/ Comments", () => {
10193 describe("when evaluating code with comments to enable rules", () => {
10194
10195 it("should report a violation", () => {
10196 const code = "/*eslint no-alert:1*/ alert('test');";
10197 const config = { rules: {} };
10198
10199 const messages = linter.verify(code, config, filename);
10200
10201 assert.strictEqual(messages.length, 1);
10202 assert.strictEqual(messages[0].ruleId, "no-alert");
10203 assert.strictEqual(messages[0].message, "Unexpected alert.");
10204 assert.include(messages[0].nodeType, "CallExpression");
10205 });
10206
10207 it("rules should not change initial config", () => {
10208 const config = {
10209 languageOptions: {
10210 sourceType: "script"
10211 },
10212 rules: { strict: 2 }
10213 };
10214 const codeA = "/*eslint strict: 0*/ function bar() { return 2; }";
10215 const codeB = "function foo() { return 1; }";
10216 let messages = linter.verify(codeA, config, filename, false);
10217
10218 assert.strictEqual(messages.length, 0);
10219
10220 messages = linter.verify(codeB, config, filename, false);
10221 assert.strictEqual(messages.length, 1);
10222 });
10223
10224 it("rules should not change initial config", () => {
10225 const config = {
10226 languageOptions: {
10227 sourceType: "script"
10228 },
10229 rules: { quotes: [2, "double"] }
10230 };
10231 const codeA = "/*eslint quotes: 0*/ function bar() { return '2'; }";
10232 const codeB = "function foo() { return '1'; }";
10233 let messages = linter.verify(codeA, config, filename, false);
10234
10235 assert.strictEqual(messages.length, 0);
10236
10237 messages = linter.verify(codeB, config, filename, false);
10238 assert.strictEqual(messages.length, 1);
10239 });
10240
10241 it("rules should not change initial config", () => {
10242 const config = { rules: { quotes: [2, "double"] } };
10243 const codeA = "/*eslint quotes: [0, \"single\"]*/ function bar() { return '2'; }";
10244 const codeB = "function foo() { return '1'; }";
10245 let messages = linter.verify(codeA, config, filename, false);
10246
10247 assert.strictEqual(messages.length, 0);
10248
10249 messages = linter.verify(codeB, config, filename, false);
10250 assert.strictEqual(messages.length, 1);
10251 });
10252
10253 it("rules should not change initial config", () => {
10254 const config = {
10255 languageOptions: {
10256 sourceType: "script"
10257 },
10258 rules: { "no-unused-vars": [2, { vars: "all" }] }
10259 };
10260 const codeA = "/*eslint no-unused-vars: [0, {\"vars\": \"local\"}]*/ var a = 44;";
10261 const codeB = "var b = 55;";
10262 let messages = linter.verify(codeA, config, filename, false);
10263
10264 assert.strictEqual(messages.length, 0);
10265
10266 messages = linter.verify(codeB, config, filename, false);
10267 assert.strictEqual(messages.length, 1);
10268 });
10269 });
10270
10271 describe("when evaluating code with invalid comments to enable rules", () => {
10272 it("should report a violation when the config is not a valid rule configuration", () => {
10273 assert.deepStrictEqual(
10274 linter.verify("/*eslint no-alert:true*/ alert('test');", {}),
10275 [
10276 {
10277 severity: 2,
10278 ruleId: "no-alert",
10279 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",
10280 line: 1,
10281 column: 1,
10282 endLine: 1,
10283 endColumn: 25,
10284 nodeType: null
10285 }
10286 ]
10287 );
10288 });
10289
10290 it("should report a violation when the config violates a rule's schema", () => {
10291 assert.deepStrictEqual(
10292 linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {}),
10293 [
10294 {
10295 severity: 2,
10296 ruleId: "no-alert",
10297 message: "Configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n",
10298 line: 1,
10299 column: 1,
10300 endLine: 1,
10301 endColumn: 63,
10302 nodeType: null
10303 }
10304 ]
10305 );
10306 });
10307 });
10308
10309 describe("when evaluating code with comments to disable rules", () => {
10310
10311 it("should not report a violation", () => {
10312 const config = { rules: { "no-alert": 1 } };
10313 const messages = linter.verify("/*eslint no-alert:0*/ alert('test');", config, filename);
10314
10315 assert.strictEqual(messages.length, 0);
10316 });
10317
10318 it("should report an error when disabling a non-existent rule in inline comment", () => {
10319 let code = "/*eslint foo:0*/ ;";
10320 let messages = linter.verify(code, {}, filename);
10321
10322 assert.strictEqual(messages.length, 1, "/*eslint*/ comment should report problem.");
10323 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
10324
10325 code = "/*eslint-disable foo*/ ;";
10326 messages = linter.verify(code, {}, filename);
10327 assert.strictEqual(messages.length, 1, "/*eslint-disable*/ comment should report problem.");
10328 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
10329
10330 code = "/*eslint-disable-line foo*/ ;";
10331 messages = linter.verify(code, {}, filename);
10332 assert.strictEqual(messages.length, 1, "/*eslint-disable-line*/ comment should report problem.");
10333 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
10334
10335 code = "/*eslint-disable-next-line foo*/ ;";
10336 messages = linter.verify(code, {}, filename);
10337 assert.strictEqual(messages.length, 1, "/*eslint-disable-next-line*/ comment should report problem.");
10338 assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
10339 });
10340
10341 it("should not report an error, when disabling a non-existent rule in config", () => {
10342 const messages = linter.verify("", { rules: { foo: 0 } }, filename);
10343
10344 assert.strictEqual(messages.length, 0);
10345 });
10346
10347 it("should throw an error when a non-existent rule in config", () => {
10348 assert.throws(() => {
10349 linter.verify("", { rules: { foo: 1 } }, filename);
10350 }, /Key "rules": Key "foo":/u);
10351
10352 assert.throws(() => {
10353 linter.verify("", { rules: { foo: 2 } }, filename);
10354 }, /Key "rules": Key "foo":/u);
10355
10356 });
10357 });
10358
10359 describe("when evaluating code with comments to enable multiple rules", () => {
10360 const code = "/*eslint no-alert:1 no-console:1*/ alert('test'); console.log('test');";
10361
10362 it("should report a violation", () => {
10363 const config = { rules: {} };
10364
10365 const messages = linter.verify(code, config, filename);
10366
10367 assert.strictEqual(messages.length, 2);
10368 assert.strictEqual(messages[0].ruleId, "no-alert");
10369 assert.strictEqual(messages[0].message, "Unexpected alert.");
10370 assert.include(messages[0].nodeType, "CallExpression");
10371 assert.strictEqual(messages[1].ruleId, "no-console");
10372 });
10373 });
10374
10375 describe("when evaluating code with comments to enable and disable multiple rules", () => {
10376 const code = "/*eslint no-alert:1 no-console:0*/ alert('test'); console.log('test');";
10377
10378 it("should report a violation", () => {
10379 const config = { rules: { "no-console": 1, "no-alert": 0 } };
10380
10381 const messages = linter.verify(code, config, filename);
10382
10383 assert.strictEqual(messages.length, 1);
10384 assert.strictEqual(messages[0].ruleId, "no-alert");
10385 assert.strictEqual(messages[0].message, "Unexpected alert.");
10386 assert.include(messages[0].nodeType, "CallExpression");
10387 });
10388 });
10389
10390 describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => {
10391
10392 let baseConfig;
10393
10394 beforeEach(() => {
10395 baseConfig = {
10396 plugins: {
10397 "test-plugin": {
10398 rules: {
10399 "test-rule"(context) {
10400 return {
10401 Literal(node) {
10402 if (node.value === "trigger violation") {
10403 context.report(node, "Reporting violation.");
10404 }
10405 }
10406 };
10407 }
10408 }
10409 }
10410 }
10411 };
10412
10413 });
10414
10415 it("should not report a violation when inline comment enables plugin rule and there's no violation", () => {
10416 const config = { ...baseConfig, rules: {} };
10417 const code = "/*eslint test-plugin/test-rule: 2*/ var a = \"no violation\";";
10418
10419 const messages = linter.verify(code, config, filename);
10420
10421 assert.strictEqual(messages.length, 0);
10422 });
10423
10424 it("should not report a violation when inline comment disables plugin rule", () => {
10425 const code = "/*eslint test-plugin/test-rule:0*/ var a = \"trigger violation\"";
10426 const config = { ...baseConfig, rules: { "test-plugin/test-rule": 1 } };
10427
10428 const messages = linter.verify(code, config, filename);
10429
10430 assert.strictEqual(messages.length, 0);
10431 });
10432
10433 it("should report a violation when the report is right before the comment", () => {
10434 const code = " /* eslint-disable */ ";
10435
10436 const config = {
10437 plugins: {
10438 test: {
10439 rules: {
10440 checker: context => ({
10441 Program() {
10442 context.report({ loc: { line: 1, column: 0 }, message: "foo" });
10443 }
10444 })
10445 }
10446 }
10447 },
10448 rules: {
10449 "test/checker": "error"
10450 }
10451 };
10452
10453 const problems = linter.verify(code, config);
10454
10455 assert.strictEqual(problems.length, 1);
10456 assert.strictEqual(problems[0].message, "foo");
10457 });
10458
10459 it("should not report a violation when the report is right at the start of the comment", () => {
10460 const code = " /* eslint-disable */ ";
10461
10462 const config = {
10463 plugins: {
10464 test: {
10465 rules: {
10466 checker: context => ({
10467 Program() {
10468 context.report({ loc: { line: 1, column: 1 }, message: "foo" });
10469 }
10470 })
10471 }
10472 }
10473 },
10474 rules: {
10475 "test/checker": "error"
10476 }
10477 };
10478
10479 const problems = linter.verify(code, config);
10480
10481 assert.strictEqual(problems.length, 0);
10482 });
10483
10484 it("rules should not change initial config", () => {
10485 const config = { ...baseConfig, rules: { "test-plugin/test-rule": 2 } };
10486 const codeA = "/*eslint test-plugin/test-rule: 0*/ var a = \"trigger violation\";";
10487 const codeB = "var a = \"trigger violation\";";
10488 let messages = linter.verify(codeA, config, filename, false);
10489
10490 assert.strictEqual(messages.length, 0);
10491
10492 messages = linter.verify(codeB, config, filename, false);
10493 assert.strictEqual(messages.length, 1);
10494 });
10495 });
10496
10497 describe("when evaluating code with comments to enable and disable all reporting", () => {
10498 it("should report a violation", () => {
10499
10500 const code = [
10501 "/*eslint-disable */",
10502 "alert('test');",
10503 "/*eslint-enable */",
10504 "alert('test');"
10505 ].join("\n");
10506 const config = { rules: { "no-alert": 1 } };
10507
10508 const messages = linter.verify(code, config, filename);
10509
10510 assert.strictEqual(messages.length, 1);
10511 assert.strictEqual(messages[0].ruleId, "no-alert");
10512 assert.strictEqual(messages[0].message, "Unexpected alert.");
10513 assert.include(messages[0].nodeType, "CallExpression");
10514 assert.strictEqual(messages[0].line, 4);
10515 });
10516
10517 it("should not report a violation", () => {
10518 const code = [
10519 "/*eslint-disable */",
10520 "alert('test');",
10521 "alert('test');"
10522 ].join("\n");
10523 const config = { rules: { "no-alert": 1 } };
10524
10525 const messages = linter.verify(code, config, filename);
10526
10527 assert.strictEqual(messages.length, 0);
10528 });
10529
10530 it("should not report a violation", () => {
10531 const code = [
10532 " alert('test1');/*eslint-disable */\n",
10533 "alert('test');",
10534 " alert('test');\n",
10535 "/*eslint-enable */alert('test2');"
10536 ].join("");
10537 const config = { rules: { "no-alert": 1 } };
10538
10539 const messages = linter.verify(code, config, filename);
10540
10541 assert.strictEqual(messages.length, 2);
10542 assert.strictEqual(messages[0].column, 21);
10543 assert.strictEqual(messages[1].column, 19);
10544 });
10545
10546 it("should report a violation", () => {
10547
10548 const code = [
10549 "/*eslint-disable */",
10550 "alert('test');",
10551 "/*eslint-disable */",
10552 "alert('test');",
10553 "/*eslint-enable*/",
10554 "alert('test');",
10555 "/*eslint-enable*/"
10556 ].join("\n");
10557
10558 const config = { rules: { "no-alert": 1 } };
10559
10560 const messages = linter.verify(code, config, filename);
10561
10562 assert.strictEqual(messages.length, 1);
10563 });
10564
10565
10566 it("should not report a violation", () => {
10567 const code = [
10568 "/*eslint-disable */",
10569 "(function(){ var b = 44;})()",
10570 "/*eslint-enable */;any();"
10571 ].join("\n");
10572
10573 const config = { rules: { "no-unused-vars": 1 } };
10574
10575 const messages = linter.verify(code, config, filename);
10576
10577 assert.strictEqual(messages.length, 0);
10578 });
10579
10580 it("should not report a violation", () => {
10581 const code = [
10582 "(function(){ /*eslint-disable */ var b = 44;})()",
10583 "/*eslint-enable */;any();"
10584 ].join("\n");
10585
10586 const config = { rules: { "no-unused-vars": 1 } };
10587
10588 const messages = linter.verify(code, config, filename);
10589
10590 assert.strictEqual(messages.length, 0);
10591 });
10592 });
10593
10594 describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => {
10595 const code = "/*eslint no-alert:1, no-console:0*/ alert('test'); console.log('test');";
10596
10597 it("should report a violation", () => {
10598 const config = { rules: { "no-console": 1, "no-alert": 0 } };
10599
10600 const messages = linter.verify(code, config, filename);
10601
10602 assert.strictEqual(messages.length, 1);
10603 assert.strictEqual(messages[0].ruleId, "no-alert");
10604 assert.strictEqual(messages[0].message, "Unexpected alert.");
10605 assert.include(messages[0].nodeType, "CallExpression");
10606 });
10607 });
10608
10609 describe("when evaluating code with comments to enable configurable rule", () => {
10610 const code = "/*eslint quotes:[2, \"double\"]*/ alert('test');";
10611
10612 it("should report a violation", () => {
10613 const config = { rules: { quotes: [2, "single"] } };
10614
10615 const messages = linter.verify(code, config, filename);
10616
10617 assert.strictEqual(messages.length, 1);
10618 assert.strictEqual(messages[0].ruleId, "quotes");
10619 assert.strictEqual(messages[0].message, "Strings must use doublequote.");
10620 assert.include(messages[0].nodeType, "Literal");
10621 });
10622 });
10623
10624 describe("when evaluating code with comments to enable configurable rule using string severity", () => {
10625 const code = "/*eslint quotes:[\"error\", \"double\"]*/ alert('test');";
10626
10627 it("should report a violation", () => {
10628 const config = { rules: { quotes: [2, "single"] } };
10629
10630 const messages = linter.verify(code, config, filename);
10631
10632 assert.strictEqual(messages.length, 1);
10633 assert.strictEqual(messages[0].ruleId, "quotes");
10634 assert.strictEqual(messages[0].message, "Strings must use doublequote.");
10635 assert.include(messages[0].nodeType, "Literal");
10636 });
10637 });
10638
10639 describe("when evaluating code with incorrectly formatted comments to disable rule", () => {
10640 it("should report a violation", () => {
10641 const code = "/*eslint no-alert:'1'*/ alert('test');";
10642
10643 const config = { rules: { "no-alert": 1 } };
10644
10645 const messages = linter.verify(code, config, filename);
10646
10647 assert.strictEqual(messages.length, 2);
10648
10649 /*
10650 * Incorrectly formatted comment threw error;
10651 * message from caught exception
10652 * may differ amongst UAs, so verifying
10653 * first part only as defined in the
10654 * parseJsonConfig function in lib/eslint.js
10655 */
10656 assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":'1'':/u);
10657 assert.strictEqual(messages[0].line, 1);
10658 assert.strictEqual(messages[0].column, 1);
10659
10660 assert.strictEqual(messages[1].ruleId, "no-alert");
10661 assert.strictEqual(messages[1].message, "Unexpected alert.");
10662 assert.include(messages[1].nodeType, "CallExpression");
10663 });
10664
10665 it("should report a violation", () => {
10666 const code = "/*eslint no-alert:abc*/ alert('test');";
10667
10668 const config = { rules: { "no-alert": 1 } };
10669
10670 const messages = linter.verify(code, config, filename);
10671
10672 assert.strictEqual(messages.length, 2);
10673
10674 /*
10675 * Incorrectly formatted comment threw error;
10676 * message from caught exception
10677 * may differ amongst UAs, so verifying
10678 * first part only as defined in the
10679 * parseJsonConfig function in lib/eslint.js
10680 */
10681 assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":abc':/u);
10682 assert.strictEqual(messages[0].line, 1);
10683 assert.strictEqual(messages[0].column, 1);
10684
10685 assert.strictEqual(messages[1].ruleId, "no-alert");
10686 assert.strictEqual(messages[1].message, "Unexpected alert.");
10687 assert.include(messages[1].nodeType, "CallExpression");
10688 });
10689
10690 it("should report a violation", () => {
10691 const code = "/*eslint no-alert:0 2*/ alert('test');";
10692
10693 const config = { rules: { "no-alert": 1 } };
10694
10695 const messages = linter.verify(code, config, filename);
10696
10697 assert.strictEqual(messages.length, 2);
10698
10699 /*
10700 * Incorrectly formatted comment threw error;
10701 * message from caught exception
10702 * may differ amongst UAs, so verifying
10703 * first part only as defined in the
10704 * parseJsonConfig function in lib/eslint.js
10705 */
10706 assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":0 2':/u);
10707 assert.strictEqual(messages[0].line, 1);
10708 assert.strictEqual(messages[0].column, 1);
10709
10710 assert.strictEqual(messages[1].ruleId, "no-alert");
10711 assert.strictEqual(messages[1].message, "Unexpected alert.");
10712 assert.include(messages[1].nodeType, "CallExpression");
10713 });
10714 });
10715
10716 describe("when evaluating code with comments which have colon in its value", () => {
10717 const code = String.raw`
10718 /* eslint max-len: [2, 100, 2, {ignoreUrls: true, ignorePattern: "data:image\\/|\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}] */
10719 alert('test');
10720 `;
10721
10722 it("should not parse errors, should report a violation", () => {
10723 const messages = linter.verify(code, {}, filename);
10724
10725 assert.strictEqual(messages.length, 1);
10726 assert.strictEqual(messages[0].ruleId, "max-len");
10727 assert.strictEqual(messages[0].message, "This line has a length of 129. Maximum allowed is 100.");
10728 assert.include(messages[0].nodeType, "Program");
10729 });
10730 });
10731
10732 describe("when evaluating code with comments that contain escape sequences", () => {
10733 const code = String.raw`
10734 /* eslint max-len: ["error", 1, { ignoreComments: true, ignorePattern: "console\\.log\\(" }] */
10735 console.log("test");
10736 consolexlog("test2");
10737 var a = "test2";
10738 `;
10739
10740 it("should validate correctly", () => {
10741 const config = { rules: {} };
10742 const messages = linter.verify(code, config, filename);
10743 const [message1, message2] = messages;
10744
10745 assert.strictEqual(messages.length, 2);
10746 assert.strictEqual(message1.ruleId, "max-len");
10747 assert.strictEqual(message1.message, "This line has a length of 21. Maximum allowed is 1.");
10748 assert.strictEqual(message1.line, 4);
10749 assert.strictEqual(message1.column, 1);
10750 assert.include(message1.nodeType, "Program");
10751 assert.strictEqual(message2.ruleId, "max-len");
10752 assert.strictEqual(message2.message, "This line has a length of 16. Maximum allowed is 1.");
10753 assert.strictEqual(message2.line, 5);
10754 assert.strictEqual(message2.column, 1);
10755 assert.include(message2.nodeType, "Program");
10756 });
10757 });
10758
10759 });
10760
10761 describe("/*eslint-disable*/ and /*eslint-enable*/", () => {
10762 it("should report a violation", () => {
10763 const code = [
10764 "/*eslint-disable no-alert */",
10765 "alert('test');",
10766 "console.log('test');" // here
10767 ].join("\n");
10768 const config = { rules: { "no-alert": 1, "no-console": 1 } };
10769
10770 const messages = linter.verify(code, config, filename);
10771
10772 assert.strictEqual(messages.length, 1);
10773
10774 assert.strictEqual(messages[0].ruleId, "no-console");
10775 });
10776
10777 it("should report no violation", () => {
10778 const code = [
10779 "/* eslint-disable quotes */",
10780 "console.log(\"foo\");",
10781 "/* eslint-enable quotes */"
10782 ].join("\n");
10783 const config = { rules: { quotes: 2 } };
10784
10785 const messages = linter.verify(code, config, filename);
10786
10787 assert.strictEqual(messages.length, 0);
10788 });
10789
10790 it("should report a violation", () => {
10791 const code = [
10792 "/*eslint-disable no-alert, no-console */",
10793 "alert('test');",
10794 "console.log('test');",
10795 "/*eslint-enable*/",
10796
10797 "alert('test');", // here
10798 "console.log('test');" // here
10799 ].join("\n");
10800 const config = { rules: { "no-alert": 1, "no-console": 1 } };
10801
10802 const messages = linter.verify(code, config, filename);
10803
10804 assert.strictEqual(messages.length, 2);
10805
10806 assert.strictEqual(messages[0].ruleId, "no-alert");
10807 assert.strictEqual(messages[0].line, 5);
10808 assert.strictEqual(messages[1].ruleId, "no-console");
10809 assert.strictEqual(messages[1].line, 6);
10810 });
10811
10812 it("should report a violation", () => {
10813 const code = [
10814 "/*eslint-disable no-alert */",
10815 "alert('test');",
10816 "console.log('test');",
10817 "/*eslint-enable no-console */",
10818
10819 "alert('test');" // here
10820 ].join("\n");
10821 const config = { rules: { "no-alert": 1, "no-console": 1 } };
10822
10823 const messages = linter.verify(code, config, filename);
10824
10825 assert.strictEqual(messages.length, 1);
10826
10827 assert.strictEqual(messages[0].ruleId, "no-console");
10828 });
10829
10830
10831 it("should report a violation", () => {
10832 const code = [
10833 "/*eslint-disable no-alert, no-console */",
10834 "alert('test');",
10835 "console.log('test');",
10836 "/*eslint-enable no-alert*/",
10837
10838 "alert('test');", // here
10839 "console.log('test');"
10840 ].join("\n");
10841 const config = { rules: { "no-alert": 1, "no-console": 1 } };
10842
10843 const messages = linter.verify(code, config, filename);
10844
10845 assert.strictEqual(messages.length, 1);
10846
10847 assert.strictEqual(messages[0].ruleId, "no-alert");
10848 assert.strictEqual(messages[0].line, 5);
10849 });
10850
10851
10852 it("should report a violation", () => {
10853 const code = [
10854 "/*eslint-disable no-alert */",
10855
10856 "/*eslint-disable no-console */",
10857 "alert('test');",
10858 "console.log('test');",
10859 "/*eslint-enable */",
10860
10861 "alert('test');", // here
10862 "console.log('test');", // here
10863
10864 "/*eslint-enable */",
10865
10866 "alert('test');", // here
10867 "console.log('test');", // here
10868
10869 "/*eslint-enable*/"
10870 ].join("\n");
10871 const config = { rules: { "no-alert": 1, "no-console": 1 } };
10872
10873 const messages = linter.verify(code, config, filename);
10874
10875 assert.strictEqual(messages.length, 4);
10876
10877 assert.strictEqual(messages[0].ruleId, "no-alert");
10878 assert.strictEqual(messages[0].line, 6);
10879
10880 assert.strictEqual(messages[1].ruleId, "no-console");
10881 assert.strictEqual(messages[1].line, 7);
10882
10883 assert.strictEqual(messages[2].ruleId, "no-alert");
10884 assert.strictEqual(messages[2].line, 9);
10885
10886 assert.strictEqual(messages[3].ruleId, "no-console");
10887 assert.strictEqual(messages[3].line, 10);
10888
10889 });
10890
10891 it("should report a violation", () => {
10892 const code = [
10893 "/*eslint-disable no-alert, no-console */",
10894 "alert('test');",
10895 "console.log('test');",
10896
10897 "/*eslint-enable no-alert */",
10898
10899 "alert('test');", // here
10900 "console.log('test');",
10901
10902 "/*eslint-enable no-console */",
10903
10904 "alert('test');", // here
10905 "console.log('test');", // here
10906 "/*eslint-enable no-console */"
10907 ].join("\n");
10908 const config = { rules: { "no-alert": 1, "no-console": 1 } };
10909
10910 const messages = linter.verify(code, config, filename);
10911
10912 assert.strictEqual(messages.length, 3);
10913
10914 assert.strictEqual(messages[0].ruleId, "no-alert");
10915 assert.strictEqual(messages[0].line, 5);
10916
10917 assert.strictEqual(messages[1].ruleId, "no-alert");
10918 assert.strictEqual(messages[1].line, 8);
10919
10920 assert.strictEqual(messages[2].ruleId, "no-console");
10921 assert.strictEqual(messages[2].line, 9);
10922
10923 });
10924
10925 it("should report a violation when severity is warn", () => {
10926 const code = [
10927 "/*eslint-disable no-alert, no-console */",
10928 "alert('test');",
10929 "console.log('test');",
10930
10931 "/*eslint-enable no-alert */",
10932
10933 "alert('test');", // here
10934 "console.log('test');",
10935
10936 "/*eslint-enable no-console */",
10937
10938 "alert('test');", // here
10939 "console.log('test');", // here
10940 "/*eslint-enable no-console */"
10941 ].join("\n");
10942 const config = { rules: { "no-alert": "warn", "no-console": "warn" } };
10943
10944 const messages = linter.verify(code, config, filename);
10945
10946 assert.strictEqual(messages.length, 3);
10947
10948 assert.strictEqual(messages[0].ruleId, "no-alert");
10949 assert.strictEqual(messages[0].line, 5);
10950
10951 assert.strictEqual(messages[1].ruleId, "no-alert");
10952 assert.strictEqual(messages[1].line, 8);
10953
10954 assert.strictEqual(messages[2].ruleId, "no-console");
10955 assert.strictEqual(messages[2].line, 9);
10956
10957 });
10958
10959 it("should report no violation", () => {
10960 const code = [
10961 "/*eslint-disable no-unused-vars */",
10962 "var foo; // eslint-disable-line no-unused-vars",
10963 "var bar;",
10964 "/* eslint-enable no-unused-vars */" // here
10965 ].join("\n");
10966 const config = { rules: { "no-unused-vars": 2 } };
10967
10968 const messages = linter.verify(code, config, filename);
10969
10970 assert.strictEqual(messages.length, 0);
10971 });
10972
10973 });
10974
10975 describe("/*eslint-disable-line*/", () => {
10976
10977 it("should report a violation", () => {
10978 const code = [
10979 "alert('test'); // eslint-disable-line no-alert",
10980 "console.log('test');" // here
10981 ].join("\n");
10982 const config = {
10983 rules: {
10984 "no-alert": 1,
10985 "no-console": 1
10986 }
10987 };
10988
10989 const messages = linter.verify(code, config, filename);
10990
10991 assert.strictEqual(messages.length, 1);
10992
10993 assert.strictEqual(messages[0].ruleId, "no-console");
10994 });
10995
10996 it("should report a violation", () => {
10997 const code = [
10998 "alert('test'); // eslint-disable-line no-alert",
10999 "console.log('test'); // eslint-disable-line no-console",
11000 "alert('test');" // here
11001 ].join("\n");
11002 const config = {
11003 rules: {
11004 "no-alert": 1,
11005 "no-console": 1
11006 }
11007 };
11008
11009 const messages = linter.verify(code, config, filename);
11010
11011 assert.strictEqual(messages.length, 1);
11012
11013 assert.strictEqual(messages[0].ruleId, "no-alert");
11014 });
11015
11016 it("should report a violation if eslint-disable-line in a block comment is not on a single line", () => {
11017 const code = [
11018 "/* eslint-disable-line",
11019 "*",
11020 "*/ console.log('test');" // here
11021 ].join("\n");
11022 const config = {
11023 rules: {
11024 "no-console": 1
11025 }
11026 };
11027
11028 const messages = linter.verify(code, config, filename);
11029
11030 assert.strictEqual(messages.length, 2);
11031
11032 assert.strictEqual(messages[1].ruleId, "no-console");
11033 });
11034
11035 it("should not disable rule and add an extra report if eslint-disable-line in a block comment is not on a single line", () => {
11036 const code = [
11037 "alert('test'); /* eslint-disable-line ",
11038 "no-alert */"
11039 ].join("\n");
11040 const config = {
11041 rules: {
11042 "no-alert": 1
11043 }
11044 };
11045
11046 const messages = linter.verify(code, config);
11047
11048 assert.deepStrictEqual(messages, [
11049 {
11050 ruleId: "no-alert",
11051 severity: 1,
11052 line: 1,
11053 column: 1,
11054 endLine: 1,
11055 endColumn: 14,
11056 message: "Unexpected alert.",
11057 messageId: "unexpected",
11058 nodeType: "CallExpression"
11059 },
11060 {
11061 ruleId: null,
11062 severity: 2,
11063 message: "eslint-disable-line comment should not span multiple lines.",
11064 line: 1,
11065 column: 16,
11066 endLine: 2,
11067 endColumn: 12,
11068 nodeType: null
11069 }
11070 ]);
11071 });
11072
11073 it("should not report a violation for eslint-disable-line in block comment", () => {
11074 const code = [
11075 "alert('test'); // eslint-disable-line no-alert",
11076 "alert('test'); /*eslint-disable-line no-alert*/"
11077 ].join("\n");
11078 const config = {
11079 rules: {
11080 "no-alert": 1
11081 }
11082 };
11083
11084 const messages = linter.verify(code, config, filename);
11085
11086 assert.strictEqual(messages.length, 0);
11087 });
11088
11089 it("should not report a violation", () => {
11090 const code = [
11091 "alert('test'); // eslint-disable-line no-alert",
11092 "console.log('test'); // eslint-disable-line no-console"
11093 ].join("\n");
11094 const config = {
11095 rules: {
11096 "no-alert": 1,
11097 "no-console": 1
11098 }
11099 };
11100
11101 const messages = linter.verify(code, config, filename);
11102
11103 assert.strictEqual(messages.length, 0);
11104 });
11105
11106 it("should not report a violation", () => {
11107 const code = [
11108 "alert('test') // eslint-disable-line no-alert, quotes, semi",
11109 "console.log('test'); // eslint-disable-line"
11110 ].join("\n");
11111 const config = {
11112 rules: {
11113 "no-alert": 1,
11114 quotes: [1, "double"],
11115 semi: [1, "always"],
11116 "no-console": 1
11117 }
11118 };
11119
11120 const messages = linter.verify(code, config, filename);
11121
11122 assert.strictEqual(messages.length, 0);
11123 });
11124
11125 it("should not report a violation", () => {
11126 const code = [
11127 "alert('test') /* eslint-disable-line no-alert, quotes, semi */",
11128 "console.log('test'); /* eslint-disable-line */"
11129 ].join("\n");
11130 const config = {
11131 rules: {
11132 "no-alert": 1,
11133 quotes: [1, "double"],
11134 semi: [1, "always"],
11135 "no-console": 1
11136 }
11137 };
11138
11139 const messages = linter.verify(code, config, filename);
11140
11141 assert.strictEqual(messages.length, 0);
11142 });
11143
11144 it("should ignore violations of multiple rules when specified in mixed comments", () => {
11145 const code = [
11146 " alert(\"test\"); /* eslint-disable-line no-alert */ // eslint-disable-line quotes"
11147 ].join("\n");
11148 const config = {
11149 rules: {
11150 "no-alert": 1,
11151 quotes: [1, "single"]
11152 }
11153 };
11154 const messages = linter.verify(code, config, filename);
11155
11156 assert.strictEqual(messages.length, 0);
11157 });
11158
11159 it("should report no violation", () => {
11160 const code = [
11161 "var foo1; // eslint-disable-line no-unused-vars",
11162 "var foo2; // eslint-disable-line no-unused-vars",
11163 "var foo3; // eslint-disable-line no-unused-vars",
11164 "var foo4; // eslint-disable-line no-unused-vars",
11165 "var foo5; // eslint-disable-line no-unused-vars"
11166 ].join("\n");
11167 const config = { rules: { "no-unused-vars": 2 } };
11168
11169 const messages = linter.verify(code, config, filename);
11170
11171 assert.strictEqual(messages.length, 0);
11172 });
11173
11174 });
11175
11176 describe("/*eslint-disable-next-line*/", () => {
11177 it("should ignore violation of specified rule on next line", () => {
11178 const code = [
11179 "// eslint-disable-next-line no-alert",
11180 "alert('test');",
11181 "console.log('test');"
11182 ].join("\n");
11183 const config = {
11184 rules: {
11185 "no-alert": 1,
11186 "no-console": 1
11187 }
11188 };
11189 const messages = linter.verify(code, config, filename);
11190
11191 assert.strictEqual(messages.length, 1);
11192 assert.strictEqual(messages[0].ruleId, "no-console");
11193 });
11194
11195 it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => {
11196 const code = [
11197 "/* eslint-disable-next-line no-alert */",
11198 "alert('test');",
11199 "console.log('test');"
11200 ].join("\n");
11201 const config = {
11202 rules: {
11203 "no-alert": 1,
11204 "no-console": 1
11205 }
11206 };
11207 const messages = linter.verify(code, config, filename);
11208
11209 assert.strictEqual(messages.length, 1);
11210 assert.strictEqual(messages[0].ruleId, "no-console");
11211 });
11212 it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => {
11213 const code = [
11214 "/* eslint-disable-next-line no-alert */",
11215 "alert('test');"
11216 ].join("\n");
11217 const config = {
11218 rules: {
11219 "no-alert": 1
11220 }
11221 };
11222 const messages = linter.verify(code, config, filename);
11223
11224 assert.strictEqual(messages.length, 0);
11225 });
11226
11227 it("should not ignore violation if block comment is not on a single line", () => {
11228 const code = [
11229 "/* eslint-disable-next-line",
11230 "no-alert */alert('test');"
11231 ].join("\n");
11232 const config = {
11233 rules: {
11234 "no-alert": 1
11235 }
11236 };
11237 const messages = linter.verify(code, config, filename);
11238
11239 assert.strictEqual(messages.length, 2);
11240 assert.strictEqual(messages[1].ruleId, "no-alert");
11241 });
11242
11243 it("should ignore violations only of specified rule", () => {
11244 const code = [
11245 "// eslint-disable-next-line no-console",
11246 "alert('test');",
11247 "console.log('test');"
11248 ].join("\n");
11249 const config = {
11250 rules: {
11251 "no-alert": 1,
11252 "no-console": 1
11253 }
11254 };
11255 const messages = linter.verify(code, config, filename);
11256
11257 assert.strictEqual(messages.length, 2);
11258 assert.strictEqual(messages[0].ruleId, "no-alert");
11259 assert.strictEqual(messages[1].ruleId, "no-console");
11260 });
11261
11262 it("should ignore violations of multiple rules when specified", () => {
11263 const code = [
11264 "// eslint-disable-next-line no-alert, quotes",
11265 "alert(\"test\");",
11266 "console.log('test');"
11267 ].join("\n");
11268 const config = {
11269 rules: {
11270 "no-alert": 1,
11271 quotes: [1, "single"],
11272 "no-console": 1
11273 }
11274 };
11275 const messages = linter.verify(code, config, filename);
11276
11277 assert.strictEqual(messages.length, 1);
11278 assert.strictEqual(messages[0].ruleId, "no-console");
11279 });
11280
11281 it("should ignore violations of multiple rules when specified in mixed comments", () => {
11282 const code = [
11283 "/* eslint-disable-next-line no-alert */ // eslint-disable-next-line quotes",
11284 "alert(\"test\");"
11285 ].join("\n");
11286 const config = {
11287 rules: {
11288 "no-alert": 1,
11289 quotes: [1, "single"]
11290 }
11291 };
11292 const messages = linter.verify(code, config, filename);
11293
11294 assert.strictEqual(messages.length, 0);
11295 });
11296
11297 it("should ignore violations of only the specified rule on next line", () => {
11298 const code = [
11299 "// eslint-disable-next-line quotes",
11300 "alert(\"test\");",
11301 "console.log('test');"
11302 ].join("\n");
11303 const config = {
11304 rules: {
11305 "no-alert": 1,
11306 quotes: [1, "single"],
11307 "no-console": 1
11308 }
11309 };
11310 const messages = linter.verify(code, config, filename);
11311
11312 assert.strictEqual(messages.length, 2);
11313 assert.strictEqual(messages[0].ruleId, "no-alert");
11314 assert.strictEqual(messages[1].ruleId, "no-console");
11315 });
11316
11317 it("should ignore violations of specified rule on next line only", () => {
11318 const code = [
11319 "alert('test');",
11320 "// eslint-disable-next-line no-alert",
11321 "alert('test');",
11322 "console.log('test');"
11323 ].join("\n");
11324 const config = {
11325 rules: {
11326 "no-alert": 1,
11327 "no-console": 1
11328 }
11329 };
11330 const messages = linter.verify(code, config, filename);
11331
11332 assert.strictEqual(messages.length, 2);
11333 assert.strictEqual(messages[0].ruleId, "no-alert");
11334 assert.strictEqual(messages[1].ruleId, "no-console");
11335 });
11336
11337 it("should ignore all rule violations on next line if none specified", () => {
11338 const code = [
11339 "// eslint-disable-next-line",
11340 "alert(\"test\");",
11341 "console.log('test')"
11342 ].join("\n");
11343 const config = {
11344 rules: {
11345 semi: [1, "never"],
11346 quotes: [1, "single"],
11347 "no-alert": 1,
11348 "no-console": 1
11349 }
11350 };
11351 const messages = linter.verify(code, config, filename);
11352
11353 assert.strictEqual(messages.length, 1);
11354 assert.strictEqual(messages[0].ruleId, "no-console");
11355 });
11356
11357 it("should ignore violations if eslint-disable-next-line is a block comment", () => {
11358 const code = [
11359 "alert('test');",
11360 "/* eslint-disable-next-line no-alert */",
11361 "alert('test');",
11362 "console.log('test');"
11363 ].join("\n");
11364 const config = {
11365 rules: {
11366 "no-alert": 1,
11367 "no-console": 1
11368 }
11369 };
11370 const messages = linter.verify(code, config, filename);
11371
11372 assert.strictEqual(messages.length, 2);
11373 assert.strictEqual(messages[0].ruleId, "no-alert");
11374 assert.strictEqual(messages[1].ruleId, "no-console");
11375 });
11376
11377 it("should report a violation", () => {
11378 const code = [
11379 "/* eslint-disable-next-line",
11380 "*",
11381 "*/",
11382 "console.log('test');" // here
11383 ].join("\n");
11384 const config = {
11385 rules: {
11386 "no-alert": 1,
11387 "no-console": 1
11388 }
11389 };
11390
11391 const messages = linter.verify(code, config, filename);
11392
11393 assert.strictEqual(messages.length, 2);
11394
11395 assert.strictEqual(messages[1].ruleId, "no-console");
11396 });
11397
11398 it("should not ignore violations if comment is of the type hashbang", () => {
11399 const code = [
11400 "#! eslint-disable-next-line no-alert",
11401 "alert('test');",
11402 "console.log('test');"
11403 ].join("\n");
11404 const config = {
11405 rules: {
11406 "no-alert": 1,
11407 "no-console": 1
11408 }
11409 };
11410 const messages = linter.verify(code, config, filename);
11411
11412 assert.strictEqual(messages.length, 2);
11413 assert.strictEqual(messages[0].ruleId, "no-alert");
11414 assert.strictEqual(messages[1].ruleId, "no-console");
11415 });
11416 });
11417
11418 describe("descriptions in directive comments", () => {
11419 it("should ignore the part preceded by '--' in '/*eslint*/'.", () => {
11420 const aaa = sinon.stub().returns({});
11421 const bbb = sinon.stub().returns({});
11422 const config = {
11423 plugins: {
11424 test: {
11425 rules: {
11426 aaa: { create: aaa },
11427 bbb: { create: bbb }
11428 }
11429 }
11430 }
11431 };
11432
11433 const messages = linter.verify(`
11434 /*eslint test/aaa:error -- test/bbb:error */
11435 console.log("hello")
11436 `, config);
11437
11438 // Don't include syntax error of the comment.
11439 assert.deepStrictEqual(messages, []);
11440
11441 // Use only `aaa`.
11442 assert.strictEqual(aaa.callCount, 1);
11443 assert.strictEqual(bbb.callCount, 0);
11444 });
11445
11446 it("should ignore the part preceded by '--' in '/*globals*/'.", () => {
11447 const messages = linter.verify(`
11448 /*globals aaa -- bbb */
11449 var aaa = {}
11450 var bbb = {}
11451 `, {
11452 languageOptions: {
11453 sourceType: "script"
11454 },
11455 rules: { "no-redeclare": "error" }
11456 });
11457
11458 // Don't include `bbb`
11459 assert.deepStrictEqual(
11460 messages,
11461 [{
11462 column: 31,
11463 endColumn: 34,
11464 line: 2,
11465 endLine: 2,
11466 message: "'aaa' is already defined by a variable declaration.",
11467 messageId: "redeclaredBySyntax",
11468 nodeType: "Block",
11469 ruleId: "no-redeclare",
11470 severity: 2
11471 }]
11472 );
11473 });
11474
11475 it("should ignore the part preceded by '--' in '/*exported*/'.", () => {
11476 const messages = linter.verify(`
11477 /*exported aaa -- bbb */
11478 var aaa = {}
11479 var bbb = {}
11480 `, {
11481 languageOptions: {
11482 sourceType: "script"
11483 },
11484 rules: { "no-unused-vars": "error" }
11485 });
11486
11487 // Don't include `aaa`
11488 assert.deepStrictEqual(
11489 messages,
11490 [{
11491 column: 25,
11492 endColumn: 28,
11493 endLine: 4,
11494 line: 4,
11495 message: "'bbb' is assigned a value but never used.",
11496 messageId: "unusedVar",
11497 nodeType: "Identifier",
11498 ruleId: "no-unused-vars",
11499 severity: 2
11500 }]
11501 );
11502 });
11503
11504 it("should ignore the part preceded by '--' in '/*eslint-disable*/'.", () => {
11505 const messages = linter.verify(`
11506 /*eslint-disable no-redeclare -- no-unused-vars */
11507 var aaa = {}
11508 var aaa = {}
11509 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
11510
11511 // Do include `no-unused-vars` but not `no-redeclare`
11512 assert.deepStrictEqual(
11513 messages,
11514 [{
11515 column: 25,
11516 endLine: 4,
11517 endColumn: 28,
11518 line: 4,
11519 message: "'aaa' is assigned a value but never used.",
11520 messageId: "unusedVar",
11521 nodeType: "Identifier",
11522 ruleId: "no-unused-vars",
11523 severity: 2
11524 }]
11525 );
11526 });
11527
11528 it("should ignore the part preceded by '--' in '/*eslint-enable*/'.", () => {
11529 const messages = linter.verify(`
11530 /*eslint-disable no-redeclare, no-unused-vars */
11531 /*eslint-enable no-redeclare -- no-unused-vars */
11532 var aaa = {}
11533 var aaa = {}
11534 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
11535
11536 // Do include `no-redeclare` but not `no-unused-vars`
11537 assert.deepStrictEqual(
11538 messages,
11539 [{
11540 column: 25,
11541 endLine: 5,
11542 endColumn: 28,
11543 line: 5,
11544 message: "'aaa' is already defined.",
11545 messageId: "redeclared",
11546 nodeType: "Identifier",
11547 ruleId: "no-redeclare",
11548 severity: 2
11549 }]
11550 );
11551 });
11552
11553 it("should ignore the part preceded by '--' in '//eslint-disable-line'.", () => {
11554 const messages = linter.verify(`
11555 var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars
11556 var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars
11557 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
11558
11559 // Do include `no-unused-vars` but not `no-redeclare`
11560 assert.deepStrictEqual(
11561 messages,
11562 [{
11563 column: 25,
11564 endLine: 3,
11565 endColumn: 28,
11566 line: 3,
11567 message: "'aaa' is assigned a value but never used.",
11568 messageId: "unusedVar",
11569 nodeType: "Identifier",
11570 ruleId: "no-unused-vars",
11571 severity: 2
11572 }]
11573 );
11574 });
11575
11576 it("should ignore the part preceded by '--' in '/*eslint-disable-line*/'.", () => {
11577 const messages = linter.verify(`
11578 var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */
11579 var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */
11580 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
11581
11582 // Do include `no-unused-vars` but not `no-redeclare`
11583 assert.deepStrictEqual(
11584 messages,
11585 [{
11586 column: 25,
11587 endLine: 3,
11588 endColumn: 28,
11589 line: 3,
11590 message: "'aaa' is assigned a value but never used.",
11591 messageId: "unusedVar",
11592 nodeType: "Identifier",
11593 ruleId: "no-unused-vars",
11594 severity: 2
11595 }]
11596 );
11597 });
11598
11599 it("should ignore the part preceded by '--' in '//eslint-disable-next-line'.", () => {
11600 const messages = linter.verify(`
11601 //eslint-disable-next-line no-redeclare -- no-unused-vars
11602 var aaa = {}
11603 //eslint-disable-next-line no-redeclare -- no-unused-vars
11604 var aaa = {}
11605 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
11606
11607 // Do include `no-unused-vars` but not `no-redeclare`
11608 assert.deepStrictEqual(
11609 messages,
11610 [{
11611 column: 25,
11612 endLine: 5,
11613 endColumn: 28,
11614 line: 5,
11615 message: "'aaa' is assigned a value but never used.",
11616 messageId: "unusedVar",
11617 nodeType: "Identifier",
11618 ruleId: "no-unused-vars",
11619 severity: 2
11620 }]
11621 );
11622 });
11623
11624 it("should ignore the part preceded by '--' in '/*eslint-disable-next-line*/'.", () => {
11625 const messages = linter.verify(`
11626 /*eslint-disable-next-line no-redeclare -- no-unused-vars */
11627 var aaa = {}
11628 /*eslint-disable-next-line no-redeclare -- no-unused-vars */
11629 var aaa = {}
11630 `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
11631
11632 // Do include `no-unused-vars` but not `no-redeclare`
11633 assert.deepStrictEqual(
11634 messages,
11635 [{
11636 column: 25,
11637 endLine: 5,
11638 endColumn: 28,
11639 line: 5,
11640 message: "'aaa' is assigned a value but never used.",
11641 messageId: "unusedVar",
11642 nodeType: "Identifier",
11643 ruleId: "no-unused-vars",
11644 severity: 2
11645 }]
11646 );
11647 });
11648
11649 it("should not ignore the part preceded by '--' if the '--' is not surrounded by whitespaces.", () => {
11650 const rule = sinon.stub().returns({});
11651 const config = {
11652 plugins: {
11653 test: {
11654 rules: {
11655 "a--rule": { create: rule }
11656 }
11657 }
11658 }
11659 };
11660
11661 const messages = linter.verify(`
11662 /*eslint test/a--rule:error */
11663 console.log("hello")
11664 `, config);
11665
11666 // Don't include syntax error of the comment.
11667 assert.deepStrictEqual(messages, []);
11668
11669 // Use `a--rule`.
11670 assert.strictEqual(rule.callCount, 1);
11671 });
11672
11673 it("should ignore the part preceded by '--' even if the '--' is longer than 2.", () => {
11674 const aaa = sinon.stub().returns({});
11675 const bbb = sinon.stub().returns({});
11676 const config = {
11677 plugins: {
11678 test: {
11679 rules: {
11680 aaa: { create: aaa },
11681 bbb: { create: bbb }
11682 }
11683 }
11684 }
11685 };
11686
11687 const messages = linter.verify(`
11688 /*eslint test/aaa:error -------- test/bbb:error */
11689 console.log("hello")
11690 `, config);
11691
11692 // Don't include syntax error of the comment.
11693 assert.deepStrictEqual(messages, []);
11694
11695 // Use only `aaa`.
11696 assert.strictEqual(aaa.callCount, 1);
11697 assert.strictEqual(bbb.callCount, 0);
11698 });
11699
11700 it("should ignore the part preceded by '--' with line breaks.", () => {
11701 const aaa = sinon.stub().returns({});
11702 const bbb = sinon.stub().returns({});
11703 const config = {
11704 plugins: {
11705 test: {
11706 rules: {
11707 aaa: { create: aaa },
11708 bbb: { create: bbb }
11709 }
11710 }
11711 }
11712 };
11713
11714 const messages = linter.verify(`
11715 /*eslint test/aaa:error
11716 --------
11717 test/bbb:error */
11718 console.log("hello")
11719 `, config);
11720
11721 // Don't include syntax error of the comment.
11722 assert.deepStrictEqual(messages, []);
11723
11724 // Use only `aaa`.
11725 assert.strictEqual(aaa.callCount, 1);
11726 assert.strictEqual(bbb.callCount, 0);
11727 });
11728 });
11729
11730 describe("allowInlineConfig option", () => {
11731 describe("when evaluating code with comments to change config when allowInlineConfig is enabled", () => {
11732 it("should report a violation for disabling rules", () => {
11733 const code = [
11734 "alert('test'); // eslint-disable-line no-alert"
11735 ].join("\n");
11736 const config = {
11737 rules: {
11738 "no-alert": 1
11739 }
11740 };
11741
11742 const messages = linter.verify(code, config, {
11743 filename,
11744 allowInlineConfig: false
11745 });
11746
11747 assert.strictEqual(messages.length, 1);
11748 assert.strictEqual(messages[0].ruleId, "no-alert");
11749 });
11750
11751 it("should report a violation for global variable declarations", () => {
11752 let ok = false;
11753 const code = [
11754 "/* global foo */"
11755 ].join("\n");
11756 const config = {
11757 plugins: {
11758 test: {
11759 rules: {
11760 test(context) {
11761 return {
11762 Program() {
11763 const scope = context.getScope();
11764 const sourceCode = context.getSourceCode();
11765 const comments = sourceCode.getAllComments();
11766
11767 assert.strictEqual(1, comments.length);
11768
11769 const foo = getVariable(scope, "foo");
11770
11771 assert.notOk(foo);
11772
11773 ok = true;
11774 }
11775 };
11776 }
11777 }
11778 }
11779 },
11780 rules: {
11781 "test/test": 2
11782 }
11783 };
11784
11785 linter.verify(code, config, { allowInlineConfig: false });
11786 assert(ok);
11787 });
11788
11789 it("should report a violation for eslint-disable", () => {
11790 const code = [
11791 "/* eslint-disable */",
11792 "alert('test');"
11793 ].join("\n");
11794 const config = {
11795 rules: {
11796 "no-alert": 1
11797 }
11798 };
11799
11800 const messages = linter.verify(code, config, {
11801 filename,
11802 allowInlineConfig: false
11803 });
11804
11805 assert.strictEqual(messages.length, 1);
11806 assert.strictEqual(messages[0].ruleId, "no-alert");
11807 });
11808
11809 it("should not report a violation for rule changes", () => {
11810 const code = [
11811 "/*eslint no-alert:2*/",
11812 "alert('test');"
11813 ].join("\n");
11814 const config = {
11815 rules: {
11816 "no-alert": 0
11817 }
11818 };
11819
11820 const messages = linter.verify(code, config, {
11821 filename,
11822 allowInlineConfig: false
11823 });
11824
11825 assert.strictEqual(messages.length, 0);
11826 });
11827
11828 it("should report a violation for disable-line", () => {
11829 const code = [
11830 "alert('test'); // eslint-disable-line"
11831 ].join("\n");
11832 const config = {
11833 rules: {
11834 "no-alert": 2
11835 }
11836 };
11837
11838 const messages = linter.verify(code, config, {
11839 filename,
11840 allowInlineConfig: false
11841 });
11842
11843 assert.strictEqual(messages.length, 1);
11844 assert.strictEqual(messages[0].ruleId, "no-alert");
11845 });
11846
11847 });
11848
11849 describe("when evaluating code with 'noInlineConfig'", () => {
11850 for (const directive of [
11851 "globals foo",
11852 "global foo",
11853 "exported foo",
11854 "eslint eqeqeq: error",
11855 "eslint-disable eqeqeq",
11856 "eslint-disable-line eqeqeq",
11857 "eslint-disable-next-line eqeqeq",
11858 "eslint-enable eqeqeq"
11859 ]) {
11860 // eslint-disable-next-line no-loop-func -- No closures
11861 it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => {
11862 const messages = linter.verify(`/* ${directive} */`, {
11863 linterOptions: {
11864 noInlineConfig: true
11865 }
11866 });
11867
11868 assert.deepStrictEqual(messages.length, 1);
11869 assert.deepStrictEqual(messages[0].fatal, void 0);
11870 assert.deepStrictEqual(messages[0].ruleId, null);
11871 assert.deepStrictEqual(messages[0].severity, 1);
11872 assert.deepStrictEqual(messages[0].message, `'/*${directive.split(" ")[0]}*/' has no effect because you have 'noInlineConfig' setting in your config.`);
11873 });
11874 }
11875
11876 for (const directive of [
11877 "eslint-disable-line eqeqeq",
11878 "eslint-disable-next-line eqeqeq"
11879 ]) {
11880 // eslint-disable-next-line no-loop-func -- No closures
11881 it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => {
11882 const messages = linter.verify(`// ${directive}`, {
11883 linterOptions: {
11884 noInlineConfig: true
11885 }
11886 });
11887
11888 assert.deepStrictEqual(messages.length, 1);
11889 assert.deepStrictEqual(messages[0].fatal, void 0);
11890 assert.deepStrictEqual(messages[0].ruleId, null);
11891 assert.deepStrictEqual(messages[0].severity, 1);
11892 assert.deepStrictEqual(messages[0].message, `'//${directive.split(" ")[0]}' has no effect because you have 'noInlineConfig' setting in your config.`);
11893 });
11894 }
11895
11896 it("should not warn if 'noInlineConfig' and '--no-inline-config' were given.", () => {
11897 const messages = linter.verify("/* globals foo */", {
11898 linterOptions: {
11899 noInlineConfig: true
11900 }
11901 }, { allowInlineConfig: false });
11902
11903 assert.deepStrictEqual(messages.length, 0);
11904 });
11905 });
11906
11907 describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => {
11908 it("should not report a violation", () => {
11909 const code = [
11910 "alert('test'); // eslint-disable-line no-alert"
11911 ].join("\n");
11912 const config = {
11913 rules: {
11914 "no-alert": 1
11915 }
11916 };
11917
11918 const messages = linter.verify(code, config, {
11919 filename,
11920 allowInlineConfig: true
11921 });
11922
11923 assert.strictEqual(messages.length, 0);
11924 });
11925 });
11926
11927 });
11928
11929 describe("reportUnusedDisableDirectives option", () => {
11930 it("reports problems for unused eslint-disable comments", () => {
11931 assert.deepStrictEqual(
11932 linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: true }),
11933 [
11934 {
11935 ruleId: null,
11936 message: "Unused eslint-disable directive (no problems were reported).",
11937 line: 1,
11938 column: 1,
11939 fix: {
11940 range: [0, 20],
11941 text: " "
11942 },
11943 severity: 2,
11944 nodeType: null
11945 }
11946 ]
11947 );
11948 });
11949
11950 it("reports problems for unused eslint-disable comments (error)", () => {
11951 assert.deepStrictEqual(
11952 linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "error" }),
11953 [
11954 {
11955 ruleId: null,
11956 message: "Unused eslint-disable directive (no problems were reported).",
11957 line: 1,
11958 column: 1,
11959 fix: {
11960 range: [0, 20],
11961 text: " "
11962 },
11963 severity: 2,
11964 nodeType: null
11965 }
11966 ]
11967 );
11968 });
11969
11970 it("reports problems for unused eslint-disable comments (warn)", () => {
11971 assert.deepStrictEqual(
11972 linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "warn" }),
11973 [
11974 {
11975 ruleId: null,
11976 message: "Unused eslint-disable directive (no problems were reported).",
11977 line: 1,
11978 column: 1,
11979 fix: {
11980 range: [0, 20],
11981 text: " "
11982 },
11983 severity: 1,
11984 nodeType: null
11985 }
11986 ]
11987 );
11988 });
11989
11990 it("reports problems for unused eslint-disable comments (in config)", () => {
11991 assert.deepStrictEqual(
11992 linter.verify("/* eslint-disable */", {
11993 linterOptions: {
11994 reportUnusedDisableDirectives: true
11995 }
11996 }),
11997 [
11998 {
11999 ruleId: null,
12000 message: "Unused eslint-disable directive (no problems were reported).",
12001 line: 1,
12002 column: 1,
12003 fix: {
12004 range: [0, 20],
12005 text: " "
12006 },
12007 severity: 1,
12008 nodeType: null
12009 }
12010 ]
12011 );
12012 });
12013
12014 it("reports problems for partially unused eslint-disable comments (in config)", () => {
12015 const code = "alert('test'); // eslint-disable-line no-alert, no-redeclare";
12016 const config = {
12017 linterOptions: {
12018 reportUnusedDisableDirectives: true
12019 },
12020 rules: {
12021 "no-alert": 1,
12022 "no-redeclare": 1
12023 }
12024 };
12025
12026 const messages = linter.verify(code, config, {
12027 filename,
12028 allowInlineConfig: true
12029 });
12030
12031 assert.deepStrictEqual(
12032 messages,
12033 [
12034 {
12035 ruleId: null,
12036 message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').",
12037 line: 1,
12038 column: 16,
12039 fix: {
12040 range: [46, 60],
12041 text: ""
12042 },
12043 severity: 1,
12044 nodeType: null
12045 }
12046 ]
12047 );
12048 });
12049
12050 describe("autofix", () => {
12051 const alwaysReportsRule = {
12052 create(context) {
12053 return {
12054 Program(node) {
12055 context.report({ message: "bad code", loc: node.loc.end });
12056 }
12057 };
12058 }
12059 };
12060
12061 const neverReportsRule = {
12062 create() {
12063 return {};
12064 }
12065 };
12066
12067 const ruleCount = 3;
12068 const usedRules = Array.from(
12069 { length: ruleCount },
12070 (_, index) => `used${index ? `-${index}` : ""}` // "used", "used-1", "used-2"
12071 );
12072 const unusedRules = usedRules.map(name => `un${name}`); // "unused", "unused-1", "unused-2"
12073
12074 const config = {
12075 plugins: {
12076 test: {
12077 rules: {}
12078 }
12079 },
12080 linterOptions: {
12081 reportUnusedDisableDirectives: true
12082 },
12083 rules: {
12084 ...Object.fromEntries(usedRules.map(name => [`test/${name}`, "error"])),
12085 ...Object.fromEntries(unusedRules.map(name => [`test/${name}`, "error"]))
12086 }
12087 };
12088
12089 beforeEach(() => {
12090 config.plugins.test.rules = {
12091 ...Object.fromEntries(usedRules.map(name => [name, alwaysReportsRule])),
12092 ...Object.fromEntries(unusedRules.map(name => [name, neverReportsRule]))
12093 };
12094 });
12095
12096 const tests = [
12097
12098 //-----------------------------------------------
12099 // Removing the entire comment
12100 //-----------------------------------------------
12101
12102 {
12103 code: "// eslint-disable-line test/unused",
12104 output: " "
12105 },
12106 {
12107 code: "foo// eslint-disable-line test/unused",
12108 output: "foo "
12109 },
12110 {
12111 code: "// eslint-disable-line ,test/unused,",
12112 output: " "
12113 },
12114 {
12115 code: "// eslint-disable-line test/unused-1, test/unused-2",
12116 output: " "
12117 },
12118 {
12119 code: "// eslint-disable-line ,test/unused-1,, test/unused-2,, -- comment",
12120 output: " "
12121 },
12122 {
12123 code: "// eslint-disable-next-line test/unused\n",
12124 output: " \n"
12125 },
12126 {
12127 code: "// eslint-disable-next-line test/unused\nfoo",
12128 output: " \nfoo"
12129 },
12130 {
12131 code: "/* eslint-disable \ntest/unused\n*/",
12132 output: " "
12133 },
12134
12135 //-----------------------------------------------
12136 // Removing only individual rules
12137 //-----------------------------------------------
12138
12139 // content before the first rule should not be changed
12140 {
12141 code: "//eslint-disable-line test/unused, test/used",
12142 output: "//eslint-disable-line test/used"
12143 },
12144 {
12145 code: "// eslint-disable-line test/unused, test/used",
12146 output: "// eslint-disable-line test/used"
12147 },
12148 {
12149 code: "// eslint-disable-line test/unused, test/used",
12150 output: "// eslint-disable-line test/used"
12151 },
12152 {
12153 code: "/*\neslint-disable test/unused, test/used*/",
12154 output: "/*\neslint-disable test/used*/"
12155 },
12156 {
12157 code: "/*\n eslint-disable test/unused, test/used*/",
12158 output: "/*\n eslint-disable test/used*/"
12159 },
12160 {
12161 code: "/*\r\neslint-disable test/unused, test/used*/",
12162 output: "/*\r\neslint-disable test/used*/"
12163 },
12164 {
12165 code: "/*\u2028eslint-disable test/unused, test/used*/",
12166 output: "/*\u2028eslint-disable test/used*/"
12167 },
12168 {
12169 code: "/*\u00A0eslint-disable test/unused, test/used*/",
12170 output: "/*\u00A0eslint-disable test/used*/"
12171 },
12172 {
12173 code: "// eslint-disable-line test/unused, test/used",
12174 output: "// eslint-disable-line test/used"
12175 },
12176 {
12177 code: "/* eslint-disable\ntest/unused, test/used*/",
12178 output: "/* eslint-disable\ntest/used*/"
12179 },
12180 {
12181 code: "/* eslint-disable\n test/unused, test/used*/",
12182 output: "/* eslint-disable\n test/used*/"
12183 },
12184 {
12185 code: "/* eslint-disable\r\ntest/unused, test/used*/",
12186 output: "/* eslint-disable\r\ntest/used*/"
12187 },
12188 {
12189 code: "/* eslint-disable\u2028test/unused, test/used*/",
12190 output: "/* eslint-disable\u2028test/used*/"
12191 },
12192 {
12193 code: "/* eslint-disable\u00A0test/unused, test/used*/",
12194 output: "/* eslint-disable\u00A0test/used*/"
12195 },
12196
12197 // when removing the first rule, the comma and all whitespace up to the next rule (or next lone comma) should also be removed
12198 {
12199 code: "// eslint-disable-line test/unused,test/used",
12200 output: "// eslint-disable-line test/used"
12201 },
12202 {
12203 code: "// eslint-disable-line test/unused, test/used",
12204 output: "// eslint-disable-line test/used"
12205 },
12206 {
12207 code: "// eslint-disable-line test/unused , test/used",
12208 output: "// eslint-disable-line test/used"
12209 },
12210 {
12211 code: "// eslint-disable-line test/unused, test/used",
12212 output: "// eslint-disable-line test/used"
12213 },
12214 {
12215 code: "// eslint-disable-line test/unused ,test/used",
12216 output: "// eslint-disable-line test/used"
12217 },
12218 {
12219 code: "/* eslint-disable test/unused\n,\ntest/used */",
12220 output: "/* eslint-disable test/used */"
12221 },
12222 {
12223 code: "/* eslint-disable test/unused \n \n,\n\n test/used */",
12224 output: "/* eslint-disable test/used */"
12225 },
12226 {
12227 code: "/* eslint-disable test/unused\u2028,\u2028test/used */",
12228 output: "/* eslint-disable test/used */"
12229 },
12230 {
12231 code: "// eslint-disable-line test/unused\u00A0,\u00A0test/used",
12232 output: "// eslint-disable-line test/used"
12233 },
12234 {
12235 code: "// eslint-disable-line test/unused,,test/used",
12236 output: "// eslint-disable-line ,test/used"
12237 },
12238 {
12239 code: "// eslint-disable-line test/unused, ,test/used",
12240 output: "// eslint-disable-line ,test/used"
12241 },
12242 {
12243 code: "// eslint-disable-line test/unused,, test/used",
12244 output: "// eslint-disable-line , test/used"
12245 },
12246 {
12247 code: "// eslint-disable-line test/unused,test/used ",
12248 output: "// eslint-disable-line test/used "
12249 },
12250 {
12251 code: "// eslint-disable-next-line test/unused,test/used\n",
12252 output: "// eslint-disable-next-line test/used\n"
12253 },
12254
12255 // when removing a rule in the middle, one comma and all whitespace between commas should also be removed
12256 {
12257 code: "// eslint-disable-line test/used-1,test/unused,test/used-2",
12258 output: "// eslint-disable-line test/used-1,test/used-2"
12259 },
12260 {
12261 code: "// eslint-disable-line test/used-1, test/unused,test/used-2",
12262 output: "// eslint-disable-line test/used-1,test/used-2"
12263 },
12264 {
12265 code: "// eslint-disable-line test/used-1,test/unused ,test/used-2",
12266 output: "// eslint-disable-line test/used-1,test/used-2"
12267 },
12268 {
12269 code: "// eslint-disable-line test/used-1, test/unused ,test/used-2",
12270 output: "// eslint-disable-line test/used-1,test/used-2"
12271 },
12272 {
12273 code: "/* eslint-disable test/used-1,\ntest/unused\n,test/used-2 */",
12274 output: "/* eslint-disable test/used-1,test/used-2 */"
12275 },
12276 {
12277 code: "/* eslint-disable test/used-1,\n\n test/unused \n \n ,test/used-2 */",
12278 output: "/* eslint-disable test/used-1,test/used-2 */"
12279 },
12280 {
12281 code: "/* eslint-disable test/used-1,\u2028test/unused\u2028,test/used-2 */",
12282 output: "/* eslint-disable test/used-1,test/used-2 */"
12283 },
12284 {
12285 code: "// eslint-disable-line test/used-1,\u00A0test/unused\u00A0,test/used-2",
12286 output: "// eslint-disable-line test/used-1,test/used-2"
12287 },
12288
12289 // when removing a rule in the middle, content around commas should not be changed
12290 {
12291 code: "// eslint-disable-line test/used-1, test/unused ,test/used-2",
12292 output: "// eslint-disable-line test/used-1,test/used-2"
12293 },
12294 {
12295 code: "// eslint-disable-line test/used-1,test/unused, test/used-2",
12296 output: "// eslint-disable-line test/used-1, test/used-2"
12297 },
12298 {
12299 code: "// eslint-disable-line test/used-1 ,test/unused,test/used-2",
12300 output: "// eslint-disable-line test/used-1 ,test/used-2"
12301 },
12302 {
12303 code: "// eslint-disable-line test/used-1 ,test/unused, test/used-2",
12304 output: "// eslint-disable-line test/used-1 , test/used-2"
12305 },
12306 {
12307 code: "// eslint-disable-line test/used-1 , test/unused , test/used-2",
12308 output: "// eslint-disable-line test/used-1 , test/used-2"
12309 },
12310 {
12311 code: "/* eslint-disable test/used-1\n,test/unused,\ntest/used-2 */",
12312 output: "/* eslint-disable test/used-1\n,\ntest/used-2 */"
12313 },
12314 {
12315 code: "/* eslint-disable test/used-1\u2028,test/unused,\u2028test/used-2 */",
12316 output: "/* eslint-disable test/used-1\u2028,\u2028test/used-2 */"
12317 },
12318 {
12319 code: "// eslint-disable-line test/used-1\u00A0,test/unused,\u00A0test/used-2",
12320 output: "// eslint-disable-line test/used-1\u00A0,\u00A0test/used-2"
12321 },
12322 {
12323 code: "// eslint-disable-line , test/unused ,test/used",
12324 output: "// eslint-disable-line ,test/used"
12325 },
12326 {
12327 code: "/* eslint-disable\n, test/unused ,test/used */",
12328 output: "/* eslint-disable\n,test/used */"
12329 },
12330 {
12331 code: "/* eslint-disable test/used-1,\n,test/unused,test/used-2 */",
12332 output: "/* eslint-disable test/used-1,\n,test/used-2 */"
12333 },
12334 {
12335 code: "/* eslint-disable test/used-1,test/unused,\n,test/used-2 */",
12336 output: "/* eslint-disable test/used-1,\n,test/used-2 */"
12337 },
12338 {
12339 code: "/* eslint-disable test/used-1,\n,test/unused,\n,test/used-2 */",
12340 output: "/* eslint-disable test/used-1,\n,\n,test/used-2 */"
12341 },
12342 {
12343 code: "// eslint-disable-line test/used, test/unused,",
12344 output: "// eslint-disable-line test/used,"
12345 },
12346 {
12347 code: "// eslint-disable-next-line test/used, test/unused,\n",
12348 output: "// eslint-disable-next-line test/used,\n"
12349 },
12350 {
12351 code: "// eslint-disable-line test/used, test/unused, ",
12352 output: "// eslint-disable-line test/used, "
12353 },
12354 {
12355 code: "// eslint-disable-line test/used, test/unused, -- comment",
12356 output: "// eslint-disable-line test/used, -- comment"
12357 },
12358 {
12359 code: "/* eslint-disable test/used, test/unused,\n*/",
12360 output: "/* eslint-disable test/used,\n*/"
12361 },
12362
12363 // when removing the last rule, the comma and all whitespace up to the previous rule (or previous lone comma) should also be removed
12364 {
12365 code: "// eslint-disable-line test/used,test/unused",
12366 output: "// eslint-disable-line test/used"
12367 },
12368 {
12369 code: "// eslint-disable-line test/used, test/unused",
12370 output: "// eslint-disable-line test/used"
12371 },
12372 {
12373 code: "// eslint-disable-line test/used ,test/unused",
12374 output: "// eslint-disable-line test/used"
12375 },
12376 {
12377 code: "// eslint-disable-line test/used , test/unused",
12378 output: "// eslint-disable-line test/used"
12379 },
12380 {
12381 code: "// eslint-disable-line test/used, test/unused",
12382 output: "// eslint-disable-line test/used"
12383 },
12384 {
12385 code: "// eslint-disable-line test/used ,test/unused",
12386 output: "// eslint-disable-line test/used"
12387 },
12388 {
12389 code: "/* eslint-disable test/used\n,\ntest/unused */",
12390 output: "/* eslint-disable test/used */"
12391 },
12392 {
12393 code: "/* eslint-disable test/used \n \n,\n\n test/unused */",
12394 output: "/* eslint-disable test/used */"
12395 },
12396 {
12397 code: "/* eslint-disable test/used\u2028,\u2028test/unused */",
12398 output: "/* eslint-disable test/used */"
12399 },
12400 {
12401 code: "// eslint-disable-line test/used\u00A0,\u00A0test/unused",
12402 output: "// eslint-disable-line test/used"
12403 },
12404 {
12405 code: "// eslint-disable-line test/used,,test/unused",
12406 output: "// eslint-disable-line test/used,"
12407 },
12408 {
12409 code: "// eslint-disable-line test/used, ,test/unused",
12410 output: "// eslint-disable-line test/used,"
12411 },
12412 {
12413 code: "/* eslint-disable test/used,\n,test/unused */",
12414 output: "/* eslint-disable test/used, */"
12415 },
12416 {
12417 code: "/* eslint-disable test/used\n, ,test/unused */",
12418 output: "/* eslint-disable test/used\n, */"
12419 },
12420
12421 // content after the last rule should not be changed
12422 {
12423 code: "// eslint-disable-line test/used,test/unused",
12424 output: "// eslint-disable-line test/used"
12425 },
12426 {
12427 code: "// eslint-disable-line test/used,test/unused ",
12428 output: "// eslint-disable-line test/used "
12429 },
12430 {
12431 code: "// eslint-disable-line test/used,test/unused ",
12432 output: "// eslint-disable-line test/used "
12433 },
12434 {
12435 code: "// eslint-disable-line test/used,test/unused -- comment",
12436 output: "// eslint-disable-line test/used -- comment"
12437 },
12438 {
12439 code: "// eslint-disable-next-line test/used,test/unused\n",
12440 output: "// eslint-disable-next-line test/used\n"
12441 },
12442 {
12443 code: "// eslint-disable-next-line test/used,test/unused \n",
12444 output: "// eslint-disable-next-line test/used \n"
12445 },
12446 {
12447 code: "/* eslint-disable test/used,test/unused\u2028*/",
12448 output: "/* eslint-disable test/used\u2028*/"
12449 },
12450 {
12451 code: "// eslint-disable-line test/used,test/unused\u00A0",
12452 output: "// eslint-disable-line test/used\u00A0"
12453 },
12454
12455 // multiply rules to remove
12456 {
12457 code: "// eslint-disable-line test/used, test/unused-1, test/unused-2",
12458 output: "// eslint-disable-line test/used"
12459 },
12460 {
12461 code: "// eslint-disable-line test/unused-1, test/used, test/unused-2",
12462 output: "// eslint-disable-line test/used"
12463 },
12464 {
12465 code: "// eslint-disable-line test/unused-1, test/unused-2, test/used",
12466 output: "// eslint-disable-line test/used"
12467 },
12468 {
12469 code: "// eslint-disable-line test/used-1, test/unused-1, test/used-2, test/unused-2",
12470 output: "// eslint-disable-line test/used-1, test/used-2"
12471 },
12472 {
12473 code: "// eslint-disable-line test/unused-1, test/used-1, test/unused-2, test/used-2",
12474 output: "// eslint-disable-line test/used-1, test/used-2"
12475 },
12476 {
12477 code: `
12478 /* eslint-disable test/unused-1,
12479 test/used-1,
12480 test/unused-2,
12481 test/used-2
12482 */
12483 `,
12484 output: `
12485 /* eslint-disable test/used-1,
12486 test/used-2
12487 */
12488 `
12489 },
12490 {
12491 code: `
12492 /* eslint-disable
12493 test/unused-1,
12494 test/used-1,
12495 test/unused-2,
12496 test/used-2
12497 */
12498 `,
12499 output: `
12500 /* eslint-disable
12501 test/used-1,
12502 test/used-2
12503 */
12504 `
12505 },
12506 {
12507 code: `
12508 /* eslint-disable
12509 test/used-1,
12510 test/unused-1,
12511 test/used-2,
12512 test/unused-2
12513 */
12514 `,
12515 output: `
12516 /* eslint-disable
12517 test/used-1,
12518 test/used-2
12519 */
12520 `
12521 },
12522 {
12523 code: `
12524 /* eslint-disable
12525 test/used-1,
12526 test/unused-1,
12527 test/used-2,
12528 test/unused-2,
12529 */
12530 `,
12531 output: `
12532 /* eslint-disable
12533 test/used-1,
12534 test/used-2,
12535 */
12536 `
12537 },
12538 {
12539 code: `
12540 /* eslint-disable
12541 ,test/unused-1
12542 ,test/used-1
12543 ,test/unused-2
12544 ,test/used-2
12545 */
12546 `,
12547 output: `
12548 /* eslint-disable
12549 ,test/used-1
12550 ,test/used-2
12551 */
12552 `
12553 },
12554 {
12555 code: `
12556 /* eslint-disable
12557 ,test/used-1
12558 ,test/unused-1
12559 ,test/used-2
12560 ,test/unused-2
12561 */
12562 `,
12563 output: `
12564 /* eslint-disable
12565 ,test/used-1
12566 ,test/used-2
12567 */
12568 `
12569 },
12570 {
12571 code: `
12572 /* eslint-disable
12573 test/used-1,
12574 test/unused-1,
12575 test/used-2,
12576 test/unused-2
12577
12578 -- comment
12579 */
12580 `,
12581 output: `
12582 /* eslint-disable
12583 test/used-1,
12584 test/used-2
12585
12586 -- comment
12587 */
12588 `
12589 },
12590
12591 // duplicates in the list
12592 {
12593 code: "// eslint-disable-line test/unused, test/unused, test/used",
12594 output: "// eslint-disable-line test/used"
12595 },
12596 {
12597 code: "// eslint-disable-line test/unused, test/used, test/unused",
12598 output: "// eslint-disable-line test/used"
12599 },
12600 {
12601 code: "// eslint-disable-line test/used, test/unused, test/unused, test/used",
12602 output: "// eslint-disable-line test/used, test/used"
12603 }
12604 ];
12605
12606 for (const { code, output } of tests) {
12607 // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach()
12608 it(code, () => {
12609 assert.strictEqual(
12610 linter.verifyAndFix(code, config).output,
12611 output
12612 );
12613 });
12614 }
12615 });
12616 });
12617
12618 });
12619
12620 describe("Default Global Variables", () => {
12621 const code = "x";
12622
12623 it("builtin global variables should be available in the global scope", () => {
12624 let spy;
12625 const config = {
12626 plugins: {
12627 test: {
12628 rules: {
12629 checker: context => {
12630 spy = sinon.spy(() => {
12631 const scope = context.getScope();
12632
12633 assert.notStrictEqual(getVariable(scope, "Object"), null);
12634 assert.notStrictEqual(getVariable(scope, "Array"), null);
12635 assert.notStrictEqual(getVariable(scope, "undefined"), null);
12636 });
12637
12638 return { Program: spy };
12639 }
12640 }
12641 }
12642 },
12643 languageOptions: {
12644 ecmaVersion: 5,
12645 sourceType: "script"
12646 },
12647 rules: {
12648 "test/checker": "error"
12649 }
12650 };
12651
12652 linter.verify(code, config);
12653 assert(spy && spy.calledOnce, "Rule should have been called.");
12654 });
12655
12656 it("ES6 global variables should be available by default", () => {
12657 let spy;
12658 const config = {
12659 plugins: {
12660 test: {
12661 rules: {
12662 checker: context => {
12663 spy = sinon.spy(() => {
12664 const scope = context.getScope();
12665
12666 assert.notStrictEqual(getVariable(scope, "Promise"), null);
12667 assert.notStrictEqual(getVariable(scope, "Symbol"), null);
12668 assert.notStrictEqual(getVariable(scope, "WeakMap"), null);
12669 });
12670
12671 return { Program: spy };
12672 }
12673 }
12674 }
12675 },
12676 languageOptions: {
12677 sourceType: "script"
12678 },
12679 rules: {
12680 "test/checker": "error"
12681 }
12682 };
12683
12684 linter.verify(code, config);
12685 assert(spy && spy.calledOnce);
12686 });
12687
12688 });
12689
12690 describe("Suggestions", () => {
12691 it("provides suggestion information for tools to use", () => {
12692
12693 const config = {
12694 plugins: {
12695 test: {
12696 rules: {
12697 "rule-with-suggestions": {
12698 meta: { hasSuggestions: true },
12699 create: context => ({
12700 Program(node) {
12701 context.report({
12702 node,
12703 message: "Incorrect spacing",
12704 suggest: [{
12705 desc: "Insert space at the beginning",
12706 fix: fixer => fixer.insertTextBefore(node, " ")
12707 }, {
12708 desc: "Insert space at the end",
12709 fix: fixer => fixer.insertTextAfter(node, " ")
12710 }]
12711 });
12712 }
12713 })
12714 }
12715 }
12716 }
12717 },
12718 rules: {
12719 "test/rule-with-suggestions": "error"
12720 }
12721 };
12722
12723 const messages = linter.verify("var a = 1;", config);
12724
12725 assert.deepStrictEqual(messages[0].suggestions, [{
12726 desc: "Insert space at the beginning",
12727 fix: {
12728 range: [0, 0],
12729 text: " "
12730 }
12731 }, {
12732 desc: "Insert space at the end",
12733 fix: {
12734 range: [10, 10],
12735 text: " "
12736 }
12737 }]);
12738 });
12739
12740 it("supports messageIds for suggestions", () => {
12741
12742 const config = {
12743 plugins: {
12744 test: {
12745 rules: {
12746 "rule-with-suggestions": {
12747 meta: {
12748 messages: {
12749 suggestion1: "Insert space at the beginning",
12750 suggestion2: "Insert space at the end"
12751 },
12752 hasSuggestions: true
12753 },
12754 create: context => ({
12755 Program(node) {
12756 context.report({
12757 node,
12758 message: "Incorrect spacing",
12759 suggest: [{
12760 messageId: "suggestion1",
12761 fix: fixer => fixer.insertTextBefore(node, " ")
12762 }, {
12763 messageId: "suggestion2",
12764 fix: fixer => fixer.insertTextAfter(node, " ")
12765 }]
12766 });
12767 }
12768 })
12769 }
12770 }
12771 }
12772 },
12773 rules: {
12774 "test/rule-with-suggestions": "error"
12775 }
12776 };
12777
12778 const messages = linter.verify("var a = 1;", config);
12779
12780 assert.deepStrictEqual(messages[0].suggestions, [{
12781 messageId: "suggestion1",
12782 desc: "Insert space at the beginning",
12783 fix: {
12784 range: [0, 0],
12785 text: " "
12786 }
12787 }, {
12788 messageId: "suggestion2",
12789 desc: "Insert space at the end",
12790 fix: {
12791 range: [10, 10],
12792 text: " "
12793 }
12794 }]);
12795 });
12796
12797 it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled", () => {
12798
12799 const config = {
12800 plugins: {
12801 test: {
12802 rules: {
12803 "rule-with-suggestions": {
12804 meta: { docs: {}, schema: [] },
12805 create: context => ({
12806 Program(node) {
12807 context.report({
12808 node,
12809 message: "hello world",
12810 suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }]
12811 });
12812 }
12813 })
12814 }
12815 }
12816 }
12817 },
12818 rules: {
12819 "test/rule-with-suggestions": "error"
12820 }
12821 };
12822
12823 assert.throws(() => {
12824 linter.verify("0", config);
12825 }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
12826 });
12827
12828 it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled and the rule has the obsolete `meta.docs.suggestion` property", () => {
12829
12830 const config = {
12831 plugins: {
12832 test: {
12833 rules: {
12834 "rule-with-meta-docs-suggestion": {
12835 meta: { docs: { suggestion: true }, schema: [] },
12836 create: context => ({
12837 Program(node) {
12838 context.report({
12839 node,
12840 message: "hello world",
12841 suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }]
12842 });
12843 }
12844 })
12845 }
12846 }
12847 }
12848 },
12849 rules: {
12850 "test/rule-with-meta-docs-suggestion": "error"
12851 }
12852 };
12853
12854 assert.throws(() => {
12855 linter.verify("0", config);
12856 }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint.");
12857 });
12858 });
12859
12860
12861 describe("Error Conditions", () => {
12862 describe("when evaluating broken code", () => {
12863 const code = BROKEN_TEST_CODE;
12864
12865 it("should report a violation with a useful parse error prefix", () => {
12866 const messages = linter.verify(code);
12867
12868 assert.strictEqual(messages.length, 1);
12869 assert.strictEqual(messages[0].severity, 2);
12870 assert.isNull(messages[0].ruleId);
12871 assert.strictEqual(messages[0].line, 1);
12872 assert.strictEqual(messages[0].column, 4);
12873 assert.isTrue(messages[0].fatal);
12874 assert.match(messages[0].message, /^Parsing error:/u);
12875 });
12876
12877 it("should report source code where the issue is present", () => {
12878 const inValidCode = [
12879 "var x = 20;",
12880 "if (x ==4 {",
12881 " x++;",
12882 "}"
12883 ];
12884 const messages = linter.verify(inValidCode.join("\n"));
12885
12886 assert.strictEqual(messages.length, 1);
12887 assert.strictEqual(messages[0].severity, 2);
12888 assert.isTrue(messages[0].fatal);
12889 assert.match(messages[0].message, /^Parsing error:/u);
12890 });
12891 });
12892
12893 describe("when using a rule which has been replaced", () => {
12894 const code = TEST_CODE;
12895
12896 it("should report the new rule", () => {
12897
12898 assert.throws(() => {
12899 linter.verify(code, { rules: { "no-comma-dangle": 2 } });
12900 }, /Key "rules": Key "no-comma-dangle": Rule "no-comma-dangle" was removed and replaced by "comma-dangle"/u);
12901
12902 });
12903 });
12904
12905 });
12906 });
12907
12908 describe("getSourceCode()", () => {
12909 const code = TEST_CODE;
12910
12911 it("should retrieve SourceCode object after reset", () => {
12912 linter.verify(code, {}, filename, true);
12913
12914 const sourceCode = linter.getSourceCode();
12915
12916 assert.isObject(sourceCode);
12917 assert.strictEqual(sourceCode.text, code);
12918 assert.isObject(sourceCode.ast);
12919 });
12920
12921 it("should retrieve SourceCode object without reset", () => {
12922 linter.verify(code, {}, filename);
12923
12924 const sourceCode = linter.getSourceCode();
12925
12926 assert.isObject(sourceCode);
12927 assert.strictEqual(sourceCode.text, code);
12928 assert.isObject(sourceCode.ast);
12929 });
12930
12931 });
12932
12933 describe("defineRule()", () => {
12934 it("should throw an error when called in flat config mode", () => {
12935 assert.throws(() => {
12936 linter.defineRule("foo", () => {});
12937 }, /This method cannot be used with flat config/u);
12938 });
12939 });
12940
12941 describe("defineRules()", () => {
12942 it("should throw an error when called in flat config mode", () => {
12943 assert.throws(() => {
12944 linter.defineRules({});
12945 }, /This method cannot be used with flat config/u);
12946 });
12947 });
12948
12949 describe("defineParser()", () => {
12950 it("should throw an error when called in flat config mode", () => {
12951 assert.throws(() => {
12952 linter.defineParser("foo", {});
12953 }, /This method cannot be used with flat config/u);
12954 });
12955 });
12956
12957 describe("getRules()", () => {
12958 it("should throw an error when called in flat config mode", () => {
12959 assert.throws(() => {
12960 linter.getRules();
12961 }, /This method cannot be used with flat config/u);
12962 });
12963 });
12964
12965 describe("version", () => {
12966 it("should return current version number", () => {
12967 const version = linter.version;
12968
12969 assert.isString(version);
12970 assert.isTrue(parseInt(version[0], 10) >= 3);
12971 });
12972 });
12973
12974 describe("verifyAndFix()", () => {
12975 it("Fixes the code", () => {
12976 const messages = linter.verifyAndFix("var a", {
12977 rules: {
12978 semi: 2
12979 }
12980 }, { filename: "test.js" });
12981
12982 assert.strictEqual(messages.output, "var a;", "Fixes were applied correctly");
12983 assert.isTrue(messages.fixed);
12984 });
12985
12986 it("does not require a third argument", () => {
12987 const fixResult = linter.verifyAndFix("var a", {
12988 rules: {
12989 semi: 2
12990 }
12991 });
12992
12993 assert.deepStrictEqual(fixResult, {
12994 fixed: true,
12995 messages: [],
12996 output: "var a;"
12997 });
12998 });
12999
13000 it("does not include suggestions in autofix results", () => {
13001 const fixResult = linter.verifyAndFix("var foo = /\\#/", {
13002 rules: {
13003 semi: 2,
13004 "no-useless-escape": 2
13005 }
13006 });
13007
13008 assert.strictEqual(fixResult.output, "var foo = /\\#/;");
13009 assert.strictEqual(fixResult.fixed, true);
13010 assert.strictEqual(fixResult.messages[0].suggestions.length > 0, true);
13011 });
13012
13013 it("does not apply autofixes when fix argument is `false`", () => {
13014 const fixResult = linter.verifyAndFix("var a", {
13015 rules: {
13016 semi: 2
13017 }
13018 }, { fix: false });
13019
13020 assert.strictEqual(fixResult.fixed, false);
13021 });
13022
13023 it("stops fixing after 10 passes", () => {
13024
13025 const config = {
13026 plugins: {
13027 test: {
13028 rules: {
13029 "add-spaces": {
13030 meta: {
13031 fixable: "whitespace"
13032 },
13033 create(context) {
13034 return {
13035 Program(node) {
13036 context.report({
13037 node,
13038 message: "Add a space before this node.",
13039 fix: fixer => fixer.insertTextBefore(node, " ")
13040 });
13041 }
13042 };
13043 }
13044 }
13045 }
13046 }
13047 },
13048 rules: {
13049 "test/add-spaces": "error"
13050 }
13051 };
13052
13053 const fixResult = linter.verifyAndFix("a", config);
13054
13055 assert.strictEqual(fixResult.fixed, true);
13056 assert.strictEqual(fixResult.output, `${" ".repeat(10)}a`);
13057 assert.strictEqual(fixResult.messages.length, 1);
13058 });
13059
13060 it("should throw an error if fix is passed but meta has no `fixable` property", () => {
13061
13062 const config = {
13063 plugins: {
13064 test: {
13065 rules: {
13066 "test-rule": {
13067 meta: {
13068 docs: {},
13069 schema: []
13070 },
13071 create: context => ({
13072 Program(node) {
13073 context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" }));
13074 }
13075 })
13076 }
13077 }
13078 }
13079 },
13080 rules: {
13081 "test/test-rule": "error"
13082 }
13083 };
13084
13085
13086 assert.throws(() => {
13087 linter.verify("0", config);
13088 }, /Fixable rules must set the `meta\.fixable` property to "code" or "whitespace".\nOccurred while linting <input>:1\nRule: "test\/test-rule"$/u);
13089 });
13090
13091 it("should throw an error if fix is passed and there is no metadata", () => {
13092
13093 const config = {
13094 plugins: {
13095 test: {
13096 rules: {
13097 "test-rule": {
13098 create: context => ({
13099 Program(node) {
13100 context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" }));
13101 }
13102 })
13103 }
13104 }
13105 }
13106 },
13107 rules: {
13108 "test/test-rule": "error"
13109 }
13110 };
13111
13112 assert.throws(() => {
13113 linter.verify("0", config);
13114 }, /Fixable rules must set the `meta\.fixable` property/u);
13115 });
13116
13117 it("should throw an error if fix is passed from a legacy-format rule", () => {
13118
13119 const config = {
13120 plugins: {
13121 test: {
13122 rules: {
13123 "test-rule": context => ({
13124 Program(node) {
13125 context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" }));
13126 }
13127 })
13128 }
13129 }
13130 },
13131 rules: {
13132 "test/test-rule": "error"
13133 }
13134 };
13135
13136
13137 assert.throws(() => {
13138 linter.verify("0", config);
13139 }, /Fixable rules must set the `meta\.fixable` property/u);
13140 });
13141 });
13142
13143 describe("Mutability", () => {
13144 let linter1 = null;
13145 let linter2 = null;
13146
13147 beforeEach(() => {
13148 linter1 = new Linter();
13149 linter2 = new Linter();
13150 });
13151
13152 describe("rules", () => {
13153 it("with no changes, same rules are loaded", () => {
13154 assert.sameDeepMembers(Array.from(linter1.getRules().keys()), Array.from(linter2.getRules().keys()));
13155 });
13156
13157 it("loading rule in one doesn't change the other", () => {
13158 linter1.defineRule("mock-rule", () => ({}));
13159
13160 assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present");
13161 assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present");
13162 });
13163 });
13164 });
13165
13166
13167 describe("processors", () => {
13168 let receivedFilenames = [];
13169 let receivedPhysicalFilenames = [];
13170 const extraConfig = {
13171 plugins: {
13172 test: {
13173 rules: {
13174 "report-original-text": {
13175 meta: {
13176
13177 },
13178 create(context) {
13179 return {
13180 Program(ast) {
13181 receivedFilenames.push(context.getFilename());
13182 receivedPhysicalFilenames.push(context.getPhysicalFilename());
13183 context.report({ node: ast, message: context.getSourceCode().text });
13184 }
13185 };
13186 }
13187 }
13188 }
13189 }
13190 }
13191 };
13192
13193 beforeEach(() => {
13194 receivedFilenames = [];
13195 receivedPhysicalFilenames = [];
13196 });
13197
13198 describe("preprocessors", () => {
13199 it("should receive text and filename.", () => {
13200 const code = "foo bar baz";
13201 const preprocess = sinon.spy(text => text.split(" "));
13202 const configs = createFlatConfigArray({});
13203
13204 configs.normalizeSync();
13205
13206 linter.verify(code, configs, { filename, preprocess });
13207
13208 assert.strictEqual(preprocess.calledOnce, true, "preprocess wasn't called");
13209 assert.deepStrictEqual(preprocess.args[0], [code, filename], "preprocess was called with the wrong arguments");
13210 });
13211
13212 it("should run preprocess only once", () => {
13213 const logs = [];
13214 const config = {
13215 files: ["*.md"],
13216 processor: {
13217 preprocess(text, filenameForText) {
13218 logs.push({
13219 text,
13220 filename: filenameForText
13221 });
13222
13223 return [{ text: "bar", filename: "0.js" }];
13224 },
13225 postprocess() {
13226 return [];
13227 }
13228 }
13229 };
13230
13231 linter.verify("foo", config, "a.md");
13232 assert.strictEqual(logs.length, 1, "preprocess() should only be called once.");
13233 });
13234
13235 it("should apply a preprocessor to the code, and lint each code sample separately", () => {
13236 const code = "foo bar baz";
13237 const configs = createFlatConfigArray([
13238 extraConfig,
13239 { rules: { "test/report-original-text": "error" } }
13240 ]);
13241
13242 configs.normalizeSync();
13243
13244 const problems = linter.verify(
13245 code,
13246 configs,
13247 {
13248 filename,
13249
13250 // Apply a preprocessor that splits the source text into spaces and lints each word individually
13251 preprocess(input) {
13252 return input.split(" ");
13253 }
13254 }
13255 );
13256
13257 assert.strictEqual(problems.length, 3);
13258 assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
13259 });
13260
13261 it("should apply a preprocessor to the code even if the preprocessor returned code block objects.", () => {
13262 const code = "foo bar baz";
13263 const configs = createFlatConfigArray([
13264 extraConfig,
13265 { rules: { "test/report-original-text": "error" } }
13266 ]);
13267
13268 configs.normalizeSync();
13269
13270 const problems = linter.verify(
13271 code,
13272 configs,
13273 {
13274 filename,
13275
13276 // Apply a preprocessor that splits the source text into spaces and lints each word individually
13277 preprocess(input) {
13278 return input.split(" ").map(text => ({
13279 filename: "block.js",
13280 text
13281 }));
13282 }
13283 }
13284 );
13285
13286 assert.strictEqual(problems.length, 3);
13287 assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
13288
13289 // filename
13290 assert.strictEqual(receivedFilenames.length, 3);
13291 assert(/^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0]));
13292 assert(/^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1]));
13293 assert(/^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2]));
13294
13295 // physical filename
13296 assert.strictEqual(receivedPhysicalFilenames.length, 3);
13297 assert.strictEqual(receivedPhysicalFilenames.every(name => name === filename), true);
13298 });
13299
13300 it("should receive text even if a SourceCode object was given.", () => {
13301 const code = "foo";
13302 const preprocess = sinon.spy(text => text.split(" "));
13303 const configs = createFlatConfigArray([
13304 extraConfig
13305 ]);
13306
13307 configs.normalizeSync();
13308
13309 linter.verify(code, configs);
13310 const sourceCode = linter.getSourceCode();
13311
13312 linter.verify(sourceCode, configs, { filename, preprocess });
13313
13314 assert.strictEqual(preprocess.calledOnce, true);
13315 assert.deepStrictEqual(preprocess.args[0], [code, filename]);
13316 });
13317
13318 it("should receive text even if a SourceCode object was given (with BOM).", () => {
13319 const code = "\uFEFFfoo";
13320 const preprocess = sinon.spy(text => text.split(" "));
13321 const configs = createFlatConfigArray([
13322 extraConfig
13323 ]);
13324
13325 configs.normalizeSync();
13326
13327 linter.verify(code, configs);
13328 const sourceCode = linter.getSourceCode();
13329
13330 linter.verify(sourceCode, configs, { filename, preprocess });
13331
13332 assert.strictEqual(preprocess.calledOnce, true);
13333 assert.deepStrictEqual(preprocess.args[0], [code, filename]);
13334 });
13335 });
13336
13337 describe("postprocessors", () => {
13338 it("should receive result and filename.", () => {
13339 const code = "foo bar baz";
13340 const preprocess = sinon.spy(text => text.split(" "));
13341 const postprocess = sinon.spy(text => [text]);
13342 const configs = createFlatConfigArray([
13343 extraConfig
13344 ]);
13345
13346 configs.normalizeSync();
13347
13348 linter.verify(code, configs, { filename, postprocess, preprocess });
13349
13350 assert.strictEqual(postprocess.calledOnce, true);
13351 assert.deepStrictEqual(postprocess.args[0], [[[], [], []], filename]);
13352 });
13353
13354 it("should apply a postprocessor to the reported messages", () => {
13355 const code = "foo bar baz";
13356 const configs = createFlatConfigArray([
13357 extraConfig,
13358 { rules: { "test/report-original-text": "error" } }
13359 ]);
13360
13361 configs.normalizeSync();
13362
13363 const problems = linter.verify(
13364 code,
13365 configs,
13366 {
13367 preprocess: input => input.split(" "),
13368
13369 /*
13370 * Apply a postprocessor that updates the locations of the reported problems
13371 * to make sure they correspond to the locations in the original text.
13372 */
13373 postprocess(problemLists) {
13374 problemLists.forEach(problemList => assert.strictEqual(problemList.length, 1));
13375 return problemLists.reduce(
13376 (combinedList, problemList, index) =>
13377 combinedList.concat(
13378 problemList.map(
13379 problem =>
13380 Object.assign(
13381 {},
13382 problem,
13383 {
13384 message: problem.message.toUpperCase(),
13385 column: problem.column + index * 4
13386 }
13387 )
13388 )
13389 ),
13390 []
13391 );
13392 }
13393 }
13394 );
13395
13396 assert.strictEqual(problems.length, 3);
13397 assert.deepStrictEqual(problems.map(problem => problem.message), ["FOO", "BAR", "BAZ"]);
13398 assert.deepStrictEqual(problems.map(problem => problem.column), [1, 5, 9]);
13399 });
13400
13401 it("should use postprocessed problem ranges when applying autofixes", () => {
13402 const code = "foo bar baz";
13403 const configs = createFlatConfigArray([
13404 extraConfig,
13405 {
13406 plugins: {
13407 test2: {
13408 rules: {
13409 "capitalize-identifiers": {
13410 meta: {
13411 fixable: "code"
13412 },
13413 create(context) {
13414 return {
13415 Identifier(node) {
13416 if (node.name !== node.name.toUpperCase()) {
13417 context.report({
13418 node,
13419 message: "Capitalize this identifier",
13420 fix: fixer => fixer.replaceText(node, node.name.toUpperCase())
13421 });
13422 }
13423 }
13424 };
13425 }
13426 }
13427 }
13428 }
13429 }
13430 },
13431 { rules: { "test2/capitalize-identifiers": "error" } }
13432 ]);
13433
13434 configs.normalizeSync();
13435
13436 const fixResult = linter.verifyAndFix(
13437 code,
13438 configs,
13439 {
13440
13441 /*
13442 * Apply a postprocessor that updates the locations of autofixes
13443 * to make sure they correspond to locations in the original text.
13444 */
13445 preprocess: input => input.split(" "),
13446 postprocess(problemLists) {
13447 return problemLists.reduce(
13448 (combinedProblems, problemList, blockIndex) =>
13449 combinedProblems.concat(
13450 problemList.map(problem =>
13451 Object.assign(problem, {
13452 fix: {
13453 text: problem.fix.text,
13454 range: problem.fix.range.map(
13455 rangeIndex => rangeIndex + blockIndex * 4
13456 )
13457 }
13458 }))
13459 ),
13460 []
13461 );
13462 }
13463 }
13464 );
13465
13466 assert.strictEqual(fixResult.fixed, true);
13467 assert.strictEqual(fixResult.messages.length, 0);
13468 assert.strictEqual(fixResult.output, "FOO BAR BAZ");
13469 });
13470 });
13471 });
13472
13473 describe("Edge cases", () => {
13474
13475 describe("Modules", () => {
13476 const moduleConfig = {
13477 languageOptions: {
13478 sourceType: "module",
13479 ecmaVersion: 6
13480 }
13481 };
13482
13483 it("should properly parse import statements when sourceType is module", () => {
13484 const code = "import foo from 'foo';";
13485 const messages = linter.verify(code, moduleConfig);
13486
13487 assert.strictEqual(messages.length, 0, "Unexpected linting error.");
13488 });
13489
13490 it("should properly parse import all statements when sourceType is module", () => {
13491 const code = "import * as foo from 'foo';";
13492 const messages = linter.verify(code, moduleConfig);
13493
13494 assert.strictEqual(messages.length, 0, "Unexpected linting error.");
13495 });
13496
13497 it("should properly parse default export statements when sourceType is module", () => {
13498 const code = "export default function initialize() {}";
13499 const messages = linter.verify(code, moduleConfig);
13500
13501 assert.strictEqual(messages.length, 0, "Unexpected linting error.");
13502 });
13503
13504 });
13505
13506
13507 // https://github.com/eslint/eslint/issues/9687
13508 it("should report an error when invalid languageOptions found", () => {
13509 let messages = linter.verify("", { languageOptions: { ecmaVersion: 222 } });
13510
13511 assert.deepStrictEqual(messages.length, 1);
13512 assert.ok(messages[0].message.includes("Invalid ecmaVersion"));
13513
13514 assert.throws(() => {
13515 linter.verify("", { languageOptions: { sourceType: "foo" } });
13516 }, /Expected "script", "module", or "commonjs"./u);
13517
13518
13519 messages = linter.verify("", { languageOptions: { ecmaVersion: 5, sourceType: "module" } });
13520
13521 assert.deepStrictEqual(messages.length, 1);
13522 assert.ok(messages[0].message.includes("sourceType 'module' is not supported when ecmaVersion < 2015"));
13523 });
13524
13525 it("should not crash when invalid parentheses syntax is encountered", () => {
13526 linter.verify("left = (aSize.width/2) - ()");
13527 });
13528
13529 it("should not crash when let is used inside of switch case", () => {
13530 linter.verify("switch(foo) { case 1: let bar=2; }", { languageOptions: { ecmaVersion: 6 } });
13531 });
13532
13533 it("should not crash when parsing destructured assignment", () => {
13534 linter.verify("var { a='a' } = {};", { languageOptions: { ecmaVersion: 6 } });
13535 });
13536
13537 it("should report syntax error when a keyword exists in object property shorthand", () => {
13538 const messages = linter.verify("let a = {this}", { languageOptions: { ecmaVersion: 6 } });
13539
13540 assert.strictEqual(messages.length, 1);
13541 assert.strictEqual(messages[0].fatal, true);
13542 });
13543
13544 it("should not crash when we reuse the SourceCode object", () => {
13545 const config = {
13546 languageOptions: {
13547 ecmaVersion: 6,
13548 parserOptions: {
13549 ecmaFeatures: { jsx: true }
13550 }
13551 }
13552 };
13553
13554 linter.verify("function render() { return <div className='test'>{hello}</div> }", config);
13555 linter.verify(linter.getSourceCode(), config);
13556 });
13557
13558 it("should reuse the SourceCode object", () => {
13559 let ast1 = null,
13560 ast2 = null;
13561
13562 const config = {
13563 plugins: {
13564 test: {
13565 rules: {
13566 "save-ast1": () => ({
13567 Program(node) {
13568 ast1 = node;
13569 }
13570 }),
13571
13572 "save-ast2": () => ({
13573 Program(node) {
13574 ast2 = node;
13575 }
13576 })
13577
13578 }
13579 }
13580 },
13581 languageOptions: {
13582 ecmaVersion: 6,
13583 parserOptions: {
13584 ecmaFeatures: { jsx: true }
13585 }
13586 }
13587 };
13588
13589
13590 linter.verify("function render() { return <div className='test'>{hello}</div> }", { ...config, rules: { "test/save-ast1": "error" } });
13591 linter.verify(linter.getSourceCode(), { ...config, rules: { "test/save-ast2": "error" } });
13592
13593 assert(ast1 !== null);
13594 assert(ast2 !== null);
13595 assert(ast1 === ast2);
13596 });
13597
13598 it("should not modify config object passed as argument", () => {
13599 const config = {};
13600
13601 Object.freeze(config);
13602 linter.verify("var", config);
13603 });
13604
13605 it("should pass 'id' to rule contexts with the rule id", () => {
13606
13607 const spy = sinon.spy(context => {
13608 assert.strictEqual(context.id, "test/foo-bar-baz");
13609 return {};
13610 });
13611
13612 const config = {
13613 plugins: {
13614 test: {
13615 rules: {
13616 "foo-bar-baz": spy
13617 }
13618 }
13619 },
13620 rules: {
13621 "test/foo-bar-baz": "error"
13622 }
13623 };
13624
13625
13626 linter.verify("x", config);
13627 assert(spy.calledOnce);
13628 });
13629
13630
13631 describe("when evaluating an empty string", () => {
13632 it("runs rules", () => {
13633
13634 const config = {
13635 plugins: {
13636 test: {
13637 rules: {
13638 "no-programs": context => ({
13639 Program(node) {
13640 context.report({ node, message: "No programs allowed." });
13641 }
13642 })
13643 }
13644 }
13645 },
13646 rules: {
13647 "test/no-programs": "error"
13648 }
13649 };
13650
13651 assert.strictEqual(
13652 linter.verify("", config).length,
13653 1
13654 );
13655 });
13656 });
13657
13658 });
13659
13660 });