Skip to content

Commit 8067f5f

Browse files
committed
in progress
1 parent dadfe54 commit 8067f5f

File tree

4 files changed

+154
-97
lines changed

4 files changed

+154
-97
lines changed

src/services/organizeImports.ts

Lines changed: 114 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import {
5050
map,
5151
MemoizeCache,
5252
memoizeCached,
53-
mergeAndDeduplicateSorted,
5453
NamedImportBindings,
5554
NamedImports,
5655
NamespaceImport,
@@ -70,9 +69,6 @@ import {
7069
tryCast,
7170
UserPreferences,
7271
} from "./_namespaces/ts";
73-
import {
74-
getDiffNum,
75-
} from "./utilities";
7672

7773
/**
7874
* Organize imports by:
@@ -100,46 +96,36 @@ export function organizeImports(
10096
// All of the old ImportDeclarations in the file, in syntactic order.
10197
const topLevelImportGroupDecls = groupByNewlineContiguous(sourceFile, sourceFile.statements.filter(isImportDeclaration));
10298

103-
let { moduleSpecifierComparer, namedImportComparer, typeOrder } = getDetectionByDiff(topLevelImportGroupDecls, preferences);
104-
10599
const DefaultComparer = getOrganizeImportsComparer(preferences, /*ignoreCase*/ true);
106-
const exportComparer = namedImportComparer ?? moduleSpecifierComparer ?? DefaultComparer;
107-
108-
moduleSpecifierComparer = moduleSpecifierComparer ?? DefaultComparer;
109-
namedImportComparer = namedImportComparer ?? DefaultComparer;
110-
typeOrder = typeOrder ?? preferences.organizeImportsTypeOrder ?? "last";
111100

112-
const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => {
113-
if (shouldRemove) importGroup = removeUnusedImports(importGroup, sourceFile, program);
114-
if (shouldCombine) importGroup = coalesceImportsWorker(importGroup, namedImportComparer ?? DefaultComparer, sourceFile, { organizeImportsTypeOrder: typeOrder });
115-
if (shouldSort) importGroup = stableSort(importGroup, (s1, s2) => compareImportsOrRequireStatements(s1, s2, moduleSpecifierComparer ?? DefaultComparer));
116-
return importGroup;
117-
};
101+
let detectedModuleCaseComparer = DefaultComparer;
102+
let detectedNamedImportCaseComparer = (s1, s2) => compareImportOrExportSpecifiers(s1, s2, DefaultComparer, {organizeImportsTypeOrder: "last"});
103+
let detectedTypeOrder : typeof preferences.organizeImportsTypeOrder = "last";
118104

119-
topLevelImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier));
105+
topLevelImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl));
120106

121107
// Exports are always used
122108
if (mode !== OrganizeImportsMode.RemoveUnused) {
123109
// All of the old ExportDeclarations in the file, in syntactic order.
124-
getTopLevelExportGroups(sourceFile).forEach(exportGroupDecl => organizeImportsWorker(exportGroupDecl, group => coalesceExportsWorker(group, exportComparer, preferences)));
110+
getTopLevelExportGroups(sourceFile).forEach(exportGroupDecl => organizeExportsWorker(exportGroupDecl));
125111
}
126112

127113
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
128114
if (!ambientModule.body) continue;
129115

130116
const ambientModuleImportGroupDecls = groupByNewlineContiguous(sourceFile, ambientModule.body.statements.filter(isImportDeclaration));
131-
ambientModuleImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl, processImportsOfSameModuleSpecifier));
117+
ambientModuleImportGroupDecls.forEach(importGroupDecl => organizeImportsWorker(importGroupDecl));
132118

133119
// Exports are always used
134120
if (mode !== OrganizeImportsMode.RemoveUnused) {
135121
const ambientModuleExportDecls = ambientModule.body.statements.filter(isExportDeclaration);
136-
organizeImportsWorker(ambientModuleExportDecls, group => coalesceExportsWorker(group, exportComparer, { organizeImportsTypeOrder: typeOrder }));
122+
organizeExportsWorker(ambientModuleExportDecls);
137123
}
138124
}
139125

140126
return changeTracker.getChanges();
141127

