Skip to content

Commit 3a6752c

Browse files
authored
feat(52883): Organize Imports doesn't treat exports the same way and merges groups (microsoft#52982)
1 parent 6823559 commit 3a6752c

File tree

5 files changed

+118
-20
lines changed

5 files changed

+118
-20
lines changed

src/services/organizeImports.ts

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export function organizeImports(
8989
const shouldCombine = shouldSort; // These are currently inseparable, but I draw a distinction for clarity and in case we add modes in the future.
9090
const shouldRemove = mode === OrganizeImportsMode.RemoveUnused || mode === OrganizeImportsMode.All;
9191
// All of the old ImportDeclarations in the file, in syntactic order.
92-
const topLevelImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration));
92+
const topLevelImportGroupDecls = groupByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration));
9393

9494
const comparer = getOrganizeImportsComparerWithDetection(preferences, shouldSort ? () => detectSortingWorker(topLevelImportGroupDecls, preferences) === SortKind.CaseInsensitive : undefined);
9595

@@ -105,14 +105,14 @@ export function organizeImports(
105105
// Exports are always used
106106
if (mode !== OrganizeImportsMode.RemoveUnused) {
107107
// All of the old ExportDeclarations in the file, in syntactic order.
108-
const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration);
109-
organizeImportsWorker(topLevelExportDecls, group => coalesceExportsWorker(group, comparer));
108+
getTopLevelExportGroups(sourceFile).forEach(exportGroupDecl =>
109+
organizeImportsWorker(exportGroupDecl, group => coalesceExportsWorker(group, comparer)));
110110
}
111111

112112
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
113113
if (!ambientModule.body) continue;
114114

115-
const ambientModuleImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, ambientModule.body.statements.filter(isImportDeclaration));
115+
const ambientModuleImportGroupDecls = groupByNewlineContiguous(sourceFile, ambientModule.body.statements.filter(isImportDeclaration));
116116
ambientModuleImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier));
117117

118118
// Exports are always used
@@ -146,7 +146,7 @@ export function organizeImports(
146146
? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiersWorker(group1[0].moduleSpecifier, group2[0].moduleSpecifier, comparer))
147147
: oldImportGroups;
148148
const newImportDecls = flatMap(sortedImportGroups, importGroup =>
149-
getExternalModuleName(importGroup[0].moduleSpecifier)
149+
getExternalModuleName(importGroup[0].moduleSpecifier) || importGroup[0].moduleSpecifier === undefined
150150
? coalesce(importGroup)
151151
: importGroup);
152152

@@ -175,30 +175,30 @@ export function organizeImports(
175175
}
176176
}
177177

178-
function groupImportsByNewlineContiguous(sourceFile: SourceFile, importDecls: ImportDeclaration[]): ImportDeclaration[][] {
178+
function groupByNewlineContiguous<T extends ImportDeclaration | ExportDeclaration>(sourceFile: SourceFile, decls: T[]): T[][] {
179179
const scanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ false, sourceFile.languageVariant);
180-
const groupImports: ImportDeclaration[][] = [];
180+
const group: T[][] = [];
181181
let groupIndex = 0;
182-
for (const topLevelImportDecl of importDecls) {
183-
if (groupImports[groupIndex] && isNewGroup(sourceFile, topLevelImportDecl, scanner)) {
182+
for (const decl of decls) {
183+
if (group[groupIndex] && isNewGroup(sourceFile, decl, scanner)) {
184184
groupIndex++;
185185
}
186186

187-
if (!groupImports[groupIndex]) {
188-
groupImports[groupIndex] = [];
187+
if (!group[groupIndex]) {
188+
group[groupIndex] = [];
189189
}
190190

191-
groupImports[groupIndex].push(topLevelImportDecl);
191+
group[groupIndex].push(decl);
192192
}
193193

194-
return groupImports;
194+
return group;
195195
}
196196

197-
// a new group is created if an import includes at least two new line
197+
// a new group is created if an import/export includes at least two new line
198198
// new line from multi-line comment doesn't count
199-
function isNewGroup(sourceFile: SourceFile, topLevelImportDecl: ImportDeclaration, scanner: Scanner) {
200-
const startPos = topLevelImportDecl.getFullStart();
201-
const endPos = topLevelImportDecl.getStart();
199+
function isNewGroup(sourceFile: SourceFile, decl: ImportDeclaration | ExportDeclaration, scanner: Scanner) {
200+
const startPos = decl.getFullStart();
201+
const endPos = decl.getStart();
202202
scanner.setText(sourceFile.text, startPos, endPos - startPos);
203203

204204
let numberOfNewLines = 0;
@@ -617,7 +617,7 @@ function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement):
617617
/** @internal */
618618
export function detectSorting(sourceFile: SourceFile, preferences: UserPreferences): SortKind {
619619
return detectSortingWorker(
620-
groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)),
620+
groupByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)),
621621
preferences);
622622
}
623623

