]> git.proxmox.com Git - pve-eslint.git/blob - eslint/tests/lib/rule-tester/rule-tester.js
6ebd82fb5ab283842b4ba8d2a102f4bc4197bb67
[pve-eslint.git] / eslint / tests / lib / rule-tester / rule-tester.js
1 /**
2 * @fileoverview Tests for ESLint Tester
3 * @author Nicholas C. Zakas
4 */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Requirements
9 //------------------------------------------------------------------------------
10 const sinon = require("sinon"),
11 EventEmitter = require("events"),
12 { RuleTester } = require("../../../lib/rule-tester"),
13 assert = require("chai").assert,
14 nodeAssert = require("assert"),
15 espree = require("espree");
16
17 const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => {
18 try {
19 nodeAssert.strictEqual(1, 2);
20 } catch (err) {
21 return err.operator;
22 }
23 throw new Error("unexpected successful assertion");
24 })();
25
26 /**
27 * Do nothing.
28 * @returns {void}
29 */
30 function noop() {
31
32 // do nothing.
33 }
34
35 //------------------------------------------------------------------------------
36 // Rewire Things
37 //------------------------------------------------------------------------------
38
39 /*
40 * So here's the situation. Because RuleTester uses it() and describe() from
41 * Mocha, any failures would show up in the output of this test file. That means
42 * when we tested that a failure is thrown, that would also count as a failure
43 * in the testing for RuleTester. In order to remove those results from the
44 * results of this file, we need to overwrite it() and describe() just in
45 * RuleTester to do nothing but run code. Effectively, it() and describe()
46 * just become regular functions inside of index.js, not at all related to Mocha.
47 * That allows the results of this file to be untainted and therefore accurate.
48 *
49 * To assert that the right arguments are passed to RuleTester.describe/it, an
50 * event emitter is used which emits the arguments.
51 */
52
53 const ruleTesterTestEmitter = new EventEmitter();
54
55 //------------------------------------------------------------------------------
56 // Tests
57 //------------------------------------------------------------------------------
58
59 describe("RuleTester", () => {
60
61 // Stub `describe()` and `it()` while this test suite.
62 before(() => {
63 RuleTester.describe = function(text, method) {
64 ruleTesterTestEmitter.emit("describe", text, method);
65 return method.call(this);
66 };
67 RuleTester.it = function(text, method) {
68 ruleTesterTestEmitter.emit("it", text, method);
69 return method.call(this);
70 };
71 });
72 after(() => {
73 RuleTester.describe = null;
74 RuleTester.it = null;
75 });
76
77 let ruleTester;
78
79 /**
80 * A helper function to verify Node.js core error messages.
81 * @param {string} actual The actual input
82 * @param {string} expected The expected input
83 * @returns {Function} Error callback to verify that the message is correct
84 * for the actual and expected input.
85 */
86 function assertErrorMatches(actual, expected) {
87 const err = new nodeAssert.AssertionError({
88 actual,
89 expected,
90 operator: NODE_ASSERT_STRICT_EQUAL_OPERATOR
91 });
92
93 return err.message;
94 }
95
96 beforeEach(() => {
97 RuleTester.resetDefaultConfig();
98 ruleTester = new RuleTester();
99 });
100
101 describe("only", () => {
102 describe("`itOnly` accessor", () => {
103 describe("when `itOnly` is set", () => {
104 before(() => {
105 RuleTester.itOnly = sinon.spy();
106 });
107 after(() => {
108 RuleTester.itOnly = void 0;
109 });
110 beforeEach(() => {
111 RuleTester.itOnly.resetHistory();
112 ruleTester = new RuleTester();
113 });
114
115 it("is called by exclusive tests", () => {
116 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
117 valid: [{
118 code: "const notVar = 42;",
119 only: true
120 }],
121 invalid: []
122 });
123
124 sinon.assert.calledWith(RuleTester.itOnly, "const notVar = 42;");
125 });
126 });
127
128 describe("when `it` is set and has an `only()` method", () => {
129 before(() => {
130 RuleTester.it.only = () => {};
131 sinon.spy(RuleTester.it, "only");
132 });
133 after(() => {
134 RuleTester.it.only = void 0;
135 });
136 beforeEach(() => {
137 RuleTester.it.only.resetHistory();
138 ruleTester = new RuleTester();
139 });
140
141 it("is called by tests with `only` set", () => {
142 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
143 valid: [{
144 code: "const notVar = 42;",
145 only: true
146 }],
147 invalid: []
148 });
149
150 sinon.assert.calledWith(RuleTester.it.only, "const notVar = 42;");
151 });
152 });
153
154 describe("when global `it` is a function that has an `only()` method", () => {
155 let originalGlobalItOnly;
156
157 before(() => {
158
159 /*
160 * We run tests with `--forbid-only`, so we have to override
161 * `it.only` to prevent the real one from being called.
162 */
163 originalGlobalItOnly = it.only;
164 it.only = () => {};
165 sinon.spy(it, "only");
166 });
167 after(() => {
168 it.only = originalGlobalItOnly;
169 });
170 beforeEach(() => {
171 it.only.resetHistory();
172 ruleTester = new RuleTester();
173 });
174
175 it("is called by tests with `only` set", () => {
176 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
177 valid: [{
178 code: "const notVar = 42;",
179 only: true
180 }],
181 invalid: []
182 });
183
184 sinon.assert.calledWith(it.only, "const notVar = 42;");
185 });
186 });
187
188 describe("when `describe` and `it` are overridden without `itOnly`", () => {
189 let originalGlobalItOnly;
190
191 before(() => {
192
193 /*
194 * These tests override `describe` and `it` already, so we
195 * don't need to override them here. We do, however, need to
196 * remove `only` from the global `it` to prevent it from
197 * being used instead.
198 */
199 originalGlobalItOnly = it.only;
200 it.only = void 0;
201 });
202 after(() => {
203 it.only = originalGlobalItOnly;
204 });
205 beforeEach(() => {
206 ruleTester = new RuleTester();
207 });
208
209 it("throws an error recommending overriding `itOnly`", () => {
210 assert.throws(() => {
211 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
212 valid: [{
213 code: "const notVar = 42;",
214 only: true
215 }],
216 invalid: []
217 });
218 }, "Set `RuleTester.itOnly` to use `only` with a custom test framework.");
219 });
220 });
221
222 describe("when global `it` is a function that does not have an `only()` method", () => {
223 let originalGlobalIt;
224 let originalRuleTesterDescribe;
225 let originalRuleTesterIt;
226
227 before(() => {
228 originalGlobalIt = global.it;
229
230 // eslint-disable-next-line no-global-assign -- Temporarily override Mocha global
231 it = () => {};
232
233 /*
234 * These tests override `describe` and `it`, so we need to
235 * un-override them here so they won't interfere.
236 */
237 originalRuleTesterDescribe = RuleTester.describe;
238 RuleTester.describe = void 0;
239 originalRuleTesterIt = RuleTester.it;
240 RuleTester.it = void 0;
241 });
242 after(() => {
243
244 // eslint-disable-next-line no-global-assign -- Restore Mocha global
245 it = originalGlobalIt;
246 RuleTester.describe = originalRuleTesterDescribe;
247 RuleTester.it = originalRuleTesterIt;
248 });
249 beforeEach(() => {
250 ruleTester = new RuleTester();
251 });
252
253 it("throws an error explaining that the current test framework does not support `only`", () => {
254 assert.throws(() => {
255 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
256 valid: [{
257 code: "const notVar = 42;",
258 only: true
259 }],
260 invalid: []
261 });
262 }, "The current test framework does not support exclusive tests with `only`.");
263 });
264 });
265 });
266
267 describe("test cases", () => {
268 const ruleName = "no-var";
269 const rule = require("../../fixtures/testers/rule-tester/no-var");
270
271 let originalRuleTesterIt;
272 let spyRuleTesterIt;
273 let originalRuleTesterItOnly;
274 let spyRuleTesterItOnly;
275
276 before(() => {
277 originalRuleTesterIt = RuleTester.it;
278 spyRuleTesterIt = sinon.spy();
279 RuleTester.it = spyRuleTesterIt;
280 originalRuleTesterItOnly = RuleTester.itOnly;
281 spyRuleTesterItOnly = sinon.spy();
282 RuleTester.itOnly = spyRuleTesterItOnly;
283 });
284 after(() => {
285 RuleTester.it = originalRuleTesterIt;
286 RuleTester.itOnly = originalRuleTesterItOnly;
287 });
288 beforeEach(() => {
289 spyRuleTesterIt.resetHistory();
290 spyRuleTesterItOnly.resetHistory();
291 ruleTester = new RuleTester();
292 });
293
294 it("isn't called for normal tests", () => {
295 ruleTester.run(ruleName, rule, {
296 valid: ["const notVar = 42;"],
297 invalid: []
298 });
299 sinon.assert.calledWith(spyRuleTesterIt, "const notVar = 42;");
300 sinon.assert.notCalled(spyRuleTesterItOnly);
301 });
302
303 it("calls it or itOnly for every test case", () => {
304
305 /*
306 * `RuleTester` doesn't implement test case exclusivity itself.
307 * Setting `only: true` just causes `RuleTester` to call
308 * whatever `only()` function is provided by the test framework
309 * instead of the regular `it()` function.
310 */
311
312 ruleTester.run(ruleName, rule, {
313 valid: [
314 "const valid = 42;",
315 {
316 code: "const onlyValid = 42;",
317 only: true
318 }
319 ],
320 invalid: [
321 {
322 code: "var invalid = 42;",
323 errors: [/^Bad var/u]
324 },
325 {
326 code: "var onlyInvalid = 42;",
327 errors: [/^Bad var/u],
328 only: true
329 }
330 ]
331 });
332
333 sinon.assert.calledWith(spyRuleTesterIt, "const valid = 42;");
334 sinon.assert.calledWith(spyRuleTesterItOnly, "const onlyValid = 42;");
335 sinon.assert.calledWith(spyRuleTesterIt, "var invalid = 42;");
336 sinon.assert.calledWith(spyRuleTesterItOnly, "var onlyInvalid = 42;");
337 });
338 });
339
340 describe("static helper wrapper", () => {
341 it("adds `only` to string test cases", () => {
342 const test = RuleTester.only("const valid = 42;");
343
344 assert.deepStrictEqual(test, {
345 code: "const valid = 42;",
346 only: true
347 });
348 });
349
350 it("adds `only` to object test cases", () => {
351 const test = RuleTester.only({ code: "const valid = 42;" });
352
353 assert.deepStrictEqual(test, {
354 code: "const valid = 42;",
355 only: true
356 });
357 });
358 });
359 });
360
361 it("should not throw an error when everything passes", () => {
362 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
363 valid: [
364 "Eval(foo)"
365 ],
366 invalid: [
367 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
368 ]
369 });
370 });
371
372 it("should throw an error when valid code is invalid", () => {
373
374 assert.throws(() => {
375 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
376 valid: [
377 "eval(foo)"
378 ],
379 invalid: [
380 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
381 ]
382 });
383 }, /Should have no errors but had 1/u);
384 });
385
386 it("should throw an error when valid code is invalid", () => {
387
388 assert.throws(() => {
389 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
390 valid: [
391 { code: "eval(foo)" }
392 ],
393 invalid: [
394 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
395 ]
396 });
397 }, /Should have no errors but had 1/u);
398 });
399
400 it("should throw an error if invalid code is valid", () => {
401
402 assert.throws(() => {
403 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
404 valid: [
405 "Eval(foo)"
406 ],
407 invalid: [
408 { code: "Eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
409 ]
410 });
411 }, /Should have 1 error but had 0/u);
412 });
413
414 it("should throw an error when the error message is wrong", () => {
415 assert.throws(() => {
416 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
417
418 // Only the invalid test matters here
419 valid: [
420 "bar = baz;"
421 ],
422 invalid: [
423 { code: "var foo = bar;", errors: [{ message: "Bad error message." }] }
424 ]
425 });
426 }, assertErrorMatches("Bad var.", "Bad error message."));
427 });
428
429 it("should throw an error when the error message regex does not match", () => {
430 assert.throws(() => {
431 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
432 valid: [],
433 invalid: [
434 { code: "var foo = bar;", errors: [{ message: /Bad error message/u }] }
435 ]
436 });
437 }, /Expected 'Bad var.' to match \/Bad error message\//u);
438 });
439
440 it("should throw an error when the error is not a supported type", () => {
441 assert.throws(() => {
442 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
443
444 // Only the invalid test matters here
445 valid: [
446 "bar = baz;"
447 ],
448 invalid: [
449 { code: "var foo = bar;", errors: [42] }
450 ]
451 });
452 }, /Error should be a string, object, or RegExp/u);
453 });
454
455 it("should throw an error when any of the errors is not a supported type", () => {
456 assert.throws(() => {
457 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
458
459 // Only the invalid test matters here
460 valid: [
461 "bar = baz;"
462 ],
463 invalid: [
464 { code: "var foo = bar; var baz = quux", errors: [{ type: "VariableDeclaration" }, null] }
465 ]
466 });
467 }, /Error should be a string, object, or RegExp/u);
468 });
469
470 it("should throw an error when the error is a string and it does not match error message", () => {
471 assert.throws(() => {
472 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
473
474 // Only the invalid test matters here
475 valid: [
476 "bar = baz;"
477 ],
478 invalid: [
479 { code: "var foo = bar;", errors: ["Bad error message."] }
480 ]
481 });
482 }, assertErrorMatches("Bad var.", "Bad error message."));
483 });
484
485 it("should throw an error when the error is a string and it does not match error message", () => {
486 assert.throws(() => {
487 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
488
489 valid: [
490 ],
491 invalid: [
492 { code: "var foo = bar;", errors: [/Bad error message/u] }
493 ]
494 });
495 }, /Expected 'Bad var.' to match \/Bad error message\//u);
496 });
497
498 it("should not throw an error when the error is a string and it matches error message", () => {
499 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
500
501 // Only the invalid test matters here
502 valid: [
503 "bar = baz;"
504 ],
505 invalid: [
506 { code: "var foo = bar;", output: " foo = bar;", errors: ["Bad var."] }
507 ]
508 });
509 });
510
511 it("should not throw an error when the error is a regex and it matches error message", () => {
512 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
513 valid: [],
514 invalid: [
515 { code: "var foo = bar;", output: " foo = bar;", errors: [/^Bad var/u] }
516 ]
517 });
518 });
519
520 it("should throw an error when the error is an object with an unknown property name", () => {
521 assert.throws(() => {
522 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
523 valid: [
524 "bar = baz;"
525 ],
526 invalid: [
527 { code: "var foo = bar;", errors: [{ Message: "Bad var." }] }
528 ]
529 });
530 }, /Invalid error property name 'Message'/u);
531 });
532
533 it("should throw an error when any of the errors is an object with an unknown property name", () => {
534 assert.throws(() => {
535 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
536 valid: [
537 "bar = baz;"
538 ],
539 invalid: [
540 {
541 code: "var foo = bar; var baz = quux",
542 errors: [
543 { message: "Bad var.", type: "VariableDeclaration" },
544 { message: "Bad var.", typo: "VariableDeclaration" }
545 ]
546 }
547 ]
548 });
549 }, /Invalid error property name 'typo'/u);
550 });
551
552 it("should not throw an error when the error is a regex in an object and it matches error message", () => {
553 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
554 valid: [],
555 invalid: [
556 { code: "var foo = bar;", output: " foo = bar;", errors: [{ message: /^Bad var/u }] }
557 ]
558 });
559 });
560
561 it("should throw an error when the expected output doesn't match", () => {
562 assert.throws(() => {
563 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
564 valid: [
565 "bar = baz;"
566 ],
567 invalid: [
568 { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration" }] }
569 ]
570 });
571 }, /Output is incorrect/u);
572 });
573
574 it("should use strict equality to compare output", () => {
575 const replaceProgramWith5Rule = {
576 meta: {
577 fixable: "code"
578 },
579
580 create: context => ({
581 Program(node) {
582 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
583 }
584 })
585 };
586
587 // Should not throw.
588 ruleTester.run("foo", replaceProgramWith5Rule, {
589 valid: [],
590 invalid: [
591 { code: "var foo = bar;", output: "5", errors: 1 }
592 ]
593 });
594
595 assert.throws(() => {
596 ruleTester.run("foo", replaceProgramWith5Rule, {
597 valid: [],
598 invalid: [
599 { code: "var foo = bar;", output: 5, errors: 1 }
600 ]
601 });
602 }, /Output is incorrect/u);
603 });
604
605 it("should throw an error when the expected output doesn't match and errors is just a number", () => {
606 assert.throws(() => {
607 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
608 valid: [
609 "bar = baz;"
610 ],
611 invalid: [
612 { code: "var foo = bar;", output: "foo = bar", errors: 1 }
613 ]
614 });
615 }, /Output is incorrect/u);
616 });
617
618 it("should not throw an error when the expected output is null and no errors produce output", () => {
619 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
620 valid: [
621 "bar = baz;"
622 ],
623 invalid: [
624 { code: "eval(x)", errors: 1, output: null },
625 { code: "eval(x); eval(y);", errors: 2, output: null }
626 ]
627 });
628 });
629
630 it("should throw an error when the expected output is null and problems produce output", () => {
631 assert.throws(() => {
632 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
633 valid: [
634 "bar = baz;"
635 ],
636 invalid: [
637 { code: "var foo = bar;", output: null, errors: 1 }
638 ]
639 });
640 }, /Expected no autofixes to be suggested/u);
641
642 assert.throws(() => {
643 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
644 valid: [
645 "bar = baz;"
646 ],
647 invalid: [
648 {
649 code: "var foo = bar; var qux = boop;",
650 output: null,
651 errors: 2
652 }
653 ]
654 });
655 }, /Expected no autofixes to be suggested/u);
656 });
657
658 it("should throw an error when the expected output is null and only some problems produce output", () => {
659 assert.throws(() => {
660 ruleTester.run("fixes-one-problem", require("../../fixtures/testers/rule-tester/fixes-one-problem"), {
661 valid: [],
662 invalid: [
663 { code: "foo", output: null, errors: 2 }
664 ]
665 });
666 }, /Expected no autofixes to be suggested/u);
667 });
668
669 it("should throw an error when the expected output isn't specified and problems produce output", () => {
670 assert.throws(() => {
671 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
672 valid: [
673 "bar = baz;"
674 ],
675 invalid: [
676 { code: "var foo = bar;", errors: 1 }
677 ]
678 });
679 }, "The rule fixed the code. Please add 'output' property.");
680 });
681
682 it("should throw an error if invalid code specifies wrong type", () => {
683 assert.throws(() => {
684 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
685 valid: [
686 "Eval(foo)"
687 ],
688 invalid: [
689 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression2" }] }
690 ]
691 });
692 }, /Error type should be CallExpression2, found CallExpression/u);
693 });
694
695 it("should throw an error if invalid code specifies wrong line", () => {
696 assert.throws(() => {
697 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
698 valid: [
699 "Eval(foo)"
700 ],
701 invalid: [
702 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 5 }] }
703 ]
704 });
705 }, /Error line should be 5/u);
706 });
707
708 it("should not skip line assertion if line is a falsy value", () => {
709 assert.throws(() => {
710 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
711 valid: [
712 "Eval(foo)"
713 ],
714 invalid: [
715 { code: "\neval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 0 }] }
716 ]
717 });
718 }, /Error line should be 0/u);
719 });
720
721 it("should throw an error if invalid code specifies wrong column", () => {
722 const wrongColumn = 10,
723 expectedErrorMessage = "Error column should be 1";
724
725 assert.throws(() => {
726 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
727 valid: ["Eval(foo)"],
728 invalid: [{
729 code: "eval(foo)",
730 errors: [{
731 message: "eval sucks.",
732 column: wrongColumn
733 }]
734 }]
735 });
736 }, expectedErrorMessage);
737 });
738
739 it("should throw error for empty error array", () => {
740 assert.throws(() => {
741 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
742 valid: [],
743 invalid: [{
744 code: "var foo;",
745 errors: []
746 }]
747 });
748 }, /Invalid cases must have at least one error/u);
749 });
750
751 it("should throw error for errors : 0", () => {
752 assert.throws(() => {
753 ruleTester.run(
754 "suggestions-messageIds",
755 require("../../fixtures/testers/rule-tester/suggestions")
756 .withMessageIds,
757 {
758 valid: [],
759 invalid: [
760 {
761 code: "var foo;",
762 errors: 0
763 }
764 ]
765 }
766 );
767 }, /Invalid cases must have 'error' value greater than 0/u);
768 });
769
770 it("should not skip column assertion if column is a falsy value", () => {
771 assert.throws(() => {
772 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
773 valid: ["Eval(foo)"],
774 invalid: [{
775 code: "var foo; eval(foo)",
776 errors: [{ message: "eval sucks.", column: 0 }]
777 }]
778 });
779 }, /Error column should be 0/u);
780 });
781
782 it("should throw an error if invalid code specifies wrong endLine", () => {
783 assert.throws(() => {
784 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
785 valid: [
786 "bar = baz;"
787 ],
788 invalid: [
789 { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endLine: 10 }] }
790 ]
791 });
792 }, "Error endLine should be 10");
793 });
794
795 it("should throw an error if invalid code specifies wrong endColumn", () => {
796 assert.throws(() => {
797 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
798 valid: [
799 "bar = baz;"
800 ],
801 invalid: [
802 { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endColumn: 10 }] }
803 ]
804 });
805 }, "Error endColumn should be 10");
806 });
807
808 it("should throw an error if invalid code has the wrong number of errors", () => {
809 assert.throws(() => {
810 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
811 valid: [
812 "Eval(foo)"
813 ],
814 invalid: [
815 {
816 code: "eval(foo)",
817 errors: [
818 { message: "eval sucks.", type: "CallExpression" },
819 { message: "eval sucks.", type: "CallExpression" }
820 ]
821 }
822 ]
823 });
824 }, /Should have 2 errors but had 1/u);
825 });
826
827 it("should throw an error if invalid code does not have errors", () => {
828 assert.throws(() => {
829 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
830 valid: [
831 "Eval(foo)"
832 ],
833 invalid: [
834 { code: "eval(foo)" }
835 ]
836 });
837 }, /Did not specify errors for an invalid test of no-eval/u);
838 });
839
840 it("should throw an error if invalid code has the wrong explicit number of errors", () => {
841 assert.throws(() => {
842 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
843 valid: [
844 "Eval(foo)"
845 ],
846 invalid: [
847 { code: "eval(foo)", errors: 2 }
848 ]
849 });
850 }, /Should have 2 errors but had 1/u);
851 });
852
853 it("should throw an error if there's a parsing error in a valid test", () => {
854 assert.throws(() => {
855 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
856 valid: [
857 "1eval('foo')"
858 ],
859 invalid: [
860 { code: "eval('foo')", errors: [{}] }
861 ]
862 });
863 }, /fatal parsing error/iu);
864 });
865
866 it("should throw an error if there's a parsing error in an invalid test", () => {
867 assert.throws(() => {
868 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
869 valid: [
870 "noeval('foo')"
871 ],
872 invalid: [
873 { code: "1eval('foo')", errors: [{}] }
874 ]
875 });
876 }, /fatal parsing error/iu);
877 });
878
879 it("should throw an error if there's a parsing error in an invalid test and errors is just a number", () => {
880 assert.throws(() => {
881 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
882 valid: [
883 "noeval('foo')"
884 ],
885 invalid: [
886 { code: "1eval('foo')", errors: 1 }
887 ]
888 });
889 }, /fatal parsing error/iu);
890 });
891
892 // https://github.com/eslint/eslint/issues/4779
893 it("should throw an error if there's a parsing error and output doesn't match", () => {
894 assert.throws(() => {
895 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
896 valid: [],
897 invalid: [
898 { code: "eval(`foo`)", output: "eval(`foo`);", errors: [{}] }
899 ]
900 });
901 }, /fatal parsing error/iu);
902 });
903
904 it("should not throw an error if invalid code has at least an expected empty error object", () => {
905 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
906 valid: ["Eval(foo)"],
907 invalid: [{
908 code: "eval(foo)",
909 errors: [{}]
910 }]
911 });
912 });
913
914 it("should pass-through the globals config of valid tests to the to rule", () => {
915 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
916 valid: [
917 "var test = 'foo'",
918 {
919 code: "var test2 = 'bar'",
920 globals: { test: true }
921 }
922 ],
923 invalid: [{ code: "bar", errors: 1 }]
924 });
925 });
926
927 it("should pass-through the globals config of invalid tests to the to rule", () => {
928 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
929 valid: ["var test = 'foo'"],
930 invalid: [
931 {
932 code: "var test = 'foo'; var foo = 'bar'",
933 errors: 1
934 },
935 {
936 code: "var test = 'foo'",
937 globals: { foo: true },
938 errors: [{ message: "Global variable foo should not be used." }]
939 }
940 ]
941 });
942 });
943
944 it("should pass-through the settings config to rules", () => {
945 ruleTester.run("no-test-settings", require("../../fixtures/testers/rule-tester/no-test-settings"), {
946 valid: [
947 {
948 code: "var test = 'bar'", settings: { test: 1 }
949 }
950 ],
951 invalid: [
952 {
953 code: "var test = 'bar'", settings: { "no-test": 22 }, errors: 1
954 }
955 ]
956 });
957 });
958
959 it("should pass-through the filename to the rule", () => {
960 (function() {
961 ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), {
962 valid: [
963 {
964 code: "var foo = 'bar'",
965 filename: "somefile.js"
966 }
967 ],
968 invalid: [
969 {
970 code: "var foo = 'bar'",
971 errors: [
972 { message: "Filename test was not defined." }
973 ]
974 }
975 ]
976 });
977 }());
978 });
979
980 it("should pass-through the options to the rule", () => {
981 ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
982 valid: [
983 {
984 code: "var foo = 'bar'",
985 options: [false]
986 }
987 ],
988 invalid: [
989 {
990 code: "var foo = 'bar'",
991 options: [true],
992 errors: [{ message: "Invalid args" }]
993 }
994 ]
995 });
996 });
997
998 it("should throw an error if the options are an object", () => {
999 assert.throws(() => {
1000 ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
1001 valid: [
1002 {
1003 code: "foo",
1004 options: { ok: true }
1005 }
1006 ],
1007 invalid: []
1008 });
1009 }, /options must be an array/u);
1010 });
1011
1012 it("should throw an error if the options are a number", () => {
1013 assert.throws(() => {
1014 ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
1015 valid: [
1016 {
1017 code: "foo",
1018 options: 0
1019 }
1020 ],
1021 invalid: []
1022 });
1023 }, /options must be an array/u);
1024 });
1025
1026 it("should pass-through the parser to the rule", () => {
1027 const spy = sinon.spy(ruleTester.linter, "verify");
1028
1029 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
1030 valid: [
1031 {
1032 code: "Eval(foo)"
1033 }
1034 ],
1035 invalid: [
1036 {
1037 code: "eval(foo)",
1038 parser: require.resolve("esprima"),
1039 errors: [{ line: 1 }]
1040 }
1041 ]
1042 });
1043 assert.strictEqual(spy.args[1][1].parser, require.resolve("esprima"));
1044 });
1045 it("should pass normalized ecmaVersion to the rule", () => {
1046 const reportEcmaVersionRule = {
1047 meta: {
1048 messages: {
1049 ecmaVersionMessage: "context.parserOptions.ecmaVersion is {{type}} {{ecmaVersion}}."
1050 }
1051 },
1052 create: context => ({
1053 Program(node) {
1054 const { ecmaVersion } = context.parserOptions;
1055
1056 context.report({
1057 node,
1058 messageId: "ecmaVersionMessage",
1059 data: { type: typeof ecmaVersion, ecmaVersion }
1060 });
1061 }
1062 })
1063 };
1064
1065 const notEspree = require.resolve("../../fixtures/parsers/empty-program-parser");
1066
1067 ruleTester.run("report-ecma-version", reportEcmaVersionRule, {
1068 valid: [],
1069 invalid: [
1070 {
1071 code: "",
1072 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }]
1073 },
1074 {
1075 code: "",
1076 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1077 parserOptions: {}
1078 },
1079 {
1080 code: "<div/>",
1081 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1082 parserOptions: { ecmaFeatures: { jsx: true } }
1083 },
1084 {
1085 code: "",
1086 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1087 parser: require.resolve("espree")
1088 },
1089 {
1090 code: "",
1091 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1092 parserOptions: { ecmaVersion: 6 }
1093 },
1094 {
1095 code: "",
1096 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1097 parserOptions: { ecmaVersion: 2015 }
1098 },
1099 {
1100 code: "",
1101 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1102 env: { browser: true }
1103 },
1104 {
1105 code: "",
1106 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1107 env: { es6: false }
1108 },
1109 {
1110 code: "",
1111 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1112 env: { es6: true }
1113 },
1114 {
1115 code: "",
1116 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }],
1117 env: { es6: false, es2017: true }
1118 },
1119 {
1120 code: "let x",
1121 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1122 env: { es6: "truthy" }
1123 },
1124 {
1125 code: "",
1126 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }],
1127 env: { es2017: true }
1128 },
1129 {
1130 code: "",
1131 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "11" } }],
1132 env: { es2020: true }
1133 },
1134 {
1135 code: "",
1136 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "12" } }],
1137 env: { es2021: true }
1138 },
1139 {
1140 code: "",
1141 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1142 parserOptions: { ecmaVersion: "latest" }
1143 },
1144 {
1145 code: "",
1146 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1147 parser: require.resolve("espree"),
1148 parserOptions: { ecmaVersion: "latest" }
1149 },
1150 {
1151 code: "<div/>",
1152 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1153 parserOptions: { ecmaVersion: "latest", ecmaFeatures: { jsx: true } }
1154 },
1155 {
1156 code: "import 'foo'",
1157 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1158 parserOptions: { ecmaVersion: "latest", sourceType: "module" }
1159 },
1160 {
1161 code: "",
1162 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1163 parserOptions: { ecmaVersion: "latest" },
1164 env: { es6: true }
1165 },
1166 {
1167 code: "",
1168 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1169 parserOptions: { ecmaVersion: "latest" },
1170 env: { es2020: true }
1171 },
1172 {
1173 code: "",
1174 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1175 parser: notEspree
1176 },
1177 {
1178 code: "",
1179 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1180 parser: notEspree,
1181 parserOptions: {}
1182 },
1183 {
1184 code: "",
1185 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }],
1186 parser: notEspree,
1187 parserOptions: { ecmaVersion: 5 }
1188 },
1189 {
1190 code: "",
1191 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1192 parser: notEspree,
1193 parserOptions: { ecmaVersion: 6 }
1194 },
1195 {
1196 code: "",
1197 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1198 parser: notEspree,
1199 parserOptions: { ecmaVersion: 2015 }
1200 },
1201 {
1202 code: "",
1203 errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }],
1204 parser: notEspree,
1205 parserOptions: { ecmaVersion: "latest" }
1206 }
1207 ]
1208 });
1209
1210 [{ parserOptions: { ecmaVersion: 6 } }, { env: { es6: true } }].forEach(options => {
1211 new RuleTester(options).run("report-ecma-version", reportEcmaVersionRule, {
1212 valid: [],
1213 invalid: [
1214 {
1215 code: "",
1216 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }]
1217 },
1218 {
1219 code: "",
1220 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1221 parserOptions: {}
1222 }
1223 ]
1224 });
1225 });
1226
1227 new RuleTester({ parser: notEspree }).run("report-ecma-version", reportEcmaVersionRule, {
1228 valid: [],
1229 invalid: [
1230 {
1231 code: "",
1232 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }]
1233 },
1234 {
1235 code: "",
1236 errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }],
1237 parserOptions: { ecmaVersion: "latest" }
1238 }
1239 ]
1240 });
1241 });
1242
1243 it("should pass-through services from parseForESLint to the rule", () => {
1244 const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser");
1245 const disallowHiRule = {
1246 create: context => ({
1247 Literal(node) {
1248 const disallowed = context.parserServices.test.getMessage(); // returns "Hi!"
1249
1250 if (node.value === disallowed) {
1251 context.report({ node, message: `Don't use '${disallowed}'` });
1252 }
1253 }
1254 })
1255 };
1256
1257 ruleTester.run("no-hi", disallowHiRule, {
1258 valid: [
1259 {
1260 code: "'Hello!'",
1261 parser: enhancedParserPath
1262 }
1263 ],
1264 invalid: [
1265 {
1266 code: "'Hi!'",
1267 parser: enhancedParserPath,
1268 errors: [{ message: "Don't use 'Hi!'" }]
1269 }
1270 ]
1271 });
1272 });
1273
1274 it("should prevent invalid options schemas", () => {
1275 assert.throws(() => {
1276 ruleTester.run("no-invalid-schema", require("../../fixtures/testers/rule-tester/no-invalid-schema"), {
1277 valid: [
1278 "var answer = 6 * 7;",
1279 { code: "var answer = 6 * 7;", options: [] }
1280 ],
1281 invalid: [
1282 { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] }
1283 ]
1284 });
1285 }, "Schema for rule no-invalid-schema is invalid:,\titems: should be object\n\titems[0].enum: should NOT have fewer than 1 items\n\titems: should match some schema in anyOf");
1286
1287 });
1288
1289 it("should prevent schema violations in options", () => {
1290 assert.throws(() => {
1291 ruleTester.run("no-schema-violation", require("../../fixtures/testers/rule-tester/no-schema-violation"), {
1292 valid: [
1293 "var answer = 6 * 7;",
1294 { code: "var answer = 6 * 7;", options: ["foo"] }
1295 ],
1296 invalid: [
1297 { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected foo." }] }
1298 ]
1299 });
1300 }, /Value "bar" should be equal to one of the allowed values./u);
1301
1302 });
1303
1304 it("should disallow invalid defaults in rules", () => {
1305 const ruleWithInvalidDefaults = {
1306 meta: {
1307 schema: [
1308 {
1309 oneOf: [
1310 { enum: ["foo"] },
1311 {
1312 type: "object",
1313 properties: {
1314 foo: {
1315 enum: ["foo", "bar"],
1316 default: "foo"
1317 }
1318 },
1319 additionalProperties: false
1320 }
1321 ]
1322 }
1323 ]
1324 },
1325 create: () => ({})
1326 };
1327
1328 assert.throws(() => {
1329 ruleTester.run("invalid-defaults", ruleWithInvalidDefaults, {
1330 valid: [
1331 {
1332 code: "foo",
1333 options: [{}]
1334 }
1335 ],
1336 invalid: []
1337 });
1338 }, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u);
1339 });
1340
1341 it("throw an error when an unknown config option is included", () => {
1342 assert.throws(() => {
1343 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
1344 valid: [
1345 { code: "Eval(foo)", foo: "bar" }
1346 ],
1347 invalid: []
1348 });
1349 }, /ESLint configuration in rule-tester is invalid./u);
1350 });
1351
1352 it("throw an error when an invalid config value is included", () => {
1353 assert.throws(() => {
1354 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
1355 valid: [
1356 { code: "Eval(foo)", env: ["es6"] }
1357 ],
1358 invalid: []
1359 });
1360 }, /Property "env" is the wrong type./u);
1361 });
1362
1363 it("should pass-through the tester config to the rule", () => {
1364 ruleTester = new RuleTester({
1365 globals: { test: true }
1366 });
1367
1368 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
1369 valid: [
1370 "var test = 'foo'",
1371 "var test2 = test"
1372 ],
1373 invalid: [{ code: "bar", errors: 1, globals: { foo: true } }]
1374 });
1375 });
1376
1377 it("should correctly set the globals configuration", () => {
1378 const config = { globals: { test: true } };
1379
1380 RuleTester.setDefaultConfig(config);
1381 assert(
1382 RuleTester.getDefaultConfig().globals.test,
1383 "The default config object is incorrect"
1384 );
1385 });
1386
1387 it("should correctly reset the global configuration", () => {
1388 const config = { globals: { test: true } };
1389
1390 RuleTester.setDefaultConfig(config);
1391 RuleTester.resetDefaultConfig();
1392 assert.deepStrictEqual(
1393 RuleTester.getDefaultConfig(),
1394 { rules: {} },
1395 "The default configuration has not reset correctly"
1396 );
1397 });
1398
1399 it("should enforce the global configuration to be an object", () => {
1400
1401 /**
1402 * Set the default config for the rules tester
1403 * @param {Object} config configuration object
1404 * @returns {Function} Function to be executed
1405 * @private
1406 */
1407 function setConfig(config) {
1408 return function() {
1409 RuleTester.setDefaultConfig(config);
1410 };
1411 }
1412 assert.throw(setConfig());
1413 assert.throw(setConfig(1));
1414 assert.throw(setConfig(3.14));
1415 assert.throw(setConfig("foo"));
1416 assert.throw(setConfig(null));
1417 assert.throw(setConfig(true));
1418 });
1419
1420 it("should pass-through the globals config to the tester then to the to rule", () => {
1421 const config = { globals: { test: true } };
1422
1423 RuleTester.setDefaultConfig(config);
1424 ruleTester = new RuleTester();
1425
1426 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
1427 valid: [
1428 "var test = 'foo'",
1429 "var test2 = test"
1430 ],
1431 invalid: [{ code: "bar", errors: 1, globals: { foo: true } }]
1432 });
1433 });
1434
1435 it("should throw an error if AST was modified", () => {
1436 assert.throws(() => {
1437 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
1438 valid: [
1439 "var foo = 0;"
1440 ],
1441 invalid: []
1442 });
1443 }, "Rule should not modify AST.");
1444 assert.throws(() => {
1445 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
1446 valid: [],
1447 invalid: [
1448 { code: "var bar = 0;", errors: ["error"] }
1449 ]
1450 });
1451 }, "Rule should not modify AST.");
1452 });
1453
1454 it("should throw an error if AST was modified (at Program)", () => {
1455 assert.throws(() => {
1456 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
1457 valid: [
1458 "var foo = 0;"
1459 ],
1460 invalid: []
1461 });
1462 }, "Rule should not modify AST.");
1463 assert.throws(() => {
1464 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
1465 valid: [],
1466 invalid: [
1467 { code: "var bar = 0;", errors: ["error"] }
1468 ]
1469 });
1470 }, "Rule should not modify AST.");
1471 });
1472
1473 it("should throw an error if AST was modified (at Program:exit)", () => {
1474 assert.throws(() => {
1475 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1476 valid: [
1477 "var foo = 0;"
1478 ],
1479 invalid: []
1480 });
1481 }, "Rule should not modify AST.");
1482 assert.throws(() => {
1483 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1484 valid: [],
1485 invalid: [
1486 { code: "var bar = 0;", errors: ["error"] }
1487 ]
1488 });
1489 }, "Rule should not modify AST.");
1490 });
1491
1492 it("should throw an error if rule uses start and end properties on nodes, tokens or comments", () => {
1493 const usesStartEndRule = {
1494 create(context) {
1495
1496 const sourceCode = context.getSourceCode();
1497
1498 return {
1499 CallExpression(node) {
1500 noop(node.arguments[1].start);
1501 },
1502 "BinaryExpression[operator='+']"(node) {
1503 noop(node.end);
1504 },
1505 "UnaryExpression[operator='-']"(node) {
1506 noop(sourceCode.getFirstToken(node).start);
1507 },
1508 ConditionalExpression(node) {
1509 noop(sourceCode.getFirstToken(node).end);
1510 },
1511 BlockStatement(node) {
1512 noop(sourceCode.getCommentsInside(node)[0].start);
1513 },
1514 ObjectExpression(node) {
1515 noop(sourceCode.getCommentsInside(node)[0].end);
1516 },
1517 Decorator(node) {
1518 noop(node.start);
1519 }
1520 };
1521 }
1522 };
1523
1524 assert.throws(() => {
1525 ruleTester.run("uses-start-end", usesStartEndRule, {
1526 valid: ["foo(a, b)"],
1527 invalid: []
1528 });
1529 }, "Use node.range[0] instead of node.start");
1530 assert.throws(() => {
1531 ruleTester.run("uses-start-end", usesStartEndRule, {
1532 valid: [],
1533 invalid: [{ code: "var a = b * (c + d) / e;", errors: 1 }]
1534 });
1535 }, "Use node.range[1] instead of node.end");
1536 assert.throws(() => {
1537 ruleTester.run("uses-start-end", usesStartEndRule, {
1538 valid: [],
1539 invalid: [{ code: "var a = -b * c;", errors: 1 }]
1540 });
1541 }, "Use token.range[0] instead of token.start");
1542 assert.throws(() => {
1543 ruleTester.run("uses-start-end", usesStartEndRule, {
1544 valid: ["var a = b ? c : d;"],
1545 invalid: []
1546 });
1547 }, "Use token.range[1] instead of token.end");
1548 assert.throws(() => {
1549 ruleTester.run("uses-start-end", usesStartEndRule, {
1550 valid: ["function f() { /* comment */ }"],
1551 invalid: []
1552 });
1553 }, "Use token.range[0] instead of token.start");
1554 assert.throws(() => {
1555 ruleTester.run("uses-start-end", usesStartEndRule, {
1556 valid: [],
1557 invalid: [{ code: "var x = //\n {\n //comment\n //\n}", errors: 1 }]
1558 });
1559 }, "Use token.range[1] instead of token.end");
1560
1561 const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser");
1562
1563 assert.throws(() => {
1564 ruleTester.run("uses-start-end", usesStartEndRule, {
1565 valid: [{ code: "foo(a, b)", parser: enhancedParserPath }],
1566 invalid: []
1567 });
1568 }, "Use node.range[0] instead of node.start");
1569 assert.throws(() => {
1570 ruleTester.run("uses-start-end", usesStartEndRule, {
1571 valid: [],
1572 invalid: [{ code: "var a = b * (c + d) / e;", parser: enhancedParserPath, errors: 1 }]
1573 });
1574 }, "Use node.range[1] instead of node.end");
1575 assert.throws(() => {
1576 ruleTester.run("uses-start-end", usesStartEndRule, {
1577 valid: [],
1578 invalid: [{ code: "var a = -b * c;", parser: enhancedParserPath, errors: 1 }]
1579 });
1580 }, "Use token.range[0] instead of token.start");
1581 assert.throws(() => {
1582 ruleTester.run("uses-start-end", usesStartEndRule, {
1583 valid: [{ code: "var a = b ? c : d;", parser: enhancedParserPath }],
1584 invalid: []
1585 });
1586 }, "Use token.range[1] instead of token.end");
1587 assert.throws(() => {
1588 ruleTester.run("uses-start-end", usesStartEndRule, {
1589 valid: [{ code: "function f() { /* comment */ }", parser: enhancedParserPath }],
1590 invalid: []
1591 });
1592 }, "Use token.range[0] instead of token.start");
1593 assert.throws(() => {
1594 ruleTester.run("uses-start-end", usesStartEndRule, {
1595 valid: [],
1596 invalid: [{ code: "var x = //\n {\n //comment\n //\n}", parser: enhancedParserPath, errors: 1 }]
1597 });
1598 }, "Use token.range[1] instead of token.end");
1599
1600 assert.throws(() => {
1601 ruleTester.run("uses-start-end", usesStartEndRule, {
1602 valid: [{ code: "@foo class A {}", parser: require.resolve("../../fixtures/parsers/enhanced-parser2") }],
1603 invalid: []
1604 });
1605 }, "Use node.range[0] instead of node.start");
1606 });
1607
1608 it("should throw an error if no test scenarios given", () => {
1609 assert.throws(() => {
1610 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"));
1611 }, "Test Scenarios for rule foo : Could not find test scenario object");
1612 });
1613
1614 it("should throw an error if no acceptable test scenario object is given", () => {
1615 assert.throws(() => {
1616 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), []);
1617 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
1618 assert.throws(() => {
1619 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), "");
1620 }, "Test Scenarios for rule foo : Could not find test scenario object");
1621 assert.throws(() => {
1622 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), 2);
1623 }, "Test Scenarios for rule foo : Could not find test scenario object");
1624 assert.throws(() => {
1625 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {});
1626 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
1627 assert.throws(() => {
1628 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1629 valid: []
1630 });
1631 }, "Test Scenarios for rule foo is invalid:\nCould not find any invalid test scenarios");
1632 assert.throws(() => {
1633 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1634 invalid: []
1635 });
1636 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios");
1637 });
1638
1639 // Nominal message/messageId use cases
1640 it("should assert match if message provided in both test and result.", () => {
1641 assert.throws(() => {
1642 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
1643 valid: [],
1644 invalid: [{ code: "foo", errors: [{ message: "something" }] }]
1645 });
1646 }, /Avoid using variables named/u);
1647
1648 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
1649 valid: [],
1650 invalid: [{ code: "foo", errors: [{ message: "Avoid using variables named 'foo'." }] }]
1651 });
1652 });
1653
1654 it("should assert match between messageId if provided in both test and result.", () => {
1655 assert.throws(() => {
1656 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1657 valid: [],
1658 invalid: [{ code: "foo", errors: [{ messageId: "unused" }] }]
1659 });
1660 }, "messageId 'avoidFoo' does not match expected messageId 'unused'.");
1661
1662 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1663 valid: [],
1664 invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
1665 });
1666 });
1667 it("should assert match between resulting message output if messageId and data provided in both test and result", () => {
1668 assert.throws(() => {
1669 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1670 valid: [],
1671 invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "notFoo" } }] }]
1672 });
1673 }, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\"");
1674 });
1675
1676 // messageId/message misconfiguration cases
1677 it("should throw if user tests for both message and messageId", () => {
1678 assert.throws(() => {
1679 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1680 valid: [],
1681 invalid: [{ code: "foo", errors: [{ message: "something", messageId: "avoidFoo" }] }]
1682 });
1683 }, "Error should not specify both 'message' and a 'messageId'.");
1684 });
1685 it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => {
1686 assert.throws(() => {
1687 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
1688 valid: [],
1689 invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
1690 });
1691 }, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'");
1692 });
1693 it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => {
1694 assert.throws(() => {
1695 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1696 valid: [],
1697 invalid: [{ code: "foo", errors: [{ messageId: "useFoo" }] }]
1698 });
1699 }, /Invalid messageId 'useFoo'/u);
1700 });
1701 it("should throw if data provided without messageId.", () => {
1702 assert.throws(() => {
1703 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1704 valid: [],
1705 invalid: [{ code: "foo", errors: [{ data: "something" }] }]
1706 });
1707 }, "Error must specify 'messageId' if 'data' is used.");
1708 });
1709
1710 // fixable rules with or without `meta` property
1711 it("should not throw an error if a rule that has `meta.fixable` produces fixes", () => {
1712 const replaceProgramWith5Rule = {
1713 meta: {
1714 fixable: "code"
1715 },
1716 create(context) {
1717 return {
1718 Program(node) {
1719 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
1720 }
1721 };
1722 }
1723 };
1724
1725 ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
1726 valid: [],
1727 invalid: [
1728 { code: "var foo = bar;", output: "5", errors: 1 }
1729 ]
1730 });
1731 });
1732 it("should throw an error if a new-format rule that doesn't have `meta` produces fixes", () => {
1733 const replaceProgramWith5Rule = {
1734 create(context) {
1735 return {
1736 Program(node) {
1737 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
1738 }
1739 };
1740 }
1741 };
1742
1743 assert.throws(() => {
1744 ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
1745 valid: [],
1746 invalid: [
1747 { code: "var foo = bar;", output: "5", errors: 1 }
1748 ]
1749 });
1750 }, /Fixable rules must set the `meta\.fixable` property/u);
1751 });
1752 it("should throw an error if a legacy-format rule produces fixes", () => {
1753
1754 /**
1755 * Legacy-format rule (a function instead of an object with `create` method).
1756 * @param {RuleContext} context The ESLint rule context object.
1757 * @returns {Object} Listeners.
1758 */
1759 function replaceProgramWith5Rule(context) {
1760 return {
1761 Program(node) {
1762 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
1763 }
1764 };
1765 }
1766
1767 assert.throws(() => {
1768 ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
1769 valid: [],
1770 invalid: [
1771 { code: "var foo = bar;", output: "5", errors: 1 }
1772 ]
1773 });
1774 }, /Fixable rules must set the `meta\.fixable` property/u);
1775 });
1776
1777 describe("suggestions", () => {
1778 it("should pass with valid suggestions (tested using desc)", () => {
1779 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1780 valid: [
1781 "var boo;"
1782 ],
1783 invalid: [{
1784 code: "var foo;",
1785 errors: [{
1786 suggestions: [{
1787 desc: "Rename identifier 'foo' to 'bar'",
1788 output: "var bar;"
1789 }]
1790 }]
1791 }]
1792 });
1793 });
1794
1795 it("should pass with suggestions on multiple lines", () => {
1796 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1797 valid: [],
1798 invalid: [
1799 {
1800 code: "function foo() {\n var foo = 1;\n}",
1801 errors: [{
1802 suggestions: [{
1803 desc: "Rename identifier 'foo' to 'bar'",
1804 output: "function bar() {\n var foo = 1;\n}"
1805 }]
1806 }, {
1807 suggestions: [{
1808 desc: "Rename identifier 'foo' to 'bar'",
1809 output: "function foo() {\n var bar = 1;\n}"
1810 }]
1811 }]
1812 }
1813 ]
1814 });
1815 });
1816
1817 it("should pass with valid suggestions (tested using messageIds)", () => {
1818 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1819 valid: [],
1820 invalid: [{
1821 code: "var foo;",
1822 errors: [{
1823 suggestions: [{
1824 messageId: "renameFoo",
1825 output: "var bar;"
1826 }, {
1827 messageId: "renameFoo",
1828 output: "var baz;"
1829 }]
1830 }]
1831 }]
1832 });
1833 });
1834
1835 it("should pass with valid suggestions (one tested using messageIds, the other using desc)", () => {
1836 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1837 valid: [],
1838 invalid: [{
1839 code: "var foo;",
1840 errors: [{
1841 suggestions: [{
1842 messageId: "renameFoo",
1843 output: "var bar;"
1844 }, {
1845 desc: "Rename identifier 'foo' to 'baz'",
1846 output: "var baz;"
1847 }]
1848 }]
1849 }]
1850 });
1851 });
1852
1853 it("should pass with valid suggestions (tested using both desc and messageIds for the same suggestion)", () => {
1854 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1855 valid: [],
1856 invalid: [{
1857 code: "var foo;",
1858 errors: [{
1859 suggestions: [{
1860 desc: "Rename identifier 'foo' to 'bar'",
1861 messageId: "renameFoo",
1862 output: "var bar;"
1863 }, {
1864 desc: "Rename identifier 'foo' to 'baz'",
1865 messageId: "renameFoo",
1866 output: "var baz;"
1867 }]
1868 }]
1869 }]
1870 });
1871 });
1872
1873 it("should pass with valid suggestions (tested using only desc on a rule that utilizes meta.messages)", () => {
1874 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1875 valid: [],
1876 invalid: [{
1877 code: "var foo;",
1878 errors: [{
1879 suggestions: [{
1880 desc: "Rename identifier 'foo' to 'bar'",
1881 output: "var bar;"
1882 }, {
1883 desc: "Rename identifier 'foo' to 'baz'",
1884 output: "var baz;"
1885 }]
1886 }]
1887 }]
1888 });
1889 });
1890
1891 it("should pass with valid suggestions (tested using messageIds and data)", () => {
1892 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1893 valid: [],
1894 invalid: [{
1895 code: "var foo;",
1896 errors: [{
1897 suggestions: [{
1898 messageId: "renameFoo",
1899 data: { newName: "bar" },
1900 output: "var bar;"
1901 }, {
1902 messageId: "renameFoo",
1903 data: { newName: "baz" },
1904 output: "var baz;"
1905 }]
1906 }]
1907 }]
1908 });
1909 });
1910
1911
1912 it("should pass when tested using empty suggestion test objects if the array length is correct", () => {
1913 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1914 valid: [],
1915 invalid: [{
1916 code: "var foo;",
1917 errors: [{
1918 suggestions: [{}, {}]
1919 }]
1920 }]
1921 });
1922 });
1923
1924 it("should support explicitly expecting no suggestions", () => {
1925 [void 0, null, false, []].forEach(suggestions => {
1926 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/no-eval"), {
1927 valid: [],
1928 invalid: [{
1929 code: "eval('var foo');",
1930 errors: [{
1931 suggestions
1932 }]
1933 }]
1934 });
1935 });
1936 });
1937
1938 it("should fail when expecting no suggestions and there are suggestions", () => {
1939 [void 0, null, false, []].forEach(suggestions => {
1940 assert.throws(() => {
1941 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1942 valid: [],
1943 invalid: [{
1944 code: "var foo;",
1945 errors: [{
1946 suggestions
1947 }]
1948 }]
1949 });
1950 }, "Error should have no suggestions on error with message: \"Avoid using identifiers named 'foo'.\"");
1951 });
1952 });
1953
1954 it("should fail when testing for suggestions that don't exist", () => {
1955 assert.throws(() => {
1956 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1957 valid: [],
1958 invalid: [{
1959 code: "var foo;",
1960 errors: [{
1961 suggestions: [{
1962 messageId: "this-does-not-exist"
1963 }]
1964 }]
1965 }]
1966 });
1967 }, "Error should have an array of suggestions. Instead received \"undefined\" on error with message: \"Bad var.\"");
1968 });
1969
1970 it("should fail when there are a different number of suggestions", () => {
1971 assert.throws(() => {
1972 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1973 valid: [],
1974 invalid: [{
1975 code: "var foo;",
1976 errors: [{
1977 suggestions: [{
1978 desc: "Rename identifier 'foo' to 'bar'",
1979 output: "var bar;"
1980 }, {
1981 desc: "Rename identifier 'foo' to 'baz'",
1982 output: "var baz;"
1983 }]
1984 }]
1985 }]
1986 });
1987 }, "Error should have 2 suggestions. Instead found 1 suggestions");
1988 });
1989
1990 it("should throw if the suggestion description doesn't match", () => {
1991 assert.throws(() => {
1992 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1993 valid: [],
1994 invalid: [{
1995 code: "var foo;",
1996 errors: [{
1997 suggestions: [{
1998 desc: "not right",
1999 output: "var baz;"
2000 }]
2001 }]
2002 }]
2003 });
2004 }, "Error Suggestion at index 0 : desc should be \"not right\" but got \"Rename identifier 'foo' to 'bar'\" instead.");
2005 });
2006
2007 it("should throw if the suggestion description doesn't match (although messageIds match)", () => {
2008 assert.throws(() => {
2009 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2010 valid: [],
2011 invalid: [{
2012 code: "var foo;",
2013 errors: [{
2014 suggestions: [{
2015 desc: "Rename identifier 'foo' to 'bar'",
2016 messageId: "renameFoo",
2017 output: "var bar;"
2018 }, {
2019 desc: "Rename id 'foo' to 'baz'",
2020 messageId: "renameFoo",
2021 output: "var baz;"
2022 }]
2023 }]
2024 }]
2025 });
2026 }, "Error Suggestion at index 1 : desc should be \"Rename id 'foo' to 'baz'\" but got \"Rename identifier 'foo' to 'baz'\" instead.");
2027 });
2028
2029 it("should throw if the suggestion messageId doesn't match", () => {
2030 assert.throws(() => {
2031 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2032 valid: [],
2033 invalid: [{
2034 code: "var foo;",
2035 errors: [{
2036 suggestions: [{
2037 messageId: "unused",
2038 output: "var bar;"
2039 }, {
2040 messageId: "renameFoo",
2041 output: "var baz;"
2042 }]
2043 }]
2044 }]
2045 });
2046 }, "Error Suggestion at index 0 : messageId should be 'unused' but got 'renameFoo' instead.");
2047 });
2048
2049 it("should throw if the suggestion messageId doesn't match (although descriptions match)", () => {
2050 assert.throws(() => {
2051 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2052 valid: [],
2053 invalid: [{
2054 code: "var foo;",
2055 errors: [{
2056 suggestions: [{
2057 desc: "Rename identifier 'foo' to 'bar'",
2058 messageId: "renameFoo",
2059 output: "var bar;"
2060 }, {
2061 desc: "Rename identifier 'foo' to 'baz'",
2062 messageId: "avoidFoo",
2063 output: "var baz;"
2064 }]
2065 }]
2066 }]
2067 });
2068 }, "Error Suggestion at index 1 : messageId should be 'avoidFoo' but got 'renameFoo' instead.");
2069 });
2070
2071 it("should throw if test specifies messageId for a rule that doesn't have meta.messages", () => {
2072 assert.throws(() => {
2073 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
2074 valid: [],
2075 invalid: [{
2076 code: "var foo;",
2077 errors: [{
2078 suggestions: [{
2079 messageId: "renameFoo",
2080 output: "var bar;"
2081 }]
2082 }]
2083 }]
2084 });
2085 }, "Error Suggestion at index 0 : Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.");
2086 });
2087
2088 it("should throw if test specifies messageId that doesn't exist in the rule's meta.messages", () => {
2089 assert.throws(() => {
2090 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2091 valid: [],
2092 invalid: [{
2093 code: "var foo;",
2094 errors: [{
2095 suggestions: [{
2096 messageId: "renameFoo",
2097 output: "var bar;"
2098 }, {
2099 messageId: "removeFoo",
2100 output: "var baz;"
2101 }]
2102 }]
2103 }]
2104 });
2105 }, "Error Suggestion at index 1 : Test has invalid messageId 'removeFoo', the rule under test allows only one of ['avoidFoo', 'unused', 'renameFoo'].");
2106 });
2107
2108 it("should throw if hydrated desc doesn't match (wrong data value)", () => {
2109 assert.throws(() => {
2110 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2111 valid: [],
2112 invalid: [{
2113 code: "var foo;",
2114 errors: [{
2115 suggestions: [{
2116 messageId: "renameFoo",
2117 data: { newName: "car" },
2118 output: "var bar;"
2119 }, {
2120 messageId: "renameFoo",
2121 data: { newName: "baz" },
2122 output: "var baz;"
2123 }]
2124 }]
2125 }]
2126 });
2127 }, "Error Suggestion at index 0 : Hydrated test desc \"Rename identifier 'foo' to 'car'\" does not match received desc \"Rename identifier 'foo' to 'bar'\".");
2128 });
2129
2130 it("should throw if hydrated desc doesn't match (wrong data key)", () => {
2131 assert.throws(() => {
2132 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2133 valid: [],
2134 invalid: [{
2135 code: "var foo;",
2136 errors: [{
2137 suggestions: [{
2138 messageId: "renameFoo",
2139 data: { newName: "bar" },
2140 output: "var bar;"
2141 }, {
2142 messageId: "renameFoo",
2143 data: { name: "baz" },
2144 output: "var baz;"
2145 }]
2146 }]
2147 }]
2148 });
2149 }, "Error Suggestion at index 1 : Hydrated test desc \"Rename identifier 'foo' to '{{ newName }}'\" does not match received desc \"Rename identifier 'foo' to 'baz'\".");
2150 });
2151
2152 it("should throw if test specifies both desc and data", () => {
2153 assert.throws(() => {
2154 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2155 valid: [],
2156 invalid: [{
2157 code: "var foo;",
2158 errors: [{
2159 suggestions: [{
2160 desc: "Rename identifier 'foo' to 'bar'",
2161 messageId: "renameFoo",
2162 data: { newName: "bar" },
2163 output: "var bar;"
2164 }, {
2165 messageId: "renameFoo",
2166 data: { newName: "baz" },
2167 output: "var baz;"
2168 }]
2169 }]
2170 }]
2171 });
2172 }, "Error Suggestion at index 0 : Test should not specify both 'desc' and 'data'.");
2173 });
2174
2175 it("should throw if test uses data but doesn't specify messageId", () => {
2176 assert.throws(() => {
2177 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2178 valid: [],
2179 invalid: [{
2180 code: "var foo;",
2181 errors: [{
2182 suggestions: [{
2183 messageId: "renameFoo",
2184 data: { newName: "bar" },
2185 output: "var bar;"
2186 }, {
2187 data: { newName: "baz" },
2188 output: "var baz;"
2189 }]
2190 }]
2191 }]
2192 });
2193 }, "Error Suggestion at index 1 : Test must specify 'messageId' if 'data' is used.");
2194 });
2195
2196 it("should throw if the resulting suggestion output doesn't match", () => {
2197 assert.throws(() => {
2198 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
2199 valid: [],
2200 invalid: [{
2201 code: "var foo;",
2202 errors: [{
2203 suggestions: [{
2204 desc: "Rename identifier 'foo' to 'bar'",
2205 output: "var baz;"
2206 }]
2207 }]
2208 }]
2209 });
2210 }, "Expected the applied suggestion fix to match the test suggestion output");
2211 });
2212
2213 it("should fail when specified suggestion isn't an object", () => {
2214 assert.throws(() => {
2215 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
2216 valid: [],
2217 invalid: [{
2218 code: "var foo;",
2219 errors: [{
2220 suggestions: [null]
2221 }]
2222 }]
2223 });
2224 }, "Test suggestion in 'suggestions' array must be an object.");
2225
2226 assert.throws(() => {
2227 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2228 valid: [],
2229 invalid: [{
2230 code: "var foo;",
2231 errors: [{
2232 suggestions: [
2233 {
2234 messageId: "renameFoo",
2235 output: "var bar;"
2236 },
2237 "Rename identifier 'foo' to 'baz'"
2238 ]
2239 }]
2240 }]
2241 });
2242 }, "Test suggestion in 'suggestions' array must be an object.");
2243 });
2244
2245 it("should fail when the suggestion is an object with an unknown property name", () => {
2246 assert.throws(() => {
2247 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
2248 valid: [
2249 "var boo;"
2250 ],
2251 invalid: [{
2252 code: "var foo;",
2253 errors: [{
2254 suggestions: [{
2255 message: "Rename identifier 'foo' to 'bar'"
2256 }]
2257 }]
2258 }]
2259 });
2260 }, /Invalid suggestion property name 'message'/u);
2261 });
2262
2263 it("should fail when any of the suggestions is an object with an unknown property name", () => {
2264 assert.throws(() => {
2265 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2266 valid: [],
2267 invalid: [{
2268 code: "var foo;",
2269 errors: [{
2270 suggestions: [{
2271 messageId: "renameFoo",
2272 output: "var bar;"
2273 }, {
2274 messageId: "renameFoo",
2275 outpt: "var baz;"
2276 }]
2277 }]
2278 }]
2279 });
2280 }, /Invalid suggestion property name 'outpt'/u);
2281 });
2282
2283 it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => {
2284 assert.throws(() => {
2285 ruleTester.run("suggestions-missing-hasSuggestions-property", require("../../fixtures/testers/rule-tester/suggestions").withoutHasSuggestionsProperty, {
2286 valid: [],
2287 invalid: [
2288 { code: "var foo = bar;", output: "5", errors: 1 }
2289 ]
2290 });
2291 }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
2292 });
2293 });
2294
2295 describe("naming test cases", () => {
2296
2297 /**
2298 * Asserts that a particular value will be emitted from an EventEmitter.
2299 * @param {EventEmitter} emitter The emitter that should emit a value
2300 * @param {string} emitType The type of emission to listen for
2301 * @param {any} expectedValue The value that should be emitted
2302 * @returns {Promise<void>} A Promise that fulfills if the value is emitted, and rejects if something else is emitted.
2303 * The Promise will be indefinitely pending if no value is emitted.
2304 */
2305 function assertEmitted(emitter, emitType, expectedValue) {
2306 return new Promise((resolve, reject) => {
2307 emitter.once(emitType, emittedValue => {
2308 if (emittedValue === expectedValue) {
2309 resolve();
2310 } else {
2311 reject(new Error(`Expected ${expectedValue} to be emitted but ${emittedValue} was emitted instead.`));
2312 }
2313 });
2314 });
2315 }
2316
2317 it("should use the first argument as the name of the test suite", () => {
2318 const assertion = assertEmitted(ruleTesterTestEmitter, "describe", "this-is-a-rule-name");
2319
2320 ruleTester.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), {
2321 valid: [],
2322 invalid: []
2323 });
2324
2325 return assertion;
2326 });
2327
2328 it("should use the test code as the name of the tests for valid code (string form)", () => {
2329 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);");
2330
2331 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2332 valid: [
2333 "valid(code);"
2334 ],
2335 invalid: []
2336 });
2337
2338 return assertion;
2339 });
2340
2341 it("should use the test code as the name of the tests for valid code (object form)", () => {
2342 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);");
2343
2344 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2345 valid: [
2346 {
2347 code: "valid(code);"
2348 }
2349 ],
2350 invalid: []
2351 });
2352
2353 return assertion;
2354 });
2355
2356 it("should use the test code as the name of the tests for invalid code", () => {
2357 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);");
2358
2359 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2360 valid: [],
2361 invalid: [
2362 {
2363 code: "var x = invalid(code);",
2364 output: " x = invalid(code);",
2365 errors: 1
2366 }
2367 ]
2368 });
2369
2370 return assertion;
2371 });
2372
2373 // https://github.com/eslint/eslint/issues/8142
2374 it("should use the empty string as the name of the test if the test case is an empty string", () => {
2375 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "");
2376
2377 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2378 valid: [
2379 {
2380 code: ""
2381 }
2382 ],
2383 invalid: []
2384 });
2385
2386 return assertion;
2387 });
2388
2389 it('should use the "name" property if set to a non-empty string', () => {
2390 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test");
2391
2392 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2393 valid: [],
2394 invalid: [
2395 {
2396 name: "my test",
2397 code: "var x = invalid(code);",
2398 output: " x = invalid(code);",
2399 errors: 1
2400 }
2401 ]
2402 });
2403
2404 return assertion;
2405 });
2406
2407 it('should use the "name" property if set to a non-empty string for valid cases too', () => {
2408 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test");
2409
2410 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2411 valid: [
2412 {
2413 name: "my test",
2414 code: "valid(code);"
2415 }
2416 ],
2417 invalid: []
2418 });
2419
2420 return assertion;
2421 });
2422
2423
2424 it('should use the test code as the name if the "name" property is set to an empty string', () => {
2425 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);");
2426
2427 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2428 valid: [],
2429 invalid: [
2430 {
2431 name: "",
2432 code: "var x = invalid(code);",
2433 output: " x = invalid(code);",
2434 errors: 1
2435 }
2436 ]
2437 });
2438
2439 return assertion;
2440 });
2441 });
2442
2443 // https://github.com/eslint/eslint/issues/11615
2444 it("should fail the case if autofix made a syntax error.", () => {
2445 assert.throw(() => {
2446 ruleTester.run(
2447 "foo",
2448 {
2449 meta: {
2450 fixable: "code"
2451 },
2452 create(context) {
2453 return {
2454 Identifier(node) {
2455 context.report({
2456 node,
2457 message: "make a syntax error",
2458 fix(fixer) {
2459 return fixer.replaceText(node, "one two");
2460 }
2461 });
2462 }
2463 };
2464 }
2465 },
2466 {
2467 valid: ["one()"],
2468 invalid: []
2469 }
2470 );
2471 }, /A fatal parsing error occurred in autofix.\nError: .+\nAutofix output:\n.+/u);
2472 });
2473
2474 describe("sanitize test cases", () => {
2475 let originalRuleTesterIt;
2476 let spyRuleTesterIt;
2477
2478 before(() => {
2479 originalRuleTesterIt = RuleTester.it;
2480 spyRuleTesterIt = sinon.spy();
2481 RuleTester.it = spyRuleTesterIt;
2482 });
2483 after(() => {
2484 RuleTester.it = originalRuleTesterIt;
2485 });
2486 beforeEach(() => {
2487 spyRuleTesterIt.resetHistory();
2488 ruleTester = new RuleTester();
2489 });
2490 it("should present newline when using back-tick as new line", () => {
2491 const code = `
2492 var foo = bar;`;
2493
2494 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
2495 valid: [],
2496 invalid: [
2497 {
2498 code,
2499 errors: [/^Bad var/u]
2500 }
2501 ]
2502 });
2503 sinon.assert.calledWith(spyRuleTesterIt, code);
2504 });
2505 it("should present \\u0000 as a string", () => {
2506 const code = "\u0000";
2507
2508 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
2509 valid: [],
2510 invalid: [
2511 {
2512 code,
2513 errors: [/^Bad var/u]
2514 }
2515 ]
2516 });
2517 sinon.assert.calledWith(spyRuleTesterIt, "\\u0000");
2518 });
2519 it("should present the pipe character correctly", () => {
2520 const code = "var foo = bar || baz;";
2521
2522 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
2523 valid: [],
2524 invalid: [
2525 {
2526 code,
2527 errors: [/^Bad var/u]
2528 }
2529 ]
2530 });
2531 sinon.assert.calledWith(spyRuleTesterIt, code);
2532 });
2533
2534 });
2535
2536 describe("SourceCode#getComments()", () => {
2537 const useGetCommentsRule = {
2538 create: context => ({
2539 Program(node) {
2540 const sourceCode = context.getSourceCode();
2541
2542 sourceCode.getComments(node);
2543 }
2544 })
2545 };
2546
2547 it("should throw if called from a valid test case", () => {
2548 assert.throws(() => {
2549 ruleTester.run("use-get-comments", useGetCommentsRule, {
2550 valid: [""],
2551 invalid: []
2552 });
2553 }, /`SourceCode#getComments\(\)` is deprecated/u);
2554 });
2555
2556 it("should throw if called from an invalid test case", () => {
2557 assert.throws(() => {
2558 ruleTester.run("use-get-comments", useGetCommentsRule, {
2559 valid: [],
2560 invalid: [{
2561 code: "",
2562 errors: [{}]
2563 }]
2564 });
2565 }, /`SourceCode#getComments\(\)` is deprecated/u);
2566 });
2567 });
2568
2569 });