@@ -130,22 +130,22 @@ namespace ts.codefix {
130130
131131 // this is a module id -> module import declaration map
132132 const cachedImportDeclarations : ( ImportDeclaration | ImportEqualsDeclaration ) [ ] [ ] = [ ] ;
133- let cachedNewImportInsertPosition : number ;
133+ let lastImportDeclaration : Node ;
134134
135135 const currentTokenMeaning = getMeaningFromLocation ( token ) ;
136136 if ( context . errorCode === Diagnostics . _0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead . code ) {
137137 const symbol = checker . getAliasedSymbol ( checker . getSymbolAtLocation ( token ) ) ;
138138 return getCodeActionForImport ( symbol , /*isDefault*/ false , /*isNamespaceImport*/ true ) ;
139139 }
140140
141- const allPotentialModules = checker . getAmbientModules ( ) ;
141+ const candidateModules = checker . getAmbientModules ( ) ;
142142 for ( const otherSourceFile of allSourceFiles ) {
143143 if ( otherSourceFile !== sourceFile && isExternalOrCommonJsModule ( otherSourceFile ) ) {
144- allPotentialModules . push ( otherSourceFile . symbol ) ;
144+ candidateModules . push ( otherSourceFile . symbol ) ;
145145 }
146146 }
147147
148- for ( const moduleSymbol of allPotentialModules ) {
148+ for ( const moduleSymbol of candidateModules ) {
149149 context . cancellationToken . throwIfCancellationRequested ( ) ;
150150
151151 // check the default export
@@ -277,14 +277,12 @@ namespace ts.codefix {
277277 * If the existing import declaration already has a named import list, just
278278 * insert the identifier into that list.
279279 */
280- const textChange = getTextChangeForImportClause ( namedImportDeclaration . importClause ) ;
280+ const fileTextChanges = getTextChangeForImportClause ( namedImportDeclaration . importClause ) ;
281281 const moduleSpecifierWithoutQuotes = stripQuotes ( namedImportDeclaration . moduleSpecifier . getText ( ) ) ;
282282 actions . push ( createCodeAction (
283283 Diagnostics . Add_0_to_existing_import_declaration_from_1 ,
284284 [ name , moduleSpecifierWithoutQuotes ] ,
285- textChange . newText ,
286- textChange . span ,
287- sourceFile . fileName ,
285+ fileTextChanges ,
288286 "InsertingIntoExistingImport" ,
289287 moduleSpecifierWithoutQuotes
290288 ) ) ;
@@ -302,49 +300,31 @@ namespace ts.codefix {
302300 return declaration . moduleReference . getText ( ) ;
303301 }
304302
305- function getTextChangeForImportClause ( importClause : ImportClause ) : TextChange {
306- const newImportText = isDefault ? `default as ${ name } ` : name ;
303+ function getTextChangeForImportClause ( importClause : ImportClause ) : FileTextChanges [ ] {
304+ // const newImportText = isDefault ? `default as ${name}` : name;
307305 const importList = < NamedImports > importClause . namedBindings ;
306+ const newImportSpecifier = createImportSpecifier ( /*propertyName*/ undefined , createIdentifier ( name ) ) ;
308307 // case 1:
309308 // original text: import default from "module"
310309 // change to: import default, { name } from "module"
311- if ( ! importList && importClause . name ) {
312- const start = importClause . name . getEnd ( ) ;
313- return {
314- newText : `, { ${ newImportText } }` ,
315- span : { start, length : 0 }
316- } ;
317- }
318-
319310 // case 2:
320311 // original text: import {} from "module"
321312 // change to: import { name } from "module"
322- if ( importList . elements . length === 0 ) {
323- const start = importList . getStart ( ) ;
324- return {
325- newText : `{ ${ newImportText } }` ,
326- span : { start, length : importList . getEnd ( ) - start }
327- } ;
313+ if ( ! importList || importList . elements . length === 0 ) {
314+ const newImportClause = createImportClause ( importClause . name , createNamedImports ( [ newImportSpecifier ] ) ) ;
315+ return createChangeTracker ( ) . replaceNode ( sourceFile , importClause , newImportClause ) . getChanges ( ) ;
328316 }
329317
330- // case 3:
331- // original text: import { foo, bar } from "module"
332- // change to: import { foo, bar, name } from "module"
333- const insertPoint = importList . elements [ importList . elements . length - 1 ] . getEnd ( ) ;
334318 /**
335319 * If the import list has one import per line, preserve that. Otherwise, insert on same line as last element
336320 * import {
337321 * foo
338322 * } from "./module";
339323 */
340- const startLine = getLineOfLocalPosition ( sourceFile , importList . getStart ( ) ) ;
341- const endLine = getLineOfLocalPosition ( sourceFile , importList . getEnd ( ) ) ;
342- const oneImportPerLine = endLine - startLine > importList . elements . length ;
343-
344- return {
345- newText : `,${ oneImportPerLine ? context . newLineCharacter : " " } ${ newImportText } ` ,
346- span : { start : insertPoint , length : 0 }
347- } ;
324+ return createChangeTracker ( ) . insertNodeInListAfter (
325+ sourceFile ,
326+ importList . elements [ importList . elements . length - 1 ] ,
327+ newImportSpecifier ) . getChanges ( ) ;
348328 }
349329
350330 function getCodeActionForNamespaceImport ( declaration : ImportDeclaration | ImportEqualsDeclaration ) : ImportCodeAction {
@@ -370,48 +350,47 @@ namespace ts.codefix {
370350 return createCodeAction (
371351 Diagnostics . Change_0_to_1 ,
372352 [ name , `${ namespacePrefix } .${ name } ` ] ,
373- `${ namespacePrefix } .` ,
374- { start : token . getStart ( ) , length : 0 } ,
375- sourceFile . fileName ,
353+ createChangeTracker ( ) . replaceNode ( sourceFile , token , createPropertyAccess ( createIdentifier ( namespacePrefix ) , name ) ) . getChanges ( ) ,
376354 "CodeChange"
377355 ) ;
378356 }
379357 }
380358
381359 function getCodeActionForNewImport ( moduleSpecifier ?: string ) : ImportCodeAction {
382- if ( ! cachedNewImportInsertPosition ) {
360+ if ( ! lastImportDeclaration ) {
383361 // insert after any existing imports
384- let lastModuleSpecifierEnd = - 1 ;
385- for ( const moduleSpecifier of sourceFile . imports ) {
386- const end = moduleSpecifier . getEnd ( ) ;
387- if ( ! lastModuleSpecifierEnd || end > lastModuleSpecifierEnd ) {
388- lastModuleSpecifierEnd = end ;
362+ for ( let i = sourceFile . statements . length - 1 ; i >= 0 ; i -- ) {
363+ const statement = sourceFile . statements [ i ] ;
364+ if ( statement . kind === SyntaxKind . ImportEqualsDeclaration || statement . kind === SyntaxKind . ImportDeclaration ) {
365+ lastImportDeclaration = statement ;
366+ break ;
389367 }
390368 }
391- cachedNewImportInsertPosition = lastModuleSpecifierEnd > 0 ? sourceFile . getLineEndOfPosition ( lastModuleSpecifierEnd ) : sourceFile . getStart ( ) ;
392369 }
393370
394371 const getCanonicalFileName = createGetCanonicalFileName ( useCaseSensitiveFileNames ) ;
395372 const moduleSpecifierWithoutQuotes = stripQuotes ( moduleSpecifier || getModuleSpecifierForNewImport ( ) ) ;
396- const importStatementText = isDefault
397- ? `import ${ name } from "${ moduleSpecifierWithoutQuotes } "`
373+ const changeTracker = createChangeTracker ( ) ;
374+ const importClause = isDefault
375+ ? createImportClause ( createIdentifier ( name ) , /*namedBindings*/ undefined )
398376 : isNamespaceImport
399- ? `import * as ${ name } from "${ moduleSpecifierWithoutQuotes } "`
400- : `import { ${ name } } from "${ moduleSpecifierWithoutQuotes } "` ;
377+ ? createImportClause ( /*name*/ undefined , createNamespaceImport ( createIdentifier ( name ) ) )
378+ : createImportClause ( /*name*/ undefined , createNamedImports ( [ createImportSpecifier ( /*propertyName*/ undefined , createIdentifier ( name ) ) ] ) ) ;
379+ const importDecl = createImportDeclaration ( /*decorators*/ undefined , /*modifiers*/ undefined , importClause , createLiteral ( moduleSpecifierWithoutQuotes ) ) ;
380+ if ( ! lastImportDeclaration ) {
381+ changeTracker . insertNodeAt ( sourceFile , sourceFile . getStart ( ) , importDecl , { suffix : `${ context . newLineCharacter } ${ context . newLineCharacter } ` } ) ;
382+ }
383+ else {
384+ changeTracker . insertNodeAfter ( sourceFile , lastImportDeclaration , importDecl , { suffix : context . newLineCharacter } ) ;
385+ }
401386
402387 // if this file doesn't have any import statements, insert an import statement and then insert a new line
403388 // between the only import statement and user code. Otherwise just insert the statement because chances
404389 // are there are already a new line seperating code and import statements.
405- const newText = cachedNewImportInsertPosition === sourceFile . getStart ( )
406- ? `${ importStatementText } ;${ context . newLineCharacter } ${ context . newLineCharacter } `
407- : `${ context . newLineCharacter } ${ importStatementText } ;` ;
408-
409390 return createCodeAction (
410391 Diagnostics . Import_0_from_1 ,
411392 [ name , `"${ moduleSpecifierWithoutQuotes } "` ] ,
412- newText ,
413- { start : cachedNewImportInsertPosition , length : 0 } ,
414- sourceFile . fileName ,
393+ changeTracker . getChanges ( ) ,
415394 "NewImport" ,
416395 moduleSpecifierWithoutQuotes
417396 ) ;
@@ -576,17 +555,19 @@ namespace ts.codefix {
576555
577556 }
578557
558+ function createChangeTracker ( ) {
559+ return textChanges . ChangeTracker . fromCodeFixContext ( context ) ; ;
560+ }
561+
579562 function createCodeAction (
580563 description : DiagnosticMessage ,
581564 diagnosticArgs : string [ ] ,
582- newText : string ,
583- span : TextSpan ,
584- fileName : string ,
565+ changes : FileTextChanges [ ] ,
585566 kind : ImportCodeActionKind ,
586567 moduleSpecifier ?: string ) : ImportCodeAction {
587568 return {
588569 description : formatMessage . apply ( undefined , [ undefined , description ] . concat ( < any [ ] > diagnosticArgs ) ) ,
589- changes : [ { fileName , textChanges : [ { newText , span } ] } ] ,
570+ changes,
590571 kind,
591572 moduleSpecifier
592573 } ;
0 commit comments