@@ -3,56 +3,44 @@ namespace ts.OrganizeImports {
33 export function organizeImports (
44 sourceFile : SourceFile ,
55 formatContext : formatting . FormatContext ,
6- host : LanguageServiceHost ,
7- cancellationToken : CancellationToken ) {
6+ host : LanguageServiceHost ) {
87
9- // All of the (old) ImportDeclarations in the file, in syntactic order.
10- const oldImportDecls : ImportDeclaration [ ] = [ ] ;
8+ // TODO (https://github.com/Microsoft/TypeScript/issues/10020): sort *within* ambient modules (find using isAmbientModule)
119
12- forEachChild ( sourceFile , node => {
13- cancellationToken . throwIfCancellationRequested ( ) ;
14- if ( isImportDeclaration ( node ) ) {
15- oldImportDecls . push ( node ) ;
16- }
17- // TODO (https://github.com/Microsoft/TypeScript/issues/10020): sort *within* ambient modules (find using isAmbientModule)
18- } ) ;
10+ // All of the old ImportDeclarations in the file, in syntactic order.
11+ const oldImportDecls = sourceFile . statements . filter ( isImportDeclaration ) ;
1912
2013 if ( oldImportDecls . length === 0 ) {
2114 return [ ] ;
2215 }
2316
24- const usedImportDecls = removeUnusedImports ( oldImportDecls ) ;
25- cancellationToken . throwIfCancellationRequested ( ) ;
26- const sortedImportDecls = sortImports ( usedImportDecls ) ;
27- cancellationToken . throwIfCancellationRequested ( ) ;
28- const coalescedImportDecls = coalesceImports ( sortedImportDecls ) ;
29- cancellationToken . throwIfCancellationRequested ( ) ;
17+ const oldValidImportDecls = oldImportDecls . filter ( importDecl => getExternalModuleName ( importDecl . moduleSpecifier ) ) ;
18+ const oldInvalidImportDecls = oldImportDecls . filter ( importDecl => ! getExternalModuleName ( importDecl . moduleSpecifier ) ) ;
3019
31- // All of the ( new) ImportDeclarations in the file, in sorted order.
32- const newImportDecls = coalescedImportDecls ;
20+ // All of the new ImportDeclarations in the file, in sorted order.
21+ const newImportDecls = coalesceImports ( sortImports ( removeUnusedImports ( oldValidImportDecls ) ) ) . concat ( oldInvalidImportDecls ) ;
3322
3423 const changeTracker = textChanges . ChangeTracker . fromContext ( { host, formatContext } ) ;
3524
36- // NB: Stopping before i === 0
37- for ( let i = oldImportDecls . length - 1 ; i > 0 ; i -- ) {
38- changeTracker . deleteNode ( sourceFile , oldImportDecls [ i ] ) ;
39- }
40-
25+ // Delete or replace the first import.
4126 if ( newImportDecls . length === 0 ) {
4227 changeTracker . deleteNode ( sourceFile , oldImportDecls [ 0 ] ) ;
4328 }
4429 else {
45- // Delete the surrounding trivia because it will have been retained in newImportDecls.
46- const replaceOptions = {
30+ // Note: Delete the surrounding trivia because it will have been retained in newImportDecls.
31+ changeTracker . replaceNodeWithNodes ( sourceFile , oldImportDecls [ 0 ] , newImportDecls , {
4732 useNonAdjustedStartPosition : false ,
4833 useNonAdjustedEndPosition : false ,
4934 suffix : getNewLineOrDefaultFromHost ( host , formatContext . options ) ,
50- } ;
51- changeTracker . replaceNodeWithNodes ( sourceFile , oldImportDecls [ 0 ] , newImportDecls , replaceOptions ) ;
35+ } ) ;
5236 }
5337
54- const changes = changeTracker . getChanges ( ) ;
55- return changes ;
38+ // Delete any subsequent imports.
39+ for ( let i = 1 ; i < oldImportDecls . length ; i ++ ) {
40+ changeTracker . deleteNode ( sourceFile , oldImportDecls [ i ] ) ;
41+ }
42+
43+ return changeTracker . getChanges ( ) ;
5644 }
5745
5846 function removeUnusedImports ( oldImports : ReadonlyArray < ImportDeclaration > ) {
@@ -61,51 +49,15 @@ namespace ts.OrganizeImports {
6149
6250 /* @internal */ // Internal for testing
6351 export function sortImports ( oldImports : ReadonlyArray < ImportDeclaration > ) {
64- if ( oldImports . length < 2 ) {
65- return oldImports ;
66- }
52+ return stableSort ( oldImports , ( import1 , import2 ) => {
53+ const name1 = getExternalModuleName ( import1 . moduleSpecifier ) ;
54+ const name2 = getExternalModuleName ( import2 . moduleSpecifier ) ;
55+ Debug . assert ( name1 !== undefined ) ;
56+ Debug . assert ( name2 !== undefined ) ;
57+ return compareBooleans ( isExternalModuleNameRelative ( name1 ) , isExternalModuleNameRelative ( name2 ) ) ||
58+ compareStringsCaseSensitive ( name1 , name2 ) ;
6759
68- // NB: declaration order determines sort order
69- const enum ModuleNameKind {
70- NonRelative ,
71- Relative ,
72- Invalid ,
73- }
74-
75- const importRecords = oldImports . map ( createImportRecord ) ;
76-
77- const sortedRecords = stableSort ( importRecords , ( import1 , import2 ) => {
78- const { name : name1 , kind : kind1 } = import1 ;
79- const { name : name2 , kind : kind2 } = import2 ;
80-
81- if ( kind1 !== kind2 ) {
82- return kind1 < kind2
83- ? Comparison . LessThan
84- : Comparison . GreaterThan ;
85- }
86-
87- // Note that we're using simple equality, retaining case-sensitivity.
88- if ( name1 !== name2 ) {
89- return name1 < name2
90- ? Comparison . LessThan
91- : Comparison . GreaterThan ;
92- }
93-
94- return Comparison . EqualTo ;
9560 } ) ;
96-
97- return sortedRecords . map ( r => r . importDeclaration ) ;
98-
99- function createImportRecord ( importDeclaration : ImportDeclaration ) {
100- const specifier = importDeclaration . moduleSpecifier ;
101- const name = getExternalModuleName ( specifier ) ;
102- if ( name ) {
103- const isRelative = isExternalModuleNameRelative ( name ) ;
104- return { importDeclaration, name, kind : isRelative ? ModuleNameKind . Relative : ModuleNameKind . NonRelative } ;
105- }
106-
107- return { importDeclaration, name : specifier . getText ( ) , kind : ModuleNameKind . Invalid } ;
108- }
10961 }
11062
11163 function getExternalModuleName ( specifier : Expression ) {
@@ -123,11 +75,13 @@ namespace ts.OrganizeImports {
12375 const groups : ImportDeclaration [ ] [ ] = [ ] ;
12476
12577 let groupName : string | undefined = getExternalModuleName ( sortedImports [ 0 ] . moduleSpecifier ) ;
78+ Debug . assert ( groupName !== undefined ) ;
12679 let group : ImportDeclaration [ ] = [ ] ;
12780
12881 for ( const importDeclaration of sortedImports ) {
12982 const moduleName = getExternalModuleName ( importDeclaration . moduleSpecifier ) ;
130- if ( moduleName && moduleName === groupName ) {
83+ Debug . assert ( moduleName !== undefined ) ;
84+ if ( moduleName === groupName ) {
13185 group . push ( importDeclaration ) ;
13286 }
13387 else if ( group . length ) {
@@ -159,38 +113,10 @@ namespace ts.OrganizeImports {
159113 const groupedImports = groupSortedImports ( sortedImports ) ;
160114 for ( const importGroup of groupedImports ) {
161115
162- let seenImportWithoutClause = false ;
163-
164- const defaultImports : Identifier [ ] = [ ] ;
165- const namespaceImports : NamespaceImport [ ] = [ ] ;
166- const namedImports : NamedImports [ ] = [ ] ;
167-
168- for ( const importDeclaration of importGroup ) {
169- if ( importDeclaration . importClause === undefined ) {
170- // Only the first such import is interesting - the others are redundant.
171- // Note: Unfortunately, we will lose trivia that was on this node.
172- if ( ! seenImportWithoutClause ) {
173- coalescedImports . push ( importDeclaration ) ;
174- }
175-
176- seenImportWithoutClause = true ;
177- continue ;
178- }
179-
180- const { name, namedBindings } = importDeclaration . importClause ;
181-
182- if ( name ) {
183- defaultImports . push ( name ) ;
184- }
116+ const { importWithoutClause, defaultImports, namespaceImports, namedImports } = getImportParts ( importGroup ) ;
185117
186- if ( namedBindings ) {
187- if ( isNamespaceImport ( namedBindings ) ) {
188- namespaceImports . push ( namedBindings ) ;
189- }
190- else {
191- namedImports . push ( namedBindings ) ;
192- }
193- }
118+ if ( importWithoutClause ) {
119+ coalescedImports . push ( importWithoutClause ) ;
194120 }
195121
196122 // Normally, we don't combine default and namespace imports, but it would be silly to
@@ -204,8 +130,6 @@ namespace ts.OrganizeImports {
204130 continue ;
205131 }
206132
207- // For convenience, we cheat and do a little sorting during coalescing.
208- // Seems reasonable since we're restructuring so much anyway.
209133 const sortedNamespaceImports = stableSort ( namespaceImports , ( n1 , n2 ) => compareIdentifiers ( n1 . name , n2 . name ) ) ;
210134
211135 for ( const namespaceImport of sortedNamespaceImports ) {
@@ -218,7 +142,7 @@ namespace ts.OrganizeImports {
218142 continue ;
219143 }
220144
221- let newDefaultImport : Identifier = undefined ;
145+ let newDefaultImport : Identifier | undefined ;
222146 const newImportSpecifiers : ImportSpecifier [ ] = [ ] ;
223147 if ( defaultImports . length === 1 ) {
224148 newDefaultImport = defaultImports [ 0 ] ;
@@ -230,18 +154,11 @@ namespace ts.OrganizeImports {
230154 }
231155 }
232156
233- for ( const namedImport of namedImports ) {
234- for ( const specifier of namedImport . elements ) {
235- newImportSpecifiers . push ( specifier ) ;
236- }
237- }
157+ newImportSpecifiers . push ( ...flatMap ( namedImports , n => n . elements ) ) ;
238158
239- const sortedImportSpecifiers = stableSort ( newImportSpecifiers , ( s1 , s2 ) => {
240- const nameComparison = compareIdentifiers ( s1 . propertyName || s1 . name , s2 . propertyName || s2 . name ) ;
241- return nameComparison !== Comparison . EqualTo
242- ? nameComparison
243- : compareIdentifiers ( s1 . name , s2 . name ) ;
244- } ) ;
159+ const sortedImportSpecifiers = stableSort ( newImportSpecifiers , ( s1 , s2 ) =>
160+ compareIdentifiers ( s1 . propertyName || s1 . name , s2 . propertyName || s2 . name ) ||
161+ compareIdentifiers ( s1 . name , s2 . name ) ) ;
245162
246163 const importClause = defaultImports . length > 0
247164 ? defaultImports [ 0 ] . parent as ImportClause
@@ -259,19 +176,46 @@ namespace ts.OrganizeImports {
259176
260177 return coalescedImports ;
261178
262- // `undefined` is the min value.
263- function compareIdentifiers ( s1 : Identifier | undefined , s2 : Identifier | undefined ) {
264- return s1 === undefined
265- ? s2 === undefined
266- ? Comparison . EqualTo
267- : Comparison . LessThan
268- : s2 === undefined
269- ? Comparison . GreaterThan
270- : s1 . text < s2 . text
271- ? Comparison . LessThan
272- : s1 . text > s2 . text
273- ? Comparison . GreaterThan
274- : Comparison . EqualTo ;
179+ function getImportParts ( importGroup : ReadonlyArray < ImportDeclaration > ) {
180+ let importWithoutClause : ImportDeclaration | undefined ;
181+ const defaultImports : Identifier [ ] = [ ] ;
182+ const namespaceImports : NamespaceImport [ ] = [ ] ;
183+ const namedImports : NamedImports [ ] = [ ] ;
184+
185+ for ( const importDeclaration of importGroup ) {
186+ if ( importDeclaration . importClause === undefined ) {
187+ // Only the first such import is interesting - the others are redundant.
188+ // Note: Unfortunately, we will lose trivia that was on this node.
189+ importWithoutClause = importWithoutClause || importDeclaration ;
190+ continue ;
191+ }
192+
193+ const { name, namedBindings } = importDeclaration . importClause ;
194+
195+ if ( name ) {
196+ defaultImports . push ( name ) ;
197+ }
198+
199+ if ( namedBindings ) {
200+ if ( isNamespaceImport ( namedBindings ) ) {
201+ namespaceImports . push ( namedBindings ) ;
202+ }
203+ else {
204+ namedImports . push ( namedBindings ) ;
205+ }
206+ }
207+ }
208+
209+ return {
210+ importWithoutClause,
211+ defaultImports,
212+ namespaceImports,
213+ namedImports,
214+ }
215+ }
216+
217+ function compareIdentifiers ( s1 : Identifier , s2 : Identifier ) {
218+ return compareStringsCaseSensitive ( s1 . text , s2 . text ) ;
275219 }
276220
277221 function updateImportDeclarationAndClause (
0 commit comments