]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/tests/lib/linter/report-translator.js
2 * @fileoverview Tests for createReportTranslator
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
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");
16 //------------------------------------------------------------------------------
18 //------------------------------------------------------------------------------
20 describe("createReportTranslator", () => {
23 * Creates a SourceCode instance out of JavaScript text
24 * @param {string} text Source text
25 * @returns {SourceCode} A SourceCode instance for that text
27 function createSourceCode(text
) {
28 return new SourceCode(
31 text
.replace(/^\uFEFF/u, ""),
43 let node
, location
, message
, translateReport
, suggestion1
, suggestion2
;
46 const sourceCode
= createSourceCode("foo\nbar");
48 node
= sourceCode
.ast
.body
[0];
49 location
= sourceCode
.ast
.body
[1].loc
.start
;
51 suggestion1
= "First suggestion";
52 suggestion2
= "Second suggestion {{interpolated}}";
53 translateReport
= createReportTranslator({
65 describe("old-style call with location", () => {
66 it("should extract the location correctly", () => {
67 assert
.deepStrictEqual(
68 translateReport(node
, location
, message
, {}),
75 nodeType
: "ExpressionStatement"
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
, {}),
93 nodeType
: "ExpressionStatement"
99 describe("new-style call with all options", () => {
100 it("should include the new-style options in the report", () => {
101 const reportDescriptor
= {
105 fix
: () => ({ range
: [1, 2], text
: "foo" }),
107 desc
: "suggestion 1",
108 fix
: () => ({ range
: [2, 3], text
: "s1" })
110 desc
: "suggestion 2",
111 fix
: () => ({ range
: [3, 4], text
: "s2" })
115 assert
.deepStrictEqual(
116 translateReport(reportDescriptor
),
123 nodeType
: "ExpressionStatement",
129 desc
: "suggestion 1",
130 fix
: { range
: [2, 3], text
: "s1" }
132 desc
: "suggestion 2",
133 fix
: { range
: [3, 4], text
: "s2" }
139 it("should translate the messageId into a message", () => {
140 const reportDescriptor
= {
143 messageId
: "testMessage",
144 fix
: () => ({ range
: [1, 2], text
: "foo" })
147 assert
.deepStrictEqual(
148 translateReport(reportDescriptor
),
153 messageId
: "testMessage",
156 nodeType
: "ExpressionStatement",
165 it("should throw when both messageId and message are provided", () => {
166 const reportDescriptor
= {
169 messageId
: "testMessage",
171 fix
: () => ({ range
: [1, 2], text
: "foo" })
175 () => translateReport(reportDescriptor
),
177 "context.report() called with a message and a messageId. Please only pass one."
181 it("should throw when an invalid messageId is provided", () => {
182 const reportDescriptor
= {
185 messageId
: "thisIsNotASpecifiedMessageId",
186 fix
: () => ({ range
: [1, 2], text
: "foo" })
190 () => translateReport(reportDescriptor
),
192 /^context\.report\(\) called with a messageId of '[^']+' which is not present in the 'messages' config:/u
196 it("should throw when no message is provided", () => {
197 const reportDescriptor
= { node
};
200 () => translateReport(reportDescriptor
),
202 "Missing `message` property in report() call; add a message that describes the linting problem."
206 it("should support messageIds for suggestions and output resulting descriptions", () => {
207 const reportDescriptor
= {
212 messageId
: "suggestion1",
213 fix
: () => ({ range
: [2, 3], text
: "s1" })
215 messageId
: "suggestion2",
216 data
: { interpolated
: "'interpolated value'" },
217 fix
: () => ({ range
: [3, 4], text
: "s2" })
221 assert
.deepStrictEqual(
222 translateReport(reportDescriptor
),
229 nodeType
: "ExpressionStatement",
231 messageId
: "suggestion1",
232 desc
: "First suggestion",
233 fix
: { range
: [2, 3], text
: "s1" }
235 messageId
: "suggestion2",
236 data
: { interpolated
: "'interpolated value'" },
237 desc
: "Second suggestion 'interpolated value'",
238 fix
: { range
: [3, 4], text
: "s2" }
244 it("should throw when a suggestion defines both a desc and messageId", () => {
245 const reportDescriptor
= {
250 desc
: "The description",
251 messageId
: "suggestion1",
252 fix
: () => ({ range
: [2, 3], text
: "s1" })
257 () => translateReport(reportDescriptor
),
259 "context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one."
263 it("should throw when a suggestion uses an invalid messageId", () => {
264 const reportDescriptor
= {
269 messageId
: "noMatchingMessage",
270 fix
: () => ({ range
: [2, 3], text
: "s1" })
275 () => translateReport(reportDescriptor
),
277 /^context\.report\(\) called with a suggest option with a messageId '[^']+' which is not present in the 'messages' config:/u
281 it("should throw when a suggestion does not provide either a desc or messageId", () => {
282 const reportDescriptor
= {
287 fix
: () => ({ range
: [2, 3], text
: "s1" })
292 () => translateReport(reportDescriptor
),
294 "context.report() called with a suggest option that doesn't have either a `desc` or `messageId`"
298 it("should throw when a suggestion does not provide a fix function", () => {
299 const reportDescriptor
= {
304 desc
: "The description",
310 () => translateReport(reportDescriptor
),
312 /^context\.report\(\) called with a suggest option without a fix function. See:/u
317 describe("combining autofixes", () => {
318 it("should merge fixes to one if 'fix' function returns an array of fixes.", () => {
319 const reportDescriptor
= {
323 fix
: () => [{ range
: [1, 2], text
: "foo" }, { range
: [4, 5], text
: "bar" }]
326 assert
.deepStrictEqual(
327 translateReport(reportDescriptor
),
334 nodeType
: "ExpressionStatement",
343 it("should merge fixes to one if 'fix' function returns an iterator of fixes.", () => {
344 const reportDescriptor
= {
349 yield { range
: [1, 2], text
: "foo" };
350 yield { range
: [4, 5], text
: "bar" };
354 assert
.deepStrictEqual(
355 translateReport(reportDescriptor
),
362 nodeType
: "ExpressionStatement",
371 it("should respect ranges of empty insertions when merging fixes to one.", () => {
372 const reportDescriptor
= {
377 yield { range
: [4, 5], text
: "cd" };
378 yield { range
: [2, 2], text
: "" };
379 yield { range
: [7, 7], text
: "" };
383 assert
.deepStrictEqual(
384 translateReport(reportDescriptor
),
391 nodeType
: "ExpressionStatement",
400 it("should pass through fixes if only one is present", () => {
401 const reportDescriptor
= {
405 fix
: () => [{ range
: [1, 2], text
: "foo" }]
408 assert
.deepStrictEqual(
409 translateReport(reportDescriptor
),
416 nodeType
: "ExpressionStatement",
425 it("should handle inserting BOM correctly.", () => {
426 const reportDescriptor
= {
430 fix
: () => [{ range
: [0, 3], text
: "\uFEFFfoo" }, { range
: [4, 5], text
: "x" }]
433 assert
.deepStrictEqual(
434 translateReport(reportDescriptor
),
441 nodeType
: "ExpressionStatement",
451 it("should handle removing BOM correctly.", () => {
452 const sourceCode
= createSourceCode("\uFEFFfoo\nbar");
454 node
= sourceCode
.ast
.body
[0];
456 const reportDescriptor
= {
459 fix
: () => [{ range
: [-1, 3], text
: "foo" }, { range
: [4, 5], text
: "x" }]
462 assert
.deepStrictEqual(
463 createReportTranslator({ ruleId
: "foo-rule", severity
: 1, sourceCode
})(reportDescriptor
),
472 nodeType
: "ExpressionStatement",
481 it("should throw an assertion error if ranges are overlapped.", () => {
482 const reportDescriptor
= {
486 fix
: () => [{ range
: [0, 3], text
: "\uFEFFfoo" }, { range
: [2, 5], text
: "x" }]
490 translateReport
.bind(null, reportDescriptor
),
491 "Fix objects must not be overlapped in a report."
495 it("should include a fix passed as the last argument when location is passed", () => {
496 assert
.deepStrictEqual(
499 { line
: 42, column
: 23 },
500 "my message {{1}}{{0}}",
502 () => ({ range
: [1, 1], text
: "" })
507 message
: "my message testing!",
510 nodeType
: "ExpressionStatement",
520 describe("suggestions", () => {
521 it("should support multiple suggestions.", () => {
522 const reportDescriptor
= {
527 desc
: "A first suggestion for the issue",
528 fix
: () => [{ range
: [1, 2], text
: "foo" }]
530 desc
: "A different suggestion for the issue",
531 fix
: () => [{ range
: [1, 3], text
: "foobar" }]
535 assert
.deepStrictEqual(
536 translateReport(reportDescriptor
),
543 nodeType
: "ExpressionStatement",
545 desc
: "A first suggestion for the issue",
546 fix
: { range
: [1, 2], text
: "foo" }
548 desc
: "A different suggestion for the issue",
549 fix
: { range
: [1, 3], text
: "foobar" }
555 it("should merge suggestion fixes to one if 'fix' function returns an array of fixes.", () => {
556 const reportDescriptor
= {
561 desc
: "A suggestion for the issue",
562 fix
: () => [{ range
: [1, 2], text
: "foo" }, { range
: [4, 5], text
: "bar" }]
566 assert
.deepStrictEqual(
567 translateReport(reportDescriptor
),
574 nodeType
: "ExpressionStatement",
576 desc
: "A suggestion for the issue",
586 it("should remove the whole suggestion if 'fix' function returned `null`.", () => {
587 const reportDescriptor
= {
592 desc
: "A suggestion for the issue",
597 assert
.deepStrictEqual(
598 translateReport(reportDescriptor
),
605 nodeType
: "ExpressionStatement"
610 it("should remove the whole suggestion if 'fix' function returned an empty array.", () => {
611 const reportDescriptor
= {
616 desc
: "A suggestion for the issue",
621 assert
.deepStrictEqual(
622 translateReport(reportDescriptor
),
629 nodeType
: "ExpressionStatement"
634 it("should remove the whole suggestion if 'fix' function returned an empty sequence.", () => {
635 const reportDescriptor
= {
640 desc
: "A suggestion for the issue",
645 assert
.deepStrictEqual(
646 translateReport(reportDescriptor
),
653 nodeType
: "ExpressionStatement"
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
= {
665 desc
: "A suggestion for the issue",
670 assert
.deepStrictEqual(
671 translateReport(reportDescriptor
),
678 nodeType
: "ExpressionStatement"
683 it("should keep suggestion before a removed suggestion.", () => {
684 const reportDescriptor
= {
689 desc
: "Suggestion with a fix",
690 fix
: () => ({ range
: [1, 2], text
: "foo" })
692 desc
: "Suggestion without a fix",
697 assert
.deepStrictEqual(
698 translateReport(reportDescriptor
),
705 nodeType
: "ExpressionStatement",
707 desc
: "Suggestion with a fix",
708 fix
: { range
: [1, 2], text
: "foo" }
714 it("should keep suggestion after a removed suggestion.", () => {
715 const reportDescriptor
= {
720 desc
: "Suggestion without a fix",
723 desc
: "Suggestion with a fix",
724 fix
: () => ({ range
: [1, 2], text
: "foo" })
728 assert
.deepStrictEqual(
729 translateReport(reportDescriptor
),
736 nodeType
: "ExpressionStatement",
738 desc
: "Suggestion with a fix",
739 fix
: { range
: [1, 2], text
: "foo" }
745 it("should remove multiple suggestions that didn't provide a fix and keep those that did.", () => {
746 const reportDescriptor
= {
752 fix
: () => ({ range
: [1, 2], text
: "foo" })
760 fix
: () => ({ range
: [1, 2], text
: "bar" })
768 fix
: () => ({ range
: [1, 2], text
: "baz" })
774 fix
: () => ({ range
: [1, 2], text
: "quux" })
778 assert
.deepStrictEqual(
779 translateReport(reportDescriptor
),
786 nodeType
: "ExpressionStatement",
789 fix
: { range
: [1, 2], text
: "foo" }
792 fix
: { range
: [1, 2], text
: "bar" }
795 fix
: { range
: [1, 2], text
: "baz" }
798 fix
: { range
: [1, 2], text
: "quux" }
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
}),
812 message
: "hello ExpressionStatement",
813 nodeType
: "ExpressionStatement",
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
} }),
826 message
: "hello ExpressionStatement",
827 nodeType
: "ExpressionStatement",
834 it("should correctly parse a message with object keys as numbers", () => {
836 translateReport(node
, "my message {{name}}{{0}}", { 0: "!", name
: "testing" }).message
,
837 "my message testing!"
841 it("should correctly parse a message with array", () => {
843 translateReport(node
, "my message {{1}}{{0}}", ["!", "testing"]).message
,
844 "my message testing!"
848 it("should allow template parameter with inner whitespace", () => {
850 translateReport(node
, "message {{parameter name}}", { "parameter name": "yay!" }).message
,
855 it("should allow template parameter with non-identifier characters", () => {
857 translateReport(node
, "message {{parameter-name}}", { "parameter-name": "yay!" }).message
,
862 it("should allow template parameter wrapped in braces", () => {
864 translateReport(node
, "message {{{param}}}", { param
: "yay!" }).message
,
869 it("should ignore template parameter with no specified value", () => {
871 translateReport(node
, "message {{parameter}}", {}).message
,
872 "message {{parameter}}"
876 it("should handle leading whitespace in template parameter", () => {
878 translateReport({ node
, message
: "message {{ parameter}}", data
: { parameter
: "yay!" } }).message
,
883 it("should handle trailing whitespace in template parameter", () => {
885 translateReport({ node
, message
: "message {{parameter }}", data
: { parameter
: "yay!" } }).message
,
890 it("should still allow inner whitespace as well as leading/trailing", () => {
892 translateReport(node
, "message {{ parameter name }}", { "parameter name": "yay!" }).message
,
897 it("should still allow non-identifier characters as well as leading/trailing whitespace", () => {
899 translateReport(node
, "message {{ parameter-name }}", { "parameter-name": "yay!" }).message
,
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"),
912 message
: "hello world",
913 nodeType
: "ExpressionStatement",
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" }),
926 message
: "hello world",
927 nodeType
: "ExpressionStatement",
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"),
940 message
: "hello world",
941 nodeType
: "ExpressionStatement",
950 it("should have 'endLine' and 'endColumn' when 'loc' property has 'end' property.", () => {
951 assert
.deepStrictEqual(
952 translateReport({ loc
: node
.loc
, message
: "hello world" }),
956 message
: "hello world",
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" }),
972 message
: "hello world",
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" }),
986 message
: "hello world",
987 nodeType
: "ExpressionStatement",
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
: "" })),
1004 message
: "my message testing!",
1005 nodeType
: "ExpressionStatement",
1010 fix
: { range
: [1, 1], text
: "" }
1016 describe("validation", () => {
1018 it("should throw an error if node is not an object", () => {
1020 () => translateReport("not a node", "hello world"),
1021 "Node must be an object"
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"),
1032 message
: "hello world",
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" }),
1046 message
: "hello world",
1054 it("should throw an error if neither node nor location is provided", () => {
1056 () => translateReport(null, "hello world"),
1057 "Node must be provided when reporting error if location is not provided"
1061 it("should throw an error if fix range is invalid", () => {
1063 () => translateReport({ node
, messageId
: "testMessage", fix
: () => ({ text
: "foo" }) }),
1064 "Fix has invalid range"
1067 for (const badRange
of [[0], [0, null], [null, 0], [void 0, 1], [0, void 0], [void 0, void 0], []]) {
1069 // eslint-disable-next-line no-loop-func -- Using arrow functions
1070 () => translateReport(
1071 { node
, messageId
: "testMessage", fix
: () => ({ range
: badRange
, text
: "foo" }) }
1073 "Fix has invalid range"
1077 // eslint-disable-next-line no-loop-func -- Using arrow functions
1078 () => translateReport(
1081 messageId
: "testMessage",
1083 { range
: [0, 0], text
: "foo" },
1084 { range
: badRange
, text
: "bar" },
1085 { range
: [1, 1], text
: "baz" }
1089 "Fix has invalid range"