Skip to content

Commit 04a3512

Browse files
authored
Merge pull request #24237 from amcasey/GH23640
Sort exports when organizeImports is run
2 parents 3bb9ccf + 8f662a9 commit 04a3512

12 files changed

+494
-18
lines changed

src/harness/unittests/organizeImports.ts

+244-7
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,6 @@ namespace ts {
4444
assert.isEmpty(OrganizeImports.coalesceImports([]));
4545
});
4646

47-
it("Sort specifiers", () => {
48-
const sortedImports = parseImports(`import { default as m, a as n, b, y, z as o } from "lib";`);
49-
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
50-
const expectedCoalescedImports = parseImports(`import { a as n, b, default as m, y, z as o } from "lib";`);
51-
assertListEqual(actualCoalescedImports, expectedCoalescedImports);
52-
});
53-
5447
it("Sort specifiers - case-insensitive", () => {
5548
const sortedImports = parseImports(`import { default as M, a as n, B, y, Z as O } from "lib";`);
5649
const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports);
@@ -181,6 +174,78 @@ namespace ts {
181174
});
182175
});
183176

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

186251
const libFile = {
@@ -471,6 +536,154 @@ import { React, Other } from "react";
471536
},
472537
reactLibFile);
473538

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

725+
function parseExports(...exportStrings: string[]): ReadonlyArray<ExportDeclaration> {
726+
const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS);
727+
const exports = filter(sourceFile.statements, isExportDeclaration);
728+
assert.equal(exports.length, exportStrings.length);
729+
return exports;
730+
}
731+
512732
function assertEqual(node1?: Node, node2?: Node) {
513733
if (node1 === undefined) {
514734
assert.isUndefined(node2);
@@ -550,6 +770,23 @@ import { React, Other } from "react";
550770
assertEqual(is1.name, is2.name);
551771
assertEqual(is1.propertyName, is2.propertyName);
552772
break;
773+
case SyntaxKind.ExportDeclaration:
774+
const ed1 = node1 as ExportDeclaration;
775+
const ed2 = node2 as ExportDeclaration;
776+
assertEqual(ed1.exportClause, ed2.exportClause);
777+
assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier);
778+
break;
779+
case SyntaxKind.NamedExports:
780+
const ne1 = node1 as NamedExports;
781+
const ne2 = node2 as NamedExports;
782+
assertListEqual(ne1.elements, ne2.elements);
783+
break;
784+
case SyntaxKind.ExportSpecifier:
785+
const es1 = node1 as ExportSpecifier;
786+
const es2 = node2 as ExportSpecifier;
787+
assertEqual(es1.name, es2.name);
788+
assertEqual(es1.propertyName, es2.propertyName);
789+
break;
553790
case SyntaxKind.Identifier:
554791
const id1 = node1 as Identifier;
555792
const id2 = node2 as Identifier;

0 commit comments

Comments
 (0)