]> git.proxmox.com Git - pve-eslint.git/blob - eslint/tests/lib/linter/report-translator.js
import 8.3.0 source
[pve-eslint.git] / eslint / tests / lib / linter / report-translator.js
1 /**
2 * @fileoverview Tests for createReportTranslator
3 * @author Teddy Katz
4 */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Requirements
9 //------------------------------------------------------------------------------
10
11 const assert = require("chai").assert;
12 const { SourceCode } = require("../../../lib/source-code");
13 const espree = require("espree");
14 const createReportTranslator = require("../../../lib/linter/report-translator");
15
16 //------------------------------------------------------------------------------
17 // Tests
18 //------------------------------------------------------------------------------
19
20 describe("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 respect ranges of empty insertions when merging fixes to one.", () => {
372 const reportDescriptor = {
373 node,
374 loc: location,
375 message,
376 *fix() {
377 yield { range: [4, 5], text: "cd" };
378 yield { range: [2, 2], text: "" };
379 yield { range: [7, 7], text: "" };
380 }
381 };
382
383 assert.deepStrictEqual(
384 translateReport(reportDescriptor),
385 {
386 ruleId: "foo-rule",
387 severity: 2,
388 message: "foo",
389 line: 2,
390 column: 1,
391 nodeType: "ExpressionStatement",
392 fix: {
393 range: [2, 7],
394 text: "o\ncdar"
395 }
396 }
397 );
398 });
399
400 it("should pass through fixes if only one is present", () => {
401 const reportDescriptor = {
402 node,
403 loc: location,
404 message,
405 fix: () => [{ range: [1, 2], text: "foo" }]
406 };
407
408 assert.deepStrictEqual(
409 translateReport(reportDescriptor),
410 {
411 ruleId: "foo-rule",
412 severity: 2,
413 message: "foo",
414 line: 2,
415 column: 1,
416 nodeType: "ExpressionStatement",
417 fix: {
418 range: [1, 2],
419 text: "foo"
420 }
421 }
422 );
423 });
424
425 it("should handle inserting BOM correctly.", () => {
426 const reportDescriptor = {
427 node,
428 loc: location,
429 message,
430 fix: () => [{ range: [0, 3], text: "\uFEFFfoo" }, { range: [4, 5], text: "x" }]
431 };
432
433 assert.deepStrictEqual(
434 translateReport(reportDescriptor),
435 {
436 ruleId: "foo-rule",
437 severity: 2,
438 message: "foo",
439 line: 2,
440 column: 1,
441 nodeType: "ExpressionStatement",
442 fix: {
443 range: [0, 5],
444 text: "\uFEFFfoo\nx"
445 }
446 }
447 );
448 });
449
450
451 it("should handle removing BOM correctly.", () => {
452 const sourceCode = createSourceCode("\uFEFFfoo\nbar");
453
454 node = sourceCode.ast.body[0];
455
456 const reportDescriptor = {
457 node,
458 message,
459 fix: () => [{ range: [-1, 3], text: "foo" }, { range: [4, 5], text: "x" }]
460 };
461
462 assert.deepStrictEqual(
463 createReportTranslator({ ruleId: "foo-rule", severity: 1, sourceCode })(reportDescriptor),
464 {
465 ruleId: "foo-rule",
466 severity: 1,
467 message: "foo",
468 line: 1,
469 column: 1,
470 endLine: 1,
471 endColumn: 4,
472 nodeType: "ExpressionStatement",
473 fix: {
474 range: [-1, 5],
475 text: "foo\nx"
476 }
477 }
478 );
479 });
480
481 it("should throw an assertion error if ranges are overlapped.", () => {
482 const reportDescriptor = {
483 node,
484 loc: location,
485 message,
486 fix: () => [{ range: [0, 3], text: "\uFEFFfoo" }, { range: [2, 5], text: "x" }]
487 };
488
489 assert.throws(
490 translateReport.bind(null, reportDescriptor),
491 "Fix objects must not be overlapped in a report."
492 );
493 });
494
495 it("should include a fix passed as the last argument when location is passed", () => {
496 assert.deepStrictEqual(
497 translateReport(
498 node,
499 { line: 42, column: 23 },
500 "my message {{1}}{{0}}",
501 ["!", "testing"],
502 () => ({ range: [1, 1], text: "" })
503 ),
504 {
505 ruleId: "foo-rule",
506 severity: 2,
507 message: "my message testing!",
508 line: 42,
509 column: 24,
510 nodeType: "ExpressionStatement",
511 fix: {
512 range: [1, 1],
513 text: ""
514 }
515 }
516 );
517 });
518 });
519
520 describe("suggestions", () => {
521 it("should support multiple suggestions.", () => {
522 const reportDescriptor = {
523 node,
524 loc: location,
525 message,
526 suggest: [{
527 desc: "A first suggestion for the issue",
528 fix: () => [{ range: [1, 2], text: "foo" }]
529 }, {
530 desc: "A different suggestion for the issue",
531 fix: () => [{ range: [1, 3], text: "foobar" }]
532 }]
533 };
534
535 assert.deepStrictEqual(
536 translateReport(reportDescriptor),
537 {
538 ruleId: "foo-rule",
539 severity: 2,
540 message: "foo",
541 line: 2,
542 column: 1,
543 nodeType: "ExpressionStatement",
544 suggestions: [{
545 desc: "A first suggestion for the issue",
546 fix: { range: [1, 2], text: "foo" }
547 }, {
548 desc: "A different suggestion for the issue",
549 fix: { range: [1, 3], text: "foobar" }
550 }]
551 }
552 );
553 });
554
555 it("should merge suggestion fixes to one if 'fix' function returns an array of fixes.", () => {
556 const reportDescriptor = {
557 node,
558 loc: location,
559 message,
560 suggest: [{
561 desc: "A suggestion for the issue",
562 fix: () => [{ range: [1, 2], text: "foo" }, { range: [4, 5], text: "bar" }]
563 }]
564 };
565
566 assert.deepStrictEqual(
567 translateReport(reportDescriptor),
568 {
569 ruleId: "foo-rule",
570 severity: 2,
571 message: "foo",
572 line: 2,
573 column: 1,
574 nodeType: "ExpressionStatement",
575 suggestions: [{
576 desc: "A suggestion for the issue",
577 fix: {
578 range: [1, 5],
579 text: "fooo\nbar"
580 }
581 }]
582 }
583 );
584 });
585
586 it("should remove the whole suggestion if 'fix' function returned `null`.", () => {
587 const reportDescriptor = {
588 node,
589 loc: location,
590 message,
591 suggest: [{
592 desc: "A suggestion for the issue",
593 fix: () => null
594 }]
595 };
596
597 assert.deepStrictEqual(
598 translateReport(reportDescriptor),
599 {
600 ruleId: "foo-rule",
601 severity: 2,
602 message: "foo",
603 line: 2,
604 column: 1,
605 nodeType: "ExpressionStatement"
606 }
607 );
608 });
609
610 it("should remove the whole suggestion if 'fix' function returned an empty array.", () => {
611 const reportDescriptor = {
612 node,
613 loc: location,
614 message,
615 suggest: [{
616 desc: "A suggestion for the issue",
617 fix: () => []
618 }]
619 };
620
621 assert.deepStrictEqual(
622 translateReport(reportDescriptor),
623 {
624 ruleId: "foo-rule",
625 severity: 2,
626 message: "foo",
627 line: 2,
628 column: 1,
629 nodeType: "ExpressionStatement"
630 }
631 );
632 });
633
634 it("should remove the whole suggestion if 'fix' function returned an empty sequence.", () => {
635 const reportDescriptor = {
636 node,
637 loc: location,
638 message,
639 suggest: [{
640 desc: "A suggestion for the issue",
641 *fix() {}
642 }]
643 };
644
645 assert.deepStrictEqual(
646 translateReport(reportDescriptor),
647 {
648 ruleId: "foo-rule",
649 severity: 2,
650 message: "foo",
651 line: 2,
652 column: 1,
653 nodeType: "ExpressionStatement"
654 }
655 );
656 });
657
658 // This isn't officially supported, but autofix works the same way
659 it("should remove the whole suggestion if 'fix' function didn't return anything.", () => {
660 const reportDescriptor = {
661 node,
662 loc: location,
663 message,
664 suggest: [{
665 desc: "A suggestion for the issue",
666 fix() {}
667 }]
668 };
669
670 assert.deepStrictEqual(
671 translateReport(reportDescriptor),
672 {
673 ruleId: "foo-rule",
674 severity: 2,
675 message: "foo",
676 line: 2,
677 column: 1,
678 nodeType: "ExpressionStatement"
679 }
680 );
681 });
682
683 it("should keep suggestion before a removed suggestion.", () => {
684 const reportDescriptor = {
685 node,
686 loc: location,
687 message,
688 suggest: [{
689 desc: "Suggestion with a fix",
690 fix: () => ({ range: [1, 2], text: "foo" })
691 }, {
692 desc: "Suggestion without a fix",
693 fix: () => null
694 }]
695 };
696
697 assert.deepStrictEqual(
698 translateReport(reportDescriptor),
699 {
700 ruleId: "foo-rule",
701 severity: 2,
702 message: "foo",
703 line: 2,
704 column: 1,
705 nodeType: "ExpressionStatement",
706 suggestions: [{
707 desc: "Suggestion with a fix",
708 fix: { range: [1, 2], text: "foo" }
709 }]
710 }
711 );
712 });
713
714 it("should keep suggestion after a removed suggestion.", () => {
715 const reportDescriptor = {
716 node,
717 loc: location,
718 message,
719 suggest: [{
720 desc: "Suggestion without a fix",
721 fix: () => null
722 }, {
723 desc: "Suggestion with a fix",
724 fix: () => ({ range: [1, 2], text: "foo" })
725 }]
726 };
727
728 assert.deepStrictEqual(
729 translateReport(reportDescriptor),
730 {
731 ruleId: "foo-rule",
732 severity: 2,
733 message: "foo",
734 line: 2,
735 column: 1,
736 nodeType: "ExpressionStatement",
737 suggestions: [{
738 desc: "Suggestion with a fix",
739 fix: { range: [1, 2], text: "foo" }
740 }]
741 }
742 );
743 });
744
745 it("should remove multiple suggestions that didn't provide a fix and keep those that did.", () => {
746 const reportDescriptor = {
747 node,
748 loc: location,
749 message,
750 suggest: [{
751 desc: "Keep #1",
752 fix: () => ({ range: [1, 2], text: "foo" })
753 }, {
754 desc: "Remove #1",
755 fix() {
756 return null;
757 }
758 }, {
759 desc: "Keep #2",
760 fix: () => ({ range: [1, 2], text: "bar" })
761 }, {
762 desc: "Remove #2",
763 fix() {
764 return [];
765 }
766 }, {
767 desc: "Keep #3",
768 fix: () => ({ range: [1, 2], text: "baz" })
769 }, {
770 desc: "Remove #3",
771 *fix() {}
772 }, {
773 desc: "Keep #4",
774 fix: () => ({ range: [1, 2], text: "quux" })
775 }]
776 };
777
778 assert.deepStrictEqual(
779 translateReport(reportDescriptor),
780 {
781 ruleId: "foo-rule",
782 severity: 2,
783 message: "foo",
784 line: 2,
785 column: 1,
786 nodeType: "ExpressionStatement",
787 suggestions: [{
788 desc: "Keep #1",
789 fix: { range: [1, 2], text: "foo" }
790 }, {
791 desc: "Keep #2",
792 fix: { range: [1, 2], text: "bar" }
793 }, {
794 desc: "Keep #3",
795 fix: { range: [1, 2], text: "baz" }
796 }, {
797 desc: "Keep #4",
798 fix: { range: [1, 2], text: "quux" }
799 }]
800 }
801 );
802 });
803 });
804
805 describe("message interpolation", () => {
806 it("should correctly parse a message when being passed all options in an old-style report", () => {
807 assert.deepStrictEqual(
808 translateReport(node, node.loc.end, "hello {{dynamic}}", { dynamic: node.type }),
809 {
810 severity: 2,
811 ruleId: "foo-rule",
812 message: "hello ExpressionStatement",
813 nodeType: "ExpressionStatement",
814 line: 1,
815 column: 4
816 }
817 );
818 });
819
820 it("should correctly parse a message when being passed all options in a new-style report", () => {
821 assert.deepStrictEqual(
822 translateReport({ node, loc: node.loc.end, message: "hello {{dynamic}}", data: { dynamic: node.type } }),
823 {
824 severity: 2,
825 ruleId: "foo-rule",
826 message: "hello ExpressionStatement",
827 nodeType: "ExpressionStatement",
828 line: 1,
829 column: 4
830 }
831 );
832 });
833
834 it("should correctly parse a message with object keys as numbers", () => {
835 assert.strictEqual(
836 translateReport(node, "my message {{name}}{{0}}", { 0: "!", name: "testing" }).message,
837 "my message testing!"
838 );
839 });
840
841 it("should correctly parse a message with array", () => {
842 assert.strictEqual(
843 translateReport(node, "my message {{1}}{{0}}", ["!", "testing"]).message,
844 "my message testing!"
845 );
846 });
847
848 it("should allow template parameter with inner whitespace", () => {
849 assert.strictEqual(
850 translateReport(node, "message {{parameter name}}", { "parameter name": "yay!" }).message,
851 "message yay!"
852 );
853 });
854
855 it("should allow template parameter with non-identifier characters", () => {
856 assert.strictEqual(
857 translateReport(node, "message {{parameter-name}}", { "parameter-name": "yay!" }).message,
858 "message yay!"
859 );
860 });
861
862 it("should allow template parameter wrapped in braces", () => {
863 assert.strictEqual(
864 translateReport(node, "message {{{param}}}", { param: "yay!" }).message,
865 "message {yay!}"
866 );
867 });
868
869 it("should ignore template parameter with no specified value", () => {
870 assert.strictEqual(
871 translateReport(node, "message {{parameter}}", {}).message,
872 "message {{parameter}}"
873 );
874 });
875
876 it("should handle leading whitespace in template parameter", () => {
877 assert.strictEqual(
878 translateReport({ node, message: "message {{ parameter}}", data: { parameter: "yay!" } }).message,
879 "message yay!"
880 );
881 });
882
883 it("should handle trailing whitespace in template parameter", () => {
884 assert.strictEqual(
885 translateReport({ node, message: "message {{parameter }}", data: { parameter: "yay!" } }).message,
886 "message yay!"
887 );
888 });
889
890 it("should still allow inner whitespace as well as leading/trailing", () => {
891 assert.strictEqual(
892 translateReport(node, "message {{ parameter name }}", { "parameter name": "yay!" }).message,
893 "message yay!"
894 );
895 });
896
897 it("should still allow non-identifier characters as well as leading/trailing whitespace", () => {
898 assert.strictEqual(
899 translateReport(node, "message {{ parameter-name }}", { "parameter-name": "yay!" }).message,
900 "message yay!"
901 );
902 });
903 });
904
905 describe("location inference", () => {
906 it("should use the provided location when given in an old-style call", () => {
907 assert.deepStrictEqual(
908 translateReport(node, { line: 42, column: 13 }, "hello world"),
909 {
910 severity: 2,
911 ruleId: "foo-rule",
912 message: "hello world",
913 nodeType: "ExpressionStatement",
914 line: 42,
915 column: 14
916 }
917 );
918 });
919
920 it("should use the provided location when given in an new-style call", () => {
921 assert.deepStrictEqual(
922 translateReport({ node, loc: { line: 42, column: 13 }, message: "hello world" }),
923 {
924 severity: 2,
925 ruleId: "foo-rule",
926 message: "hello world",
927 nodeType: "ExpressionStatement",
928 line: 42,
929 column: 14
930 }
931 );
932 });
933
934 it("should extract the start and end locations from a node if no location is provided", () => {
935 assert.deepStrictEqual(
936 translateReport(node, "hello world"),
937 {
938 severity: 2,
939 ruleId: "foo-rule",
940 message: "hello world",
941 nodeType: "ExpressionStatement",
942 line: 1,
943 column: 1,
944 endLine: 1,
945 endColumn: 4
946 }
947 );
948 });
949
950 it("should have 'endLine' and 'endColumn' when 'loc' property has 'end' property.", () => {
951 assert.deepStrictEqual(
952 translateReport({ loc: node.loc, message: "hello world" }),
953 {
954 severity: 2,
955 ruleId: "foo-rule",
956 message: "hello world",
957 nodeType: null,
958 line: 1,
959 column: 1,
960 endLine: 1,
961 endColumn: 4
962 }
963 );
964 });
965
966 it("should not have 'endLine' and 'endColumn' when 'loc' property does not have 'end' property.", () => {
967 assert.deepStrictEqual(
968 translateReport({ loc: node.loc.start, message: "hello world" }),
969 {
970 severity: 2,
971 ruleId: "foo-rule",
972 message: "hello world",
973 nodeType: null,
974 line: 1,
975 column: 1
976 }
977 );
978 });
979
980 it("should infer an 'endLine' and 'endColumn' property when using the object-based context.report API", () => {
981 assert.deepStrictEqual(
982 translateReport({ node, message: "hello world" }),
983 {
984 severity: 2,
985 ruleId: "foo-rule",
986 message: "hello world",
987 nodeType: "ExpressionStatement",
988 line: 1,
989 column: 1,
990 endLine: 1,
991 endColumn: 4
992 }
993 );
994 });
995 });
996
997 describe("converting old-style calls", () => {
998 it("should include a fix passed as the last argument when location is not passed", () => {
999 assert.deepStrictEqual(
1000 translateReport(node, "my message {{1}}{{0}}", ["!", "testing"], () => ({ range: [1, 1], text: "" })),
1001 {
1002 severity: 2,
1003 ruleId: "foo-rule",
1004 message: "my message testing!",
1005 nodeType: "ExpressionStatement",
1006 line: 1,
1007 column: 1,
1008 endLine: 1,
1009 endColumn: 4,
1010 fix: { range: [1, 1], text: "" }
1011 }
1012 );
1013 });
1014 });
1015
1016 describe("validation", () => {
1017
1018 it("should throw an error if node is not an object", () => {
1019 assert.throws(
1020 () => translateReport("not a node", "hello world"),
1021 "Node must be an object"
1022 );
1023 });
1024
1025
1026 it("should not throw an error if location is provided and node is not in an old-style call", () => {
1027 assert.deepStrictEqual(
1028 translateReport(null, { line: 1, column: 1 }, "hello world"),
1029 {
1030 severity: 2,
1031 ruleId: "foo-rule",
1032 message: "hello world",
1033 nodeType: null,
1034 line: 1,
1035 column: 2
1036 }
1037 );
1038 });
1039
1040 it("should not throw an error if location is provided and node is not in a new-style call", () => {
1041 assert.deepStrictEqual(
1042 translateReport({ loc: { line: 1, column: 1 }, message: "hello world" }),
1043 {
1044 severity: 2,
1045 ruleId: "foo-rule",
1046 message: "hello world",
1047 nodeType: null,
1048 line: 1,
1049 column: 2
1050 }
1051 );
1052 });
1053
1054 it("should throw an error if neither node nor location is provided", () => {
1055 assert.throws(
1056 () => translateReport(null, "hello world"),
1057 "Node must be provided when reporting error if location is not provided"
1058 );
1059 });
1060
1061 it("should throw an error if fix range is invalid", () => {
1062 assert.throws(
1063 () => translateReport({ node, messageId: "testMessage", fix: () => ({ text: "foo" }) }),
1064 "Fix has invalid range"
1065 );
1066
1067 for (const badRange of [[0], [0, null], [null, 0], [void 0, 1], [0, void 0], [void 0, void 0], []]) {
1068 assert.throws(
1069 // eslint-disable-next-line no-loop-func -- Using arrow functions
1070 () => translateReport(
1071 { node, messageId: "testMessage", fix: () => ({ range: badRange, text: "foo" }) }
1072 ),
1073 "Fix has invalid range"
1074 );
1075
1076 assert.throws(
1077 // eslint-disable-next-line no-loop-func -- Using arrow functions
1078 () => translateReport(
1079 {
1080 node,
1081 messageId: "testMessage",
1082 fix: () => [
1083 { range: [0, 0], text: "foo" },
1084 { range: badRange, text: "bar" },
1085 { range: [1, 1], text: "baz" }
1086 ]
1087 }
1088 ),
1089 "Fix has invalid range"
1090 );
1091 }
1092 });
1093 });
1094 });