]> git.proxmox.com Git - pve-eslint.git/blame - eslint/tests/lib/linter/report-translator.js
import eslint 7.28.0
[pve-eslint.git] / eslint / tests / lib / linter / report-translator.js
CommitLineData
eb39fafa
DC
1/**
2 * @fileoverview Tests for createReportTranslator
3 * @author Teddy Katz
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const assert = require("chai").assert;
12const { SourceCode } = require("../../../lib/source-code");
13const espree = require("espree");
14const createReportTranslator = require("../../../lib/linter/report-translator");
15
16//------------------------------------------------------------------------------
17// Tests
18//------------------------------------------------------------------------------
19
20describe("createReportTranslator", () => {
21
22 /**
23 * Creates a SourceCode instance out of JavaScript text
24 * @param {string} text Source text
25 * @returns {SourceCode} A SourceCode instance for that text
26 */
27 function createSourceCode(text) {
28 return new SourceCode(
29 text,
30 espree.parse(
31 text.replace(/^\uFEFF/u, ""),
32 {
33 loc: true,
34 range: true,
35 raw: true,
36 tokens: true,
37 comment: true
38 }
39 )
40 );
41 }
42
43 let node, location, message, translateReport, suggestion1, suggestion2;
44
45 beforeEach(() => {
46 const sourceCode = createSourceCode("foo\nbar");
47
48 node = sourceCode.ast.body[0];
49 location = sourceCode.ast.body[1].loc.start;
50 message = "foo";
51 suggestion1 = "First suggestion";
52 suggestion2 = "Second suggestion {{interpolated}}";
53 translateReport = createReportTranslator({
54 ruleId: "foo-rule",
55 severity: 2,
56 sourceCode,
57 messageIds: {
58 testMessage: message,
59 suggestion1,
60 suggestion2
61 }
62 });
63 });
64
65 describe("old-style call with location", () => {
66 it("should extract the location correctly", () => {
67 assert.deepStrictEqual(
68 translateReport(node, location, message, {}),
69 {
70 ruleId: "foo-rule",
71 severity: 2,
72 message: "foo",
73 line: 2,
74 column: 1,
75 nodeType: "ExpressionStatement"
76 }
77 );
78 });
79 });
80
81 describe("old-style call without location", () => {
82 it("should use the start location and end location of the node", () => {
83 assert.deepStrictEqual(
84 translateReport(node, message, {}),
85 {
86 ruleId: "foo-rule",
87 severity: 2,
88 message: "foo",
89 line: 1,
90 column: 1,
91 endLine: 1,
92 endColumn: 4,
93 nodeType: "ExpressionStatement"
94 }
95 );
96 });
97 });
98
99 describe("new-style call with all options", () => {
100 it("should include the new-style options in the report", () => {
101 const reportDescriptor = {
102 node,
103 loc: location,
104 message,
105 fix: () => ({ range: [1, 2], text: "foo" }),
106 suggest: [{
107 desc: "suggestion 1",
108 fix: () => ({ range: [2, 3], text: "s1" })
109 }, {
110 desc: "suggestion 2",
111 fix: () => ({ range: [3, 4], text: "s2" })
112 }]
113 };
114
115 assert.deepStrictEqual(
116 translateReport(reportDescriptor),
117 {
118 ruleId: "foo-rule",
119 severity: 2,
120 message: "foo",
121 line: 2,
122 column: 1,
123 nodeType: "ExpressionStatement",
124 fix: {
125 range: [1, 2],
126 text: "foo"
127 },
128 suggestions: [{
129 desc: "suggestion 1",
130 fix: { range: [2, 3], text: "s1" }
131 }, {
132 desc: "suggestion 2",
133 fix: { range: [3, 4], text: "s2" }
134 }]
135 }
136 );
137 });
138
139 it("should translate the messageId into a message", () => {
140 const reportDescriptor = {
141 node,
142 loc: location,
143 messageId: "testMessage",
144 fix: () => ({ range: [1, 2], text: "foo" })
145 };
146
147 assert.deepStrictEqual(
148 translateReport(reportDescriptor),
149 {
150 ruleId: "foo-rule",
151 severity: 2,
152 message: "foo",
153 messageId: "testMessage",
154 line: 2,
155 column: 1,
156 nodeType: "ExpressionStatement",
157 fix: {
158 range: [1, 2],
159 text: "foo"
160 }
161 }
162 );
163 });
164
165 it("should throw when both messageId and message are provided", () => {
166 const reportDescriptor = {
167 node,
168 loc: location,
169 messageId: "testMessage",
170 message: "bar",
171 fix: () => ({ range: [1, 2], text: "foo" })
172 };
173
174 assert.throws(
175 () => translateReport(reportDescriptor),
176 TypeError,
177 "context.report() called with a message and a messageId. Please only pass one."
178 );
179 });
180
181 it("should throw when an invalid messageId is provided", () => {
182 const reportDescriptor = {
183 node,
184 loc: location,
185 messageId: "thisIsNotASpecifiedMessageId",
186 fix: () => ({ range: [1, 2], text: "foo" })
187 };
188
189 assert.throws(
190 () => translateReport(reportDescriptor),
191 TypeError,
192 /^context\.report\(\) called with a messageId of '[^']+' which is not present in the 'messages' config:/u
193 );
194 });
195
196 it("should throw when no message is provided", () => {
197 const reportDescriptor = { node };
198
199 assert.throws(
200 () => translateReport(reportDescriptor),
201 TypeError,
202 "Missing `message` property in report() call; add a message that describes the linting problem."
203 );
204 });
205
206 it("should support messageIds for suggestions and output resulting descriptions", () => {
207 const reportDescriptor = {
208 node,
209 loc: location,
210 message,
211 suggest: [{
212 messageId: "suggestion1",
213 fix: () => ({ range: [2, 3], text: "s1" })
214 }, {
215 messageId: "suggestion2",
216 data: { interpolated: "'interpolated value'" },
217 fix: () => ({ range: [3, 4], text: "s2" })
218 }]
219 };
220
221 assert.deepStrictEqual(
222 translateReport(reportDescriptor),
223 {
224 ruleId: "foo-rule",
225 severity: 2,
226 message: "foo",
227 line: 2,
228 column: 1,
229 nodeType: "ExpressionStatement",
230 suggestions: [{
231 messageId: "suggestion1",
232 desc: "First suggestion",
233 fix: { range: [2, 3], text: "s1" }
234 }, {
235 messageId: "suggestion2",
236 data: { interpolated: "'interpolated value'" },
237 desc: "Second suggestion 'interpolated value'",
238 fix: { range: [3, 4], text: "s2" }
239 }]
240 }
241 );
242 });
243
244 it("should throw when a suggestion defines both a desc and messageId", () => {
245 const reportDescriptor = {
246 node,
247 loc: location,
248 message,
249 suggest: [{
250 desc: "The description",
251 messageId: "suggestion1",
252 fix: () => ({ range: [2, 3], text: "s1" })
253 }]
254 };
255
256 assert.throws(
257 () => translateReport(reportDescriptor),
258 TypeError,
259 "context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one."
260 );
261 });
262
263 it("should throw when a suggestion uses an invalid messageId", () => {
264 const reportDescriptor = {
265 node,
266 loc: location,
267 message,
268 suggest: [{
269 messageId: "noMatchingMessage",
270 fix: () => ({ range: [2, 3], text: "s1" })
271 }]
272 };
273
274 assert.throws(
275 () => translateReport(reportDescriptor),
276 TypeError,
277 /^context\.report\(\) called with a suggest option with a messageId '[^']+' which is not present in the 'messages' config:/u
278 );
279 });
280
281 it("should throw when a suggestion does not provide either a desc or messageId", () => {
282 const reportDescriptor = {
283 node,
284 loc: location,
285 message,
286 suggest: [{
287 fix: () => ({ range: [2, 3], text: "s1" })
288 }]
289 };
290
291 assert.throws(
292 () => translateReport(reportDescriptor),
293 TypeError,
294 "context.report() called with a suggest option that doesn't have either a `desc` or `messageId`"
295 );
296 });
297
298 it("should throw when a suggestion does not provide a fix function", () => {
299 const reportDescriptor = {
300 node,
301 loc: location,
302 message,
303 suggest: [{
304 desc: "The description",
305 fix: false
306 }]
307 };
308
309 assert.throws(
310 () => translateReport(reportDescriptor),
311 TypeError,
312 /^context\.report\(\) called with a suggest option without a fix function. See:/u
313 );
314 });
315 });
316
317 describe("combining autofixes", () => {
318 it("should merge fixes to one if 'fix' function returns an array of fixes.", () => {
319 const reportDescriptor = {
320 node,
321 loc: location,
322 message,
323 fix: () => [{ range: [1, 2], text: "foo" }, { range: [4, 5], text: "bar" }]
324 };
325
326 assert.deepStrictEqual(
327 translateReport(reportDescriptor),
328 {
329 ruleId: "foo-rule",
330 severity: 2,
331 message: "foo",
332 line: 2,
333 column: 1,
334 nodeType: "ExpressionStatement",
335 fix: {
336 range: [1, 5],
337 text: "fooo\nbar"
338 }
339 }
340 );
341 });
342
343 it("should merge fixes to one if 'fix' function returns an iterator of fixes.", () => {
344 const reportDescriptor = {
345 node,
346 loc: location,
347 message,
348 *fix() {
349 yield { range: [1, 2], text: "foo" };
350 yield { range: [4, 5], text: "bar" };
351 }
352 };
353
354 assert.deepStrictEqual(
355 translateReport(reportDescriptor),
356 {
357 ruleId: "foo-rule",
358 severity: 2,
359 message: "foo",
360 line: 2,
361 column: 1,
362 nodeType: "ExpressionStatement",
363 fix: {
364 range: [1, 5],
365 text: "fooo\nbar"
366 }
367 }
368 );
369 });
370
371 it("should pass through fixes if only one is present", () => {
372 const reportDescriptor = {
373 node,
374 loc: location,
375 message,
376 fix: () => [{ range: [1, 2], text: "foo" }]
377 };
378
379 assert.deepStrictEqual(
380 translateReport(reportDescriptor),
381 {
382 ruleId: "foo-rule",
383 severity: 2,
384 message: "foo",
385 line: 2,
386 column: 1,
387 nodeType: "ExpressionStatement",
388 fix: {
389 range: [1, 2],
390 text: "foo"
391 }
392 }
393 );
394 });
395
396 it("should handle inserting BOM correctly.", () => {
397 const reportDescriptor = {
398 node,
399 loc: location,
400 message,
401 fix: () => [{ range: [0, 3], text: "\uFEFFfoo" }, { range: [4, 5], text: "x" }]
402 };
403
404 assert.deepStrictEqual(
405 translateReport(reportDescriptor),
406 {
407 ruleId: "foo-rule",
408 severity: 2,
409 message: "foo",
410 line: 2,
411 column: 1,
412 nodeType: "ExpressionStatement",
413 fix: {
414 range: [0, 5],
415 text: "\uFEFFfoo\nx"
416 }
417 }
418 );
419 });
420
421
422 it("should handle removing BOM correctly.", () => {
423 const sourceCode = createSourceCode("\uFEFFfoo\nbar");
424
425 node = sourceCode.ast.body[0];
426
427 const reportDescriptor = {
428 node,
429 message,
430 fix: () => [{ range: [-1, 3], text: "foo" }, { range: [4, 5], text: "x" }]
431 };
432
433 assert.deepStrictEqual(
434 createReportTranslator({ ruleId: "foo-rule", severity: 1, sourceCode })(reportDescriptor),
435 {
436 ruleId: "foo-rule",
437 severity: 1,
438 message: "foo",
439 line: 1,
440 column: 1,
441 endLine: 1,
442 endColumn: 4,
443 nodeType: "ExpressionStatement",
444 fix: {
445 range: [-1, 5],
446 text: "foo\nx"
447 }
448 }
449 );
450 });
451
452 it("should throw an assertion error if ranges are overlapped.", () => {
453 const reportDescriptor = {
454 node,
455 loc: location,
456 message,
457 fix: () => [{ range: [0, 3], text: "\uFEFFfoo" }, { range: [2, 5], text: "x" }]
458 };
459
460 assert.throws(
461 translateReport.bind(null, reportDescriptor),
462 "Fix objects must not be overlapped in a report."
463 );
464 });
465
466 it("should include a fix passed as the last argument when location is passed", () => {
467 assert.deepStrictEqual(
468 translateReport(
469 node,
470 { line: 42, column: 23 },
471 "my message {{1}}{{0}}",
472 ["!", "testing"],
473 () => ({ range: [1, 1], text: "" })
474 ),
475 {
476 ruleId: "foo-rule",
477 severity: 2,
478 message: "my message testing!",
479 line: 42,
480 column: 24,
481 nodeType: "ExpressionStatement",
482 fix: {
483 range: [1, 1],
484 text: ""
485 }
486 }
487 );
488 });
489 });
490
491 describe("suggestions", () => {
492 it("should support multiple suggestions.", () => {
493 const reportDescriptor = {
494 node,
495 loc: location,
496 message,
497 suggest: [{
498 desc: "A first suggestion for the issue",
499 fix: () => [{ range: [1, 2], text: "foo" }]
500 }, {
501 desc: "A different suggestion for the issue",
502 fix: () => [{ range: [1, 3], text: "foobar" }]
503 }]
504 };
505
506 assert.deepStrictEqual(
507 translateReport(reportDescriptor),
508 {
509 ruleId: "foo-rule",
510 severity: 2,
511 message: "foo",
512 line: 2,
513 column: 1,
514 nodeType: "ExpressionStatement",
515 suggestions: [{
516 desc: "A first suggestion for the issue",
517 fix: { range: [1, 2], text: "foo" }
518 }, {
519 desc: "A different suggestion for the issue",
520 fix: { range: [1, 3], text: "foobar" }
521 }]
522 }
523 );
524 });
525
526 it("should merge suggestion fixes to one if 'fix' function returns an array of fixes.", () => {
527 const reportDescriptor = {
528 node,
529 loc: location,
530 message,
531 suggest: [{
532 desc: "A suggestion for the issue",
533 fix: () => [{ range: [1, 2], text: "foo" }, { range: [4, 5], text: "bar" }]
534 }]
535 };
536
537 assert.deepStrictEqual(
538 translateReport(reportDescriptor),
539 {
540 ruleId: "foo-rule",
541 severity: 2,
542 message: "foo",
543 line: 2,
544 column: 1,
545 nodeType: "ExpressionStatement",
546 suggestions: [{
547 desc: "A suggestion for the issue",
548 fix: {
549 range: [1, 5],
550 text: "fooo\nbar"
551 }
552 }]
553 }
554 );
555 });
6f036462
TL
556
557 it("should remove the whole suggestion if 'fix' function returned `null`.", () => {
558 const reportDescriptor = {
559 node,
560 loc: location,
561 message,
562 suggest: [{
563 desc: "A suggestion for the issue",
564 fix: () => null
565 }]
566 };
567
568 assert.deepStrictEqual(
569 translateReport(reportDescriptor),
570 {
571 ruleId: "foo-rule",
572 severity: 2,
573 message: "foo",
574 line: 2,
575 column: 1,
576 nodeType: "ExpressionStatement"
577 }
578 );
579 });
580
581 it("should remove the whole suggestion if 'fix' function returned an empty array.", () => {
582 const reportDescriptor = {
583 node,
584 loc: location,
585 message,
586 suggest: [{
587 desc: "A suggestion for the issue",
588 fix: () => []
589 }]
590 };
591
592 assert.deepStrictEqual(
593 translateReport(reportDescriptor),
594 {
595 ruleId: "foo-rule",
596 severity: 2,
597 message: "foo",
598 line: 2,
599 column: 1,
600 nodeType: "ExpressionStatement"
601 }
602 );
603 });
604
605 it("should remove the whole suggestion if 'fix' function returned an empty sequence.", () => {
606 const reportDescriptor = {
607 node,
608 loc: location,
609 message,
610 suggest: [{
611 desc: "A suggestion for the issue",
612 *fix() {}
613 }]
614 };
615
616 assert.deepStrictEqual(
617 translateReport(reportDescriptor),
618 {
619 ruleId: "foo-rule",
620 severity: 2,
621 message: "foo",
622 line: 2,
623 column: 1,
624 nodeType: "ExpressionStatement"
625 }
626 );
627 });
628
456be15e 629 // This isn't officially supported, but autofix works the same way
6f036462
TL
630 it("should remove the whole suggestion if 'fix' function didn't return anything.", () => {
631 const reportDescriptor = {
632 node,
633 loc: location,
634 message,
635 suggest: [{
636 desc: "A suggestion for the issue",
637 fix() {}
638 }]
639 };
640
641 assert.deepStrictEqual(
642 translateReport(reportDescriptor),
643 {
644 ruleId: "foo-rule",
645 severity: 2,
646 message: "foo",
647 line: 2,
648 column: 1,
649 nodeType: "ExpressionStatement"
650 }
651 );
652 });
653
654 it("should keep suggestion before a removed suggestion.", () => {
655 const reportDescriptor = {
656 node,
657 loc: location,
658 message,
659 suggest: [{
660 desc: "Suggestion with a fix",
661 fix: () => ({ range: [1, 2], text: "foo" })
662 }, {
663 desc: "Suggestion without a fix",
664 fix: () => null
665 }]
666 };
667
668 assert.deepStrictEqual(
669 translateReport(reportDescriptor),
670 {
671 ruleId: "foo-rule",
672 severity: 2,
673 message: "foo",
674 line: 2,
675 column: 1,
676 nodeType: "ExpressionStatement",
677 suggestions: [{
678 desc: "Suggestion with a fix",
679 fix: { range: [1, 2], text: "foo" }
680 }]
681 }
682 );
683 });
684
685 it("should keep suggestion after a removed suggestion.", () => {
686 const reportDescriptor = {
687 node,
688 loc: location,
689 message,
690 suggest: [{
691 desc: "Suggestion without a fix",
692 fix: () => null
693 }, {
694 desc: "Suggestion with a fix",
695 fix: () => ({ range: [1, 2], text: "foo" })
696 }]
697 };
698
699 assert.deepStrictEqual(
700 translateReport(reportDescriptor),
701 {
702 ruleId: "foo-rule",
703 severity: 2,
704 message: "foo",
705 line: 2,
706 column: 1,
707 nodeType: "ExpressionStatement",
708 suggestions: [{
709 desc: "Suggestion with a fix",
710 fix: { range: [1, 2], text: "foo" }
711 }]
712 }
713 );
714 });
715
716 it("should remove multiple suggestions that didn't provide a fix and keep those that did.", () => {
717 const reportDescriptor = {
718 node,
719 loc: location,
720 message,
721 suggest: [{
722 desc: "Keep #1",
723 fix: () => ({ range: [1, 2], text: "foo" })
724 }, {
725 desc: "Remove #1",
726 fix() {
727 return null;
728 }
729 }, {
730 desc: "Keep #2",
731 fix: () => ({ range: [1, 2], text: "bar" })
732 }, {
733 desc: "Remove #2",
734 fix() {
735 return [];
736 }
737 }, {
738 desc: "Keep #3",
739 fix: () => ({ range: [1, 2], text: "baz" })
740 }, {
741 desc: "Remove #3",
742 *fix() {}
743 }, {
744 desc: "Keep #4",
745 fix: () => ({ range: [1, 2], text: "quux" })
746 }]
747 };
748
749 assert.deepStrictEqual(
750 translateReport(reportDescriptor),
751 {
752 ruleId: "foo-rule",
753 severity: 2,
754 message: "foo",
755 line: 2,
756 column: 1,
757 nodeType: "ExpressionStatement",
758 suggestions: [{
759 desc: "Keep #1",
760 fix: { range: [1, 2], text: "foo" }
761 }, {
762 desc: "Keep #2",
763 fix: { range: [1, 2], text: "bar" }
764 }, {
765 desc: "Keep #3",
766 fix: { range: [1, 2], text: "baz" }
767 }, {
768 desc: "Keep #4",
769 fix: { range: [1, 2], text: "quux" }
770 }]
771 }
772 );
773 });
eb39fafa
DC
774 });
775
776 describe("message interpolation", () => {
777 it("should correctly parse a message when being passed all options in an old-style report", () => {
778 assert.deepStrictEqual(
779 translateReport(node, node.loc.end, "hello {{dynamic}}", { dynamic: node.type }),
780 {
781 severity: 2,
782 ruleId: "foo-rule",
783 message: "hello ExpressionStatement",
784 nodeType: "ExpressionStatement",
785 line: 1,
786 column: 4
787 }
788 );
789 });
790
791 it("should correctly parse a message when being passed all options in a new-style report", () => {
792 assert.deepStrictEqual(
793 translateReport({ node, loc: node.loc.end, message: "hello {{dynamic}}", data: { dynamic: node.type } }),
794 {
795 severity: 2,
796 ruleId: "foo-rule",
797 message: "hello ExpressionStatement",
798 nodeType: "ExpressionStatement",
799 line: 1,
800 column: 4
801 }
802 );
803 });
804
805 it("should correctly parse a message with object keys as numbers", () => {
806 assert.strictEqual(
807 translateReport(node, "my message {{name}}{{0}}", { 0: "!", name: "testing" }).message,
808 "my message testing!"
809 );
810 });
811
812 it("should correctly parse a message with array", () => {
813 assert.strictEqual(
814 translateReport(node, "my message {{1}}{{0}}", ["!", "testing"]).message,
815 "my message testing!"
816 );
817 });
818
819 it("should allow template parameter with inner whitespace", () => {
820 assert.strictEqual(
821 translateReport(node, "message {{parameter name}}", { "parameter name": "yay!" }).message,
822 "message yay!"
823 );
824 });
825
826 it("should allow template parameter with non-identifier characters", () => {
827 assert.strictEqual(
828 translateReport(node, "message {{parameter-name}}", { "parameter-name": "yay!" }).message,
829 "message yay!"
830 );
831 });
832
833 it("should allow template parameter wrapped in braces", () => {
834 assert.strictEqual(
835 translateReport(node, "message {{{param}}}", { param: "yay!" }).message,
836 "message {yay!}"
837 );
838 });
839
840 it("should ignore template parameter with no specified value", () => {
841 assert.strictEqual(
842 translateReport(node, "message {{parameter}}", {}).message,
843 "message {{parameter}}"
844 );
845 });
846
847 it("should handle leading whitespace in template parameter", () => {
848 assert.strictEqual(
849 translateReport({ node, message: "message {{ parameter}}", data: { parameter: "yay!" } }).message,
850 "message yay!"
851 );
852 });
853
854 it("should handle trailing whitespace in template parameter", () => {
855 assert.strictEqual(
856 translateReport({ node, message: "message {{parameter }}", data: { parameter: "yay!" } }).message,
857 "message yay!"
858 );
859 });
860
861 it("should still allow inner whitespace as well as leading/trailing", () => {
862 assert.strictEqual(
863 translateReport(node, "message {{ parameter name }}", { "parameter name": "yay!" }).message,
864 "message yay!"
865 );
866 });
867
868 it("should still allow non-identifier characters as well as leading/trailing whitespace", () => {
869 assert.strictEqual(
870 translateReport(node, "message {{ parameter-name }}", { "parameter-name": "yay!" }).message,
871 "message yay!"
872 );
873 });
874 });
875
876 describe("location inference", () => {
877 it("should use the provided location when given in an old-style call", () => {
878 assert.deepStrictEqual(
879 translateReport(node, { line: 42, column: 13 }, "hello world"),
880 {
881 severity: 2,
882 ruleId: "foo-rule",
883 message: "hello world",
884 nodeType: "ExpressionStatement",
885 line: 42,
886 column: 14
887 }
888 );
889 });
890
891 it("should use the provided location when given in an new-style call", () => {
892 assert.deepStrictEqual(
893 translateReport({ node, loc: { line: 42, column: 13 }, message: "hello world" }),
894 {
895 severity: 2,
896 ruleId: "foo-rule",
897 message: "hello world",
898 nodeType: "ExpressionStatement",
899 line: 42,
900 column: 14
901 }
902 );
903 });
904
905 it("should extract the start and end locations from a node if no location is provided", () => {
906 assert.deepStrictEqual(
907 translateReport(node, "hello world"),
908 {
909 severity: 2,
910 ruleId: "foo-rule",
911 message: "hello world",
912 nodeType: "ExpressionStatement",
913 line: 1,
914 column: 1,
915 endLine: 1,
916 endColumn: 4
917 }
918 );
919 });
920
921 it("should have 'endLine' and 'endColumn' when 'loc' property has 'end' property.", () => {
922 assert.deepStrictEqual(
923 translateReport({ loc: node.loc, message: "hello world" }),
924 {
925 severity: 2,
926 ruleId: "foo-rule",
927 message: "hello world",
928 nodeType: null,
929 line: 1,
930 column: 1,
931 endLine: 1,
932 endColumn: 4
933 }
934 );
935 });
936
937 it("should not have 'endLine' and 'endColumn' when 'loc' property does not have 'end' property.", () => {
938 assert.deepStrictEqual(
939 translateReport({ loc: node.loc.start, message: "hello world" }),
940 {
941 severity: 2,
942 ruleId: "foo-rule",
943 message: "hello world",
944 nodeType: null,
945 line: 1,
946 column: 1
947 }
948 );
949 });
950
951 it("should infer an 'endLine' and 'endColumn' property when using the object-based context.report API", () => {
952 assert.deepStrictEqual(
953 translateReport({ node, message: "hello world" }),
954 {
955 severity: 2,
956 ruleId: "foo-rule",
957 message: "hello world",
958 nodeType: "ExpressionStatement",
959 line: 1,
960 column: 1,
961 endLine: 1,
962 endColumn: 4
963 }
964 );
965 });
966 });
967
968 describe("converting old-style calls", () => {
969 it("should include a fix passed as the last argument when location is not passed", () => {
970 assert.deepStrictEqual(
971 translateReport(node, "my message {{1}}{{0}}", ["!", "testing"], () => ({ range: [1, 1], text: "" })),
972 {
973 severity: 2,
974 ruleId: "foo-rule",
975 message: "my message testing!",
976 nodeType: "ExpressionStatement",
977 line: 1,
978 column: 1,
979 endLine: 1,
980 endColumn: 4,
981 fix: { range: [1, 1], text: "" }
982 }
983 );
984 });
985 });
986
987 describe("validation", () => {
988
989 it("should throw an error if node is not an object", () => {
990 assert.throws(
991 () => translateReport("not a node", "hello world"),
992 "Node must be an object"
993 );
994 });
995
996
997 it("should not throw an error if location is provided and node is not in an old-style call", () => {
998 assert.deepStrictEqual(
999 translateReport(null, { line: 1, column: 1 }, "hello world"),
1000 {
1001 severity: 2,
1002 ruleId: "foo-rule",
1003 message: "hello world",
1004 nodeType: null,
1005 line: 1,
1006 column: 2
1007 }
1008 );
1009 });
1010
1011 it("should not throw an error if location is provided and node is not in a new-style call", () => {
1012 assert.deepStrictEqual(
1013 translateReport({ loc: { line: 1, column: 1 }, message: "hello world" }),
1014 {
1015 severity: 2,
1016 ruleId: "foo-rule",
1017 message: "hello world",
1018 nodeType: null,
1019 line: 1,
1020 column: 2
1021 }
1022 );
1023 });
1024
1025 it("should throw an error if neither node nor location is provided", () => {
1026 assert.throws(
1027 () => translateReport(null, "hello world"),
1028 "Node must be provided when reporting error if location is not provided"
1029 );
1030 });
5422a9cc
TL
1031
1032 it("should throw an error if fix range is invalid", () => {
1033 assert.throws(
1034 () => translateReport({ node, messageId: "testMessage", fix: () => ({ text: "foo" }) }),
1035 "Fix has invalid range"
1036 );
1037
1038 for (const badRange of [[0], [0, null], [null, 0], [void 0, 1], [0, void 0], [void 0, void 0], []]) {
1039 assert.throws(
1040 // eslint-disable-next-line no-loop-func
1041 () => translateReport(
1042 { node, messageId: "testMessage", fix: () => ({ range: badRange, text: "foo" }) }
1043 ),
1044 "Fix has invalid range"
1045 );
1046
1047 assert.throws(
1048 // eslint-disable-next-line no-loop-func
1049 () => translateReport(
1050 {
1051 node,
1052 messageId: "testMessage",
1053 fix: () => [
1054 { range: [0, 0], text: "foo" },
1055 { range: badRange, text: "bar" },
1056 { range: [1, 1], text: "baz" }
1057 ]
1058 }
1059 ),
1060 "Fix has invalid range"
1061 );
1062 }
1063 });
eb39fafa
DC
1064 });
1065});