142-
function organizeImportsWorker<T extends ImportDeclaration | ExportDeclaration>(
128+
function organizeDeclsWorker<T extends ImportDeclaration | ExportDeclaration>(
143129
oldImportDecls: readonly T[],
144130
coalesce: (group: readonly T[]) => readonly T[],
145131
) {
@@ -158,7 +144,7 @@ export function organizeImports(
158144
? group(oldImportDecls, importDecl => getExternalModuleName(importDecl.moduleSpecifier)!)
159145
: [oldImportDecls];
160146
const sortedImportGroups = shouldSort
161-
? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiersWorker(group1[0].moduleSpecifier, group2[0].moduleSpecifier, moduleSpecifierComparer ?? DefaultComparer))
147+
? stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiersWorker(group1[0].moduleSpecifier, group2[0].moduleSpecifier, detectedModuleCaseComparer))
162148
: oldImportGroups;
163149
const newImportDecls = flatMap(sortedImportGroups, importGroup =>
164150
getExternalModuleName(importGroup[0].moduleSpecifier) || importGroup[0].moduleSpecifier === undefined
@@ -188,6 +174,32 @@ export function organizeImports(
188174
}, hasTrailingComment);
189175
}
190176
}
177+
178+
function organizeImportsWorker(oldImportDecls: readonly ImportDeclaration[], comparer?: Comparer<string>) {
179+
if (!comparer) {
180+
const { moduleSpecifierComparer, namedImportComparer, typeOrder } = getDetectionByDiff(topLevelImportGroupDecls, preferences)
181+
182+
detectedModuleCaseComparer = moduleSpecifierComparer ?? detectedModuleCaseComparer;
183+
detectedNamedImportCaseComparer = namedImportComparer ?? detectedNamedImportCaseComparer;
184+
detectedTypeOrder = typeOrder ?? detectedTypeOrder;
185+
// TODO return unset comparer
186+
}
187+
188+
const processImportsOfSameModuleSpecifier = (importGroup: readonly ImportDeclaration[]) => {
189+
if (shouldRemove) importGroup = removeUnusedImports(importGroup, sourceFile, program);
190+
if (shouldCombine) importGroup = coalesceImportsWorker(importGroup, detectedNamedImportCaseComparer, sourceFile, { organizeImportsTypeOrder: detectedTypeOrder });
191+
if (shouldSort) importGroup = stableSort(importGroup, (s1, s2) => compareImportsOrRequireStatements(s1, s2, detectedModuleCaseComparer));
192+
return importGroup;
193+
};
194+
195+
organizeDeclsWorker(oldImportDecls, processImportsOfSameModuleSpecifier);
196+
// return { moduleSpecifierComparer, namedImportComparer, typeOrder };
197+
}
198+
199+
function organizeExportsWorker(oldExportDecls: readonly ExportDeclaration[], comparer?: Comparer<string>) {
200+
const useComparer = comparer ?? DefaultComparer;
201+
organizeDeclsWorker(oldExportDecls, group => coalesceExportsWorker(group, useComparer, { organizeImportsTypeOrder: detectedTypeOrder ?? preferences.organizeImportsTypeOrder }))
202+
}
191203
}
192204

