]> git.proxmox.com Git - pve-eslint.git/blame - eslint/tests/lib/rule-tester/rule-tester.js
import 8.4.0 source
[pve-eslint.git] / eslint / tests / lib / rule-tester / rule-tester.js
CommitLineData
eb39fafa
DC
1/**
2 * @fileoverview Tests for ESLint Tester
3 * @author Nicholas C. Zakas
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10const sinon = require("sinon"),
11 EventEmitter = require("events"),
12 { RuleTester } = require("../../../lib/rule-tester"),
13 assert = require("chai").assert,
609c276f
TL
14 nodeAssert = require("assert"),
15 espree = require("espree");
eb39fafa
DC
16
17const 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
5422a9cc
TL
26/**
27 * Do nothing.
28 * @returns {void}
29 */
30function noop() {
31
32 // do nothing.
33}
34
eb39fafa
DC
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
53const ruleTesterTestEmitter = new EventEmitter();
54
55//------------------------------------------------------------------------------
56// Tests
57//------------------------------------------------------------------------------
58
59describe("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
609c276f
TL
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
eb39fafa
DC
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 = {
6f036462
TL
576 meta: {
577 fixable: "code"
578 },
579
eb39fafa
DC
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
6f036462
TL
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
eb39fafa
DC
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"),
6f036462 1039 errors: [{ line: 1 }]
eb39fafa
DC
1040 }
1041 ]
1042 });
1043 assert.strictEqual(spy.args[1][1].parser, require.resolve("esprima"));
1044 });
34eeec05 1045
609c276f
TL
1046 it("should pass normalized ecmaVersion to the rule", () => {
1047 const reportEcmaVersionRule = {
1048 meta: {
1049 messages: {
1050 ecmaVersionMessage: "context.parserOptions.ecmaVersion is {{type}} {{ecmaVersion}}."
1051 }
1052 },
1053 create: context => ({
1054 Program(node) {
1055 const { ecmaVersion } = context.parserOptions;
1056
1057 context.report({
1058 node,
1059 messageId: "ecmaVersionMessage",
1060 data: { type: typeof ecmaVersion, ecmaVersion }
1061 });
1062 }
1063 })
1064 };
1065
1066 const notEspree = require.resolve("../../fixtures/parsers/empty-program-parser");
1067
1068 ruleTester.run("report-ecma-version", reportEcmaVersionRule, {
1069 valid: [],
1070 invalid: [
1071 {
1072 code: "",
1073 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }]
1074 },
1075 {
1076 code: "",
1077 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1078 parserOptions: {}
1079 },
1080 {
1081 code: "<div/>",
1082 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1083 parserOptions: { ecmaFeatures: { jsx: true } }
1084 },
1085 {
1086 code: "",
1087 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1088 parser: require.resolve("espree")
1089 },
1090 {
1091 code: "",
1092 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1093 parserOptions: { ecmaVersion: 6 }
1094 },
1095 {
1096 code: "",
1097 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1098 parserOptions: { ecmaVersion: 2015 }
1099 },
1100 {
1101 code: "",
1102 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1103 env: { browser: true }
1104 },
1105 {
1106 code: "",
1107 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1108 env: { es6: false }
1109 },
1110 {
1111 code: "",
1112 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1113 env: { es6: true }
1114 },
1115 {
1116 code: "",
1117 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }],
1118 env: { es6: false, es2017: true }
1119 },
1120 {
1121 code: "let x",
1122 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1123 env: { es6: "truthy" }
1124 },
1125 {
1126 code: "",
1127 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }],
1128 env: { es2017: true }
1129 },
1130 {
1131 code: "",
1132 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "11" } }],
1133 env: { es2020: true }
1134 },
1135 {
1136 code: "",
1137 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "12" } }],
1138 env: { es2021: true }
1139 },
1140 {
1141 code: "",
1142 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1143 parserOptions: { ecmaVersion: "latest" }
1144 },
1145 {
1146 code: "",
1147 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1148 parser: require.resolve("espree"),
1149 parserOptions: { ecmaVersion: "latest" }
1150 },
1151 {
1152 code: "<div/>",
1153 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1154 parserOptions: { ecmaVersion: "latest", ecmaFeatures: { jsx: true } }
1155 },
1156 {
1157 code: "import 'foo'",
1158 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1159 parserOptions: { ecmaVersion: "latest", sourceType: "module" }
1160 },
1161 {
1162 code: "",
1163 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1164 parserOptions: { ecmaVersion: "latest" },
1165 env: { es6: true }
1166 },
1167 {
1168 code: "",
1169 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
1170 parserOptions: { ecmaVersion: "latest" },
1171 env: { es2020: true }
1172 },
34eeec05
TL
1173
1174 // Non-Espree parsers normalize ecmaVersion if it's not "latest"
609c276f
TL
1175 {
1176 code: "",
1177 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1178 parser: notEspree
1179 },
1180 {
1181 code: "",
1182 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
1183 parser: notEspree,
1184 parserOptions: {}
1185 },
1186 {
1187 code: "",
1188 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }],
1189 parser: notEspree,
1190 parserOptions: { ecmaVersion: 5 }
1191 },
1192 {
1193 code: "",
1194 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1195 parser: notEspree,
1196 parserOptions: { ecmaVersion: 6 }
1197 },
1198 {
1199 code: "",
34eeec05 1200 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: 6 } }],
609c276f
TL
1201 parser: notEspree,
1202 parserOptions: { ecmaVersion: 2015 }
1203 },
1204 {
1205 code: "",
1206 errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }],
1207 parser: notEspree,
1208 parserOptions: { ecmaVersion: "latest" }
1209 }
1210 ]
1211 });
1212
1213 [{ parserOptions: { ecmaVersion: 6 } }, { env: { es6: true } }].forEach(options => {
1214 new RuleTester(options).run("report-ecma-version", reportEcmaVersionRule, {
1215 valid: [],
1216 invalid: [
1217 {
1218 code: "",
1219 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }]
1220 },
1221 {
1222 code: "",
1223 errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
1224 parserOptions: {}
1225 }
1226 ]
1227 });
1228 });
1229
1230 new RuleTester({ parser: notEspree }).run("report-ecma-version", reportEcmaVersionRule, {
1231 valid: [],
1232 invalid: [
1233 {
1234 code: "",
1235 errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }]
1236 },
1237 {
1238 code: "",
1239 errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }],
1240 parserOptions: { ecmaVersion: "latest" }
1241 }
1242 ]
1243 });
1244 });
eb39fafa
DC
1245
1246 it("should pass-through services from parseForESLint to the rule", () => {
1247 const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser");
1248 const disallowHiRule = {
1249 create: context => ({
1250 Literal(node) {
1251 const disallowed = context.parserServices.test.getMessage(); // returns "Hi!"
1252
1253 if (node.value === disallowed) {
1254 context.report({ node, message: `Don't use '${disallowed}'` });
1255 }
1256 }
1257 })
1258 };
1259
1260 ruleTester.run("no-hi", disallowHiRule, {
1261 valid: [
1262 {
1263 code: "'Hello!'",
1264 parser: enhancedParserPath
1265 }
1266 ],
1267 invalid: [
1268 {
1269 code: "'Hi!'",
1270 parser: enhancedParserPath,
1271 errors: [{ message: "Don't use 'Hi!'" }]
1272 }
1273 ]
1274 });
1275 });
1276
1277 it("should prevent invalid options schemas", () => {
1278 assert.throws(() => {
1279 ruleTester.run("no-invalid-schema", require("../../fixtures/testers/rule-tester/no-invalid-schema"), {
1280 valid: [
1281 "var answer = 6 * 7;",
1282 { code: "var answer = 6 * 7;", options: [] }
1283 ],
1284 invalid: [
1285 { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] }
1286 ]
1287 });
1288 }, "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");
1289
1290 });
1291
1292 it("should prevent schema violations in options", () => {
1293 assert.throws(() => {
1294 ruleTester.run("no-schema-violation", require("../../fixtures/testers/rule-tester/no-schema-violation"), {
1295 valid: [
1296 "var answer = 6 * 7;",
1297 { code: "var answer = 6 * 7;", options: ["foo"] }
1298 ],
1299 invalid: [
1300 { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected foo." }] }
1301 ]
1302 });
1303 }, /Value "bar" should be equal to one of the allowed values./u);
1304
1305 });
1306
1307 it("should disallow invalid defaults in rules", () => {
1308 const ruleWithInvalidDefaults = {
1309 meta: {
1310 schema: [
1311 {
1312 oneOf: [
1313 { enum: ["foo"] },
1314 {
1315 type: "object",
1316 properties: {
1317 foo: {
1318 enum: ["foo", "bar"],
1319 default: "foo"
1320 }
1321 },
1322 additionalProperties: false
1323 }
1324 ]
1325 }
1326 ]
1327 },
1328 create: () => ({})
1329 };
1330
1331 assert.throws(() => {
1332 ruleTester.run("invalid-defaults", ruleWithInvalidDefaults, {
1333 valid: [
1334 {
1335 code: "foo",
1336 options: [{}]
1337 }
1338 ],
1339 invalid: []
1340 });
1341 }, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u);
1342 });
1343
1344 it("throw an error when an unknown config option is included", () => {
1345 assert.throws(() => {
1346 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
1347 valid: [
1348 { code: "Eval(foo)", foo: "bar" }
1349 ],
1350 invalid: []
1351 });
1352 }, /ESLint configuration in rule-tester is invalid./u);
1353 });
1354
1355 it("throw an error when an invalid config value is included", () => {
1356 assert.throws(() => {
1357 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
1358 valid: [
1359 { code: "Eval(foo)", env: ["es6"] }
1360 ],
1361 invalid: []
1362 });
1363 }, /Property "env" is the wrong type./u);
1364 });
1365
1366 it("should pass-through the tester config to the rule", () => {
1367 ruleTester = new RuleTester({
1368 globals: { test: true }
1369 });
1370
1371 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
1372 valid: [
1373 "var test = 'foo'",
1374 "var test2 = test"
1375 ],
1376 invalid: [{ code: "bar", errors: 1, globals: { foo: true } }]
1377 });
1378 });
1379
1380 it("should correctly set the globals configuration", () => {
1381 const config = { globals: { test: true } };
1382
1383 RuleTester.setDefaultConfig(config);
1384 assert(
1385 RuleTester.getDefaultConfig().globals.test,
1386 "The default config object is incorrect"
1387 );
1388 });
1389
1390 it("should correctly reset the global configuration", () => {
1391 const config = { globals: { test: true } };
1392
1393 RuleTester.setDefaultConfig(config);
1394 RuleTester.resetDefaultConfig();
1395 assert.deepStrictEqual(
1396 RuleTester.getDefaultConfig(),
1397 { rules: {} },
1398 "The default configuration has not reset correctly"
1399 );
1400 });
1401
1402 it("should enforce the global configuration to be an object", () => {
1403
1404 /**
1405 * Set the default config for the rules tester
1406 * @param {Object} config configuration object
1407 * @returns {Function} Function to be executed
1408 * @private
1409 */
1410 function setConfig(config) {
1411 return function() {
1412 RuleTester.setDefaultConfig(config);
1413 };
1414 }
1415 assert.throw(setConfig());
1416 assert.throw(setConfig(1));
1417 assert.throw(setConfig(3.14));
1418 assert.throw(setConfig("foo"));
1419 assert.throw(setConfig(null));
1420 assert.throw(setConfig(true));
1421 });
1422
1423 it("should pass-through the globals config to the tester then to the to rule", () => {
1424 const config = { globals: { test: true } };
1425
1426 RuleTester.setDefaultConfig(config);
1427 ruleTester = new RuleTester();
1428
1429 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
1430 valid: [
1431 "var test = 'foo'",
1432 "var test2 = test"
1433 ],
1434 invalid: [{ code: "bar", errors: 1, globals: { foo: true } }]
1435 });
1436 });
1437
1438 it("should throw an error if AST was modified", () => {
1439 assert.throws(() => {
1440 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
1441 valid: [
1442 "var foo = 0;"
1443 ],
1444 invalid: []
1445 });
1446 }, "Rule should not modify AST.");
1447 assert.throws(() => {
1448 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
1449 valid: [],
1450 invalid: [
1451 { code: "var bar = 0;", errors: ["error"] }
1452 ]
1453 });
1454 }, "Rule should not modify AST.");
1455 });
1456
1457 it("should throw an error if AST was modified (at Program)", () => {
1458 assert.throws(() => {
1459 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
1460 valid: [
1461 "var foo = 0;"
1462 ],
1463 invalid: []
1464 });
1465 }, "Rule should not modify AST.");
1466 assert.throws(() => {
1467 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
1468 valid: [],
1469 invalid: [
1470 { code: "var bar = 0;", errors: ["error"] }
1471 ]
1472 });
1473 }, "Rule should not modify AST.");
1474 });
1475
1476 it("should throw an error if AST was modified (at Program:exit)", () => {
1477 assert.throws(() => {
1478 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1479 valid: [
1480 "var foo = 0;"
1481 ],
1482 invalid: []
1483 });
1484 }, "Rule should not modify AST.");
1485 assert.throws(() => {
1486 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1487 valid: [],
1488 invalid: [
1489 { code: "var bar = 0;", errors: ["error"] }
1490 ]
1491 });
1492 }, "Rule should not modify AST.");
1493 });
1494
1495 it("should throw an error if rule uses start and end properties on nodes, tokens or comments", () => {
1496 const usesStartEndRule = {
1497 create(context) {
1498
1499 const sourceCode = context.getSourceCode();
1500
1501 return {
1502 CallExpression(node) {
1503 noop(node.arguments[1].start);
1504 },
1505 "BinaryExpression[operator='+']"(node) {
1506 noop(node.end);
1507 },
1508 "UnaryExpression[operator='-']"(node) {
1509 noop(sourceCode.getFirstToken(node).start);
1510 },
1511 ConditionalExpression(node) {
1512 noop(sourceCode.getFirstToken(node).end);
1513 },
1514 BlockStatement(node) {
1515 noop(sourceCode.getCommentsInside(node)[0].start);
1516 },
1517 ObjectExpression(node) {
1518 noop(sourceCode.getCommentsInside(node)[0].end);
1519 },
1520 Decorator(node) {
1521 noop(node.start);
1522 }
1523 };
1524 }
1525 };
1526
1527 assert.throws(() => {
1528 ruleTester.run("uses-start-end", usesStartEndRule, {
1529 valid: ["foo(a, b)"],
1530 invalid: []
1531 });
1532 }, "Use node.range[0] instead of node.start");
1533 assert.throws(() => {
1534 ruleTester.run("uses-start-end", usesStartEndRule, {
1535 valid: [],
1536 invalid: [{ code: "var a = b * (c + d) / e;", errors: 1 }]
1537 });
1538 }, "Use node.range[1] instead of node.end");
1539 assert.throws(() => {
1540 ruleTester.run("uses-start-end", usesStartEndRule, {
1541 valid: [],
1542 invalid: [{ code: "var a = -b * c;", errors: 1 }]
1543 });
1544 }, "Use token.range[0] instead of token.start");
1545 assert.throws(() => {
1546 ruleTester.run("uses-start-end", usesStartEndRule, {
1547 valid: ["var a = b ? c : d;"],
1548 invalid: []
1549 });
1550 }, "Use token.range[1] instead of token.end");
1551 assert.throws(() => {
1552 ruleTester.run("uses-start-end", usesStartEndRule, {
1553 valid: ["function f() { /* comment */ }"],
1554 invalid: []
1555 });
1556 }, "Use token.range[0] instead of token.start");
1557 assert.throws(() => {
1558 ruleTester.run("uses-start-end", usesStartEndRule, {
1559 valid: [],
1560 invalid: [{ code: "var x = //\n {\n //comment\n //\n}", errors: 1 }]
1561 });
1562 }, "Use token.range[1] instead of token.end");
1563
1564 const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser");
1565
1566 assert.throws(() => {
1567 ruleTester.run("uses-start-end", usesStartEndRule, {
1568 valid: [{ code: "foo(a, b)", parser: enhancedParserPath }],
1569 invalid: []
1570 });
1571 }, "Use node.range[0] instead of node.start");
1572 assert.throws(() => {
1573 ruleTester.run("uses-start-end", usesStartEndRule, {
1574 valid: [],
1575 invalid: [{ code: "var a = b * (c + d) / e;", parser: enhancedParserPath, errors: 1 }]
1576 });
1577 }, "Use node.range[1] instead of node.end");
1578 assert.throws(() => {
1579 ruleTester.run("uses-start-end", usesStartEndRule, {
1580 valid: [],
1581 invalid: [{ code: "var a = -b * c;", parser: enhancedParserPath, errors: 1 }]
1582 });
1583 }, "Use token.range[0] instead of token.start");
1584 assert.throws(() => {
1585 ruleTester.run("uses-start-end", usesStartEndRule, {
1586 valid: [{ code: "var a = b ? c : d;", parser: enhancedParserPath }],
1587 invalid: []
1588 });
1589 }, "Use token.range[1] instead of token.end");
1590 assert.throws(() => {
1591 ruleTester.run("uses-start-end", usesStartEndRule, {
1592 valid: [{ code: "function f() { /* comment */ }", parser: enhancedParserPath }],
1593 invalid: []
1594 });
1595 }, "Use token.range[0] instead of token.start");
1596 assert.throws(() => {
1597 ruleTester.run("uses-start-end", usesStartEndRule, {
1598 valid: [],
1599 invalid: [{ code: "var x = //\n {\n //comment\n //\n}", parser: enhancedParserPath, errors: 1 }]
1600 });
1601 }, "Use token.range[1] instead of token.end");
1602
1603 assert.throws(() => {
1604 ruleTester.run("uses-start-end", usesStartEndRule, {
1605 valid: [{ code: "@foo class A {}", parser: require.resolve("../../fixtures/parsers/enhanced-parser2") }],
1606 invalid: []
1607 });
1608 }, "Use node.range[0] instead of node.start");
1609 });
1610
1611 it("should throw an error if no test scenarios given", () => {
1612 assert.throws(() => {
1613 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"));
1614 }, "Test Scenarios for rule foo : Could not find test scenario object");
1615 });
1616
1617 it("should throw an error if no acceptable test scenario object is given", () => {
1618 assert.throws(() => {
1619 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), []);
1620 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
1621 assert.throws(() => {
1622 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), "");
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"), 2);
1626 }, "Test Scenarios for rule foo : Could not find test scenario object");
1627 assert.throws(() => {
1628 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {});
1629 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
1630 assert.throws(() => {
1631 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1632 valid: []
1633 });
1634 }, "Test Scenarios for rule foo is invalid:\nCould not find any invalid test scenarios");
1635 assert.throws(() => {
1636 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1637 invalid: []
1638 });
1639 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios");
1640 });
1641
1642 // Nominal message/messageId use cases
1643 it("should assert match if message provided in both test and result.", () => {
1644 assert.throws(() => {
1645 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
1646 valid: [],
1647 invalid: [{ code: "foo", errors: [{ message: "something" }] }]
1648 });
1649 }, /Avoid using variables named/u);
1650
1651 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
1652 valid: [],
1653 invalid: [{ code: "foo", errors: [{ message: "Avoid using variables named 'foo'." }] }]
1654 });
1655 });
1656
1657 it("should assert match between messageId if provided in both test and result.", () => {
1658 assert.throws(() => {
1659 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1660 valid: [],
1661 invalid: [{ code: "foo", errors: [{ messageId: "unused" }] }]
1662 });
1663 }, "messageId 'avoidFoo' does not match expected messageId 'unused'.");
1664
1665 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1666 valid: [],
1667 invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
1668 });
1669 });
1670 it("should assert match between resulting message output if messageId and data provided in both test and result", () => {
1671 assert.throws(() => {
1672 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1673 valid: [],
1674 invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "notFoo" } }] }]
1675 });
1676 }, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\"");
1677 });
1678
1679 // messageId/message misconfiguration cases
1680 it("should throw if user tests for both message and messageId", () => {
1681 assert.throws(() => {
1682 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1683 valid: [],
1684 invalid: [{ code: "foo", errors: [{ message: "something", messageId: "avoidFoo" }] }]
1685 });
1686 }, "Error should not specify both 'message' and a 'messageId'.");
1687 });
1688 it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => {
1689 assert.throws(() => {
1690 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
1691 valid: [],
1692 invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
1693 });
1694 }, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'");
1695 });
1696 it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => {
1697 assert.throws(() => {
1698 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1699 valid: [],
1700 invalid: [{ code: "foo", errors: [{ messageId: "useFoo" }] }]
1701 });
1702 }, /Invalid messageId 'useFoo'/u);
1703 });
1704 it("should throw if data provided without messageId.", () => {
1705 assert.throws(() => {
1706 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1707 valid: [],
1708 invalid: [{ code: "foo", errors: [{ data: "something" }] }]
1709 });
1710 }, "Error must specify 'messageId' if 'data' is used.");
1711 });
1712
6f036462
TL
1713 // fixable rules with or without `meta` property
1714 it("should not throw an error if a rule that has `meta.fixable` produces fixes", () => {
1715 const replaceProgramWith5Rule = {
1716 meta: {
1717 fixable: "code"
1718 },
1719 create(context) {
1720 return {
1721 Program(node) {
1722 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
1723 }
1724 };
1725 }
1726 };
1727
1728 ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
1729 valid: [],
1730 invalid: [
1731 { code: "var foo = bar;", output: "5", errors: 1 }
1732 ]
1733 });
1734 });
1735 it("should throw an error if a new-format rule that doesn't have `meta` produces fixes", () => {
1736 const replaceProgramWith5Rule = {
1737 create(context) {
1738 return {
1739 Program(node) {
1740 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
1741 }
1742 };
1743 }
1744 };
1745
1746 assert.throws(() => {
1747 ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
1748 valid: [],
1749 invalid: [
1750 { code: "var foo = bar;", output: "5", errors: 1 }
1751 ]
1752 });
609c276f 1753 }, /Fixable rules must set the `meta\.fixable` property/u);
6f036462
TL
1754 });
1755 it("should throw an error if a legacy-format rule produces fixes", () => {
1756
1757 /**
1758 * Legacy-format rule (a function instead of an object with `create` method).
1759 * @param {RuleContext} context The ESLint rule context object.
1760 * @returns {Object} Listeners.
1761 */
1762 function replaceProgramWith5Rule(context) {
1763 return {
1764 Program(node) {
1765 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
1766 }
1767 };
1768 }
1769
1770 assert.throws(() => {
1771 ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
1772 valid: [],
1773 invalid: [
1774 { code: "var foo = bar;", output: "5", errors: 1 }
1775 ]
1776 });
609c276f 1777 }, /Fixable rules must set the `meta\.fixable` property/u);
6f036462
TL
1778 });
1779
eb39fafa
DC
1780 describe("suggestions", () => {
1781 it("should pass with valid suggestions (tested using desc)", () => {
1782 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1783 valid: [
1784 "var boo;"
1785 ],
1786 invalid: [{
1787 code: "var foo;",
1788 errors: [{
1789 suggestions: [{
1790 desc: "Rename identifier 'foo' to 'bar'",
1791 output: "var bar;"
1792 }]
1793 }]
1794 }]
1795 });
1796 });
1797
1798 it("should pass with suggestions on multiple lines", () => {
1799 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1800 valid: [],
1801 invalid: [
1802 {
1803 code: "function foo() {\n var foo = 1;\n}",
1804 errors: [{
1805 suggestions: [{
1806 desc: "Rename identifier 'foo' to 'bar'",
1807 output: "function bar() {\n var foo = 1;\n}"
1808 }]
1809 }, {
1810 suggestions: [{
1811 desc: "Rename identifier 'foo' to 'bar'",
1812 output: "function foo() {\n var bar = 1;\n}"
1813 }]
1814 }]
1815 }
1816 ]
1817 });
1818 });
1819
1820 it("should pass with valid suggestions (tested using messageIds)", () => {
1821 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1822 valid: [],
1823 invalid: [{
1824 code: "var foo;",
1825 errors: [{
1826 suggestions: [{
1827 messageId: "renameFoo",
1828 output: "var bar;"
1829 }, {
1830 messageId: "renameFoo",
1831 output: "var baz;"
1832 }]
1833 }]
1834 }]
1835 });
1836 });
1837
1838 it("should pass with valid suggestions (one tested using messageIds, the other using desc)", () => {
1839 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1840 valid: [],
1841 invalid: [{
1842 code: "var foo;",
1843 errors: [{
1844 suggestions: [{
1845 messageId: "renameFoo",
1846 output: "var bar;"
1847 }, {
1848 desc: "Rename identifier 'foo' to 'baz'",
1849 output: "var baz;"
1850 }]
1851 }]
1852 }]
1853 });
1854 });
1855
1856 it("should pass with valid suggestions (tested using both desc and messageIds for the same suggestion)", () => {
1857 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1858 valid: [],
1859 invalid: [{
1860 code: "var foo;",
1861 errors: [{
1862 suggestions: [{
1863 desc: "Rename identifier 'foo' to 'bar'",
1864 messageId: "renameFoo",
1865 output: "var bar;"
1866 }, {
1867 desc: "Rename identifier 'foo' to 'baz'",
1868 messageId: "renameFoo",
1869 output: "var baz;"
1870 }]
1871 }]
1872 }]
1873 });
1874 });
1875
1876 it("should pass with valid suggestions (tested using only desc on a rule that utilizes meta.messages)", () => {
1877 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1878 valid: [],
1879 invalid: [{
1880 code: "var foo;",
1881 errors: [{
1882 suggestions: [{
1883 desc: "Rename identifier 'foo' to 'bar'",
1884 output: "var bar;"
1885 }, {
1886 desc: "Rename identifier 'foo' to 'baz'",
1887 output: "var baz;"
1888 }]
1889 }]
1890 }]
1891 });
1892 });
1893
1894 it("should pass with valid suggestions (tested using messageIds and data)", () => {
1895 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1896 valid: [],
1897 invalid: [{
1898 code: "var foo;",
1899 errors: [{
1900 suggestions: [{
1901 messageId: "renameFoo",
1902 data: { newName: "bar" },
1903 output: "var bar;"
1904 }, {
1905 messageId: "renameFoo",
1906 data: { newName: "baz" },
1907 output: "var baz;"
1908 }]
1909 }]
1910 }]
1911 });
1912 });
1913
1914
1915 it("should pass when tested using empty suggestion test objects if the array length is correct", () => {
1916 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1917 valid: [],
1918 invalid: [{
1919 code: "var foo;",
1920 errors: [{
1921 suggestions: [{}, {}]
1922 }]
1923 }]
1924 });
1925 });
1926
1927 it("should support explicitly expecting no suggestions", () => {
1928 [void 0, null, false, []].forEach(suggestions => {
1929 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/no-eval"), {
1930 valid: [],
1931 invalid: [{
1932 code: "eval('var foo');",
1933 errors: [{
1934 suggestions
1935 }]
1936 }]
1937 });
1938 });
1939 });
1940
1941 it("should fail when expecting no suggestions and there are suggestions", () => {
1942 [void 0, null, false, []].forEach(suggestions => {
1943 assert.throws(() => {
1944 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1945 valid: [],
1946 invalid: [{
1947 code: "var foo;",
1948 errors: [{
1949 suggestions
1950 }]
1951 }]
1952 });
1953 }, "Error should have no suggestions on error with message: \"Avoid using identifiers named 'foo'.\"");
1954 });
1955 });
1956
1957 it("should fail when testing for suggestions that don't exist", () => {
1958 assert.throws(() => {
1959 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1960 valid: [],
1961 invalid: [{
1962 code: "var foo;",
1963 errors: [{
1964 suggestions: [{
1965 messageId: "this-does-not-exist"
1966 }]
1967 }]
1968 }]
1969 });
1970 }, "Error should have an array of suggestions. Instead received \"undefined\" on error with message: \"Bad var.\"");
1971 });
1972
1973 it("should fail when there are a different number of suggestions", () => {
1974 assert.throws(() => {
1975 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1976 valid: [],
1977 invalid: [{
1978 code: "var foo;",
1979 errors: [{
1980 suggestions: [{
1981 desc: "Rename identifier 'foo' to 'bar'",
1982 output: "var bar;"
1983 }, {
1984 desc: "Rename identifier 'foo' to 'baz'",
1985 output: "var baz;"
1986 }]
1987 }]
1988 }]
1989 });
1990 }, "Error should have 2 suggestions. Instead found 1 suggestions");
1991 });
1992
1993 it("should throw if the suggestion description doesn't match", () => {
1994 assert.throws(() => {
1995 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1996 valid: [],
1997 invalid: [{
1998 code: "var foo;",
1999 errors: [{
2000 suggestions: [{
2001 desc: "not right",
2002 output: "var baz;"
2003 }]
2004 }]
2005 }]
2006 });
2007 }, "Error Suggestion at index 0 : desc should be \"not right\" but got \"Rename identifier 'foo' to 'bar'\" instead.");
2008 });
2009
2010 it("should throw if the suggestion description doesn't match (although messageIds match)", () => {
2011 assert.throws(() => {
2012 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2013 valid: [],
2014 invalid: [{
2015 code: "var foo;",
2016 errors: [{
2017 suggestions: [{
2018 desc: "Rename identifier 'foo' to 'bar'",
2019 messageId: "renameFoo",
2020 output: "var bar;"
2021 }, {
2022 desc: "Rename id 'foo' to 'baz'",
2023 messageId: "renameFoo",
2024 output: "var baz;"
2025 }]
2026 }]
2027 }]
2028 });
2029 }, "Error Suggestion at index 1 : desc should be \"Rename id 'foo' to 'baz'\" but got \"Rename identifier 'foo' to 'baz'\" instead.");
2030 });
2031
2032 it("should throw if the suggestion messageId doesn't match", () => {
2033 assert.throws(() => {
2034 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2035 valid: [],
2036 invalid: [{
2037 code: "var foo;",
2038 errors: [{
2039 suggestions: [{
2040 messageId: "unused",
2041 output: "var bar;"
2042 }, {
2043 messageId: "renameFoo",
2044 output: "var baz;"
2045 }]
2046 }]
2047 }]
2048 });
2049 }, "Error Suggestion at index 0 : messageId should be 'unused' but got 'renameFoo' instead.");
2050 });
2051
2052 it("should throw if the suggestion messageId doesn't match (although descriptions match)", () => {
2053 assert.throws(() => {
2054 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2055 valid: [],
2056 invalid: [{
2057 code: "var foo;",
2058 errors: [{
2059 suggestions: [{
2060 desc: "Rename identifier 'foo' to 'bar'",
2061 messageId: "renameFoo",
2062 output: "var bar;"
2063 }, {
2064 desc: "Rename identifier 'foo' to 'baz'",
2065 messageId: "avoidFoo",
2066 output: "var baz;"
2067 }]
2068 }]
2069 }]
2070 });
2071 }, "Error Suggestion at index 1 : messageId should be 'avoidFoo' but got 'renameFoo' instead.");
2072 });
2073
2074 it("should throw if test specifies messageId for a rule that doesn't have meta.messages", () => {
2075 assert.throws(() => {
2076 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
2077 valid: [],
2078 invalid: [{
2079 code: "var foo;",
2080 errors: [{
2081 suggestions: [{
2082 messageId: "renameFoo",
2083 output: "var bar;"
2084 }]
2085 }]
2086 }]
2087 });
2088 }, "Error Suggestion at index 0 : Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.");
2089 });
2090
2091 it("should throw if test specifies messageId that doesn't exist in the rule's meta.messages", () => {
2092 assert.throws(() => {
2093 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2094 valid: [],
2095 invalid: [{
2096 code: "var foo;",
2097 errors: [{
2098 suggestions: [{
2099 messageId: "renameFoo",
2100 output: "var bar;"
2101 }, {
2102 messageId: "removeFoo",
2103 output: "var baz;"
2104 }]
2105 }]
2106 }]
2107 });
2108 }, "Error Suggestion at index 1 : Test has invalid messageId 'removeFoo', the rule under test allows only one of ['avoidFoo', 'unused', 'renameFoo'].");
2109 });
2110
2111 it("should throw if hydrated desc doesn't match (wrong data value)", () => {
2112 assert.throws(() => {
2113 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2114 valid: [],
2115 invalid: [{
2116 code: "var foo;",
2117 errors: [{
2118 suggestions: [{
2119 messageId: "renameFoo",
2120 data: { newName: "car" },
2121 output: "var bar;"
2122 }, {
2123 messageId: "renameFoo",
2124 data: { newName: "baz" },
2125 output: "var baz;"
2126 }]
2127 }]
2128 }]
2129 });
2130 }, "Error Suggestion at index 0 : Hydrated test desc \"Rename identifier 'foo' to 'car'\" does not match received desc \"Rename identifier 'foo' to 'bar'\".");
2131 });
2132
2133 it("should throw if hydrated desc doesn't match (wrong data key)", () => {
2134 assert.throws(() => {
2135 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2136 valid: [],
2137 invalid: [{
2138 code: "var foo;",
2139 errors: [{
2140 suggestions: [{
2141 messageId: "renameFoo",
2142 data: { newName: "bar" },
2143 output: "var bar;"
2144 }, {
2145 messageId: "renameFoo",
2146 data: { name: "baz" },
2147 output: "var baz;"
2148 }]
2149 }]
2150 }]
2151 });
2152 }, "Error Suggestion at index 1 : Hydrated test desc \"Rename identifier 'foo' to '{{ newName }}'\" does not match received desc \"Rename identifier 'foo' to 'baz'\".");
2153 });
2154
2155 it("should throw if test specifies both desc and data", () => {
2156 assert.throws(() => {
2157 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2158 valid: [],
2159 invalid: [{
2160 code: "var foo;",
2161 errors: [{
2162 suggestions: [{
2163 desc: "Rename identifier 'foo' to 'bar'",
2164 messageId: "renameFoo",
2165 data: { newName: "bar" },
2166 output: "var bar;"
2167 }, {
2168 messageId: "renameFoo",
2169 data: { newName: "baz" },
2170 output: "var baz;"
2171 }]
2172 }]
2173 }]
2174 });
2175 }, "Error Suggestion at index 0 : Test should not specify both 'desc' and 'data'.");
2176 });
2177
2178 it("should throw if test uses data but doesn't specify messageId", () => {
2179 assert.throws(() => {
2180 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2181 valid: [],
2182 invalid: [{
2183 code: "var foo;",
2184 errors: [{
2185 suggestions: [{
2186 messageId: "renameFoo",
2187 data: { newName: "bar" },
2188 output: "var bar;"
2189 }, {
2190 data: { newName: "baz" },
2191 output: "var baz;"
2192 }]
2193 }]
2194 }]
2195 });
2196 }, "Error Suggestion at index 1 : Test must specify 'messageId' if 'data' is used.");
2197 });
2198
2199 it("should throw if the resulting suggestion output doesn't match", () => {
2200 assert.throws(() => {
2201 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
2202 valid: [],
2203 invalid: [{
2204 code: "var foo;",
2205 errors: [{
2206 suggestions: [{
2207 desc: "Rename identifier 'foo' to 'bar'",
2208 output: "var baz;"
2209 }]
2210 }]
2211 }]
2212 });
2213 }, "Expected the applied suggestion fix to match the test suggestion output");
2214 });
2215
2216 it("should fail when specified suggestion isn't an object", () => {
2217 assert.throws(() => {
2218 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
2219 valid: [],
2220 invalid: [{
2221 code: "var foo;",
2222 errors: [{
2223 suggestions: [null]
2224 }]
2225 }]
2226 });
2227 }, "Test suggestion in 'suggestions' array must be an object.");
2228
2229 assert.throws(() => {
2230 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2231 valid: [],
2232 invalid: [{
2233 code: "var foo;",
2234 errors: [{
2235 suggestions: [
2236 {
2237 messageId: "renameFoo",
2238 output: "var bar;"
2239 },
2240 "Rename identifier 'foo' to 'baz'"
2241 ]
2242 }]
2243 }]
2244 });
2245 }, "Test suggestion in 'suggestions' array must be an object.");
2246 });
2247
2248 it("should fail when the suggestion is an object with an unknown property name", () => {
2249 assert.throws(() => {
2250 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
2251 valid: [
2252 "var boo;"
2253 ],
2254 invalid: [{
2255 code: "var foo;",
2256 errors: [{
2257 suggestions: [{
2258 message: "Rename identifier 'foo' to 'bar'"
2259 }]
2260 }]
2261 }]
2262 });
2263 }, /Invalid suggestion property name 'message'/u);
2264 });
2265
2266 it("should fail when any of the suggestions is an object with an unknown property name", () => {
2267 assert.throws(() => {
2268 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
2269 valid: [],
2270 invalid: [{
2271 code: "var foo;",
2272 errors: [{
2273 suggestions: [{
2274 messageId: "renameFoo",
2275 output: "var bar;"
2276 }, {
2277 messageId: "renameFoo",
2278 outpt: "var baz;"
2279 }]
2280 }]
2281 }]
2282 });
2283 }, /Invalid suggestion property name 'outpt'/u);
2284 });
609c276f
TL
2285
2286 it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => {
2287 assert.throws(() => {
2288 ruleTester.run("suggestions-missing-hasSuggestions-property", require("../../fixtures/testers/rule-tester/suggestions").withoutHasSuggestionsProperty, {
2289 valid: [],
2290 invalid: [
2291 { code: "var foo = bar;", output: "5", errors: 1 }
2292 ]
2293 });
2294 }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
2295 });
eb39fafa
DC
2296 });
2297
2298 describe("naming test cases", () => {
2299
2300 /**
2301 * Asserts that a particular value will be emitted from an EventEmitter.
2302 * @param {EventEmitter} emitter The emitter that should emit a value
2303 * @param {string} emitType The type of emission to listen for
609c276f
TL
2304 * @param {any} expectedValue The value that should be emitted
2305 * @returns {Promise<void>} A Promise that fulfills if the value is emitted, and rejects if something else is emitted.
eb39fafa
DC
2306 * The Promise will be indefinitely pending if no value is emitted.
2307 */
2308 function assertEmitted(emitter, emitType, expectedValue) {
2309 return new Promise((resolve, reject) => {
2310 emitter.once(emitType, emittedValue => {
2311 if (emittedValue === expectedValue) {
2312 resolve();
2313 } else {
2314 reject(new Error(`Expected ${expectedValue} to be emitted but ${emittedValue} was emitted instead.`));
2315 }
2316 });
2317 });
2318 }
2319
2320 it("should use the first argument as the name of the test suite", () => {
2321 const assertion = assertEmitted(ruleTesterTestEmitter, "describe", "this-is-a-rule-name");
2322
2323 ruleTester.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), {
2324 valid: [],
2325 invalid: []
2326 });
2327
2328 return assertion;
2329 });
2330
2331 it("should use the test code as the name of the tests for valid code (string form)", () => {
2332 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);");
2333
2334 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2335 valid: [
2336 "valid(code);"
2337 ],
2338 invalid: []
2339 });
2340
2341 return assertion;
2342 });
2343
2344 it("should use the test code as the name of the tests for valid code (object form)", () => {
2345 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);");
2346
2347 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2348 valid: [
2349 {
2350 code: "valid(code);"
2351 }
2352 ],
2353 invalid: []
2354 });
2355
2356 return assertion;
2357 });
2358
2359 it("should use the test code as the name of the tests for invalid code", () => {
2360 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);");
2361
2362 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2363 valid: [],
2364 invalid: [
2365 {
2366 code: "var x = invalid(code);",
2367 output: " x = invalid(code);",
2368 errors: 1
2369 }
2370 ]
2371 });
2372
2373 return assertion;
2374 });
2375
2376 // https://github.com/eslint/eslint/issues/8142
2377 it("should use the empty string as the name of the test if the test case is an empty string", () => {
2378 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "");
2379
2380 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2381 valid: [
2382 {
2383 code: ""
2384 }
2385 ],
2386 invalid: []
2387 });
2388
2389 return assertion;
2390 });
609c276f
TL
2391
2392 it('should use the "name" property if set to a non-empty string', () => {
2393 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test");
2394
2395 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2396 valid: [],
2397 invalid: [
2398 {
2399 name: "my test",
2400 code: "var x = invalid(code);",
2401 output: " x = invalid(code);",
2402 errors: 1
2403 }
2404 ]
2405 });
2406
2407 return assertion;
2408 });
2409
2410 it('should use the "name" property if set to a non-empty string for valid cases too', () => {
2411 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test");
2412
2413 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2414 valid: [
2415 {
2416 name: "my test",
2417 code: "valid(code);"
2418 }
2419 ],
2420 invalid: []
2421 });
2422
2423 return assertion;
2424 });
2425
2426
2427 it('should use the test code as the name if the "name" property is set to an empty string', () => {
2428 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);");
2429
2430 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
2431 valid: [],
2432 invalid: [
2433 {
2434 name: "",
2435 code: "var x = invalid(code);",
2436 output: " x = invalid(code);",
2437 errors: 1
2438 }
2439 ]
2440 });
2441
2442 return assertion;
2443 });
eb39fafa
DC
2444 });
2445
2446 // https://github.com/eslint/eslint/issues/11615
2447 it("should fail the case if autofix made a syntax error.", () => {
2448 assert.throw(() => {
2449 ruleTester.run(
2450 "foo",
609c276f
TL
2451 {
2452 meta: {
2453 fixable: "code"
2454 },
2455 create(context) {
2456 return {
2457 Identifier(node) {
2458 context.report({
2459 node,
2460 message: "make a syntax error",
2461 fix(fixer) {
2462 return fixer.replaceText(node, "one two");
2463 }
2464 });
eb39fafa 2465 }
609c276f 2466 };
eb39fafa 2467 }
609c276f 2468 },
eb39fafa
DC
2469 {
2470 valid: ["one()"],
2471 invalid: []
2472 }
2473 );
56c4a2cb 2474 }, /A fatal parsing error occurred in autofix.\nError: .+\nAutofix output:\n.+/u);
eb39fafa
DC
2475 });
2476
2477 describe("sanitize test cases", () => {
2478 let originalRuleTesterIt;
2479 let spyRuleTesterIt;
2480
2481 before(() => {
2482 originalRuleTesterIt = RuleTester.it;
2483 spyRuleTesterIt = sinon.spy();
2484 RuleTester.it = spyRuleTesterIt;
2485 });
2486 after(() => {
2487 RuleTester.it = originalRuleTesterIt;
2488 });
2489 beforeEach(() => {
2490 spyRuleTesterIt.resetHistory();
2491 ruleTester = new RuleTester();
2492 });
2493 it("should present newline when using back-tick as new line", () => {
2494 const code = `
2495 var foo = bar;`;
2496
2497 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
2498 valid: [],
2499 invalid: [
2500 {
2501 code,
2502 errors: [/^Bad var/u]
2503 }
2504 ]
2505 });
2506 sinon.assert.calledWith(spyRuleTesterIt, code);
2507 });
2508 it("should present \\u0000 as a string", () => {
2509 const code = "\u0000";
2510
2511 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
2512 valid: [],
2513 invalid: [
2514 {
2515 code,
2516 errors: [/^Bad var/u]
2517 }
2518 ]
2519 });
2520 sinon.assert.calledWith(spyRuleTesterIt, "\\u0000");
2521 });
2522 it("should present the pipe character correctly", () => {
2523 const code = "var foo = bar || baz;";
2524
2525 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
2526 valid: [],
2527 invalid: [
2528 {
2529 code,
2530 errors: [/^Bad var/u]
2531 }
2532 ]
2533 });
2534 sinon.assert.calledWith(spyRuleTesterIt, code);
2535 });
2536
2537 });
6f036462 2538
609c276f
TL
2539 describe("SourceCode#getComments()", () => {
2540 const useGetCommentsRule = {
2541 create: context => ({
2542 Program(node) {
2543 const sourceCode = context.getSourceCode();
2544
2545 sourceCode.getComments(node);
2546 }
2547 })
2548 };
2549
2550 it("should throw if called from a valid test case", () => {
2551 assert.throws(() => {
2552 ruleTester.run("use-get-comments", useGetCommentsRule, {
2553 valid: [""],
2554 invalid: []
2555 });
2556 }, /`SourceCode#getComments\(\)` is deprecated/u);
2557 });
2558
2559 it("should throw if called from an invalid test case", () => {
2560 assert.throws(() => {
2561 ruleTester.run("use-get-comments", useGetCommentsRule, {
2562 valid: [],
2563 invalid: [{
2564 code: "",
2565 errors: [{}]
2566 }]
2567 });
2568 }, /`SourceCode#getComments\(\)` is deprecated/u);
2569 });
2570 });
2571
eb39fafa 2572});