feat(52883): Organize Imports doesn't treat exports the same way and merges groups (#52982)

This commit is contained in:
Oleksandr T
2023-03-03 17:03:21 -08:00
committed by GitHub
parent 6823559fa9
commit 3a6752ca14
5 changed files with 118 additions and 20 deletions
+49 -18
View File
@@ -89,7 +89,7 @@ export function organizeImports(
const shouldCombine = shouldSort; // These are currently inseparable, but I draw a distinction for clarity and in case we add modes in the future.
const shouldRemove = mode === OrganizeImportsMode.RemoveUnused || mode === OrganizeImportsMode.All;
// All of the old ImportDeclarations in the file, in syntactic order.
const topLevelImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration));
const topLevelImportGroupDecls = groupByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration));
const comparer = getOrganizeImportsComparerWithDetection(preferences, shouldSort ? () => detectSortingWorker(topLevelImportGroupDecls, preferences) === SortKind.CaseInsensitive : undefined);
@@ -105,14 +105,14 @@ export function organizeImports(
// Exports are always used
if (mode !== OrganizeImportsMode.RemoveUnused) {
// All of the old ExportDeclarations in the file, in syntactic order.
const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration);
organizeImportsWorker(topLevelExportDecls, group => coalesceExportsWorker(group, comparer));
getTopLevelExportGroups(sourceFile).forEach(exportGroupDecl =>
organizeImportsWorker(exportGroupDecl, group => coalesceExportsWorker(group, comparer)));
}
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
if (!ambientModule.body) continue;
const ambientModuleImportGroupDecls = groupImportsByNewlineContiguous(sourceFile, ambientModule.body.statements.filter(isImportDeclaration));
const ambientModuleImportGroupDecls = groupByNewlineContiguous(sourceFile, ambientModule.body.statements.filter(isImportDeclaration));
ambientModuleImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier));
// Exports are always used
@@ -146,7 +146,7 @@ export function organizeImports(
? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiersWorker(group1[0].moduleSpecifier, group2[0].moduleSpecifier, comparer))
: oldImportGroups;
const newImportDecls = flatMap(sortedImportGroups, importGroup =>
getExternalModuleName(importGroup[0].moduleSpecifier)
getExternalModuleName(importGroup[0].moduleSpecifier) || importGroup[0].moduleSpecifier === undefined
? coalesce(importGroup)
: importGroup);
@@ -175,30 +175,30 @@ export function organizeImports(
}
}
function groupImportsByNewlineContiguous(sourceFile: SourceFile, importDecls: ImportDeclaration[]): ImportDeclaration[][] {
function groupByNewlineContiguous<T extends ImportDeclaration | ExportDeclaration>(sourceFile: SourceFile, decls: T[]): T[][] {
const scanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ false, sourceFile.languageVariant);
const groupImports: ImportDeclaration[][] = [];
const group: T[][] = [];
let groupIndex = 0;
for (const topLevelImportDecl of importDecls) {
if (groupImports[groupIndex] && isNewGroup(sourceFile, topLevelImportDecl, scanner)) {
for (const decl of decls) {
if (group[groupIndex] && isNewGroup(sourceFile, decl, scanner)) {
groupIndex++;
}
if (!groupImports[groupIndex]) {
groupImports[groupIndex] = [];
if (!group[groupIndex]) {
group[groupIndex] = [];
}
groupImports[groupIndex].push(topLevelImportDecl);
group[groupIndex].push(decl);
}
return groupImports;
return group;
}
// a new group is created if an import includes at least two new line
// a new group is created if an import/export includes at least two new line
// new line from multi-line comment doesn't count
function isNewGroup(sourceFile: SourceFile, topLevelImportDecl: ImportDeclaration, scanner: Scanner) {
const startPos = topLevelImportDecl.getFullStart();
const endPos = topLevelImportDecl.getStart();
function isNewGroup(sourceFile: SourceFile, decl: ImportDeclaration | ExportDeclaration, scanner: Scanner) {
const startPos = decl.getFullStart();
const endPos = decl.getStart();
scanner.setText(sourceFile.text, startPos, endPos - startPos);
let numberOfNewLines = 0;
@@ -617,7 +617,7 @@ function getModuleSpecifierExpression(declaration: AnyImportOrRequireStatement):
/** @internal */
export function detectSorting(sourceFile: SourceFile, preferences: UserPreferences): SortKind {
return detectSortingWorker(
groupImportsByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)),
groupByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration)),
preferences);
}
@@ -824,3 +824,34 @@ function getOrganizeImportsComparerWithDetection(preferences: UserPreferences, d
const ignoreCase = typeof preferences.organizeImportsIgnoreCase === "boolean" ? preferences.organizeImportsIgnoreCase : detectIgnoreCase?.() ?? false;
return getOrganizeImportsComparer(preferences, ignoreCase);
}
function getTopLevelExportGroups(sourceFile: SourceFile) {
const topLevelExportGroups: ExportDeclaration[][] = [];
const statements = sourceFile.statements;
const len = length(statements);
let i = 0;
let groupIndex = 0;
while (i < len) {
if (isExportDeclaration(statements[i])) {
if (topLevelExportGroups[groupIndex] === undefined) {
topLevelExportGroups[groupIndex] = [];
}
const exportDecl = statements[i] as ExportDeclaration;
if (exportDecl.moduleSpecifier) {
topLevelExportGroups[groupIndex].push(exportDecl);
i++;
}
else {
while (i < len && isExportDeclaration(statements[i])) {
topLevelExportGroups[groupIndex].push(statements[i++] as ExportDeclaration);
}
groupIndex++;
}
}
else {
i++;
}
}
return flatMap(topLevelExportGroups, exportGroupDecls => groupByNewlineContiguous(sourceFile, exportGroupDecls));
}
@@ -13,11 +13,12 @@ export * from "lib";
// ==ORGANIZED==
export * from "lib";
export { D, E } from "lib";
export { D } from "lib";
declare module "mod" {
export * from "lib";
export { F1, F2 } from "lib";
}
export * from "lib";
export { E } from "lib";
@@ -0,0 +1,33 @@
/// <reference path="fourslash.ts" />
// @filename: /A.ts
////export interface A {}
////export function bFuncA(a: A) {}
// @filename: /B.ts
////export interface B {}
////export function bFuncB(b: B) {}
// @filename: /C.ts
////export interface C {}
////export function bFuncC(c: C) {}
// @filename: /test.ts
////export { C } from "./C";
////export { B } from "./B";
////export { A } from "./A";
////
////export { bFuncC } from "./C";
////export { bFuncB } from "./B";
////export { bFuncA } from "./A";
goTo.file("/test.ts");
verify.organizeImports(
`export { A } from "./A";
export { B } from "./B";
export { C } from "./C";
export { bFuncA } from "./A";
export { bFuncB } from "./B";
export { bFuncC } from "./C";
`);
@@ -0,0 +1,21 @@
/// <reference path="fourslash.ts" />
////const a = 1;
////export { a };
////
////const b = 1;
////export { b };
////
////const c = 1;
////export { c };
verify.organizeImports(
`const a = 1;
export { a };
const b = 1;
export { b };
const c = 1;
export { c };
`);
@@ -0,0 +1,12 @@
/// <reference path="fourslash.ts" />
////const a = 1;
////const b = 1;
////export { a };
////export { b };
verify.organizeImports(
`const a = 1;
const b = 1;
export { a, b };
`);