193205
function groupByNewlineContiguous<T extends ImportDeclaration | ExportDeclaration>(sourceFile: SourceFile, decls: T[]): T[][] {
@@ -967,19 +979,20 @@ export function getDetectionByDiff(importDeclsByGroup: ImportDeclaration[][], pr
967979
const CASE_SENSITIVE_COMPARER = getOrganizeImportsComparer(preferences, /*ignoreCase*/ false);
968980
comparers = [CASE_INSENSITIVE_COMPARER, CASE_SENSITIVE_COMPARER];
969981

970-
const moduleSpecifiersByGroup: string[][] = [];
971-
importDeclsByGroup.forEach(importGroup => {
972-
// turns importdeclbygroup into string[][] of module specifiers by group to detect sorting on module specifiers
973-
moduleSpecifiersByGroup.push(importGroup.map(i => getExternalModuleName(i.moduleSpecifier)!));
974-
});
975-
comparer.moduleSpecifierComparer = detectCaseSensitivityByDiff(moduleSpecifiersByGroup);
982+
getModuleSpecifierNames(importDeclsByGroup, comparer, detectCaseSensitivityBySort);
976983
}
977984

978985
// filter for import declarations with named imports. Will be a flat array of import declarations without separations by group
986+
let bothNamedImports = false;
979987
const importDeclsWithNamed = importDeclsByGroup.map(importGroup =>
980988
importGroup.filter(i => {
981-
const namedImports = tryCast(i.importClause?.namedBindings, isNamedImports)?.elements.length! > 1;
982-
return namedImports;
989+
const namedImports = tryCast(i.importClause?.namedBindings, isNamedImports)?.elements;
990+
if (!namedImports?.length) return false;
991+
if (!bothNamedImports && namedImports.some(n => n.isTypeOnly) && namedImports.some(n => !n.isTypeOnly)) {
992+
//todo:improve check
993+
bothNamedImports = true;
994+
}
995+
return true;
983996
})
984997
).flat();
985998

@@ -988,27 +1001,37 @@ export function getDetectionByDiff(importDeclsByGroup: ImportDeclaration[][], pr
9881001

9891002
// TODO combine imports with the same module specifier
9901003

991-
let bothNamedImports = false;
9921004
// formats the code in order to detect type order
9931005
const namedImportsByDecl = importDeclsWithNamed.map(importDecl => {
994-
const originalNamedImports = tryCast(importDecl.importClause?.namedBindings, isNamedImports)?.elements.map(n => n.name.text);
995-
if (!originalNamedImports) return { originalNamedImports: [] };
996-
const { regular, type } = groupBy((importDecl.importClause?.namedBindings as NamedImports).elements, s => s.isTypeOnly ? "type" : "regular");
997-
const regularImportNames = regular?.map(n => n.name.text);
998-
const typeImportNames = type?.map(n => n.name.text);
999-
if (regularImportNames && typeImportNames) {
1000-
bothNamedImports = true;
1001-
}
1002-
return { regularImportNames, typeImportNames, originalNamedImports };
1003-
});
1004-
1005-
const { namedImportComparer, typeOrder } = detectNamedImportOrganizationByDiff(namedImportsByDecl, bothNamedImports);
1006+
return tryCast(importDecl.importClause?.namedBindings, isNamedImports)?.elements;
1007+
}).filter(elements => elements !== undefined) as any as ImportSpecifier[][];
1008+
// const originalNamedImports = tryCast(importDecl.importClause?.namedBindings, isNamedImports)?.elements.map(n => n.name.text);
1009+
// if (!originalNamedImports) return { originalNamedImports: [] };
1010+
// const { regular, type } = groupBy((importDecl.importClause?.namedBindings as NamedImports).elements, s => s.isTypeOnly ? "type" : "regular");
1011+
// const regularImportNames = regular?.map(n => n.name.text);
1012+
// const typeImportNames = type?.map(n => n.name.text);
1013+
// if (regularImportNames && typeImportNames) {
1014+
// bothNamedImports = true;
1015+
// }
1016+
// return { regularImportNames, typeImportNames, originalNamedImports };
1017+
// });
1018+
1019+
const { namedImportComparer, typeOrder } = detectNamedImportOrganizationBySort(namedImportsByDecl);
10061020
comparer.namedImportComparer = namedImportComparer;
10071021
comparer.typeOrder = typeOrder;
10081022

10091023
return comparer;
10101024

