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