• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    describe("unittests:: services:: organizeImports", () => {
3        describe("Sort imports", () => {
4            it("Sort - non-relative vs non-relative", () => {
5                assertSortsBefore(
6                    `import y from "lib1";`,
7                    `import x from "lib2";`);
8            });
9
10            it("Sort - relative vs relative", () => {
11                assertSortsBefore(
12                    `import y from "./lib1";`,
13                    `import x from "./lib2";`);
14            });
15
16            it("Sort - relative vs non-relative", () => {
17                assertSortsBefore(
18                    `import y from "lib";`,
19                    `import x from "./lib";`);
20            });
21
22            it("Sort - case-insensitive", () => {
23                assertSortsBefore(
24                    `import y from "a";`,
25                    `import x from "Z";`);
26                assertSortsBefore(
27                    `import y from "A";`,
28                    `import x from "z";`);
29            });
30
31            function assertSortsBefore(importString1: string, importString2: string) {
32                const [{moduleSpecifier: moduleSpecifier1}, {moduleSpecifier: moduleSpecifier2}] = parseImports(importString1, importString2);
33                assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier1, moduleSpecifier2), Comparison.LessThan);
34                assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier2, moduleSpecifier1), Comparison.GreaterThan);
35            }
36        });
37
38        describe("Coalesce imports", () => {
39            it("No imports", () => {
40                assert.isEmpty(OrganizeImports.coalesceImports([]));
41            });
42
43            it("Sort specifiers - case-insensitive", () => {
44                const sortedImports = parseImports(`import { default as M, a as n, B, y, Z as O } from "lib";`);
45                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
46                const expectedCoalescedImports = parseImports(`import { a as n, B, default as M, y, Z as O } from "lib";`);
47                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
48            });
49
50            it("Combine side-effect-only imports", () => {
51                const sortedImports = parseImports(
52                    `import "lib";`,
53                    `import "lib";`);
54                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
55                const expectedCoalescedImports = parseImports(`import "lib";`);
56                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
57            });
58
59            it("Combine namespace imports", () => {
60                const sortedImports = parseImports(
61                    `import * as x from "lib";`,
62                    `import * as y from "lib";`);
63                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
64                const expectedCoalescedImports = sortedImports;
65                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
66            });
67
68            it("Combine default imports", () => {
69                const sortedImports = parseImports(
70                    `import x from "lib";`,
71                    `import y from "lib";`);
72                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
73                const expectedCoalescedImports = parseImports(`import { default as x, default as y } from "lib";`);
74                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
75            });
76
77            it("Combine property imports", () => {
78                const sortedImports = parseImports(
79                    `import { x } from "lib";`,
80                    `import { y as z } from "lib";`);
81                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
82                const expectedCoalescedImports = parseImports(`import { x, y as z } from "lib";`);
83                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
84            });
85
86            it("Combine side-effect-only import with namespace import", () => {
87                const sortedImports = parseImports(
88                    `import "lib";`,
89                    `import * as x from "lib";`);
90                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
91                const expectedCoalescedImports = sortedImports;
92                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
93            });
94
95            it("Combine side-effect-only import with default import", () => {
96                const sortedImports = parseImports(
97                    `import "lib";`,
98                    `import x from "lib";`);
99                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
100                const expectedCoalescedImports = sortedImports;
101                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
102            });
103
104            it("Combine side-effect-only import with property import", () => {
105                const sortedImports = parseImports(
106                    `import "lib";`,
107                    `import { x } from "lib";`);
108                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
109                const expectedCoalescedImports = sortedImports;
110                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
111            });
112
113            it("Combine namespace import with default import", () => {
114                const sortedImports = parseImports(
115                    `import * as x from "lib";`,
116                    `import y from "lib";`);
117                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
118                const expectedCoalescedImports = parseImports(
119                    `import y, * as x from "lib";`);
120                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
121            });
122
123            it("Combine namespace import with property import", () => {
124                const sortedImports = parseImports(
125                    `import * as x from "lib";`,
126                    `import { y } from "lib";`);
127                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
128                const expectedCoalescedImports = sortedImports;
129                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
130            });
131
132            it("Combine default import with property import", () => {
133                const sortedImports = parseImports(
134                    `import x from "lib";`,
135                    `import { y } from "lib";`);
136                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
137                const expectedCoalescedImports = parseImports(
138                    `import x, { y } from "lib";`);
139                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
140            });
141
142            it("Combine many imports", () => {
143                const sortedImports = parseImports(
144                    `import "lib";`,
145                    `import * as y from "lib";`,
146                    `import w from "lib";`,
147                    `import { b } from "lib";`,
148                    `import "lib";`,
149                    `import * as x from "lib";`,
150                    `import z from "lib";`,
151                    `import { a } from "lib";`);
152                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
153                const expectedCoalescedImports = parseImports(
154                    `import "lib";`,
155                    `import * as x from "lib";`,
156                    `import * as y from "lib";`,
157                    `import { a, b, default as w, default as z } from "lib";`);
158                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
159            });
160
161            // This is descriptive, rather than normative
162            it("Combine two namespace imports with one default import", () => {
163                const sortedImports = parseImports(
164                    `import * as x from "lib";`,
165                    `import * as y from "lib";`,
166                    `import z from "lib";`);
167                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
168                const expectedCoalescedImports = sortedImports;
169                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
170            });
171
172            it("Combine type-only imports separately from other imports", () => {
173                const sortedImports = parseImports(
174                    `import type { x } from "lib";`,
175                    `import type { y } from "lib";`,
176                    `import { z } from "lib";`);
177                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
178                const expectedCoalescedImports = parseImports(
179                    `import { z } from "lib";`,
180                    `import type { x, y } from "lib";`);
181                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
182            });
183
184            it("Do not combine type-only default, namespace, or named imports with each other", () => {
185                const sortedImports = parseImports(
186                    `import type { x } from "lib";`,
187                    `import type * as y from "lib";`,
188                    `import type z from "lib";`);
189                // Default import could be rewritten as a named import to combine with `x`,
190                // but seems of debatable merit.
191                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
192                const expectedCoalescedImports = actualCoalescedImports;
193                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
194            });
195        });
196
197        describe("Coalesce exports", () => {
198            it("No exports", () => {
199                assert.isEmpty(OrganizeImports.coalesceExports([]));
200            });
201
202            it("Sort specifiers - case-insensitive", () => {
203                const sortedExports = parseExports(`export { default as M, a as n, B, y, Z as O } from "lib";`);
204                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
205                const expectedCoalescedExports = parseExports(`export { a as n, B, default as M, y, Z as O } from "lib";`);
206                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
207            });
208
209            it("Sort specifiers - type-only", () => {
210                const sortedImports = parseImports(`import { type z, y, type x, c, type b, a } from "lib";`);
211                const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
212                const expectedCoalescedImports = parseImports(`import { a, c, y, type b, type x, type z } from "lib";`);
213                assertListEqual(actualCoalescedImports, expectedCoalescedImports);
214            });
215
216            it("Combine namespace re-exports", () => {
217                const sortedExports = parseExports(
218                    `export * from "lib";`,
219                    `export * from "lib";`);
220                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
221                const expectedCoalescedExports = parseExports(`export * from "lib";`);
222                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
223            });
224
225            it("Combine property exports", () => {
226                const sortedExports = parseExports(
227                    `export { x };`,
228                    `export { y as z };`);
229                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
230                const expectedCoalescedExports = parseExports(`export { x, y as z };`);
231                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
232            });
233
234            it("Combine property re-exports", () => {
235                const sortedExports = parseExports(
236                    `export { x } from "lib";`,
237                    `export { y as z } from "lib";`);
238                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
239                const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`);
240                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
241            });
242
243            it("Combine namespace re-export with property re-export", () => {
244                const sortedExports = parseExports(
245                    `export * from "lib";`,
246                    `export { y } from "lib";`);
247                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
248                const expectedCoalescedExports = sortedExports;
249                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
250            });
251
252            it("Combine many exports", () => {
253                const sortedExports = parseExports(
254                    `export { x };`,
255                    `export { y as w, z as default };`,
256                    `export { w as q };`);
257                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
258                const expectedCoalescedExports = parseExports(
259                    `export { w as q, x, y as w, z as default };`);
260                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
261            });
262
263            it("Combine many re-exports", () => {
264                const sortedExports = parseExports(
265                    `export { x as a, y } from "lib";`,
266                    `export * from "lib";`,
267                    `export { z as b } from "lib";`);
268                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
269                const expectedCoalescedExports = parseExports(
270                    `export * from "lib";`,
271                    `export { x as a, y, z as b } from "lib";`);
272                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
273            });
274
275            it("Keep type-only exports separate", () => {
276                const sortedExports = parseExports(
277                    `export { x };`,
278                    `export type { y };`);
279                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
280                const expectedCoalescedExports = sortedExports;
281                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
282            });
283
284            it("Combine type-only exports", () => {
285                const sortedExports = parseExports(
286                    `export type { x };`,
287                    `export type { y };`);
288                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
289                const expectedCoalescedExports = parseExports(
290                    `export type { x, y };`);
291                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
292            });
293        });
294
295
296        describe("Baselines", () => {
297
298            const libFile = {
299                path: "/lib.ts",
300                content: `
301export function F1();
302export default function F2();
303`,
304            };
305
306            const reactLibFile = {
307                path: "/react.ts",
308                content: `
309export const React = {
310createElement: (_type, _props, _children) => {},
311};
312
313export const Other = 1;
314`,
315            };
316
317            // Don't bother to actually emit a baseline for this.
318            it("NoImports", () => {
319                const testFile = {
320                    path: "/a.ts",
321                    content: "function F() { }",
322                };
323                const languageService = makeLanguageService(testFile);
324                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
325                assert.isEmpty(changes);
326            });
327
328            it("doesn't crash on shorthand ambient module", () => {
329                const testFile = {
330                    path: "/a.ts",
331                    content: "declare module '*';",
332                };
333                const languageService = makeLanguageService(testFile);
334                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
335                assert.isEmpty(changes);
336            });
337
338            it("doesn't return any changes when the text would be identical", () => {
339                const testFile = {
340                    path: "/a.ts",
341                    content: `import { f } from 'foo';\nf();`
342                };
343                const languageService = makeLanguageService(testFile);
344                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
345                assert.isEmpty(changes);
346            });
347
348            testOrganizeImports("Renamed_used",
349                /*skipDestructiveCodeActions*/ false,
350                {
351                    path: "/test.ts",
352                    content: `
353import { F1 as EffOne, F2 as EffTwo } from "lib";
354EffOne();
355`,
356                },
357                libFile);
358
359            testOrganizeImports("Simple",
360                /*skipDestructiveCodeActions*/ false,
361                {
362                    path: "/test.ts",
363                    content: `
364import { F1, F2 } from "lib";
365import * as NS from "lib";
366import D from "lib";
367
368NS.F1();
369D();
370F1();
371F2();
372`,
373                },
374                libFile);
375
376            testOrganizeImports("Unused_Some",
377                /*skipDestructiveCodeActions*/ false,
378                {
379                    path: "/test.ts",
380                    content: `
381import { F1, F2 } from "lib";
382import * as NS from "lib";
383import D from "lib";
384
385D();
386`,
387                },
388                libFile);
389
390                describe("skipDestructiveCodeActions=true", () => {
391                    testOrganizeImports("Syntax_Error_Body_skipDestructiveCodeActions",
392                        /*skipDestructiveCodeActions*/ true,
393                        {
394                            path: "/test.ts",
395                            content: `
396import { F1, F2 } from "lib";
397import * as NS from "lib";
398import D from "lib";
399
400class class class;
401D;
402`,
403                        },
404                        libFile);
405                    });
406
407                    testOrganizeImports("Syntax_Error_Imports_skipDestructiveCodeActions",
408                        /*skipDestructiveCodeActions*/ true,
409                        {
410                            path: "/test.ts",
411                            content: `
412import { F1, F2 class class class; } from "lib";
413import * as NS from "lib";
414class class class;
415import D from "lib";
416
417D;
418`,
419                        },
420                        libFile);
421
422                describe("skipDestructiveCodeActions=false", () => {
423                    testOrganizeImports("Syntax_Error_Body",
424                        /*skipDestructiveCodeActions*/ false,
425                        {
426                            path: "/test.ts",
427                            content: `
428import { F1, F2 } from "lib";
429import * as NS from "lib";
430import D from "lib";
431
432class class class;
433D;
434`,
435                        },
436                        libFile);
437
438                    testOrganizeImports("Syntax_Error_Imports",
439                        /*skipDestructiveCodeActions*/ false,
440                        {
441                            path: "/test.ts",
442                            content: `
443import { F1, F2 class class class; } from "lib";
444import * as NS from "lib";
445class class class;
446import D from "lib";
447
448D;
449`,
450                        },
451                        libFile);
452                    });
453
454                it("doesn't return any changes when the text would be identical", () => {
455                    const testFile = {
456                        path: "/a.ts",
457                        content: `import { f } from 'foo';\nf();`
458                    };
459                    const languageService = makeLanguageService(testFile);
460                    const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
461                    assert.isEmpty(changes);
462                });
463
464            testOrganizeImports("Unused_All",
465                /*skipDestructiveCodeActions*/ false,
466                {
467                    path: "/test.ts",
468                    content: `
469import { F1, F2 } from "lib";
470import * as NS from "lib";
471import D from "lib";
472`,
473                },
474                libFile);
475
476            it("Unused_Empty", () => {
477                const testFile = {
478                    path: "/test.ts",
479                    content: `
480import { } from "lib";
481`,
482                };
483                const languageService = makeLanguageService(testFile);
484                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
485                assert.isEmpty(changes);
486            });
487
488            testOrganizeImports("Unused_false_positive_module_augmentation",
489                /*skipDestructiveCodeActions*/ false,
490                {
491                    path: "/test.d.ts",
492                    content: `
493import foo from 'foo';
494import { Caseless } from 'caseless';
495
496declare module 'foo' {}
497declare module 'caseless' {
498    interface Caseless {
499        test(name: KeyType): boolean;
500    }
501}`
502                });
503
504            testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file",
505                /*skipDestructiveCodeActions*/ false,
506                {
507                    path: "/test.ts",
508                    content: `
509import foo from 'foo';
510import { Caseless } from 'caseless';
511
512declare module 'foo' {}
513declare module 'caseless' {
514    interface Caseless {
515        test(name: KeyType): boolean;
516    }
517}`
518            });
519
520            it("Unused_false_positive_shorthand_assignment", () => {
521                const testFile = {
522                    path: "/test.ts",
523                    content: `
524import { x } from "a";
525const o = { x };
526`
527                };
528                const languageService = makeLanguageService(testFile);
529                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
530                assert.isEmpty(changes);
531            });
532
533            it("Unused_false_positive_export_shorthand", () => {
534                const testFile = {
535                    path: "/test.ts",
536                    content: `
537import { x } from "a";
538export { x };
539`
540                };
541                const languageService = makeLanguageService(testFile);
542                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
543                assert.isEmpty(changes);
544            });
545
546            testOrganizeImports("MoveToTop",
547                /*skipDestructiveCodeActions*/ false,
548                {
549                    path: "/test.ts",
550                    content: `
551import { F1, F2 } from "lib";
552F1();
553F2();
554import * as NS from "lib";
555NS.F1();
556import D from "lib";
557D();
558`,
559                },
560                libFile);
561
562            /* eslint-disable no-template-curly-in-string */
563            testOrganizeImports("MoveToTop_Invalid",
564                /*skipDestructiveCodeActions*/ false,
565                {
566                    path: "/test.ts",
567                    content: `
568import { F1, F2 } from "lib";
569F1();
570F2();
571import * as NS from "lib";
572NS.F1();
573import b from ${"`${'lib'}`"};
574import a from ${"`${'lib'}`"};
575import D from "lib";
576D();
577`,
578                },
579                libFile);
580            /* eslint-enable no-template-curly-in-string */
581
582            testOrganizeImports("TypeOnly",
583                /*skipDestructiveCodeActions*/ false,
584                {
585                    path: "/test.ts",
586                    content: `
587import { X } from "lib";
588import type Y from "lib";
589import { Z } from "lib";
590import type { A, B } from "lib";
591
592export { A, B, X, Y, Z };`
593                });
594
595            testOrganizeImports("CoalesceMultipleModules",
596                /*skipDestructiveCodeActions*/ false,
597                {
598                    path: "/test.ts",
599                    content: `
600import { d } from "lib1";
601import { b } from "lib1";
602import { c } from "lib2";
603import { a } from "lib2";
604a + b + c + d;
605`,
606                },
607                { path: "/lib1.ts", content: "export const b = 1, d = 2;" },
608                { path: "/lib2.ts", content: "export const a = 3, c = 4;" });
609
610            testOrganizeImports("CoalesceTrivia",
611                /*skipDestructiveCodeActions*/ false,
612                {
613                    path: "/test.ts",
614                    content: `
615/*A*/import /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I
616/*J*/import /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R
617
618F1();
619F2();
620`,
621                },
622                libFile);
623
624            testOrganizeImports("SortTrivia",
625                /*skipDestructiveCodeActions*/ false,
626                {
627                    path: "/test.ts",
628                    content: `
629/*A*/import /*B*/ "lib2" /*C*/;/*D*/ //E
630/*F*/import /*G*/ "lib1" /*H*/;/*I*/ //J
631`,
632                },
633                { path: "/lib1.ts", content: "" },
634                { path: "/lib2.ts", content: "" });
635
636            testOrganizeImports("UnusedTrivia1",
637                /*skipDestructiveCodeActions*/ false,
638                {
639                    path: "/test.ts",
640                    content: `
641/*A*/import /*B*/ { /*C*/ F1 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I
642`,
643                },
644                libFile);
645
646            testOrganizeImports("UnusedTrivia2",
647                /*skipDestructiveCodeActions*/ false,
648                {
649                    path: "/test.ts",
650                    content: `
651/*A*/import /*B*/ { /*C*/ F1 /*D*/, /*E*/ F2 /*F*/ } /*G*/ from /*H*/ "lib" /*I*/;/*J*/ //K
652
653F1();
654`,
655                },
656                libFile);
657
658            testOrganizeImports("UnusedHeaderComment",
659                /*skipDestructiveCodeActions*/ false,
660                {
661                    path: "/test.ts",
662                    content: `
663// Header
664import { F1 } from "lib";
665`,
666                },
667                libFile);
668
669            testOrganizeImports("SortHeaderComment",
670                /*skipDestructiveCodeActions*/ false,
671                {
672                    path: "/test.ts",
673                    content: `
674// Header
675import "lib2";
676import "lib1";
677`,
678                },
679                { path: "/lib1.ts", content: "" },
680                { path: "/lib2.ts", content: "" });
681
682            testOrganizeImports("AmbientModule",
683                /*skipDestructiveCodeActions*/ false,
684                {
685                    path: "/test.ts",
686                    content: `
687declare module "mod" {
688    import { F1 } from "lib";
689    import * as NS from "lib";
690    import { F2 } from "lib";
691
692    function F(f1: {} = F1, f2: {} = F2) {}
693}
694`,
695                },
696                libFile);
697
698            testOrganizeImports("TopLevelAndAmbientModule",
699                /*skipDestructiveCodeActions*/ false,
700                {
701                    path: "/test.ts",
702                    content: `
703import D from "lib";
704
705declare module "mod" {
706    import { F1 } from "lib";
707    import * as NS from "lib";
708    import { F2 } from "lib";
709
710    function F(f1: {} = F1, f2: {} = F2) {}
711}
712
713import E from "lib";
714import "lib";
715
716D();
717`,
718                },
719                libFile);
720
721            testOrganizeImports("JsxFactoryUsedJsx",
722                /*skipDestructiveCodeActions*/ false,
723                {
724                    path: "/test.jsx",
725                    content: `
726import { React, Other } from "react";
727
728<div/>;
729`,
730                },
731                reactLibFile);
732
733            testOrganizeImports("JsxFactoryUsedJs",
734                /*skipDestructiveCodeActions*/ false,
735                {
736                    path: "/test.js",
737                    content: `
738import { React, Other } from "react";
739
740<div/>;
741`,
742                },
743                reactLibFile);
744
745            testOrganizeImports("JsxFactoryUsedTsx",
746                /*skipDestructiveCodeActions*/ false,
747                {
748                    path: "/test.tsx",
749                    content: `
750import { React, Other } from "react";
751
752<div/>;
753`,
754                },
755                reactLibFile);
756
757            // TS files are not JSX contexts, so the parser does not treat
758            // `<div/>` as a JSX element.
759            testOrganizeImports("JsxFactoryUsedTs",
760                /*skipDestructiveCodeActions*/ false,
761                {
762                    path: "/test.ts",
763                    content: `
764import { React, Other } from "react";
765
766<div/>;
767`,
768                },
769                reactLibFile);
770
771            testOrganizeImports("JsxFactoryUnusedJsx",
772                /*skipDestructiveCodeActions*/ false,
773                {
774                    path: "/test.jsx",
775                    content: `
776import { React, Other } from "react";
777`,
778                },
779                reactLibFile);
780
781            // Note: Since the file extension does not end with "x", the jsx compiler option
782            // will not be enabled.  The import should be retained regardless.
783            testOrganizeImports("JsxFactoryUnusedJs",
784                /*skipDestructiveCodeActions*/ false,
785                {
786                    path: "/test.js",
787                    content: `
788import { React, Other } from "react";
789`,
790                },
791                reactLibFile);
792
793            testOrganizeImports("JsxFactoryUnusedTsx",
794                /*skipDestructiveCodeActions*/ false,
795                {
796                    path: "/test.tsx",
797                    content: `
798import { React, Other } from "react";
799`,
800                },
801                reactLibFile);
802
803            testOrganizeImports("JsxFactoryUnusedTs",
804                /*skipDestructiveCodeActions*/ false,
805                {
806                    path: "/test.ts",
807                    content: `
808import { React, Other } from "react";
809`,
810                },
811                reactLibFile);
812
813            testOrganizeImports("JsxPragmaTsx",
814                /*skipDestructiveCodeActions*/ false,
815                {
816                    path: "/test.tsx",
817                    content: `/** @jsx jsx */
818
819import { Global, jsx } from '@emotion/core';
820import * as React from 'react';
821
822export const App: React.FunctionComponent = _ => <Global><h1>Hello!</h1></Global>
823`,
824                },
825                {
826                    path: "/@emotion/core/index.d.ts",
827                    content: `import {  createElement } from 'react'
828export const jsx: typeof createElement;
829export function Global(props: any): ReactElement<any>;`
830                },
831                {
832                    path: reactLibFile.path,
833                    content: `${reactLibFile.content}
834export namespace React {
835    interface FunctionComponent {
836    }
837}
838`
839                }
840            );
841
842            testOrganizeImports("JsxFragmentPragmaTsx",
843                /*skipDestructiveCodeActions*/ false,
844                {
845                    path: "/test.tsx",
846                    content: `/** @jsx h */
847/** @jsxFrag frag */
848import { h, frag } from "@foo/core";
849
850const elem = <><div>Foo</div></>;
851`,
852                },
853                {
854                    path: "/@foo/core/index.d.ts",
855                    content: `export function h(): void;
856export function frag(): void;
857`
858                }
859            );
860
861            describe("Exports", () => {
862
863                testOrganizeExports("MoveToTop",
864                    {
865                        path: "/test.ts",
866                        content: `
867export { F1, F2 } from "lib";
8681;
869export * from "lib";
8702;
871`,
872                    },
873                    libFile);
874
875                /* eslint-disable no-template-curly-in-string */
876                testOrganizeExports("MoveToTop_Invalid",
877                    {
878                        path: "/test.ts",
879                        content: `
880export { F1, F2 } from "lib";
8811;
882export * from "lib";
8832;
884export { b } from ${"`${'lib'}`"};
885export { a } from ${"`${'lib'}`"};
886export { D } from "lib";
8873;
888`,
889                    },
890                    libFile);
891                /* eslint-enable no-template-curly-in-string */
892
893                testOrganizeExports("MoveToTop_WithImportsFirst",
894                    {
895                        path: "/test.ts",
896                        content: `
897import { F1, F2 } from "lib";
8981;
899export { F1, F2 } from "lib";
9002;
901import * as NS from "lib";
9023;
903export * from "lib";
9044;
905F1(); F2(); NS.F1();
906`,
907                    },
908                    libFile);
909
910                testOrganizeExports("MoveToTop_WithExportsFirst",
911                    {
912                        path: "/test.ts",
913                        content: `
914export { F1, F2 } from "lib";
9151;
916import { F1, F2 } from "lib";
9172;
918export * from "lib";
9193;
920import * as NS from "lib";
9214;
922F1(); F2(); NS.F1();
923`,
924                    },
925                    libFile);
926
927                testOrganizeExports("CoalesceMultipleModules",
928                    {
929                        path: "/test.ts",
930                        content: `
931export { d } from "lib1";
932export { b } from "lib1";
933export { c } from "lib2";
934export { a } from "lib2";
935`,
936                    },
937                    { path: "/lib1.ts", content: "export const b = 1, d = 2;" },
938                    { path: "/lib2.ts", content: "export const a = 3, c = 4;" });
939
940                testOrganizeExports("CoalesceTrivia",
941                    {
942                        path: "/test.ts",
943                        content: `
944/*A*/export /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I
945/*J*/export /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R
946`,
947                    },
948                    libFile);
949
950                testOrganizeExports("SortTrivia",
951                    {
952                        path: "/test.ts",
953                        content: `
954/*A*/export /*B*/ * /*C*/ from /*D*/ "lib2" /*E*/;/*F*/ //G
955/*H*/export /*I*/ * /*J*/ from /*K*/ "lib1" /*L*/;/*M*/ //N
956`,
957                    },
958                    { path: "/lib1.ts", content: "" },
959                    { path: "/lib2.ts", content: "" });
960
961                testOrganizeExports("SortHeaderComment",
962                    {
963                        path: "/test.ts",
964                        content: `
965// Header
966export * from "lib2";
967export * from "lib1";
968`,
969                    },
970                    { path: "/lib1.ts", content: "" },
971                    { path: "/lib2.ts", content: "" });
972
973                testOrganizeExports("AmbientModule",
974                    {
975                        path: "/test.ts",
976                        content: `
977declare module "mod" {
978    export { F1 } from "lib";
979    export * from "lib";
980    export { F2 } from "lib";
981}
982    `,
983                    },
984                    libFile);
985
986                testOrganizeExports("TopLevelAndAmbientModule",
987                    {
988                        path: "/test.ts",
989                        content: `
990export { D } from "lib";
991
992declare module "mod" {
993    export { F1 } from "lib";
994    export * from "lib";
995    export { F2 } from "lib";
996}
997
998export { E } from "lib";
999export * from "lib";
1000`,
1001                    },
1002                    libFile);
1003            });
1004
1005            function testOrganizeExports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
1006                testOrganizeImports(`${testName}.exports`, /*skipDestructiveCodeActions*/ true, testFile, ...otherFiles);
1007            }
1008
1009            function testOrganizeImports(testName: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
1010                it(testName, () => runBaseline(`organizeImports/${testName}.ts`, skipDestructiveCodeActions, testFile, ...otherFiles));
1011            }
1012
1013            function runBaseline(baselinePath: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
1014                const { path: testPath, content: testContent } = testFile;
1015                const languageService = makeLanguageService(testFile, ...otherFiles);
1016                const changes = languageService.organizeImports({ skipDestructiveCodeActions, type: "file", fileName: testPath }, testFormatSettings, emptyOptions);
1017                assert.equal(changes.length, 1);
1018                assert.equal(changes[0].fileName, testPath);
1019
1020                const newText = textChanges.applyChanges(testContent, changes[0].textChanges);
1021                Harness.Baseline.runBaseline(baselinePath, [
1022                    "// ==ORIGINAL==",
1023                    testContent,
1024                    "// ==ORGANIZED==",
1025                    newText,
1026                ].join(newLineCharacter));
1027            }
1028
1029            function makeLanguageService(...files: TestFSWithWatch.File[]) {
1030                const host = projectSystem.createServerHost(files);
1031                const projectService = projectSystem.createProjectService(host, { useSingleInferredProject: true });
1032                projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? JsxEmit.React : JsxEmit.None });
1033                files.forEach(f => projectService.openClientFile(f.path));
1034                return projectService.inferredProjects[0].getLanguageService();
1035            }
1036        });
1037
1038        function parseImports(...importStrings: string[]): readonly ImportDeclaration[] {
1039            const sourceFile = createSourceFile("a.ts", importStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS);
1040            const imports = filter(sourceFile.statements, isImportDeclaration);
1041            assert.equal(imports.length, importStrings.length);
1042            return imports;
1043        }
1044
1045        function parseExports(...exportStrings: string[]): readonly ExportDeclaration[] {
1046            const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS);
1047            const exports = filter(sourceFile.statements, isExportDeclaration);
1048            assert.equal(exports.length, exportStrings.length);
1049            return exports;
1050        }
1051
1052        function assertEqual(node1?: Node, node2?: Node) {
1053            if (node1 === undefined) {
1054                assert.isUndefined(node2);
1055                return;
1056            }
1057            else if (node2 === undefined) {
1058                assert.isUndefined(node1); // Guaranteed to fail
1059                return;
1060            }
1061
1062            assert.equal(node1.kind, node2.kind);
1063
1064            switch (node1.kind) {
1065                case SyntaxKind.ImportDeclaration:
1066                    const decl1 = node1 as ImportDeclaration;
1067                    const decl2 = node2 as ImportDeclaration;
1068                    assertEqual(decl1.importClause, decl2.importClause);
1069                    assertEqual(decl1.moduleSpecifier, decl2.moduleSpecifier);
1070                    break;
1071                case SyntaxKind.ImportClause:
1072                    const clause1 = node1 as ImportClause;
1073                    const clause2 = node2 as ImportClause;
1074                    assertEqual(clause1.name, clause2.name);
1075                    assertEqual(clause1.namedBindings, clause2.namedBindings);
1076                    break;
1077                case SyntaxKind.NamespaceImport:
1078                    const nsi1 = node1 as NamespaceImport;
1079                    const nsi2 = node2 as NamespaceImport;
1080                    assertEqual(nsi1.name, nsi2.name);
1081                    break;
1082                case SyntaxKind.NamedImports:
1083                    const ni1 = node1 as NamedImports;
1084                    const ni2 = node2 as NamedImports;
1085                    assertListEqual(ni1.elements, ni2.elements);
1086                    break;
1087                case SyntaxKind.ImportSpecifier:
1088                    const is1 = node1 as ImportSpecifier;
1089                    const is2 = node2 as ImportSpecifier;
1090                    assertEqual(is1.name, is2.name);
1091                    assertEqual(is1.propertyName, is2.propertyName);
1092                    break;
1093                case SyntaxKind.ExportDeclaration:
1094                    const ed1 = node1 as ExportDeclaration;
1095                    const ed2 = node2 as ExportDeclaration;
1096                    assertEqual(ed1.exportClause, ed2.exportClause);
1097                    assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier);
1098                    break;
1099                case SyntaxKind.NamedExports:
1100                    const ne1 = node1 as NamedExports;
1101                    const ne2 = node2 as NamedExports;
1102                    assertListEqual(ne1.elements, ne2.elements);
1103                    break;
1104                case SyntaxKind.ExportSpecifier:
1105                    const es1 = node1 as ExportSpecifier;
1106                    const es2 = node2 as ExportSpecifier;
1107                    assertEqual(es1.name, es2.name);
1108                    assertEqual(es1.propertyName, es2.propertyName);
1109                    break;
1110                case SyntaxKind.Identifier:
1111                    const id1 = node1 as Identifier;
1112                    const id2 = node2 as Identifier;
1113                    assert.equal(id1.text, id2.text);
1114                    break;
1115                case SyntaxKind.StringLiteral:
1116                case SyntaxKind.NoSubstitutionTemplateLiteral:
1117                    const sl1 = node1 as LiteralLikeNode;
1118                    const sl2 = node2 as LiteralLikeNode;
1119                    assert.equal(sl1.text, sl2.text);
1120                    break;
1121                default:
1122                    assert.equal(node1.getText(), node2.getText());
1123                    break;
1124            }
1125        }
1126
1127        function assertListEqual(list1: readonly Node[], list2: readonly Node[]) {
1128            if (list1 === undefined || list2 === undefined) {
1129                assert.isUndefined(list1);
1130                assert.isUndefined(list2);
1131                return;
1132            }
1133
1134            assert.equal(list1.length, list2.length);
1135            for (let i = 0; i < list1.length; i++) {
1136                assertEqual(list1[i], list2[i]);
1137            }
1138        }
1139    });
1140}
1141