]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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 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 | }); |