Skip to content

Commit a327241

Browse files
committed
Sort exports when organizeImports is run
Note that there's no attempt to remove unused exports. Fixes microsoft#23640
1 parent 6450490 commit a327241

12 files changed

+493
-6
lines changed

src/harness/unittests/organizeImports.ts

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,85 @@ namespace ts {
181181
});
182182
});
183183

184+
describe("Coalesce exports", () => {
185+
it("No exports", () => {
186+
assert.isEmpty(OrganizeImports.coalesceExports([]));
187+
});
188+
189+
it("Sort specifiers", () => {
190+
const sortedExports = parseExports(`export { default as m, a as n, b, y, z as o } from "lib";`);
191+
const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
192+
const expectedCoalescedExports = parseExports(`export { a as n, b, default as m, y, z as o } from "lib";`);
193+
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
194+
});
195+
196+
it("Sort specifiers - case-insensitive", () => {
197+
const sortedExports = parseExports(`export { default as M, a as n, B, y, Z as O } from "lib";`);
198+
const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
199+
const expectedCoalescedExports = parseExports(`export { a as n, B, default as M, y, Z as O } from "lib";`);
200+
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
201+
});
202+
203+
it("Combine namespace re-exports", () => {
204+
const sortedExports = parseExports(
205+
`export * from "lib";`,
206+
`export * from "lib";`);
207+
const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
208+
const expectedCoalescedExports = parseExports(`export * from "lib";`);
209+
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
210+
});
211+
212+
it("Combine property exports", () => {
213+
const sortedExports = parseExports(
214+
`export { x };`,
215+
`export { y as z };`);
216+
const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
217+
const expectedCoalescedExports = parseExports(`export { x, y as z };`);
218+
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
219+
});
220+
221+
it("Combine property re-exports", () => {
222+
const sortedExports = parseExports(
223+
`export { x } from "lib";`,
224+
`export { y as z } from "lib";`);
225+
const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
226+
const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`);
227+
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
228+
});
229+
230+
it("Combine namespace re-export with property re-export", () => {
231+
const sortedExports = parseExports(
232+
`export * from "lib";`,
233+
`export { y } from "lib";`);
234+
const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
235+
const expectedCoalescedExports = sortedExports;
236+
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
237+
});
238+
239+
it("Combine many exports", () => {
240+
const sortedExports = parseExports(
241+
`export { x };`,
242+
`export { y as w, z as default };`,
243+
`export { w as q };`);
244+
const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
245+
const expectedCoalescedExports = parseExports(
246+
`export { w as q, x, y as w, z as default };`);
247+
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
248+
});
249+
250+
it("Combine many re-exports", () => {
251+
const sortedExports = parseExports(
252+
`export { x as a, y } from "lib";`,
253+
`export * from "lib";`,
254+
`export { z as b } from "lib";`);
255+
const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports);
256+
const expectedCoalescedExports = parseExports(
257+
`export * from "lib";`,
258+
`export { x as a, y, z as b } from "lib";`);
259+
assertListEqual(actualCoalescedExports, expectedCoalescedExports);
260+
});
261+
});
262+
184263
describe("Baselines", () => {
185264

186265
const libFile = {
@@ -471,6 +550,154 @@ import { React, Other } from "react";
471550
},
472551
reactLibFile);
473552

553+
describe("Exports", () => {
554+
555+
testOrganizeExports("MoveToTop",
556+
{
557+
path: "/test.ts",
558+
content: `
559+
export { F1, F2 } from "lib";
560+
1;
561+
export * from "lib";
562+
2;
563+
`,
564+
},
565+
libFile);
566+
567+
// tslint:disable no-invalid-template-strings
568+
testOrganizeExports("MoveToTop_Invalid",
569+
{
570+
path: "/test.ts",
571+
content: `
572+
export { F1, F2 } from "lib";
573+
1;
574+
export * from "lib";
575+
2;
576+
export { b } from ${"`${'lib'}`"};
577+
export { a } from ${"`${'lib'}`"};
578+
export { D } from "lib";
579+
3;
580+
`,
581+
},
582+
libFile);
583+
// tslint:enable no-invalid-template-strings
584+
585+
testOrganizeExports("MoveToTop_WithImportsFirst",
586+
{
587+
path: "/test.ts",
588+
content: `
589+
import { F1, F2 } from "lib";
590+
1;
591+
export { F1, F2 } from "lib";
592+
2;
593+
import * as NS from "lib";
594+
3;
595+
export * from "lib";
596+
4;
597+
F1(); F2(); NS.F1();
598+
`,
599+
},
600+
libFile);
601+
602+
testOrganizeExports("MoveToTop_WithExportsFirst",
603+
{
604+
path: "/test.ts",
605+
content: `
606+
export { F1, F2 } from "lib";
607+
1;
608+
import { F1, F2 } from "lib";
609+
2;
610+
export * from "lib";
611+
3;
612+
import * as NS from "lib";
613+
4;
614+
F1(); F2(); NS.F1();
615+
`,
616+
},
617+
libFile);
618+
619+
testOrganizeExports("CoalesceMultipleModules",
620+
{
621+
path: "/test.ts",
622+
content: `
623+
export { d } from "lib1";
624+
export { b } from "lib1";
625+
export { c } from "lib2";
626+
export { a } from "lib2";
627+
`,
628+
},
629+
{ path: "/lib1.ts", content: "export const b = 1, d = 2;" },
630+
{ path: "/lib2.ts", content: "export const a = 3, c = 4;" });
631+
632+
testOrganizeExports("CoalesceTrivia",
633+
{
634+
path: "/test.ts",
635+
content: `
636+
/*A*/export /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I
637+
/*J*/export /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R
638+
`,
639+
},
640+
libFile);
641+
642+
testOrganizeExports("SortTrivia",
643+
{
644+
path: "/test.ts",
645+
content: `
646+
/*A*/export /*B*/ * /*C*/ from /*D*/ "lib2" /*E*/;/*F*/ //G
647+
/*H*/export /*I*/ * /*J*/ from /*K*/ "lib1" /*L*/;/*M*/ //N
648+
`,
649+
},
650+
{ path: "/lib1.ts", content: "" },
651+
{ path: "/lib2.ts", content: "" });
652+
653+
testOrganizeExports("SortHeaderComment",
654+
{
655+
path: "/test.ts",
656+
content: `
657+
// Header
658+
export * from "lib2";
659+
export * from "lib1";
660+
`,
661+
},
662+
{ path: "/lib1.ts", content: "" },
663+
{ path: "/lib2.ts", content: "" });
664+
665+
testOrganizeExports("AmbientModule",
666+
{
667+
path: "/test.ts",
668+
content: `
669+
declare module "mod" {
670+
export { F1 } from "lib";
671+
export * from "lib";
672+
export { F2 } from "lib";
673+
}
674+
`,
675+
},
676+
libFile);
677+
678+
testOrganizeExports("TopLevelAndAmbientModule",
679+
{
680+
path: "/test.ts",
681+
content: `
682+
export { D } from "lib";
683+
684+
declare module "mod" {
685+
export { F1 } from "lib";
686+
export * from "lib";
687+
export { F2 } from "lib";
688+
}
689+
690+
export { E } from "lib";
691+
export * from "lib";
692+
`,
693+
},
694+
libFile);
695+
});
696+
697+
function testOrganizeExports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
698+
testOrganizeImports(`${testName}.exports`, testFile, ...otherFiles);
699+
}
700+
474701
function testOrganizeImports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) {
475702
it(testName, () => runBaseline(`organizeImports/${testName}.ts`, testFile, ...otherFiles));
476703
}
@@ -509,6 +736,13 @@ import { React, Other } from "react";
509736
return imports;
510737
}
511738

739+
function parseExports(...exportStrings: string[]): ReadonlyArray<ExportDeclaration> {
740+
const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS);
741+
const exports = filter(sourceFile.statements, isExportDeclaration);
742+
assert.equal(exports.length, exportStrings.length);
743+
return exports;
744+
}
745+
512746
function assertEqual(node1?: Node, node2?: Node) {
513747
if (node1 === undefined) {
514748
assert.isUndefined(node2);
@@ -550,6 +784,23 @@ import { React, Other } from "react";
550784
assertEqual(is1.name, is2.name);
551785
assertEqual(is1.propertyName, is2.propertyName);
552786
break;
787+
case SyntaxKind.ExportDeclaration:
788+
const ed1 = node1 as ExportDeclaration;
789+
const ed2 = node2 as ExportDeclaration;
790+
assertEqual(ed1.exportClause, ed2.exportClause);
791+
assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier);
792+
break;
793+
case SyntaxKind.NamedExports:
794+
const ne1 = node1 as NamedExports;
795+
const ne2 = node2 as NamedExports;
796+
assertListEqual(ne1.elements, ne2.elements);
797+
break;
798+
case SyntaxKind.ExportSpecifier:
799+
const es1 = node1 as ExportSpecifier;
800+
const es2 = node2 as ExportSpecifier;
801+
assertEqual(es1.name, es2.name);
802+
assertEqual(es1.propertyName, es2.propertyName);
803+
break;
553804
case SyntaxKind.Identifier:
554805
const id1 = node1 as Identifier;
555806
const id2 = node2 as Identifier;

0 commit comments

Comments
 (0)