@@ -824,3 +824,34 @@ function getOrganizeImportsComparerWithDetection(preferences: UserPreferences, d
824824
const ignoreCase = typeof preferences.organizeImportsIgnoreCase === "boolean" ? preferences.organizeImportsIgnoreCase : detectIgnoreCase?.() ?? false;
825825
return getOrganizeImportsComparer(preferences, ignoreCase);
826826
}
827+
828+
function getTopLevelExportGroups(sourceFile: SourceFile) {
829+
const topLevelExportGroups: ExportDeclaration[][] = [];
830+
const statements = sourceFile.statements;
831+
const len = length(statements);
832+
833+
let i = 0;
834+
let groupIndex = 0;
835+
while (i < len) {
836+
if (isExportDeclaration(statements[i])) {
837+
if (topLevelExportGroups[groupIndex] === undefined) {
838+
topLevelExportGroups[groupIndex] = [];
839+
}
840+
const exportDecl = statements[i] as ExportDeclaration;
841+
if (exportDecl.moduleSpecifier) {
842+
topLevelExportGroups[groupIndex].push(exportDecl);
843+
i++;
844+
}
845+
else {
846+
while (i < len && isExportDeclaration(statements[i])) {
847+
topLevelExportGroups[groupIndex].push(statements[i++] as ExportDeclaration);
848+
}
849+
groupIndex++;
850+
}
851+
}
852+
else {
853+
i++;
854+
}
855+
}
856+
return flatMap(topLevelExportGroups, exportGroupDecls => groupByNewlineContiguous(sourceFile, exportGroupDecls));
857+
}

tests/baselines/reference/organizeImports/TopLevelAndAmbientModule.exports.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ export * from "lib";
1313

1414
// ==ORGANIZED==
1515

16-
export * from "lib";
17-
export { D, E } from "lib";
16+
export { D } from "lib";
1817

1918
declare module "mod" {
2019
export * from "lib";
2120
export { F1, F2 } from "lib";
2221
}
2322

23+
export * from "lib";
24+
export { E } from "lib";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @filename: /A.ts
4+
////export interface A {}
5+
////export function bFuncA(a: A) {}
6+
7+
// @filename: /B.ts
8+
////export interface B {}
9+
////export function bFuncB(b: B) {}
10+
11+
// @filename: /C.ts
12+
////export interface C {}
13+
////export function bFuncC(c: C) {}
14+
15+
// @filename: /test.ts
16+
////export { C } from "./C";
17+
////export { B } from "./B";
18+
////export { A } from "./A";
19+
////
20+
////export { bFuncC } from "./C";
21+
////export { bFuncB } from "./B";
22+
////export { bFuncA } from "./A";
23+
24+
goTo.file("/test.ts");
25+
verify.organizeImports(
26+
`export { A } from "./A";
27+
export { B } from "./B";
28+
export { C } from "./C";
29+
30+
export { bFuncA } from "./A";
31+
export { bFuncB } from "./B";
32+
export { bFuncC } from "./C";
33+
`);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////const a = 1;
4+
////export { a };
5+
////
6+
////const b = 1;
7+
////export { b };
8+
////
9+
////const c = 1;
10+
////export { c };
11+
12+
verify.organizeImports(
13+
`const a = 1;
14+
export { a };
15+
16+
const b = 1;
17+
export { b };
18+
19+
const c = 1;
20+
export { c };
21+
`);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////const a = 1;
4+
////const b = 1;
5+
////export { a };
6+
////export { b };
7+
8+
verify.organizeImports(
9+
`const a = 1;
10+
const b = 1;
11+
export { a, b };
12+
`);

0 commit comments

Comments
 (0)