1011-
function detectCaseSensitivityByDiff(originalGroups: string[][]): Comparer<string> {
1025+
function getSortedMeasure<T>(arr: readonly T[], comparer: Comparer<T>) {
1026+
let i = 0;
1027+
for (let j = 0; j < arr.length-1; j++) {
1028+
if (comparer(arr[j], arr[j+1]) > 0) {
1029+
i++;
1030+
}
1031+
}
1032+
return i;
1033+
}
1034+
function detectCaseSensitivityBySort(originalGroups: string[][]): Comparer<string> {
10121035
// each entry in originalGroups will be sorted and compared against the original entry.
10131036
// the total diff of each comparison is the sum of the diffs of all groups
10141037
let bestComparer;
@@ -1020,8 +1043,8 @@ export function getDetectionByDiff(importDeclsByGroup: ImportDeclaration[][], pr
10201043
for (const listToSort of originalGroups) {
10211044
if (listToSort.length <= 1) continue;
10221045

1023-
const sortedList = sort(listToSort, curComparer) as any as string[];
1024-
const diff = getDiffNum(listToSort, sortedList);
1046+
// const sortedList = sort(listToSort, curComparer) as any as string[];
1047+
const diff = getSortedMeasure(listToSort, curComparer);
10251048
diffOfCurrentComparer += diff;
10261049
}
10271050

@@ -1033,26 +1056,26 @@ export function getDetectionByDiff(importDeclsByGroup: ImportDeclaration[][], pr
10331056
return bestComparer ?? comparers[0];
10341057
}
10351058

1036-
interface NamedImportByDecl {
1037-
originalNamedImports: string[];
1038-
regularImportNames?: string[];
1039-
typeImportNames?: string[];
1040-
}
1041-
function detectNamedImportOrganizationByDiff(originalGroups: NamedImportByDecl[], bothNamedImports: boolean): { namedImportComparer: Comparer<string>; typeOrder?: "first" | "last" | "inline"; } {
1059+
// interface NamedImportByDecl {
1060+
// originalNamedImports: string[];
1061+
// regularImportNames?: string[];
1062+
// typeImportNames?: string[];
1063+
// }
1064+
function detectNamedImportOrganizationBySort(originalGroups: ImportSpecifier[][]): { namedImportComparer: Comparer<string>; typeOrder?: "first" | "last" | "inline"; } {
10421065
// if we don't have any import statements with both named regular and type imports, we do not need to detect a type ordering
10431066
if (!bothNamedImports) {
1044-
return { namedImportComparer: detectCaseSensitivityByDiff(originalGroups.map(i => i.originalNamedImports)) };
1045-
}
1046-
if (preferences.organizeImportsTypeOrder !== undefined) {
1047-
switch (preferences.organizeImportsTypeOrder) {
1048-
case "first":
1049-
return { namedImportComparer: detectCaseSensitivityByDiff(originalGroups.map(i => [i.typeImportNames ?? [], i.regularImportNames ?? []]).flat()), typeOrder: "first" };
1050-
case "last":
1051-
return { namedImportComparer: detectCaseSensitivityByDiff(originalGroups.map(i => [i.typeImportNames ?? [], i.regularImportNames ?? []]).flat()), typeOrder: "last" };
1052-
case "inline":
1053-
return { namedImportComparer: detectCaseSensitivityByDiff(originalGroups.map(i => i.originalNamedImports)), typeOrder: "inline" };
1054-
}
1067+
return { namedImportComparer: detectCaseSensitivityBySort(originalGroups.map(i => i.map(n =>n.name.text))) };
10551068
}
1069+
// if (preferences.organizeImportsTypeOrder !== undefined) {
1070+
// switch (preferences.organizeImportsTypeOrder) {
1071+
// case "first":
1072+
// return { namedImportComparer: detectCaseSensitivityBySort(originalGroups.map(i => [i.typeImportNames ?? [], i.regularImportNames ?? []]).flat()), typeOrder: "first" };
1073+
// case "last":
1074+
// return { namedImportComparer: detectCaseSensitivityBySort(originalGroups.map(i => [i.typeImportNames ?? [], i.regularImportNames ?? []]).flat()), typeOrder: "last" };
1075+
// case "inline":
1076+
// return { namedImportComparer: detectCaseSensitivityBySort(originalGroups.map(i => i.originalNamedImports)), typeOrder: "inline" };
1077+
// }
1078+
// }
10561079

10571080
type TypeOrder = "first" | "last" | "inline";
10581081

@@ -1062,24 +1085,24 @@ export function getDetectionByDiff(importDeclsByGroup: ImportDeclaration[][], pr
10621085
for (const curComparer of comparers) {
10631086
const currDiff = { first: 0, last: 0, inline: 0 };
10641087

1065-
for (const { regularImportNames, typeImportNames, originalNamedImports } of originalGroups) {
1066-
if (!regularImportNames || !typeImportNames) {
1067-
const sortedList = sort(originalNamedImports, curComparer) as any as string[];
1068-
const diff = getDiffNum(originalNamedImports, sortedList);
1069-
for (const typeOrder in currDiff) {
1070-
currDiff[typeOrder as TypeOrder] += diff;
1071-
}
1072-
continue;
1073-
}
1088+
for (const importDecl of originalGroups) {
1089+
// if (!regularImportNames || !typeImportNames) {
1090+
// const sortedList = sort(originalNamedImports, curComparer) as any as string[];
1091+
// const diff = getSortedMeasure(originalNamedImports, comparer);
1092+
// for (const typeOrder in currDiff) {
1093+
// currDiff[typeOrder as TypeOrder] += diff;
1094+
// }
1095+
// continue;
1096+
// }
10741097

10751098
// ordering
1076-
const sortedRegular = sort(regularImportNames, curComparer);
1077-
const sortedType = sort(typeImportNames, curComparer);
1078-
const sortedInline = mergeAndDeduplicateSorted(sortedRegular, sortedType, curComparer);
1099+
// const sortedRegular = sort(regularImportNames, curComparer);
1100+
// const sortedType = sort(typeImportNames, curComparer);
1101+
// const sortedInline = mergeAndDeduplicateSorted(sortedRegular, sortedType, curComparer);
10791102

1080-
currDiff.inline += getDiffNum(originalNamedImports, sortedInline as any as string[]);
1081-
currDiff.first += getDiffNum(originalNamedImports, sortedType.concat(sortedRegular));
1082-
currDiff.last += getDiffNum(originalNamedImports, sortedRegular.concat(sortedType));
1103+
currDiff.inline += getSortedMeasure(importDecl, (n1, n2) => compareImportOrExportSpecifiers(n1, n2, curComparer, {organizeImportsTypeOrder: "inline"}));
1104+
currDiff.first += getSortedMeasure(importDecl, (n1, n2) => compareImportOrExportSpecifiers(n1, n2, curComparer, {organizeImportsTypeOrder: "first"}));
1105+
currDiff.last += getSortedMeasure(importDecl, (n1, n2) => compareImportOrExportSpecifiers(n1, n2, curComparer, {organizeImportsTypeOrder: "last"}));
10831106
}
10841107
for (const key in currDiff) {
10851108
const typeOrder = key as TypeOrder;
@@ -1090,16 +1113,25 @@ export function getDetectionByDiff(importDeclsByGroup: ImportDeclaration[][], pr
10901113
}
10911114
}
10921115

1093-
if (bestDiff.last <= bestDiff.first && bestDiff.last <= bestDiff.inline) {
1094-
return { namedImportComparer: bestComparer.last, typeOrder: "last" };
1095-
}
10961116
if (bestDiff.inline <= bestDiff.first && bestDiff.inline <= bestDiff.last) {
10971117
return { namedImportComparer: bestComparer.inline, typeOrder: "inline" };
10981118
}
1119+
if (bestDiff.last <= bestDiff.first && bestDiff.last <= bestDiff.inline) {
1120+
return { namedImportComparer: bestComparer.last, typeOrder: "last" };
1121+
}
10991122
if (bestDiff.first <= bestDiff.inline && bestDiff.first <= bestDiff.last) {
11001123
return { namedImportComparer: bestComparer.first, typeOrder: "first" };
11011124
}
11021125
// hopefully never hit.....
11031126
return { namedImportComparer: bestComparer.last, typeOrder: "last" };
11041127
}
11051128
}
1129+
function getModuleSpecifierNames(importDeclsByGroup: ImportDeclaration[][], comparer: { moduleSpecifierComparer: Comparer<string>; namedImportComparer?: Comparer<string> | undefined; typeOrder?: "first" | "last" | "inline" | undefined; }, detectCaseSensitivityByDiff: (originalGroups: string[][]) => Comparer<string>) {
1130+
const moduleSpecifiersByGroup: string[][] = [];
1131+
importDeclsByGroup.forEach(importGroup => {
1132+
// turns importdeclbygroup into string[][] of module specifiers by group to detect sorting on module specifiers
1133+
moduleSpecifiersByGroup.push(importGroup.map(i => getExternalModuleName(i.moduleSpecifier)!));
1134+
});
1135+
comparer.moduleSpecifierComparer = detectCaseSensitivityByDiff(moduleSpecifiersByGroup);
1136+
}
1137+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowSyntheticDefaultImports: true
4+
// @moduleResolution: node
5+
// @noUnusedLocals: true
6+
// @target: es2018
7+
8+
//// declare module "mod" {
9+
//// import { F1 } from "lib";
10+
//// import * as NS from "lib";
11+
//// import { F2 } from "lib";
12+
////
13+
//// function F(f1: {} = F1, f2: {} = F2) {}
14+
//// }
15+
16+
verify.organizeImports(
17+
`declare module "mod" {
18+
import { F1, F2 } from "lib";
19+
20+
function F(f1: {} = F1, f2: {} = F2) {}
21+
}`,
22+
/*mode*/ undefined
23+
);
24+

tests/cases/fourslash/organizeImportsType2.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@ export { A, C, type B };
4747
`,
4848
undefined,
4949
{ organizeImportsTypeOrder : "last" }
50-
);
50+
);
51+

0 commit comments

Comments
 (0)