]>
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 pass through fixes if only one is present", () => {
372 const reportDescriptor
= {
376 fix
: () => [{ range
: [1, 2], text
: "foo" }]
379 assert
.deepStrictEqual(
380 translateReport(reportDescriptor
),
387 nodeType
: "ExpressionStatement",
396 it("should handle inserting BOM correctly.", () => {
397 const reportDescriptor
= {
401 fix
: () => [{ range
: [0, 3], text
: "\uFEFFfoo" }, { range
: [4, 5], text
: "x" }]
404 assert
.deepStrictEqual(
405 translateReport(reportDescriptor
),
412 nodeType
: "ExpressionStatement",
422 it("should handle removing BOM correctly.", () => {
423 const sourceCode
= createSourceCode("\uFEFFfoo\nbar");
425 node
= sourceCode
.ast
.body
[0];
427 const reportDescriptor
= {
430 fix
: () => [{ range
: [-1, 3], text
: "foo" }, { range
: [4, 5], text
: "x" }]
433 assert
.deepStrictEqual(
434 createReportTranslator({ ruleId
: "foo-rule", severity
: 1, sourceCode
})(reportDescriptor
),
443 nodeType
: "ExpressionStatement",
452 it("should throw an assertion error if ranges are overlapped.", () => {
453 const reportDescriptor
= {
457 fix
: () => [{ range
: [0, 3], text
: "\uFEFFfoo" }, { range
: [2, 5], text
: "x" }]
461 translateReport
.bind(null, reportDescriptor
),
462 "Fix objects must not be overlapped in a report."
466 it("should include a fix passed as the last argument when location is passed", () => {
467 assert
.deepStrictEqual(
470 { line
: 42, column
: 23 },
471 "my message {{1}}{{0}}",
473 () => ({ range
: [1, 1], text
: "" })
478 message
: "my message testing!",
481 nodeType
: "ExpressionStatement",
491 describe("suggestions", () => {
492 it("should support multiple suggestions.", () => {
493 const reportDescriptor
= {
498 desc
: "A first suggestion for the issue",
499 fix
: () => [{ range
: [1, 2], text
: "foo" }]
501 desc
: "A different suggestion for the issue",
502 fix
: () => [{ range
: [1, 3], text
: "foobar" }]
506 assert
.deepStrictEqual(
507 translateReport(reportDescriptor
),
514 nodeType
: "ExpressionStatement",
516 desc
: "A first suggestion for the issue",
517 fix
: { range
: [1, 2], text
: "foo" }
519 desc
: "A different suggestion for the issue",
520 fix
: { range
: [1, 3], text
: "foobar" }
526 it("should merge suggestion fixes to one if 'fix' function returns an array of fixes.", () => {
527 const reportDescriptor
= {
532 desc
: "A suggestion for the issue",
533 fix
: () => [{ range
: [1, 2], text
: "foo" }, { range
: [4, 5], text
: "bar" }]
537 assert
.deepStrictEqual(
538 translateReport(reportDescriptor
),
545 nodeType
: "ExpressionStatement",
547 desc
: "A suggestion for the issue",
557 it("should remove the whole suggestion if 'fix' function returned `null`.", () => {
558 const reportDescriptor
= {
563 desc
: "A suggestion for the issue",
568 assert
.deepStrictEqual(
569 translateReport(reportDescriptor
),
576 nodeType
: "ExpressionStatement"
581 it("should remove the whole suggestion if 'fix' function returned an empty array.", () => {
582 const reportDescriptor
= {
587 desc
: "A suggestion for the issue",
592 assert
.deepStrictEqual(
593 translateReport(reportDescriptor
),
600 nodeType
: "ExpressionStatement"
605 it("should remove the whole suggestion if 'fix' function returned an empty sequence.", () => {
606 const reportDescriptor
= {
611 desc
: "A suggestion for the issue",
616 assert
.deepStrictEqual(
617 translateReport(reportDescriptor
),
624 nodeType
: "ExpressionStatement"
629 // This isn't officially supported, but autofix works the same way
630 it("should remove the whole suggestion if 'fix' function didn't return anything.", () => {
631 const reportDescriptor
= {
636 desc
: "A suggestion for the issue",
641 assert
.deepStrictEqual(
642 translateReport(reportDescriptor
),
649 nodeType
: "ExpressionStatement"
654 it("should keep suggestion before a removed suggestion.", () => {
655 const reportDescriptor
= {
660 desc
: "Suggestion with a fix",
661 fix
: () => ({ range
: [1, 2], text
: "foo" })
663 desc
: "Suggestion without a fix",
668 assert
.deepStrictEqual(
669 translateReport(reportDescriptor
),
676 nodeType
: "ExpressionStatement",
678 desc
: "Suggestion with a fix",
679 fix
: { range
: [1, 2], text
: "foo" }
685 it("should keep suggestion after a removed suggestion.", () => {
686 const reportDescriptor
= {
691 desc
: "Suggestion without a fix",
694 desc
: "Suggestion with a fix",
695 fix
: () => ({ range
: [1, 2], text
: "foo" })
699 assert
.deepStrictEqual(
700 translateReport(reportDescriptor
),
707 nodeType
: "ExpressionStatement",
709 desc
: "Suggestion with a fix",
710 fix
: { range
: [1, 2], text
: "foo" }
716 it("should remove multiple suggestions that didn't provide a fix and keep those that did.", () => {
717 const reportDescriptor
= {
723 fix
: () => ({ range
: [1, 2], text
: "foo" })
731 fix
: () => ({ range
: [1, 2], text
: "bar" })
739 fix
: () => ({ range
: [1, 2], text
: "baz" })
745 fix
: () => ({ range
: [1, 2], text
: "quux" })
749 assert
.deepStrictEqual(
750 translateReport(reportDescriptor
),
757 nodeType
: "ExpressionStatement",
760 fix
: { range
: [1, 2], text
: "foo" }
763 fix
: { range
: [1, 2], text
: "bar" }
766 fix
: { range
: [1, 2], text
: "baz" }
769 fix
: { range
: [1, 2], text
: "quux" }
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
}),
783 message
: "hello ExpressionStatement",
784 nodeType
: "ExpressionStatement",
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
} }),
797 message
: "hello ExpressionStatement",
798 nodeType
: "ExpressionStatement",
805 it("should correctly parse a message with object keys as numbers", () => {
807 translateReport(node
, "my message {{name}}{{0}}", { 0: "!", name
: "testing" }).message
,
808 "my message testing!"
812 it("should correctly parse a message with array", () => {
814 translateReport(node
, "my message {{1}}{{0}}", ["!", "testing"]).message
,
815 "my message testing!"
819 it("should allow template parameter with inner whitespace", () => {
821 translateReport(node
, "message {{parameter name}}", { "parameter name": "yay!" }).message
,
826 it("should allow template parameter with non-identifier characters", () => {
828 translateReport(node
, "message {{parameter-name}}", { "parameter-name": "yay!" }).message
,
833 it("should allow template parameter wrapped in braces", () => {
835 translateReport(node
, "message {{{param}}}", { param
: "yay!" }).message
,
840 it("should ignore template parameter with no specified value", () => {
842 translateReport(node
, "message {{parameter}}", {}).message
,
843 "message {{parameter}}"
847 it("should handle leading whitespace in template parameter", () => {
849 translateReport({ node
, message
: "message {{ parameter}}", data
: { parameter
: "yay!" } }).message
,
854 it("should handle trailing whitespace in template parameter", () => {
856 translateReport({ node
, message
: "message {{parameter }}", data
: { parameter
: "yay!" } }).message
,
861 it("should still allow inner whitespace as well as leading/trailing", () => {
863 translateReport(node
, "message {{ parameter name }}", { "parameter name": "yay!" }).message
,
868 it("should still allow non-identifier characters as well as leading/trailing whitespace", () => {
870 translateReport(node
, "message {{ parameter-name }}", { "parameter-name": "yay!" }).message
,
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"),
883 message
: "hello world",
884 nodeType
: "ExpressionStatement",
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" }),
897 message
: "hello world",
898 nodeType
: "ExpressionStatement",
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"),
911 message
: "hello world",
912 nodeType
: "ExpressionStatement",
921 it("should have 'endLine' and 'endColumn' when 'loc' property has 'end' property.", () => {
922 assert
.deepStrictEqual(
923 translateReport({ loc
: node
.loc
, message
: "hello world" }),
927 message
: "hello world",
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" }),
943 message
: "hello world",
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" }),
957 message
: "hello world",
958 nodeType
: "ExpressionStatement",
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
: "" })),
975 message
: "my message testing!",
976 nodeType
: "ExpressionStatement",
981 fix
: { range
: [1, 1], text
: "" }
987 describe("validation", () => {
989 it("should throw an error if node is not an object", () => {
991 () => translateReport("not a node", "hello world"),
992 "Node must be an object"
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"),
1003 message
: "hello world",
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" }),
1017 message
: "hello world",
1025 it("should throw an error if neither node nor location is provided", () => {
1027 () => translateReport(null, "hello world"),
1028 "Node must be provided when reporting error if location is not provided"
1032 it("should throw an error if fix range is invalid", () => {
1034 () => translateReport({ node
, messageId
: "testMessage", fix
: () => ({ text
: "foo" }) }),
1035 "Fix has invalid range"
1038 for (const badRange
of [[0], [0, null], [null, 0], [void 0, 1], [0, void 0], [void 0, void 0], []]) {
1040 // eslint-disable-next-line no-loop-func
1041 () => translateReport(
1042 { node
, messageId
: "testMessage", fix
: () => ({ range
: badRange
, text
: "foo" }) }
1044 "Fix has invalid range"
1048 // eslint-disable-next-line no-loop-func
1049 () => translateReport(
1052 messageId
: "testMessage",
1054 { range
: [0, 0], text
: "foo" },
1055 { range
: badRange
, text
: "bar" },
1056 { range
: [1, 1], text
: "baz" }
1060 "Fix has invalid range"