• 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("Combine namespace re-exports", () => {
210                const sortedExports = parseExports(
211                    `export * from "lib";`,
212                    `export * from "lib";`);
213                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
214                const expectedCoalescedExports = parseExports(`export * from "lib";`);
215                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
216            });
217
218            it("Combine property exports", () => {
219                const sortedExports = parseExports(
220                    `export { x };`,
221                    `export { y as z };`);
222                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
223                const expectedCoalescedExports = parseExports(`export { x, y as z };`);
224                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
225            });
226
227            it("Combine property re-exports", () => {
228                const sortedExports = parseExports(
229                    `export { x } from "lib";`,
230                    `export { y as z } from "lib";`);
231                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
232                const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`);
233                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
234            });
235
236            it("Combine namespace re-export with property re-export", () => {
237                const sortedExports = parseExports(
238                    `export * from "lib";`,
239                    `export { y } from "lib";`);
240                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
241                const expectedCoalescedExports = sortedExports;
242                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
243            });
244
245            it("Combine many exports", () => {
246                const sortedExports = parseExports(
247                    `export { x };`,
248                    `export { y as w, z as default };`,
249                    `export { w as q };`);
250                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
251                const expectedCoalescedExports = parseExports(
252                    `export { w as q, x, y as w, z as default };`);
253                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
254            });
255
256            it("Combine many re-exports", () => {
257                const sortedExports = parseExports(
258                    `export { x as a, y } from "lib";`,
259                    `export * from "lib";`,
260                    `export { z as b } from "lib";`);
261                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
262                const expectedCoalescedExports = parseExports(
263                    `export * from "lib";`,
264                    `export { x as a, y, z as b } from "lib";`);
265                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
266            });
267
268            it("Keep type-only exports separate", () => {
269                const sortedExports = parseExports(
270                    `export { x };`,
271                    `export type { y };`);
272                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
273                const expectedCoalescedExports = sortedExports;
274                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
275            });
276
277            it("Combine type-only exports", () => {
278                const sortedExports = parseExports(
279                    `export type { x };`,
280                    `export type { y };`);
281                const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
282                const expectedCoalescedExports = parseExports(
283                    `export type { x, y };`);
284                assertListEqual(actualCoalescedExports, expectedCoalescedExports);
285            });
286        });
287
288
289        describe("Baselines", () => {
290
291            const libFile = {
292                path: "/lib.ts",
293                content: `
294export function F1();
295export default function F2();
296`,
297            };
298
299            const reactLibFile = {
300                path: "/react.ts",
301                content: `
302export const React = {
303createElement: (_type, _props, _children) => {},
304};
305
306export const Other = 1;
307`,
308            };
309
310            // Don't bother to actually emit a baseline for this.
311            it("NoImports", () => {
312                const testFile = {
313                    path: "/a.ts",
314                    content: "function F() { }",
315                };
316                const languageService = makeLanguageService(testFile);
317                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
318                assert.isEmpty(changes);
319            });
320
321            it("doesn't crash on shorthand ambient module", () => {
322                const testFile = {
323                    path: "/a.ts",
324                    content: "declare module '*';",
325                };
326                const languageService = makeLanguageService(testFile);
327                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
328                assert.isEmpty(changes);
329            });
330
331            it("doesn't return any changes when the text would be identical", () => {
332                const testFile = {
333                    path: "/a.ts",
334                    content: `import { f } from 'foo';\nf();`
335                };
336                const languageService = makeLanguageService(testFile);
337                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
338                assert.isEmpty(changes);
339            });
340
341            testOrganizeImports("Renamed_used",
342                {
343                    path: "/test.ts",
344                    content: `
345import { F1 as EffOne, F2 as EffTwo } from "lib";
346EffOne();
347`,
348                },
349                libFile);
350
351            testOrganizeImports("Simple",
352                {
353                    path: "/test.ts",
354                    content: `
355import { F1, F2 } from "lib";
356import * as NS from "lib";
357import D from "lib";
358
359NS.F1();
360D();
361F1();
362F2();
363`,
364                },
365                libFile);
366
367            testOrganizeImports("Unused_Some",
368                {
369                    path: "/test.ts",
370                    content: `
371import { F1, F2 } from "lib";
372import * as NS from "lib";
373import D from "lib";
374
375D();
376`,
377                },
378                libFile);
379
380                it("doesn't return any changes when the text would be identical", () => {
381                    const testFile = {
382                        path: "/a.ts",
383                        content: `import { f } from 'foo';\nf();`
384                    };
385                    const languageService = makeLanguageService(testFile);
386                    const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
387                    assert.isEmpty(changes);
388                });
389
390            testOrganizeImports("Unused_All",
391                {
392                    path: "/test.ts",
393                    content: `
394import { F1, F2 } from "lib";
395import * as NS from "lib";
396import D from "lib";
397`,
398                },
399                libFile);
400
401            it("Unused_Empty", () => {
402                const testFile = {
403                    path: "/test.ts",
404                    content: `
405import { } from "lib";
406`,
407                };
408                const languageService = makeLanguageService(testFile);
409                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
410                assert.isEmpty(changes);
411            });
412
413            testOrganizeImports("Unused_false_positive_module_augmentation",
414                {
415                    path: "/test.d.ts",
416                    content: `
417import foo from 'foo';
418import { Caseless } from 'caseless';
419
420declare module 'foo' {}
421declare module 'caseless' {
422    interface Caseless {
423        test(name: KeyType): boolean;
424    }
425}`
426                });
427
428            testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file",
429                {
430                    path: "/test.ts",
431                    content: `
432import foo from 'foo';
433import { Caseless } from 'caseless';
434
435declare module 'foo' {}
436declare module 'caseless' {
437    interface Caseless {
438        test(name: KeyType): boolean;
439    }
440}`
441            });
442
443            it("Unused_false_positive_shorthand_assignment", () => {
444                const testFile = {
445                    path: "/test.ts",
446                    content: `
447import { x } from "a";
448const o = { x };
449`
450                };
451                const languageService = makeLanguageService(testFile);
452                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
453                assert.isEmpty(changes);
454            });
455
456            it("Unused_false_positive_export_shorthand", () => {
457                const testFile = {
458                    path: "/test.ts",
459                    content: `
460import { x } from "a";
461export { x };
462`
463                };
464                const languageService = makeLanguageService(testFile);
465                const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions);
466                assert.isEmpty(changes);
467            });
468
469            testOrganizeImports("MoveToTop",
470                {
471                    path: "/test.ts",
472                    content: `
473import { F1, F2 } from "lib";
474F1();
475F2();
476import * as NS from "lib";
477NS.F1();
478import D from "lib";
479D();
480`,
481                },
482                libFile);
483
484            /* eslint-disable no-template-curly-in-string */
485            testOrganizeImports("MoveToTop_Invalid",
486                {
487                    path: "/test.ts",
488                    content: `
489import { F1, F2 } from "lib";
490F1();
491F2();
492import * as NS from "lib";
493NS.F1();
494import b from ${"`${'lib'}`"};
495import a from ${"`${'lib'}`"};
496import D from "lib";
497D();
498`,
499                },
500                libFile);
501            /* eslint-enable no-template-curly-in-string */
502
503            testOrganizeImports("TypeOnly",
504                {
505                    path: "/test.ts",
506                    content: `
507import { X } from "lib";
508import type Y from "lib";
509import { Z } from "lib";
510import type { A, B } from "lib";
511
512export { A, B, X, Y, Z };`
513                });
514
515            testOrganizeImports("CoalesceMultipleModules",
516                {
517                    path: "/test.ts",
518                    content: `
519import { d } from "lib1";
520import { b } from "lib1";
521import { c } from "lib2";
522import { a } from "lib2";
523a + b + c + d;
524`,
525                },
526                { path: "/lib1.ts", content: "export const b = 1, d = 2;" },
527                { path: "/lib2.ts", content: "export const a = 3, c = 4;" });
528
529            testOrganizeImports("CoalesceTrivia",
530                {
531                    path: "/test.ts",
532                    content: `
533/*A*/import /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I
534/*J*/import /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R
535
536F1();
537F2();
538`,
539                },
540                libFile);
541
542            testOrganizeImports("SortTrivia",
543                {
544                    path: "/test.ts",
545                    content: `
546/*A*/import /*B*/ "lib2" /*C*/;/*D*/ //E
547/*F*/import /*G*/ "lib1" /*H*/;/*I*/ //J
548`,
549                },
550                { path: "/lib1.ts", content: "" },
551                { path: "/lib2.ts", content: "" });
552
553            testOrganizeImports("UnusedTrivia1",
554                {
555                    path: "/test.ts",
556                    content: `
557/*A*/import /*B*/ { /*C*/ F1 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I
558`,
559                },
560                libFile);
561
562            testOrganizeImports("UnusedTrivia2",
563                {
564                    path: "/test.ts",
565                    content: `
566/*A*/import /*B*/ { /*C*/ F1 /*D*/, /*E*/ F2 /*F*/ } /*G*/ from /*H*/ "lib" /*I*/;/*J*/ //K
567
568F1();
569`,
570                },
571                libFile);
572
573            testOrganizeImports("UnusedHeaderComment",
574                {
575                    path: "/test.ts",
576                    content: `
577// Header
578import { F1 } from "lib";
579`,
580                },
581                libFile);
582
583            testOrganizeImports("SortHeaderComment",
584                {
585                    path: "/test.ts",
586                    content: `
587// Header
588import "lib2";
589import "lib1";
590`,
591                },
592                { path: "/lib1.ts", content: "" },
593                { path: "/lib2.ts", content: "" });
594
595                testOrganizeImports("SortComments",
596                {
597                    path: "/test.ts",
598                    content: `
599// Header
600import "lib3";
601// Comment2
602import "lib2";
603// Comment1
604import "lib1";
605`,
606                },
607                { path: "/lib1.ts", content: "" },
608                { path: "/lib2.ts", content: "" },
609                { path: "/lib3.ts", content: "" });
610
611            testOrganizeImports("AmbientModule",
612                {
613                    path: "/test.ts",
614                    content: `
615declare module "mod" {
616    import { F1 } from "lib";
617    import * as NS from "lib";
618    import { F2 } from "lib";
619
620    function F(f1: {} = F1, f2: {} = F2) {}
621}
622`,
623                },
624                libFile);
625
626            testOrganizeImports("TopLevelAndAmbientModule",
627                {
628                    path: "/test.ts",
629                    content: `
630import D from "lib";
631
632declare module "mod" {
633    import { F1 } from "lib";
634    import * as NS from "lib";
635    import { F2 } from "lib";
636
637    function F(f1: {} = F1, f2: {} = F2) {}
638}
639
640import E from "lib";
641import "lib";
642
643D();
644`,
645                },
646                libFile);
647
648            testOrganizeImports("JsxFactoryUsedJsx",
649                {
650                    path: "/test.jsx",
651                    content: `
652import { React, Other } from "react";
653
654<div/>;
655`,
656                },
657                reactLibFile);
658
659            testOrganizeImports("JsxFactoryUsedJs",
660                {
661                    path: "/test.js",
662                    content: `
663import { React, Other } from "react";
664
665<div/>;
666`,
667                },
668                reactLibFile);
669
670            testOrganizeImports("JsxFactoryUsedTsx",
671                {
672                    path: "/test.tsx",
673                    content: `
674import { React, Other } from "react";
675
676<div/>;
677`,
678                },
679                reactLibFile);
680
681            // TS files are not JSX contexts, so the parser does not treat
682            // `<div/>` as a JSX element.
683            testOrganizeImports("JsxFactoryUsedTs",
684                {
685                    path: "/test.ts",
686                    content: `
687import { React, Other } from "react";
688
689<div/>;
690`,
691                },
692                reactLibFile);
693
694            testOrganizeImports("JsxFactoryUnusedJsx",
695                {
696                    path: "/test.jsx",
697                    content: `
698import { React, Other } from "react";
699`,
700                },
701                reactLibFile);
702
703            // Note: Since the file extension does not end with "x", the jsx compiler option
704            // will not be enabled.  The import should be retained regardless.
705            testOrganizeImports("JsxFactoryUnusedJs",
706                {
707                    path: "/test.js",
708                    content: `
709import { React, Other } from "react";
710`,
711                },
712                reactLibFile);
713
714            testOrganizeImports("JsxFactoryUnusedTsx",
715                {
716                    path: "/test.tsx",
717                    content: `
718import { React, Other } from "react";
719`,
720                },
721                reactLibFile);
722
723            testOrganizeImports("JsxFactoryUnusedTs",
724                {
725                    path: "/test.ts",
726                    content: `
727import { React, Other } from "react";
728`,
729                },
730                reactLibFile);
731
732            testOrganizeImports("JsxPragmaTsx",
733                {
734                    path: "/test.tsx",
735                    content: `/** @jsx jsx */
736
737import { Global, jsx } from '@emotion/core';
738import * as React from 'react';
739
740export const App: React.FunctionComponent = _ => <Global><h1>Hello!</h1></Global>
741`,
742                },
743                {
744                    path: "/@emotion/core/index.d.ts",
745                    content: `import {  createElement } from 'react'
746export const jsx: typeof createElement;
747export function Global(props: any): ReactElement<any>;`
748                },
749                {
750                    path: reactLibFile.path,
751                    content: `${reactLibFile.content}
752export namespace React {
753    interface FunctionComponent {
754    }
755}
756`
757                }
758            );
759
760            testOrganizeImports("JsxFragmentPragmaTsx",
761                {
762                    path: "/test.tsx",
763                    content: `/** @jsx h */
764/** @jsxFrag frag */
765import { h, frag } from "@foo/core";
766
767const elem = <><div>Foo</div></>;
768`,
769                },
770                {
771                    path: "/@foo/core/index.d.ts",
772                    content: `export function h(): void;
773export function frag(): void;
774`
775                }
776            );
777
778            describe("Exports", () => {
779
780                testOrganizeExports("MoveToTop",
781                    {
782                        path: "/test.ts",
783                        content: `
784export { F1, F2 } from "lib";
7851;
786export * from "lib";
7872;
788`,
789                    },
790                    libFile);
791
792                /* eslint-disable no-template-curly-in-string */
793                testOrganizeExports("MoveToTop_Invalid",
794                    {
795                        path: "/test.ts",
796                        content: `
797export { F1, F2 } from "lib";
7981;
799export * from "lib";
8002;
801export { b } from ${"`${'lib'}`"};
802export { a } from ${"`${'lib'}`"};
803export { D } from "lib";
8043;
805`,
806                    },
807                    libFile);
808                /* eslint-enable no-template-curly-in-string */
809
810                testOrganizeExports("MoveToTop_WithImportsFirst",
811                    {
812                        path: "/test.ts",
813                        content: `
814import { F1, F2 } from "lib";
8151;
816export { F1, F2 } from "lib";
8172;
818import * as NS from "lib";
8193;
820export * from "lib";
8214;
822F1(); F2(); NS.F1();
823`,
824                    },
825                    libFile);
826
827                testOrganizeExports("MoveToTop_WithExportsFirst",
828                    {
829                        path: "/test.ts",
830                        content: `
831export { F1, F2 } from "lib";
8321;
833import { F1, F2 } from "lib";
8342;
835export * from "lib";
8363;
837import * as NS from "lib";
8384;
839F1(); F2(); NS.F1();
840`,
841                    },
842                    libFile);
843
844                testOrganizeExports("CoalesceMultipleModules",
845                    {
846                        path: "/test.ts",
847                        content: `
848export { d } from "lib1";
849export { b } from "lib1";
850export { c } from "lib2";
851export { a } from "lib2";
852`,
853                    },
854                    { path: "/lib1.ts", content: "export const b = 1, d = 2;" },
855                    { path: "/lib2.ts", content: "export const a = 3, c = 4;" });
856
857                testOrganizeExports("CoalesceTrivia",
858                    {
859                        path: "/test.ts",
860                        content: `
861/*A*/export /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I
862/*J*/export /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R
863`,
864                    },
865                    libFile);
866
867                testOrganizeExports("SortTrivia",
868                    {
869                        path: "/test.ts",
870                        content: `
871/*A*/export /*B*/ * /*C*/ from /*D*/ "lib2" /*E*/;/*F*/ //G
872/*H*/export /*I*/ * /*J*/ from /*K*/ "lib1" /*L*/;/*M*/ //N
873`,
874                    },
875                    { path: "/lib1.ts", content: "" },
876                    { path: "/lib2.ts", content: "" });
877
878                testOrganizeExports("SortHeaderComment",
879                    {
880                        path: "/test.ts",
881                        content: `
882// Header
883export * from "lib2";
884export * from "lib1";
885`,
886                    },
887                    { path: "/lib1.ts", content: "" },
888                    { path: "/lib2.ts", content: "" });
889
890                testOrganizeExports("AmbientModule",
891                    {
892                        path: "/test.ts",
893                        content: `
894declare module "mod" {
895    export { F1 } from "lib";
896    export * from "lib";
897    export { F2 } from "lib";
898}
899    `,
900                    },
901                    libFile);
902
903                testOrganizeExports("TopLevelAndAmbientModule",
904                    {
905                        path: "/test.ts",
906                        content: `
907export { D } from "lib";
908
909declare module "mod" {
910    export { F1 } from "lib";
911    export * from "lib";
912    export { F2 } from "lib";
913}
914
915export { E } from "lib";
916export * from "lib";
917`,
918                    },
919                    libFile);
920            });
921
922            function testOrganizeExports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
923                testOrganizeImports(`${testName}.exports`, testFile, ...otherFiles);
924            }
925
926            function testOrganizeImports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
927                it(testName, () => runBaseline(`organizeImports/${testName}.ts`, testFile, ...otherFiles));
928            }
929
930            function runBaseline(baselinePath: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
931                const { path: testPath, content: testContent } = testFile;
932                const languageService = makeLanguageService(testFile, ...otherFiles);
933                const changes = languageService.organizeImports({ type: "file", fileName: testPath }, testFormatSettings, emptyOptions);
934                assert.equal(changes.length, 1);
935                assert.equal(changes[0].fileName, testPath);
936
937                const newText = textChanges.applyChanges(testContent, changes[0].textChanges);
938                Harness.Baseline.runBaseline(baselinePath, [
939                    "// ==ORIGINAL==",
940                    testContent,
941                    "// ==ORGANIZED==",
942                    newText,
943                ].join(newLineCharacter));
944            }
945
946            function makeLanguageService(...files: TestFSWithWatch.File[]) {
947                const host = projectSystem.createServerHost(files);
948                const projectService = projectSystem.createProjectService(host, { useSingleInferredProject: true });
949                projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? JsxEmit.React : JsxEmit.None });
950                files.forEach(f => projectService.openClientFile(f.path));
951                return projectService.inferredProjects[0].getLanguageService();
952            }
953        });
954
955        function parseImports(...importStrings: string[]): readonly ImportDeclaration[] {
956            const sourceFile = createSourceFile("a.ts", importStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS);
957            const imports = filter(sourceFile.statements, isImportDeclaration);
958            assert.equal(imports.length, importStrings.length);
959            return imports;
960        }
961
962        function parseExports(...exportStrings: string[]): readonly ExportDeclaration[] {
963            const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS);
964            const exports = filter(sourceFile.statements, isExportDeclaration);
965            assert.equal(exports.length, exportStrings.length);
966            return exports;
967        }
968
969        function assertEqual(node1?: Node, node2?: Node) {
970            if (node1 === undefined) {
971                assert.isUndefined(node2);
972                return;
973            }
974            else if (node2 === undefined) {
975                assert.isUndefined(node1); // Guaranteed to fail
976                return;
977            }
978
979            assert.equal(node1.kind, node2.kind);
980
981            switch (node1.kind) {
982                case SyntaxKind.ImportDeclaration:
983                    const decl1 = node1 as ImportDeclaration;
984                    const decl2 = node2 as ImportDeclaration;
985                    assertEqual(decl1.importClause, decl2.importClause);
986                    assertEqual(decl1.moduleSpecifier, decl2.moduleSpecifier);
987                    break;
988                case SyntaxKind.ImportClause:
989                    const clause1 = node1 as ImportClause;
990                    const clause2 = node2 as ImportClause;
991                    assertEqual(clause1.name, clause2.name);
992                    assertEqual(clause1.namedBindings, clause2.namedBindings);
993                    break;
994                case SyntaxKind.NamespaceImport:
995                    const nsi1 = node1 as NamespaceImport;
996                    const nsi2 = node2 as NamespaceImport;
997                    assertEqual(nsi1.name, nsi2.name);
998                    break;
999                case SyntaxKind.NamedImports:
1000                    const ni1 = node1 as NamedImports;
1001                    const ni2 = node2 as NamedImports;
1002                    assertListEqual(ni1.elements, ni2.elements);
1003                    break;
1004                case SyntaxKind.ImportSpecifier:
1005                    const is1 = node1 as ImportSpecifier;
1006                    const is2 = node2 as ImportSpecifier;
1007                    assertEqual(is1.name, is2.name);
1008                    assertEqual(is1.propertyName, is2.propertyName);
1009                    break;
1010                case SyntaxKind.ExportDeclaration:
1011                    const ed1 = node1 as ExportDeclaration;
1012                    const ed2 = node2 as ExportDeclaration;
1013                    assertEqual(ed1.exportClause, ed2.exportClause);
1014                    assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier);
1015                    break;
1016                case SyntaxKind.NamedExports:
1017                    const ne1 = node1 as NamedExports;
1018                    const ne2 = node2 as NamedExports;
1019                    assertListEqual(ne1.elements, ne2.elements);
1020                    break;
1021                case SyntaxKind.ExportSpecifier:
1022                    const es1 = node1 as ExportSpecifier;
1023                    const es2 = node2 as ExportSpecifier;
1024                    assertEqual(es1.name, es2.name);
1025                    assertEqual(es1.propertyName, es2.propertyName);
1026                    break;
1027                case SyntaxKind.Identifier:
1028                    const id1 = node1 as Identifier;
1029                    const id2 = node2 as Identifier;
1030                    assert.equal(id1.text, id2.text);
1031                    break;
1032                case SyntaxKind.StringLiteral:
1033                case SyntaxKind.NoSubstitutionTemplateLiteral:
1034                    const sl1 = node1 as LiteralLikeNode;
1035                    const sl2 = node2 as LiteralLikeNode;
1036                    assert.equal(sl1.text, sl2.text);
1037                    break;
1038                default:
1039                    assert.equal(node1.getText(), node2.getText());
1040                    break;
1041            }
1042        }
1043
1044        function assertListEqual(list1: readonly Node[], list2: readonly Node[]) {
1045            if (list1 === undefined || list2 === undefined) {
1046                assert.isUndefined(list1);
1047                assert.isUndefined(list2);
1048                return;
1049            }
1050
1051            assert.equal(list1.length, list2.length);
1052            for (let i = 0; i < list1.length; i++) {
1053                assertEqual(list1[i], list2[i]);
1054            }
1055        }
1056    });
1057}
1058