@@ -10,15 +10,14 @@ namespace ts.codefix {
10
10
Diagnostics . _0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead . code ,
11
11
Diagnostics . _0_only_refers_to_a_type_but_is_being_used_as_a_value_here . code ,
12
12
] ,
13
- getCodeActions : getImportCodeActions ,
13
+ getCodeActions : context => context . errorCode === Diagnostics . _0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead . code
14
+ ? getActionsForUMDImport ( context )
15
+ : getActionsForNonUMDImport ( context ) ,
14
16
// TODO: GH#20315
15
17
fixIds : [ ] ,
16
18
getAllCodeActions : notImplemented ,
17
19
} ) ;
18
20
19
- // Map from module Id to an array of import declarations in that module.
20
- type ImportDeclarationMap = ExistingImportInfo [ ] [ ] ;
21
-
22
21
interface SymbolContext extends textChanges . TextChangesContext {
23
22
sourceFile : SourceFile ;
24
23
symbolName : string ;
@@ -30,7 +29,6 @@ namespace ts.codefix {
30
29
checker : TypeChecker ;
31
30
compilerOptions : CompilerOptions ;
32
31
getCanonicalFileName : GetCanonicalFileName ;
33
- cachedImportDeclarations ?: ImportDeclarationMap ;
34
32
preferences : UserPreferences ;
35
33
}
36
34
@@ -50,7 +48,6 @@ namespace ts.codefix {
50
48
program,
51
49
checker,
52
50
compilerOptions : program . getCompilerOptions ( ) ,
53
- cachedImportDeclarations : [ ] ,
54
51
getCanonicalFileName : createGetCanonicalFileName ( hostUsesCaseSensitiveFileNames ( context . host ) ) ,
55
52
symbolName,
56
53
symbolToken,
@@ -130,8 +127,19 @@ namespace ts.codefix {
130
127
}
131
128
132
129
function getCodeActionsForImport_separateExistingAndNew ( exportInfos : ReadonlyArray < SymbolExportInfo > , context : ImportCodeFixContext , useExisting : Push < CodeFixAction > , addNew : Push < CodeFixAction > ) : void {
133
- const existingImports = flatMap ( exportInfos , info =>
134
- getImportDeclarations ( info , context . checker , context . sourceFile , context . cachedImportDeclarations ) ) ;
130
+ const existingImports = flatMap ( exportInfos , info => getExistingImportDeclarations ( info , context . checker , context . sourceFile ) ) ;
131
+
132
+ append ( useExisting , tryUseExistingNamespaceImport ( existingImports , context , context . symbolToken , context . checker ) ) ;
133
+ const addToExisting = tryAddToExistingImport ( existingImports , context ) ;
134
+
135
+ if ( addToExisting ) {
136
+ useExisting . push ( addToExisting ) ;
137
+ }
138
+ else { // Don't bother providing an action to add a new import if we can add to an existing one.
139
+ getCodeActionsForAddImport ( exportInfos , context , existingImports , addNew ) ;
140
+ }
141
+ }
142
+ function tryUseExistingNamespaceImport ( existingImports : ReadonlyArray < ExistingImportInfo > , context : SymbolContext , symbolToken : Node | undefined , checker : TypeChecker ) : CodeFixAction | undefined {
135
143
// It is possible that multiple import statements with the same specifier exist in the file.
136
144
// e.g.
137
145
//
@@ -144,18 +152,26 @@ namespace ts.codefix {
144
152
// 1. change "member3" to "ns.member3"
145
153
// 2. add "member3" to the second import statement's import list
146
154
// and it is up to the user to decide which one fits best.
147
- if ( context . symbolToken && isIdentifier ( context . symbolToken ) ) {
148
- for ( const { declaration } of existingImports ) {
149
- const namespace = getNamespaceImportName ( declaration ) ;
150
- if ( namespace ) {
151
- const moduleSymbol = context . checker . getAliasedSymbol ( context . checker . getSymbolAtLocation ( namespace ) ! ) ;
152
- if ( moduleSymbol && moduleSymbol . exports ! . has ( escapeLeadingUnderscores ( context . symbolName ) ) ) {
153
- useExisting . push ( getCodeActionForUseExistingNamespaceImport ( namespace . text , context , context . symbolToken ) ) ;
154
- }
155
+ return ! symbolToken || ! isIdentifier ( symbolToken ) ? undefined : firstDefined ( existingImports , ( { declaration } ) => {
156
+ const namespace = getNamespaceImportName ( declaration ) ;
157
+ if ( namespace ) {
158
+ const moduleSymbol = namespace && checker . getAliasedSymbol ( checker . getSymbolAtLocation ( namespace ) ! ) ;
159
+ if ( moduleSymbol && moduleSymbol . exports ! . has ( escapeLeadingUnderscores ( context . symbolName ) ) ) {
160
+ return getCodeActionForUseExistingNamespaceImport ( namespace . text , context , symbolToken ) ;
155
161
}
156
162
}
157
- }
158
- getCodeActionsForAddImport ( exportInfos , context , existingImports , useExisting , addNew ) ;
163
+ } ) ;
164
+ }
165
+ function tryAddToExistingImport ( existingImports : ReadonlyArray < ExistingImportInfo > , context : SymbolContext ) : CodeFixAction | undefined {
166
+ return firstDefined ( existingImports , ( { declaration, importKind } ) => {
167
+ if ( declaration . kind === SyntaxKind . ImportDeclaration && declaration . importClause ) {
168
+ const changes = tryUpdateExistingImport ( context , declaration . importClause , importKind ) ;
169
+ if ( changes ) {
170
+ const moduleSpecifierWithoutQuotes = stripQuotes ( declaration . moduleSpecifier . getText ( ) ) ;
171
+ return createCodeAction ( Diagnostics . Add_0_to_existing_import_declaration_from_1 , [ context . symbolName , moduleSpecifierWithoutQuotes ] , changes ) ;
172
+ }
173
+ }
174
+ } ) ;
159
175
}
160
176
161
177
function getNamespaceImportName ( declaration : AnyImportSyntax ) : Identifier | undefined {
@@ -168,18 +184,12 @@ namespace ts.codefix {
168
184
}
169
185
}
170
186
171
- // TODO(anhans): This doesn't seem important to cache... just use an iterator instead of creating a new array?
172
- function getImportDeclarations ( { moduleSymbol, importKind } : SymbolExportInfo , checker : TypeChecker , { imports } : SourceFile , cachedImportDeclarations : ImportDeclarationMap = [ ] ) : ReadonlyArray < ExistingImportInfo > {
173
- const moduleSymbolId = getUniqueSymbolId ( moduleSymbol , checker ) ;
174
- let cached = cachedImportDeclarations [ moduleSymbolId ] ;
175
- if ( ! cached ) {
176
- cached = cachedImportDeclarations [ moduleSymbolId ] = mapDefined < StringLiteralLike , ExistingImportInfo > ( imports , moduleSpecifier => {
177
- const i = importFromModuleSpecifier ( moduleSpecifier ) ;
178
- return ( i . kind === SyntaxKind . ImportDeclaration || i . kind === SyntaxKind . ImportEqualsDeclaration )
179
- && checker . getSymbolAtLocation ( moduleSpecifier ) === moduleSymbol ? { declaration : i , importKind } : undefined ;
180
- } ) ;
181
- }
182
- return cached ;
187
+ function getExistingImportDeclarations ( { moduleSymbol, importKind } : SymbolExportInfo , checker : TypeChecker , { imports } : SourceFile ) : ReadonlyArray < ExistingImportInfo > {
188
+ return mapDefined < StringLiteralLike , ExistingImportInfo > ( imports , moduleSpecifier => {
189
+ const i = importFromModuleSpecifier ( moduleSpecifier ) ;
190
+ return ( i . kind === SyntaxKind . ImportDeclaration || i . kind === SyntaxKind . ImportEqualsDeclaration )
191
+ && checker . getSymbolAtLocation ( moduleSpecifier ) === moduleSymbol ? { declaration : i , importKind } : undefined ;
192
+ } ) ;
183
193
}
184
194
185
195
function getCodeActionForNewImport ( context : SymbolContext & { preferences : UserPreferences } , { moduleSpecifier, importKind } : NewImportInfo ) : CodeFixAction {
@@ -258,23 +268,8 @@ namespace ts.codefix {
258
268
exportInfos : ReadonlyArray < SymbolExportInfo > ,
259
269
ctx : ImportCodeFixContext ,
260
270
existingImports : ReadonlyArray < ExistingImportInfo > ,
261
- useExisting : Push < CodeFixAction > ,
262
271
addNew : Push < CodeFixAction > ,
263
272
) : void {
264
- const fromExistingImport = firstDefined ( existingImports , ( { declaration, importKind } ) => {
265
- if ( declaration . kind === SyntaxKind . ImportDeclaration && declaration . importClause ) {
266
- const changes = tryUpdateExistingImport ( ctx , ( isImportClause ( declaration . importClause ) && declaration . importClause || undefined ) ! , importKind ) ; // TODO: GH#18217
267
- if ( changes ) {
268
- const moduleSpecifierWithoutQuotes = stripQuotes ( declaration . moduleSpecifier . getText ( ) ) ;
269
- return createCodeAction ( Diagnostics . Add_0_to_existing_import_declaration_from_1 , [ ctx . symbolName , moduleSpecifierWithoutQuotes ] , changes ) ;
270
- }
271
- }
272
- } ) ;
273
- if ( fromExistingImport ) {
274
- useExisting . push ( fromExistingImport ) ;
275
- return ;
276
- }
277
-
278
273
const existingDeclaration = firstDefined ( existingImports , newImportInfoFromExistingSpecifier ) ;
279
274
const newImportInfos = existingDeclaration
280
275
? [ existingDeclaration ]
@@ -348,12 +343,6 @@ namespace ts.codefix {
348
343
return createCodeAction ( Diagnostics . Change_0_to_1 , [ symbolName , `${ namespacePrefix } .${ symbolName } ` ] , changes ) ;
349
344
}
350
345
351
- function getImportCodeActions ( context : CodeFixContext ) : CodeFixAction [ ] | undefined {
352
- return context . errorCode === Diagnostics . _0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead . code
353
- ? getActionsForUMDImport ( context )
354
- : getActionsForNonUMDImport ( context ) ;
355
- }
356
-
357
346
function getActionsForUMDImport ( context : CodeFixContext ) : CodeFixAction [ ] | undefined {
358
347
const token = getTokenAtPosition ( context . sourceFile , context . span . start , /*includeJsDocComment*/ false ) ;
359
348
const checker = context . program . getTypeChecker ( ) ;
@@ -422,11 +411,18 @@ namespace ts.codefix {
422
411
? checker . getJsxNamespace ( )
423
412
: isIdentifier ( symbolToken ) ? symbolToken . text : undefined ;
424
413
if ( ! symbolName ) return undefined ;
425
-
426
414
// "default" is a keyword and not a legal identifier for the import, so we don't expect it here
427
415
Debug . assert ( symbolName !== "default" ) ;
428
- const currentTokenMeaning = getMeaningFromLocation ( symbolToken ) ;
429
416
417
+ const addToExistingDeclaration : CodeFixAction [ ] = [ ] ;
418
+ const addNewDeclaration : CodeFixAction [ ] = [ ] ;
419
+ getExportInfos ( symbolName , getMeaningFromLocation ( symbolToken ) , cancellationToken , sourceFile , checker , program ) . forEach ( exportInfos => {
420
+ getCodeActionsForImport_separateExistingAndNew ( exportInfos , convertToImportCodeFixContext ( context , symbolToken , symbolName ) , addToExistingDeclaration , addNewDeclaration ) ;
421
+ } ) ;
422
+ return [ ...addToExistingDeclaration , ...addNewDeclaration ] ;
423
+ }
424
+
425
+ function getExportInfos ( symbolName : string , currentTokenMeaning : SemanticMeaning , cancellationToken : CancellationToken , sourceFile : SourceFile , checker : TypeChecker , program : Program ) : ReadonlyMap < ReadonlyArray < SymbolExportInfo > > {
430
426
// For each original symbol, keep all re-exports of that symbol together so we can call `getCodeActionsForImport` on the whole group at once.
431
427
// Maps symbol id to info for modules providing that symbol (original export + re-exports).
432
428
const originalSymbolToExportInfos = createMultiMap < SymbolExportInfo > ( ) ;
@@ -464,20 +460,12 @@ namespace ts.codefix {
464
460
}
465
461
else if ( isExportSpecifier ( declaration ) ) {
466
462
Debug . assert ( declaration . name . escapedText === InternalSymbolName . Default ) ;
467
- if ( declaration . propertyName ) {
468
- return declaration . propertyName . escapedText ;
469
- }
463
+ return declaration . propertyName && declaration . propertyName . escapedText ;
470
464
}
471
465
} ) ;
472
466
}
473
467
} ) ;
474
-
475
- const addToExistingDeclaration : CodeFixAction [ ] = [ ] ;
476
- const addNewDeclaration : CodeFixAction [ ] = [ ] ;
477
- originalSymbolToExportInfos . forEach ( exportInfos => {
478
- getCodeActionsForImport_separateExistingAndNew ( exportInfos , convertToImportCodeFixContext ( context , symbolToken , symbolName ) , addToExistingDeclaration , addNewDeclaration ) ;
479
- } ) ;
480
- return [ ...addToExistingDeclaration , ...addNewDeclaration ] ;
468
+ return originalSymbolToExportInfos ;
481
469
}
482
470
483
471
function checkSymbolHasMeaning ( { declarations } : Symbol , meaning : SemanticMeaning ) : boolean {
0 commit comments