1/* @internal */ 2namespace ts.codefix { 3 export const importFixName = "import"; 4 const importFixId = "fixMissingImport"; 5 const errorCodes: readonly number[] = [ 6 Diagnostics.Cannot_find_name_0.code, 7 Diagnostics.Cannot_find_name_0_Did_you_mean_1.code, 8 Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code, 9 Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.code, 10 Diagnostics.Cannot_find_namespace_0.code, 11 Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code, 12 Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here.code, 13 Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer.code, 14 Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type.code, 15 ]; 16 17 registerCodeFix({ 18 errorCodes, 19 getCodeActions(context) { 20 const { errorCode, preferences, sourceFile, span, program } = context; 21 const info = getFixInfos(context, errorCode, span.start, /*useAutoImportProvider*/ true); 22 if (!info) return undefined; 23 const quotePreference = getQuotePreference(sourceFile, preferences); 24 return info.map(({ fix, symbolName, errorIdentifierText }) => codeActionForFix( 25 context, 26 sourceFile, 27 symbolName, 28 fix, 29 /*includeSymbolNameInDescription*/ symbolName !== errorIdentifierText, 30 quotePreference, 31 program.getCompilerOptions())); 32 }, 33 fixIds: [importFixId], 34 getAllCodeActions: context => { 35 const { sourceFile, program, preferences, host, cancellationToken } = context; 36 const importAdder = createImportAdderWorker(sourceFile, program, /*useAutoImportProvider*/ true, preferences, host, cancellationToken); 37 eachDiagnostic(context, errorCodes, diag => importAdder.addImportFromDiagnostic(diag, context)); 38 return createCombinedCodeActions(textChanges.ChangeTracker.with(context, importAdder.writeFixes)); 39 }, 40 }); 41 42 /** 43 * Computes multiple import additions to a file and writes them to a ChangeTracker. 44 */ 45 export interface ImportAdder { 46 hasFixes(): boolean; 47 addImportFromDiagnostic: (diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) => void; 48 addImportFromExportedSymbol: (exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean) => void; 49 writeFixes: (changeTracker: textChanges.ChangeTracker) => void; 50 } 51 52 export function createImportAdder(sourceFile: SourceFile, program: Program, preferences: UserPreferences, host: LanguageServiceHost, cancellationToken?: CancellationToken): ImportAdder { 53 return createImportAdderWorker(sourceFile, program, /*useAutoImportProvider*/ false, preferences, host, cancellationToken); 54 } 55 56 interface AddToExistingState { 57 readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern; 58 defaultImport: Import | undefined; 59 readonly namedImports: ESMap<string, AddAsTypeOnly>; 60 } 61 62 function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAutoImportProvider: boolean, preferences: UserPreferences, host: LanguageServiceHost, cancellationToken: CancellationToken | undefined): ImportAdder { 63 const compilerOptions = program.getCompilerOptions(); 64 // Namespace fixes don't conflict, so just build a list. 65 const addToNamespace: FixUseNamespaceImport[] = []; 66 const importType: FixAddJsdocTypeImport[] = []; 67 /** Keys are import clause node IDs. */ 68 const addToExisting = new Map<string, AddToExistingState>(); 69 70 type NewImportsKey = `${0 | 1}|${string}`; 71 /** Use `getNewImportEntry` for access */ 72 const newImports = new Map<NewImportsKey, Mutable<ImportsCollection & { useRequire: boolean }>>(); 73 return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes, hasFixes }; 74 75 function addImportFromDiagnostic(diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) { 76 const info = getFixInfos(context, diagnostic.code, diagnostic.start, useAutoImportProvider); 77 if (!info || !info.length) return; 78 addImport(first(info)); 79 } 80 81 function addImportFromExportedSymbol(exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean) { 82 const moduleSymbol = Debug.checkDefined(exportedSymbol.parent); 83 const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions)); 84 const checker = program.getTypeChecker(); 85 const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker)); 86 const exportInfo = getAllExportInfoForSymbol(sourceFile, symbol, symbolName, /*isJsxTagName*/ false, program, host, preferences, cancellationToken); 87 const useRequire = shouldUseRequire(sourceFile, program); 88 const fix = getImportFixForSymbol(sourceFile, Debug.checkDefined(exportInfo), moduleSymbol, program, /*useNamespaceInfo*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences); 89 if (fix) { 90 addImport({ fix, symbolName, errorIdentifierText: undefined }); 91 } 92 } 93 94 function addImport(info: FixInfo) { 95 const { fix, symbolName } = info; 96 switch (fix.kind) { 97 case ImportFixKind.UseNamespace: 98 addToNamespace.push(fix); 99 break; 100 case ImportFixKind.JsdocTypeImport: 101 importType.push(fix); 102 break; 103 case ImportFixKind.AddToExisting: { 104 const { importClauseOrBindingPattern, importKind, addAsTypeOnly } = fix; 105 const key = String(getNodeId(importClauseOrBindingPattern)); 106 let entry = addToExisting.get(key); 107 if (!entry) { 108 addToExisting.set(key, entry = { importClauseOrBindingPattern, defaultImport: undefined, namedImports: new Map() }); 109 } 110 if (importKind === ImportKind.Named) { 111 const prevValue = entry?.namedImports.get(symbolName); 112 entry.namedImports.set(symbolName, reduceAddAsTypeOnlyValues(prevValue, addAsTypeOnly)); 113 } 114 else { 115 Debug.assert(entry.defaultImport === undefined || entry.defaultImport.name === symbolName, "(Add to Existing) Default import should be missing or match symbolName"); 116 entry.defaultImport = { 117 name: symbolName, 118 addAsTypeOnly: reduceAddAsTypeOnlyValues(entry.defaultImport?.addAsTypeOnly, addAsTypeOnly), 119 }; 120 } 121 break; 122 } 123 case ImportFixKind.AddNew: { 124 const { moduleSpecifier, importKind, useRequire, addAsTypeOnly } = fix; 125 const entry = getNewImportEntry(moduleSpecifier, importKind, useRequire, addAsTypeOnly); 126 Debug.assert(entry.useRequire === useRequire, "(Add new) Tried to add an `import` and a `require` for the same module"); 127 128 switch (importKind) { 129 case ImportKind.Default: 130 Debug.assert(entry.defaultImport === undefined || entry.defaultImport.name === symbolName, "(Add new) Default import should be missing or match symbolName"); 131 entry.defaultImport = { name: symbolName, addAsTypeOnly: reduceAddAsTypeOnlyValues(entry.defaultImport?.addAsTypeOnly, addAsTypeOnly) }; 132 break; 133 case ImportKind.Named: 134 const prevValue = (entry.namedImports ||= new Map()).get(symbolName); 135 entry.namedImports.set(symbolName, reduceAddAsTypeOnlyValues(prevValue, addAsTypeOnly)); 136 break; 137 case ImportKind.CommonJS: 138 case ImportKind.Namespace: 139 Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName, "Namespacelike import shoudl be missing or match symbolName"); 140 entry.namespaceLikeImport = { importKind, name: symbolName, addAsTypeOnly }; 141 break; 142 } 143 break; 144 } 145 case ImportFixKind.PromoteTypeOnly: 146 // Excluding from fix-all 147 break; 148 default: 149 Debug.assertNever(fix, `fix wasn't never - got kind ${(fix as ImportFix).kind}`); 150 } 151 152 function reduceAddAsTypeOnlyValues(prevValue: AddAsTypeOnly | undefined, newValue: AddAsTypeOnly): AddAsTypeOnly { 153 // `NotAllowed` overrides `Required` because one addition of a new import might be required to be type-only 154 // because of `--importsNotUsedAsValues=error`, but if a second addition of the same import is `NotAllowed` 155 // to be type-only, the reason the first one was `Required` - the unused runtime dependency - is now moot. 156 // Alternatively, if one addition is `Required` because it has no value meaning under `--preserveValueImports` 157 // and `--isolatedModules`, it should be impossible for another addition to be `NotAllowed` since that would 158 // mean a type is being referenced in a value location. 159 return Math.max(prevValue ?? 0, newValue); 160 } 161 162 function getNewImportEntry(moduleSpecifier: string, importKind: ImportKind, useRequire: boolean, addAsTypeOnly: AddAsTypeOnly): Mutable<ImportsCollection & { useRequire: boolean }> { 163 // A default import that requires type-only makes the whole import type-only. 164 // (We could add `default` as a named import, but that style seems undesirable.) 165 // Under `--preserveValueImports` and `--importsNotUsedAsValues=error`, if a 166 // module default-exports a type but named-exports some values (weird), you would 167 // have to use a type-only default import and non-type-only named imports. These 168 // require two separate import declarations, so we build this into the map key. 169 const typeOnlyKey = newImportsKey(moduleSpecifier, /*topLevelTypeOnly*/ true); 170 const nonTypeOnlyKey = newImportsKey(moduleSpecifier, /*topLevelTypeOnly*/ false); 171 const typeOnlyEntry = newImports.get(typeOnlyKey); 172 const nonTypeOnlyEntry = newImports.get(nonTypeOnlyKey); 173 const newEntry: ImportsCollection & { useRequire: boolean } = { 174 defaultImport: undefined, 175 namedImports: undefined, 176 namespaceLikeImport: undefined, 177 useRequire 178 }; 179 if (importKind === ImportKind.Default && addAsTypeOnly === AddAsTypeOnly.Required) { 180 if (typeOnlyEntry) return typeOnlyEntry; 181 newImports.set(typeOnlyKey, newEntry); 182 return newEntry; 183 } 184 if (addAsTypeOnly === AddAsTypeOnly.Allowed && (typeOnlyEntry || nonTypeOnlyEntry)) { 185 return (typeOnlyEntry || nonTypeOnlyEntry)!; 186 } 187 if (nonTypeOnlyEntry) { 188 return nonTypeOnlyEntry; 189 } 190 newImports.set(nonTypeOnlyKey, newEntry); 191 return newEntry; 192 } 193 194 function newImportsKey(moduleSpecifier: string, topLevelTypeOnly: boolean): NewImportsKey { 195 return `${topLevelTypeOnly ? 1 : 0}|${moduleSpecifier}`; 196 } 197 } 198 199 function writeFixes(changeTracker: textChanges.ChangeTracker) { 200 const quotePreference = getQuotePreference(sourceFile, preferences); 201 for (const fix of addToNamespace) { 202 addNamespaceQualifier(changeTracker, sourceFile, fix); 203 } 204 for (const fix of importType) { 205 addImportType(changeTracker, sourceFile, fix, quotePreference); 206 } 207 addToExisting.forEach(({ importClauseOrBindingPattern, defaultImport, namedImports }) => { 208 doAddExistingFix( 209 changeTracker, 210 sourceFile, 211 importClauseOrBindingPattern, 212 defaultImport, 213 arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })), 214 compilerOptions); 215 }); 216 217 let newDeclarations: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[] | undefined; 218 newImports.forEach(({ useRequire, defaultImport, namedImports, namespaceLikeImport }, key) => { 219 const moduleSpecifier = key.slice(2); // From `${0 | 1}|${moduleSpecifier}` format 220 const getDeclarations = useRequire ? getNewRequires : getNewImports; 221 const declarations = getDeclarations( 222 moduleSpecifier, 223 quotePreference, 224 defaultImport, 225 namedImports && arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })), 226 namespaceLikeImport); 227 newDeclarations = combine(newDeclarations, declarations); 228 }); 229 if (newDeclarations) { 230 insertImports(changeTracker, sourceFile, newDeclarations, /*blankLineBetween*/ true); 231 } 232 } 233 234 function hasFixes() { 235 return addToNamespace.length > 0 || importType.length > 0 || addToExisting.size > 0 || newImports.size > 0; 236 } 237 } 238 239 /** 240 * Computes module specifiers for multiple import additions to a file. 241 */ 242 export interface ImportSpecifierResolver { 243 getModuleSpecifierForBestExportInfo( 244 exportInfo: readonly SymbolExportInfo[], 245 symbolName: string, 246 position: number, 247 isValidTypeOnlyUseSite: boolean, 248 fromCacheOnly?: boolean 249 ): { exportInfo?: SymbolExportInfo, moduleSpecifier: string, computedWithoutCacheCount: number } | undefined; 250 } 251 252 export function createImportSpecifierResolver(importingFile: SourceFile, program: Program, host: LanguageServiceHost, preferences: UserPreferences): ImportSpecifierResolver { 253 const packageJsonImportFilter = createPackageJsonImportFilter(importingFile, preferences, host); 254 const importMap = createExistingImportMap(program.getTypeChecker(), importingFile, program.getCompilerOptions()); 255 return { getModuleSpecifierForBestExportInfo }; 256 257 function getModuleSpecifierForBestExportInfo( 258 exportInfo: readonly SymbolExportInfo[], 259 symbolName: string, 260 position: number, 261 isValidTypeOnlyUseSite: boolean, 262 fromCacheOnly?: boolean, 263 ): { exportInfo?: SymbolExportInfo, moduleSpecifier: string, computedWithoutCacheCount: number } | undefined { 264 const { fixes, computedWithoutCacheCount } = getImportFixes( 265 exportInfo, 266 { symbolName, position }, 267 isValidTypeOnlyUseSite, 268 /*useRequire*/ false, 269 program, 270 importingFile, 271 host, 272 preferences, 273 importMap, 274 fromCacheOnly); 275 const result = getBestFix(fixes, importingFile, program, packageJsonImportFilter, host); 276 return result && { ...result, computedWithoutCacheCount }; 277 } 278 } 279 280 // Sorted with the preferred fix coming first. 281 const enum ImportFixKind { UseNamespace, JsdocTypeImport, AddToExisting, AddNew, PromoteTypeOnly } 282 // These should not be combined as bitflags, but are given powers of 2 values to 283 // easily detect conflicts between `NotAllowed` and `Required` by giving them a unique sum. 284 // They're also ordered in terms of increasing priority for a fix-all scenario (see 285 // `reduceAddAsTypeOnlyValues`). 286 const enum AddAsTypeOnly { 287 Allowed = 1 << 0, 288 Required = 1 << 1, 289 NotAllowed = 1 << 2, 290 } 291 type ImportFix = FixUseNamespaceImport | FixAddJsdocTypeImport | FixAddToExistingImport | FixAddNewImport | FixPromoteTypeOnlyImport; 292 type ImportFixWithModuleSpecifier = FixUseNamespaceImport | FixAddJsdocTypeImport | FixAddToExistingImport | FixAddNewImport; 293 294 // Properties are be undefined if fix is derived from an existing import 295 interface ImportFixBase { 296 readonly isReExport?: boolean; 297 readonly exportInfo?: SymbolExportInfo; 298 readonly moduleSpecifier: string; 299 } 300 interface FixUseNamespaceImport extends ImportFixBase { 301 readonly kind: ImportFixKind.UseNamespace; 302 readonly namespacePrefix: string; 303 readonly position: number; 304 } 305 interface FixAddJsdocTypeImport extends ImportFixBase { 306 readonly kind: ImportFixKind.JsdocTypeImport; 307 readonly position: number; 308 readonly isReExport: boolean; 309 readonly exportInfo: SymbolExportInfo; 310 } 311 interface FixAddToExistingImport extends ImportFixBase { 312 readonly kind: ImportFixKind.AddToExisting; 313 readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern; 314 readonly importKind: ImportKind.Default | ImportKind.Named; 315 readonly addAsTypeOnly: AddAsTypeOnly; 316 } 317 interface FixAddNewImport extends ImportFixBase { 318 readonly kind: ImportFixKind.AddNew; 319 readonly importKind: ImportKind; 320 readonly addAsTypeOnly: AddAsTypeOnly; 321 readonly useRequire: boolean; 322 } 323 interface FixPromoteTypeOnlyImport { 324 readonly kind: ImportFixKind.PromoteTypeOnly; 325 readonly typeOnlyAliasDeclaration: TypeOnlyAliasDeclaration; 326 } 327 328 329 /** Information needed to augment an existing import declaration. */ 330 interface FixAddToExistingImportInfo { 331 readonly declaration: AnyImportOrRequire; 332 readonly importKind: ImportKind; 333 readonly targetFlags: SymbolFlags; 334 readonly symbol: Symbol; 335 } 336 337 export function getImportCompletionAction( 338 targetSymbol: Symbol, 339 moduleSymbol: Symbol, 340 sourceFile: SourceFile, 341 symbolName: string, 342 isJsxTagName: boolean, 343 host: LanguageServiceHost, 344 program: Program, 345 formatContext: formatting.FormatContext, 346 position: number, 347 preferences: UserPreferences, 348 cancellationToken: CancellationToken, 349 ): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } { 350 const compilerOptions = program.getCompilerOptions(); 351 352 const exportInfos = pathIsBareSpecifier(stripQuotes(moduleSymbol.name)) 353 ? [getSingleExportInfoForSymbol(targetSymbol, moduleSymbol, program, host)] 354 : getAllExportInfoForSymbol(sourceFile, targetSymbol, symbolName, isJsxTagName, program, host, preferences, cancellationToken); 355 356 Debug.assertIsDefined(exportInfos); 357 const useRequire = shouldUseRequire(sourceFile, program); 358 const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position)); 359 const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, program, { symbolName, position }, isValidTypeOnlyUseSite, useRequire, host, preferences)); 360 return { 361 moduleSpecifier: fix.moduleSpecifier, 362 codeAction: codeFixActionToCodeAction(codeActionForFix( 363 { host, formatContext, preferences }, 364 sourceFile, 365 symbolName, 366 fix, 367 /*includeSymbolNameInDescription*/ false, 368 getQuotePreference(sourceFile, preferences), compilerOptions)) 369 }; 370 } 371 372 export function getPromoteTypeOnlyCompletionAction(sourceFile: SourceFile, symbolToken: Identifier, program: Program, host: LanguageServiceHost, formatContext: formatting.FormatContext, preferences: UserPreferences) { 373 const compilerOptions = program.getCompilerOptions(); 374 const symbolName = single(getSymbolNamesToImport(sourceFile, program.getTypeChecker(), symbolToken, compilerOptions)); 375 const fix = getTypeOnlyPromotionFix(sourceFile, symbolToken, symbolName, program); 376 const includeSymbolNameInDescription = symbolName !== symbolToken.text; 377 return fix && codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, includeSymbolNameInDescription, QuotePreference.Double, compilerOptions)); 378 } 379 380 function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, program: Program, useNamespaceInfo: { position: number, symbolName: string } | undefined, isValidTypeOnlyUseSite: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) { 381 Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol || info.symbol.parent === moduleSymbol), "Some exportInfo should match the specified moduleSymbol"); 382 const packageJsonImportFilter = createPackageJsonImportFilter(sourceFile, preferences, host); 383 return getBestFix(getImportFixes(exportInfos, useNamespaceInfo, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences).fixes, sourceFile, program, packageJsonImportFilter, host); 384 } 385 386 function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction { 387 return { description, changes, commands }; 388 } 389 390 function getAllExportInfoForSymbol(importingFile: SourceFile, symbol: Symbol, symbolName: string, preferCapitalized: boolean, program: Program, host: LanguageServiceHost, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): readonly SymbolExportInfo[] | undefined { 391 const getChecker = createGetChecker(program, host); 392 return getExportInfoMap(importingFile, host, program, preferences, cancellationToken) 393 .search(importingFile.path, preferCapitalized, name => name === symbolName, info => { 394 if (skipAlias(info[0].symbol, getChecker(info[0].isFromPackageJson)) === symbol) { 395 return info; 396 } 397 }); 398 } 399 400 function getSingleExportInfoForSymbol(symbol: Symbol, moduleSymbol: Symbol, program: Program, host: LanguageServiceHost): SymbolExportInfo { 401 const compilerOptions = program.getCompilerOptions(); 402 const mainProgramInfo = getInfoWithChecker(program.getTypeChecker(), /*isFromPackageJson*/ false); 403 if (mainProgramInfo) { 404 return mainProgramInfo; 405 } 406 const autoImportProvider = host.getPackageJsonAutoImportProvider?.()?.getTypeChecker(); 407 return Debug.checkDefined(autoImportProvider && getInfoWithChecker(autoImportProvider, /*isFromPackageJson*/ true), `Could not find symbol in specified module for code actions`); 408 409 function getInfoWithChecker(checker: TypeChecker, isFromPackageJson: boolean): SymbolExportInfo | undefined { 410 const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions); 411 if (defaultInfo && skipAlias(defaultInfo.symbol, checker) === symbol) { 412 return { symbol: defaultInfo.symbol, moduleSymbol, moduleFileName: undefined, exportKind: defaultInfo.exportKind, targetFlags: skipAlias(symbol, checker).flags, isFromPackageJson }; 413 } 414 const named = checker.tryGetMemberInModuleExportsAndProperties(symbol.name, moduleSymbol); 415 if (named && skipAlias(named, checker) === symbol) { 416 return { symbol: named, moduleSymbol, moduleFileName: undefined, exportKind: ExportKind.Named, targetFlags: skipAlias(symbol, checker).flags, isFromPackageJson }; 417 } 418 } 419 } 420 421 function getImportFixes( 422 exportInfos: readonly SymbolExportInfo[], 423 useNamespaceInfo: { 424 symbolName: string, 425 position: number, 426 } | undefined, 427 /** undefined only for missing JSX namespace */ 428 isValidTypeOnlyUseSite: boolean, 429 useRequire: boolean, 430 program: Program, 431 sourceFile: SourceFile, 432 host: LanguageServiceHost, 433 preferences: UserPreferences, 434 importMap = createExistingImportMap(program.getTypeChecker(), sourceFile, program.getCompilerOptions()), 435 fromCacheOnly?: boolean, 436 ): { computedWithoutCacheCount: number, fixes: readonly ImportFixWithModuleSpecifier[] } { 437 const checker = program.getTypeChecker(); 438 const existingImports = flatMap(exportInfos, importMap.getImportsForExportInfo); 439 const useNamespace = useNamespaceInfo && tryUseExistingNamespaceImport(existingImports, useNamespaceInfo.symbolName, useNamespaceInfo.position, checker); 440 const addToExisting = tryAddToExistingImport(existingImports, isValidTypeOnlyUseSite, checker, program.getCompilerOptions()); 441 if (addToExisting) { 442 // Don't bother providing an action to add a new import if we can add to an existing one. 443 return { 444 computedWithoutCacheCount: 0, 445 fixes: [...(useNamespace ? [useNamespace] : emptyArray), addToExisting], 446 }; 447 } 448 449 const { fixes, computedWithoutCacheCount = 0 } = getFixesForAddImport( 450 exportInfos, 451 existingImports, 452 program, 453 sourceFile, 454 useNamespaceInfo?.position, 455 isValidTypeOnlyUseSite, 456 useRequire, 457 host, 458 preferences, 459 fromCacheOnly); 460 return { 461 computedWithoutCacheCount, 462 fixes: [...(useNamespace ? [useNamespace] : emptyArray), ...fixes], 463 }; 464 } 465 466 function tryUseExistingNamespaceImport(existingImports: readonly FixAddToExistingImportInfo[], symbolName: string, position: number, checker: TypeChecker): FixUseNamespaceImport | undefined { 467 // It is possible that multiple import statements with the same specifier exist in the file. 468 // e.g. 469 // 470 // import * as ns from "foo"; 471 // import { member1, member2 } from "foo"; 472 // 473 // member3/**/ <-- cusor here 474 // 475 // in this case we should provie 2 actions: 476 // 1. change "member3" to "ns.member3" 477 // 2. add "member3" to the second import statement's import list 478 // and it is up to the user to decide which one fits best. 479 return firstDefined(existingImports, ({ declaration }): FixUseNamespaceImport | undefined => { 480 const namespacePrefix = getNamespaceLikeImportText(declaration); 481 const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(declaration)?.text; 482 if (namespacePrefix && moduleSpecifier) { 483 const moduleSymbol = getTargetModuleFromNamespaceLikeImport(declaration, checker); 484 if (moduleSymbol && moduleSymbol.exports!.has(escapeLeadingUnderscores(symbolName))) { 485 return { kind: ImportFixKind.UseNamespace, namespacePrefix, position, moduleSpecifier }; 486 } 487 } 488 }); 489 } 490 491 function getTargetModuleFromNamespaceLikeImport(declaration: AnyImportOrRequire, checker: TypeChecker) { 492 switch (declaration.kind) { 493 case SyntaxKind.VariableDeclaration: 494 return checker.resolveExternalModuleName(declaration.initializer.arguments[0]); 495 case SyntaxKind.ImportEqualsDeclaration: 496 return checker.getAliasedSymbol(declaration.symbol); 497 case SyntaxKind.ImportDeclaration: 498 const namespaceImport = tryCast(declaration.importClause?.namedBindings, isNamespaceImport); 499 return namespaceImport && checker.getAliasedSymbol(namespaceImport.symbol); 500 default: 501 return Debug.assertNever(declaration); 502 } 503 } 504 505 function getNamespaceLikeImportText(declaration: AnyImportOrRequire) { 506 switch (declaration.kind) { 507 case SyntaxKind.VariableDeclaration: 508 return tryCast(declaration.name, isIdentifier)?.text; 509 case SyntaxKind.ImportEqualsDeclaration: 510 return declaration.name.text; 511 case SyntaxKind.ImportDeclaration: 512 return tryCast(declaration.importClause?.namedBindings, isNamespaceImport)?.name.text; 513 default: 514 return Debug.assertNever(declaration); 515 } 516 } 517 518 function getAddAsTypeOnly( 519 isValidTypeOnlyUseSite: boolean, 520 isForNewImportDeclaration: boolean, 521 symbol: Symbol, 522 targetFlags: SymbolFlags, 523 checker: TypeChecker, 524 compilerOptions: CompilerOptions 525 ) { 526 if (!isValidTypeOnlyUseSite) { 527 // Can't use a type-only import if the usage is an emitting position 528 return AddAsTypeOnly.NotAllowed; 529 } 530 if (isForNewImportDeclaration && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error) { 531 // Not writing a (top-level) type-only import here would create an error because the runtime dependency is unnecessary 532 return AddAsTypeOnly.Required; 533 } 534 if (compilerOptions.isolatedModules && compilerOptions.preserveValueImports && 535 (!(targetFlags & SymbolFlags.Value) || !!checker.getTypeOnlyAliasDeclaration(symbol)) 536 ) { 537 // A type-only import is required for this symbol if under these settings if the symbol will 538 // be erased, which will happen if the target symbol is purely a type or if it was exported/imported 539 // as type-only already somewhere between this import and the target. 540 return AddAsTypeOnly.Required; 541 } 542 return AddAsTypeOnly.Allowed; 543 } 544 545 function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], isValidTypeOnlyUseSite: boolean, checker: TypeChecker, compilerOptions: CompilerOptions): FixAddToExistingImport | undefined { 546 return firstDefined(existingImports, ({ declaration, importKind, symbol, targetFlags }): FixAddToExistingImport | undefined => { 547 if (importKind === ImportKind.CommonJS || importKind === ImportKind.Namespace || declaration.kind === SyntaxKind.ImportEqualsDeclaration) { 548 // These kinds of imports are not combinable with anything 549 return undefined; 550 } 551 552 if (declaration.kind === SyntaxKind.VariableDeclaration) { 553 return (importKind === ImportKind.Named || importKind === ImportKind.Default) && declaration.name.kind === SyntaxKind.ObjectBindingPattern 554 ? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: declaration.name, importKind, moduleSpecifier: declaration.initializer.arguments[0].text, addAsTypeOnly: AddAsTypeOnly.NotAllowed } 555 : undefined; 556 } 557 558 const { importClause } = declaration; 559 if (!importClause || !isStringLiteralLike(declaration.moduleSpecifier)) return undefined; 560 const { name, namedBindings } = importClause; 561 // A type-only import may not have both a default and named imports, so the only way a name can 562 // be added to an existing type-only import is adding a named import to existing named bindings. 563 if (importClause.isTypeOnly && !(importKind === ImportKind.Named && namedBindings)) return undefined; 564 565 // N.B. we don't have to figure out whether to use the main program checker 566 // or the AutoImportProvider checker because we're adding to an existing import; the existence of 567 // the import guarantees the symbol came from the main program. 568 const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ false, symbol, targetFlags, checker, compilerOptions); 569 570 if (importKind === ImportKind.Default && ( 571 name || // Cannot add a default import to a declaration that already has one 572 addAsTypeOnly === AddAsTypeOnly.Required && namedBindings // Cannot add a default import as type-only if the import already has named bindings 573 )) return undefined; 574 if ( 575 importKind === ImportKind.Named && 576 namedBindings?.kind === SyntaxKind.NamespaceImport // Cannot add a named import to a declaration that has a namespace import 577 ) return undefined; 578 579 return { 580 kind: ImportFixKind.AddToExisting, 581 importClauseOrBindingPattern: importClause, 582 importKind, 583 moduleSpecifier: declaration.moduleSpecifier.text, 584 addAsTypeOnly, 585 }; 586 }); 587 } 588 589 function createExistingImportMap(checker: TypeChecker, importingFile: SourceFile, compilerOptions: CompilerOptions) { 590 let importMap: MultiMap<SymbolId, AnyImportOrRequire> | undefined; 591 for (const moduleSpecifier of importingFile.imports) { 592 const i = importFromModuleSpecifier(moduleSpecifier); 593 if (isVariableDeclarationInitializedToRequire(i.parent)) { 594 const moduleSymbol = checker.resolveExternalModuleName(moduleSpecifier); 595 if (moduleSymbol) { 596 (importMap ||= createMultiMap()).add(getSymbolId(moduleSymbol), i.parent); 597 } 598 } 599 else if (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration) { 600 const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); 601 if (moduleSymbol) { 602 (importMap ||= createMultiMap()).add(getSymbolId(moduleSymbol), i); 603 } 604 } 605 } 606 607 return { 608 getImportsForExportInfo: ({ moduleSymbol, exportKind, targetFlags, symbol }: SymbolExportInfo): readonly FixAddToExistingImportInfo[] => { 609 // Can't use an es6 import for a type in JS. 610 if (!(targetFlags & SymbolFlags.Value) && isSourceFileJS(importingFile)) return emptyArray; 611 const matchingDeclarations = importMap?.get(getSymbolId(moduleSymbol)); 612 if (!matchingDeclarations) return emptyArray; 613 const importKind = getImportKind(importingFile, exportKind, compilerOptions); 614 return matchingDeclarations.map(declaration => ({ declaration, importKind, symbol, targetFlags })); 615 } 616 }; 617 } 618 619 function shouldUseRequire(sourceFile: SourceFile, program: Program): boolean { 620 // 1. TypeScript files don't use require variable declarations 621 if (!isSourceFileJS(sourceFile)) { 622 return false; 623 } 624 625 // 2. If the current source file is unambiguously CJS or ESM, go with that 626 if (sourceFile.commonJsModuleIndicator && !sourceFile.externalModuleIndicator) return true; 627 if (sourceFile.externalModuleIndicator && !sourceFile.commonJsModuleIndicator) return false; 628 629 // 3. If there's a tsconfig/jsconfig, use its module setting 630 const compilerOptions = program.getCompilerOptions(); 631 if (compilerOptions.configFile) { 632 return getEmitModuleKind(compilerOptions) < ModuleKind.ES2015; 633 } 634 635 // 4. Match the first other JS file in the program that's unambiguously CJS or ESM 636 for (const otherFile of program.getSourceFiles()) { 637 if (otherFile === sourceFile || !isSourceFileJS(otherFile) || program.isSourceFileFromExternalLibrary(otherFile)) continue; 638 if (otherFile.commonJsModuleIndicator && !otherFile.externalModuleIndicator) return true; 639 if (otherFile.externalModuleIndicator && !otherFile.commonJsModuleIndicator) return false; 640 } 641 642 // 5. Literally nothing to go on 643 return true; 644 } 645 646 function createGetChecker(program: Program, host: LanguageServiceHost) { 647 return memoizeOne((isFromPackageJson: boolean) => isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker()); 648 } 649 650 function getNewImportFixes( 651 program: Program, 652 sourceFile: SourceFile, 653 position: number | undefined, 654 isValidTypeOnlyUseSite: boolean, 655 useRequire: boolean, 656 exportInfo: readonly SymbolExportInfo[], 657 host: LanguageServiceHost, 658 preferences: UserPreferences, 659 fromCacheOnly?: boolean, 660 ): { computedWithoutCacheCount: number, fixes: readonly (FixAddNewImport | FixAddJsdocTypeImport)[] } { 661 const isJs = isSourceFileJS(sourceFile); 662 const compilerOptions = program.getCompilerOptions(); 663 const moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host); 664 const getChecker = createGetChecker(program, host); 665 const rejectNodeModulesRelativePaths = moduleResolutionUsesNodeModules(getEmitModuleResolutionKind(compilerOptions)); 666 const getModuleSpecifiers = fromCacheOnly 667 ? (moduleSymbol: Symbol) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false }) 668 : (moduleSymbol: Symbol, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences); 669 670 let computedWithoutCacheCount = 0; 671 const fixes = flatMap(exportInfo, (exportInfo, i) => { 672 const checker = getChecker(exportInfo.isFromPackageJson); 673 const { computedWithoutCache, moduleSpecifiers } = getModuleSpecifiers(exportInfo.moduleSymbol, checker); 674 const importedSymbolHasValueMeaning = !!(exportInfo.targetFlags & SymbolFlags.Value); 675 const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, exportInfo.symbol, exportInfo.targetFlags, checker, compilerOptions); 676 computedWithoutCacheCount += computedWithoutCache ? 1 : 0; 677 return mapDefined(moduleSpecifiers, (moduleSpecifier): FixAddNewImport | FixAddJsdocTypeImport | undefined => 678 rejectNodeModulesRelativePaths && pathContainsNodeModules(moduleSpecifier) ? undefined : 679 // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types. 680 !importedSymbolHasValueMeaning && isJs && position !== undefined ? { kind: ImportFixKind.JsdocTypeImport, moduleSpecifier, position, exportInfo, isReExport: i > 0 } : 681 { 682 kind: ImportFixKind.AddNew, 683 moduleSpecifier, 684 importKind: getImportKind(sourceFile, exportInfo.exportKind, compilerOptions), 685 useRequire, 686 addAsTypeOnly, 687 exportInfo, 688 isReExport: i > 0, 689 } 690 ); 691 }); 692 693 return { computedWithoutCacheCount, fixes }; 694 } 695 696 function getFixesForAddImport( 697 exportInfos: readonly SymbolExportInfo[], 698 existingImports: readonly FixAddToExistingImportInfo[], 699 program: Program, 700 sourceFile: SourceFile, 701 position: number | undefined, 702 isValidTypeOnlyUseSite: boolean, 703 useRequire: boolean, 704 host: LanguageServiceHost, 705 preferences: UserPreferences, 706 fromCacheOnly?: boolean, 707 ): { computedWithoutCacheCount?: number, fixes: readonly (FixAddNewImport | FixAddJsdocTypeImport)[] } { 708 const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, isValidTypeOnlyUseSite, useRequire, program.getTypeChecker(), program.getCompilerOptions())); 709 return existingDeclaration ? { fixes: [existingDeclaration] } : getNewImportFixes(program, sourceFile, position, isValidTypeOnlyUseSite, useRequire, exportInfos, host, preferences, fromCacheOnly); 710 } 711 712 function newImportInfoFromExistingSpecifier( 713 { declaration, importKind, symbol, targetFlags }: FixAddToExistingImportInfo, 714 isValidTypeOnlyUseSite: boolean, 715 useRequire: boolean, 716 checker: TypeChecker, 717 compilerOptions: CompilerOptions 718 ): FixAddNewImport | undefined { 719 const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(declaration)?.text; 720 if (moduleSpecifier) { 721 const addAsTypeOnly = useRequire 722 ? AddAsTypeOnly.NotAllowed 723 : getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, symbol, targetFlags, checker, compilerOptions); 724 return { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, addAsTypeOnly, useRequire }; 725 } 726 } 727 728 interface FixInfo { 729 readonly fix: ImportFix; 730 readonly symbolName: string; 731 readonly errorIdentifierText: string | undefined; 732 readonly isJsxNamespaceFix?: boolean; 733 } 734 function getFixInfos(context: CodeFixContextBase, errorCode: number, pos: number, useAutoImportProvider: boolean): readonly FixInfo[] | undefined { 735 const symbolToken = getTokenAtPosition(context.sourceFile, pos); 736 let info; 737 if (errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code) { 738 info = getFixesInfoForUMDImport(context, symbolToken); 739 } 740 else if (!isIdentifier(symbolToken)) { 741 return undefined; 742 } 743 else if (errorCode === Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type.code) { 744 const symbolName = single(getSymbolNamesToImport(context.sourceFile, context.program.getTypeChecker(), symbolToken, context.program.getCompilerOptions())); 745 const fix = getTypeOnlyPromotionFix(context.sourceFile, symbolToken, symbolName, context.program); 746 return fix && [{ fix, symbolName, errorIdentifierText: symbolToken.text }]; 747 } 748 else { 749 info = getFixesInfoForNonUMDImport(context, symbolToken, useAutoImportProvider); 750 } 751 752 const packageJsonImportFilter = createPackageJsonImportFilter(context.sourceFile, context.preferences, context.host); 753 return info && sortFixInfo(info, context.sourceFile, context.program, packageJsonImportFilter, context.host); 754 } 755 756 function sortFixInfo(fixes: readonly (FixInfo & { fix: ImportFixWithModuleSpecifier })[], sourceFile: SourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter, host: LanguageServiceHost): readonly (FixInfo & { fix: ImportFixWithModuleSpecifier })[] { 757 const _toPath = (fileName: string) => toPath(fileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)); 758 return sort(fixes, (a, b) => 759 compareBooleans(!!a.isJsxNamespaceFix, !!b.isJsxNamespaceFix) || 760 compareValues(a.fix.kind, b.fix.kind) || 761 compareModuleSpecifiers(a.fix, b.fix, sourceFile, program, packageJsonImportFilter.allowsImportingSpecifier, _toPath)); 762 } 763 764 function getBestFix(fixes: readonly ImportFixWithModuleSpecifier[], sourceFile: SourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter, host: LanguageServiceHost): ImportFixWithModuleSpecifier | undefined { 765 if (!some(fixes)) return; 766 // These will always be placed first if available, and are better than other kinds 767 if (fixes[0].kind === ImportFixKind.UseNamespace || fixes[0].kind === ImportFixKind.AddToExisting) { 768 return fixes[0]; 769 } 770 771 return fixes.reduce((best, fix) => 772 // Takes true branch of conditional if `fix` is better than `best` 773 compareModuleSpecifiers( 774 fix, 775 best, 776 sourceFile, 777 program, 778 packageJsonImportFilter.allowsImportingSpecifier, 779 fileName => toPath(fileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)), 780 ) === Comparison.LessThan ? fix : best 781 ); 782 } 783 784 /** @returns `Comparison.LessThan` if `a` is better than `b`. */ 785 function compareModuleSpecifiers( 786 a: ImportFixWithModuleSpecifier, 787 b: ImportFixWithModuleSpecifier, 788 importingFile: SourceFile, 789 program: Program, 790 allowsImportingSpecifier: (specifier: string) => boolean, 791 toPath: (fileName: string) => Path, 792 ): Comparison { 793 if (a.kind !== ImportFixKind.UseNamespace && b.kind !== ImportFixKind.UseNamespace) { 794 return compareBooleans(allowsImportingSpecifier(b.moduleSpecifier), allowsImportingSpecifier(a.moduleSpecifier)) 795 || compareNodeCoreModuleSpecifiers(a.moduleSpecifier, b.moduleSpecifier, importingFile, program) 796 || compareBooleans( 797 isFixPossiblyReExportingImportingFile(a, importingFile, program.getCompilerOptions(), toPath), 798 isFixPossiblyReExportingImportingFile(b, importingFile, program.getCompilerOptions(), toPath)) 799 || compareNumberOfDirectorySeparators(a.moduleSpecifier, b.moduleSpecifier); 800 } 801 return Comparison.EqualTo; 802 } 803 804 // This is a simple heuristic to try to avoid creating an import cycle with a barrel re-export. 805 // E.g., do not `import { Foo } from ".."` when you could `import { Foo } from "../Foo"`. 806 // This can produce false positives or negatives if re-exports cross into sibling directories 807 // (e.g. `export * from "../whatever"`) or are not named "index" (we don't even try to consider 808 // this if we're in a resolution mode where you can't drop trailing "/index" from paths). 809 function isFixPossiblyReExportingImportingFile(fix: ImportFixWithModuleSpecifier, importingFile: SourceFile, compilerOptions: CompilerOptions, toPath: (fileName: string) => Path): boolean { 810 if (fix.isReExport && 811 fix.exportInfo?.moduleFileName && 812 getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && 813 isIndexFileName(fix.exportInfo.moduleFileName) 814 ) { 815 const reExportDir = toPath(getDirectoryPath(fix.exportInfo.moduleFileName)); 816 return startsWith((importingFile.path), reExportDir); 817 } 818 return false; 819 } 820 821 function isIndexFileName(fileName: string) { 822 return getBaseFileName(fileName, [".js", ".jsx", ".d.ts", ".ts", ".tsx"], /*ignoreCase*/ true) === "index"; 823 } 824 825 function compareNodeCoreModuleSpecifiers(a: string, b: string, importingFile: SourceFile, program: Program): Comparison { 826 if (startsWith(a, "node:") && !startsWith(b, "node:")) return shouldUseUriStyleNodeCoreModules(importingFile, program) ? Comparison.LessThan : Comparison.GreaterThan; 827 if (startsWith(b, "node:") && !startsWith(a, "node:")) return shouldUseUriStyleNodeCoreModules(importingFile, program) ? Comparison.GreaterThan : Comparison.LessThan; 828 return Comparison.EqualTo; 829 } 830 831 function getFixesInfoForUMDImport({ sourceFile, program, host, preferences }: CodeFixContextBase, token: Node): (FixInfo & { fix: ImportFixWithModuleSpecifier })[] | undefined { 832 const checker = program.getTypeChecker(); 833 const umdSymbol = getUmdSymbol(token, checker); 834 if (!umdSymbol) return undefined; 835 const symbol = checker.getAliasedSymbol(umdSymbol); 836 const symbolName = umdSymbol.name; 837 const exportInfo: readonly SymbolExportInfo[] = [{ symbol: umdSymbol, moduleSymbol: symbol, moduleFileName: undefined, exportKind: ExportKind.UMD, targetFlags: symbol.flags, isFromPackageJson: false }]; 838 const useRequire = shouldUseRequire(sourceFile, program); 839 const position = isIdentifier(token) ? token.getStart(sourceFile) : undefined; 840 const fixes = getImportFixes(exportInfo, position ? { position, symbolName } : undefined, /*isValidTypeOnlyUseSite*/ false, useRequire, program, sourceFile, host, preferences).fixes; 841 return fixes.map(fix => ({ fix, symbolName, errorIdentifierText: tryCast(token, isIdentifier)?.text })); 842 } 843 function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined { 844 // try the identifier to see if it is the umd symbol 845 const umdSymbol = isIdentifier(token) ? checker.getSymbolAtLocation(token) : undefined; 846 if (isUMDExportSymbol(umdSymbol)) return umdSymbol; 847 848 // The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`. 849 const { parent } = token; 850 return (isJsxOpeningLikeElement(parent) && parent.tagName === token) || isJsxOpeningFragment(parent) 851 ? tryCast(checker.resolveName(checker.getJsxNamespace(parent), isJsxOpeningLikeElement(parent) ? token : parent, SymbolFlags.Value, /*excludeGlobals*/ false), isUMDExportSymbol) 852 : undefined; 853 } 854 855 /** 856 * @param forceImportKeyword Indicates that the user has already typed `import`, so the result must start with `import`. 857 * (In other words, do not allow `const x = require("...")` for JS files.) 858 */ 859 export function getImportKind(importingFile: SourceFile, exportKind: ExportKind, compilerOptions: CompilerOptions, forceImportKeyword?: boolean): ImportKind { 860 switch (exportKind) { 861 case ExportKind.Named: return ImportKind.Named; 862 case ExportKind.Default: return ImportKind.Default; 863 case ExportKind.ExportEquals: return getExportEqualsImportKind(importingFile, compilerOptions, !!forceImportKeyword); 864 case ExportKind.UMD: return getUmdImportKind(importingFile, compilerOptions, !!forceImportKeyword); 865 default: return Debug.assertNever(exportKind); 866 } 867 } 868 869 function getUmdImportKind(importingFile: SourceFile, compilerOptions: CompilerOptions, forceImportKeyword: boolean): ImportKind { 870 // Import a synthetic `default` if enabled. 871 if (getAllowSyntheticDefaultImports(compilerOptions)) { 872 return ImportKind.Default; 873 } 874 875 // When a synthetic `default` is unavailable, use `import..require` if the module kind supports it. 876 const moduleKind = getEmitModuleKind(compilerOptions); 877 switch (moduleKind) { 878 case ModuleKind.AMD: 879 case ModuleKind.CommonJS: 880 case ModuleKind.UMD: 881 if (isInJSFile(importingFile)) { 882 return isExternalModule(importingFile) || forceImportKeyword ? ImportKind.Namespace : ImportKind.CommonJS; 883 } 884 return ImportKind.CommonJS; 885 case ModuleKind.System: 886 case ModuleKind.ES2015: 887 case ModuleKind.ES2020: 888 case ModuleKind.ES2022: 889 case ModuleKind.ESNext: 890 case ModuleKind.None: 891 // Fall back to the `import * as ns` style import. 892 return ImportKind.Namespace; 893 case ModuleKind.Node16: 894 case ModuleKind.NodeNext: 895 return importingFile.impliedNodeFormat === ModuleKind.ESNext ? ImportKind.Namespace : ImportKind.CommonJS; 896 default: 897 return Debug.assertNever(moduleKind, `Unexpected moduleKind ${moduleKind}`); 898 } 899 } 900 901 function getFixesInfoForNonUMDImport({ sourceFile, program, cancellationToken, host, preferences }: CodeFixContextBase, symbolToken: Identifier, useAutoImportProvider: boolean): readonly (FixInfo & { fix: ImportFixWithModuleSpecifier })[] | undefined { 902 const checker = program.getTypeChecker(); 903 const compilerOptions = program.getCompilerOptions(); 904 return flatMap(getSymbolNamesToImport(sourceFile, checker, symbolToken, compilerOptions), symbolName => { 905 // "default" is a keyword and not a legal identifier for the import, but appears as an identifier. 906 if (symbolName === InternalSymbolName.Default) { 907 return undefined; 908 } 909 const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite(symbolToken); 910 const useRequire = shouldUseRequire(sourceFile, program); 911 const exportInfo = getExportInfos(symbolName, isJSXTagName(symbolToken), getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, program, useAutoImportProvider, host, preferences); 912 const fixes = arrayFrom(flatMapIterator(exportInfo.entries(), ([_, exportInfos]) => 913 getImportFixes(exportInfos, { symbolName, position: symbolToken.getStart(sourceFile) }, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences).fixes)); 914 return fixes.map(fix => ({ fix, symbolName, errorIdentifierText: symbolToken.text, isJsxNamespaceFix: symbolName !== symbolToken.text })); 915 }); 916 } 917 918 function getTypeOnlyPromotionFix(sourceFile: SourceFile, symbolToken: Identifier, symbolName: string, program: Program): FixPromoteTypeOnlyImport | undefined { 919 const checker = program.getTypeChecker(); 920 const symbol = checker.resolveName(symbolName, symbolToken, SymbolFlags.Value, /*excludeGlobals*/ true); 921 if (!symbol) return undefined; 922 923 const typeOnlyAliasDeclaration = checker.getTypeOnlyAliasDeclaration(symbol); 924 if (!typeOnlyAliasDeclaration || getSourceFileOfNode(typeOnlyAliasDeclaration) !== sourceFile) return undefined; 925 926 return { kind: ImportFixKind.PromoteTypeOnly, typeOnlyAliasDeclaration }; 927 } 928 929 function getSymbolNamesToImport(sourceFile: SourceFile, checker: TypeChecker, symbolToken: Identifier, compilerOptions: CompilerOptions): string[] { 930 const parent = symbolToken.parent; 931 if ((isJsxOpeningLikeElement(parent) || isJsxClosingElement(parent)) && parent.tagName === symbolToken && jsxModeNeedsExplicitImport(compilerOptions.jsx)) { 932 const jsxNamespace = checker.getJsxNamespace(sourceFile); 933 if (needsJsxNamespaceFix(jsxNamespace, symbolToken, checker)) { 934 const needsComponentNameFix = !isIntrinsicJsxName(symbolToken.text) && !checker.resolveName(symbolToken.text, symbolToken, SymbolFlags.Value, /*excludeGlobals*/ false); 935 return needsComponentNameFix ? [symbolToken.text, jsxNamespace] : [jsxNamespace]; 936 } 937 } 938 return [symbolToken.text]; 939 } 940 941 function needsJsxNamespaceFix(jsxNamespace: string, symbolToken: Identifier, checker: TypeChecker) { 942 if (isIntrinsicJsxName(symbolToken.text)) return true; // If we were triggered by a matching error code on an intrinsic, the error must have been about missing the JSX factory 943 const namespaceSymbol = checker.resolveName(jsxNamespace, symbolToken, SymbolFlags.Value, /*excludeGlobals*/ true); 944 return !namespaceSymbol || some(namespaceSymbol.declarations, isTypeOnlyImportOrExportDeclaration) && !(namespaceSymbol.flags & SymbolFlags.Value); 945 } 946 947 // Returns a map from an exported symbol's ID to a list of every way it's (re-)exported. 948 function getExportInfos( 949 symbolName: string, 950 isJsxTagName: boolean, 951 currentTokenMeaning: SemanticMeaning, 952 cancellationToken: CancellationToken, 953 fromFile: SourceFile, 954 program: Program, 955 useAutoImportProvider: boolean, 956 host: LanguageServiceHost, 957 preferences: UserPreferences, 958 ): ReadonlyESMap<string, readonly SymbolExportInfo[]> { 959 // For each original symbol, keep all re-exports of that symbol together so we can call `getCodeActionsForImport` on the whole group at once. 960 // Maps symbol id to info for modules providing that symbol (original export + re-exports). 961 const originalSymbolToExportInfos = createMultiMap<SymbolExportInfo>(); 962 const packageJsonFilter = createPackageJsonImportFilter(fromFile, preferences, host); 963 const moduleSpecifierCache = host.getModuleSpecifierCache?.(); 964 const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => { 965 return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host); 966 }); 967 function addSymbol(moduleSymbol: Symbol, toFile: SourceFile | undefined, exportedSymbol: Symbol, exportKind: ExportKind, program: Program, isFromPackageJson: boolean): void { 968 const moduleSpecifierResolutionHost = getModuleSpecifierResolutionHost(isFromPackageJson); 969 if (toFile && isImportableFile(program, fromFile, toFile, preferences, packageJsonFilter, moduleSpecifierResolutionHost, moduleSpecifierCache) || 970 !toFile && packageJsonFilter.allowsImportingAmbientModule(moduleSymbol, moduleSpecifierResolutionHost) 971 ) { 972 const checker = program.getTypeChecker(); 973 originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { symbol: exportedSymbol, moduleSymbol, moduleFileName: toFile?.fileName, exportKind, targetFlags: skipAlias(exportedSymbol, checker).flags, isFromPackageJson }); 974 } 975 } 976 forEachExternalModuleToImportFrom(program, host, preferences, useAutoImportProvider, (moduleSymbol, sourceFile, program, isFromPackageJson) => { 977 const checker = program.getTypeChecker(); 978 cancellationToken.throwIfCancellationRequested(); 979 980 const compilerOptions = program.getCompilerOptions(); 981 const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions); 982 if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions), isJsxTagName) === symbolName) && symbolHasMeaning(defaultInfo.symbolForMeaning, currentTokenMeaning)) { 983 addSymbol(moduleSymbol, sourceFile, defaultInfo.symbol, defaultInfo.exportKind, program, isFromPackageJson); 984 } 985 986 // check exports with the same name 987 const exportSymbolWithIdenticalName = checker.tryGetMemberInModuleExportsAndProperties(symbolName, moduleSymbol); 988 if (exportSymbolWithIdenticalName && symbolHasMeaning(exportSymbolWithIdenticalName, currentTokenMeaning)) { 989 addSymbol(moduleSymbol, sourceFile, exportSymbolWithIdenticalName, ExportKind.Named, program, isFromPackageJson); 990 } 991 }); 992 return originalSymbolToExportInfos; 993 } 994 995 function getExportEqualsImportKind(importingFile: SourceFile, compilerOptions: CompilerOptions, forceImportKeyword: boolean): ImportKind { 996 const allowSyntheticDefaults = getAllowSyntheticDefaultImports(compilerOptions); 997 const isJS = isInJSFile(importingFile); 998 // 1. 'import =' will not work in es2015+ TS files, so the decision is between a default 999 // and a namespace import, based on allowSyntheticDefaultImports/esModuleInterop. 1000 if (!isJS && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015) { 1001 return allowSyntheticDefaults ? ImportKind.Default : ImportKind.Namespace; 1002 } 1003 // 2. 'import =' will not work in JavaScript, so the decision is between a default import, 1004 // a namespace import, and const/require. 1005 if (isJS) { 1006 return isExternalModule(importingFile) || forceImportKeyword 1007 ? allowSyntheticDefaults ? ImportKind.Default : ImportKind.Namespace 1008 : ImportKind.CommonJS; 1009 } 1010 // 3. At this point the most correct choice is probably 'import =', but people 1011 // really hate that, so look to see if the importing file has any precedent 1012 // on how to handle it. 1013 for (const statement of importingFile.statements) { 1014 // `import foo` parses as an ImportEqualsDeclaration even though it could be an ImportDeclaration 1015 if (isImportEqualsDeclaration(statement) && !nodeIsMissing(statement.moduleReference)) { 1016 return ImportKind.CommonJS; 1017 } 1018 } 1019 // 4. We have no precedent to go on, so just use a default import if 1020 // allowSyntheticDefaultImports/esModuleInterop is enabled. 1021 return allowSyntheticDefaults ? ImportKind.Default : ImportKind.CommonJS; 1022 } 1023 1024 function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: SourceFile, symbolName: string, fix: ImportFix, includeSymbolNameInDescription: boolean, quotePreference: QuotePreference, compilerOptions: CompilerOptions): CodeFixAction { 1025 let diag!: DiagnosticAndArguments; 1026 const changes = textChanges.ChangeTracker.with(context, tracker => { 1027 diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, includeSymbolNameInDescription, quotePreference, compilerOptions); 1028 }); 1029 return createCodeFixAction(importFixName, changes, diag, importFixId, Diagnostics.Add_all_missing_imports); 1030 } 1031 function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, includeSymbolNameInDescription: boolean, quotePreference: QuotePreference, compilerOptions: CompilerOptions): DiagnosticAndArguments { 1032 switch (fix.kind) { 1033 case ImportFixKind.UseNamespace: 1034 addNamespaceQualifier(changes, sourceFile, fix); 1035 return [Diagnostics.Change_0_to_1, symbolName, `${fix.namespacePrefix}.${symbolName}`]; 1036 case ImportFixKind.JsdocTypeImport: 1037 addImportType(changes, sourceFile, fix, quotePreference); 1038 return [Diagnostics.Change_0_to_1, symbolName, getImportTypePrefix(fix.moduleSpecifier, quotePreference) + symbolName]; 1039 case ImportFixKind.AddToExisting: { 1040 const { importClauseOrBindingPattern, importKind, addAsTypeOnly, moduleSpecifier } = fix; 1041 doAddExistingFix( 1042 changes, 1043 sourceFile, 1044 importClauseOrBindingPattern, 1045 importKind === ImportKind.Default ? { name: symbolName, addAsTypeOnly } : undefined, 1046 importKind === ImportKind.Named ? [{ name: symbolName, addAsTypeOnly }] : emptyArray, 1047 compilerOptions); 1048 const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier); 1049 return includeSymbolNameInDescription 1050 ? [Diagnostics.Import_0_from_1, symbolName, moduleSpecifierWithoutQuotes] 1051 : [Diagnostics.Update_import_from_0, moduleSpecifierWithoutQuotes]; 1052 } 1053 case ImportFixKind.AddNew: { 1054 const { importKind, moduleSpecifier, addAsTypeOnly, useRequire } = fix; 1055 const getDeclarations = useRequire ? getNewRequires : getNewImports; 1056 const defaultImport: Import | undefined = importKind === ImportKind.Default ? { name: symbolName, addAsTypeOnly } : undefined; 1057 const namedImports: Import[] | undefined = importKind === ImportKind.Named ? [{ name: symbolName, addAsTypeOnly }] : undefined; 1058 const namespaceLikeImport = importKind === ImportKind.Namespace || importKind === ImportKind.CommonJS ? { importKind, name: symbolName, addAsTypeOnly } : undefined; 1059 insertImports(changes, sourceFile, getDeclarations(moduleSpecifier, quotePreference, defaultImport, namedImports, namespaceLikeImport), /*blankLineBetween*/ true); 1060 return includeSymbolNameInDescription 1061 ? [Diagnostics.Import_0_from_1, symbolName, moduleSpecifier] 1062 : [Diagnostics.Add_import_from_0, moduleSpecifier]; 1063 } 1064 case ImportFixKind.PromoteTypeOnly: { 1065 const { typeOnlyAliasDeclaration } = fix; 1066 const promotedDeclaration = promoteFromTypeOnly(changes, typeOnlyAliasDeclaration, compilerOptions, sourceFile); 1067 return promotedDeclaration.kind === SyntaxKind.ImportSpecifier 1068 ? [Diagnostics.Remove_type_from_import_of_0_from_1, symbolName, getModuleSpecifierText(promotedDeclaration.parent.parent)] 1069 : [Diagnostics.Remove_type_from_import_declaration_from_0, getModuleSpecifierText(promotedDeclaration)]; 1070 } 1071 default: 1072 return Debug.assertNever(fix, `Unexpected fix kind ${(fix as ImportFix).kind}`); 1073 } 1074 } 1075 1076 function getModuleSpecifierText(promotedDeclaration: ImportClause | ImportEqualsDeclaration): string { 1077 return promotedDeclaration.kind === SyntaxKind.ImportEqualsDeclaration 1078 ? tryCast(tryCast(promotedDeclaration.moduleReference, isExternalModuleReference)?.expression, isStringLiteralLike)?.text || promotedDeclaration.moduleReference.getText() 1079 : cast(promotedDeclaration.parent.moduleSpecifier, isStringLiteral).text; 1080 } 1081 1082 function promoteFromTypeOnly(changes: textChanges.ChangeTracker, aliasDeclaration: TypeOnlyAliasDeclaration, compilerOptions: CompilerOptions, sourceFile: SourceFile) { 1083 // See comment in `doAddExistingFix` on constant with the same name. 1084 const convertExistingToTypeOnly = compilerOptions.preserveValueImports && compilerOptions.isolatedModules; 1085 switch (aliasDeclaration.kind) { 1086 case SyntaxKind.ImportSpecifier: 1087 if (aliasDeclaration.isTypeOnly) { 1088 if (aliasDeclaration.parent.elements.length > 1 && OrganizeImports.importSpecifiersAreSorted(aliasDeclaration.parent.elements)) { 1089 changes.delete(sourceFile, aliasDeclaration); 1090 const newSpecifier = factory.updateImportSpecifier(aliasDeclaration, /*isTypeOnly*/ false, aliasDeclaration.propertyName, aliasDeclaration.name); 1091 const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(aliasDeclaration.parent.elements, newSpecifier); 1092 changes.insertImportSpecifierAtIndex(sourceFile, newSpecifier, aliasDeclaration.parent, insertionIndex); 1093 } 1094 else { 1095 changes.deleteRange(sourceFile, aliasDeclaration.getFirstToken()!); 1096 } 1097 return aliasDeclaration; 1098 } 1099 else { 1100 Debug.assert(aliasDeclaration.parent.parent.isTypeOnly); 1101 promoteImportClause(aliasDeclaration.parent.parent); 1102 return aliasDeclaration.parent.parent; 1103 } 1104 case SyntaxKind.ImportClause: 1105 promoteImportClause(aliasDeclaration); 1106 return aliasDeclaration; 1107 case SyntaxKind.NamespaceImport: 1108 promoteImportClause(aliasDeclaration.parent); 1109 return aliasDeclaration.parent; 1110 case SyntaxKind.ImportEqualsDeclaration: 1111 changes.deleteRange(sourceFile, aliasDeclaration.getChildAt(1)); 1112 return aliasDeclaration; 1113 default: 1114 Debug.failBadSyntaxKind(aliasDeclaration); 1115 } 1116 1117 function promoteImportClause(importClause: ImportClause) { 1118 changes.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(importClause, sourceFile)); 1119 if (convertExistingToTypeOnly) { 1120 const namedImports = tryCast(importClause.namedBindings, isNamedImports); 1121 if (namedImports && namedImports.elements.length > 1) { 1122 if (OrganizeImports.importSpecifiersAreSorted(namedImports.elements) && 1123 aliasDeclaration.kind === SyntaxKind.ImportSpecifier && 1124 namedImports.elements.indexOf(aliasDeclaration) !== 0 1125 ) { 1126 // The import specifier being promoted will be the only non-type-only, 1127 // import in the NamedImports, so it should be moved to the front. 1128 changes.delete(sourceFile, aliasDeclaration); 1129 changes.insertImportSpecifierAtIndex(sourceFile, aliasDeclaration, namedImports, 0); 1130 } 1131 for (const element of namedImports.elements) { 1132 if (element !== aliasDeclaration && !element.isTypeOnly) { 1133 changes.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, element); 1134 } 1135 } 1136 } 1137 } 1138 } 1139 } 1140 1141 function doAddExistingFix( 1142 changes: textChanges.ChangeTracker, 1143 sourceFile: SourceFile, 1144 clause: ImportClause | ObjectBindingPattern, 1145 defaultImport: Import | undefined, 1146 namedImports: readonly Import[], 1147 compilerOptions: CompilerOptions, 1148 ): void { 1149 if (clause.kind === SyntaxKind.ObjectBindingPattern) { 1150 if (defaultImport) { 1151 addElementToBindingPattern(clause, defaultImport.name, "default"); 1152 } 1153 for (const specifier of namedImports) { 1154 addElementToBindingPattern(clause, specifier.name, /*propertyName*/ undefined); 1155 } 1156 return; 1157 } 1158 1159 const promoteFromTypeOnly = clause.isTypeOnly && some([defaultImport, ...namedImports], i => i?.addAsTypeOnly === AddAsTypeOnly.NotAllowed); 1160 const existingSpecifiers = clause.namedBindings && tryCast(clause.namedBindings, isNamedImports)?.elements; 1161 // If we are promoting from a type-only import and `--isolatedModules` and `--preserveValueImports` 1162 // are enabled, we need to make every existing import specifier type-only. It may be possible that 1163 // some of them don't strictly need to be marked type-only (if they have a value meaning and are 1164 // never used in an emitting position). These are allowed to be imported without being type-only, 1165 // but the user has clearly already signified that they don't need them to be present at runtime 1166 // by placing them in a type-only import. So, just mark each specifier as type-only. 1167 const convertExistingToTypeOnly = promoteFromTypeOnly && compilerOptions.preserveValueImports && compilerOptions.isolatedModules; 1168 1169 if (defaultImport) { 1170 Debug.assert(!clause.name, "Cannot add a default import to an import clause that already has one"); 1171 changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), factory.createIdentifier(defaultImport.name), { suffix: ", " }); 1172 } 1173 1174 if (namedImports.length) { 1175 const newSpecifiers = stableSort( 1176 namedImports.map(namedImport => factory.createImportSpecifier( 1177 (!clause.isTypeOnly || promoteFromTypeOnly) && needsTypeOnly(namedImport), 1178 /*propertyName*/ undefined, 1179 factory.createIdentifier(namedImport.name))), 1180 OrganizeImports.compareImportOrExportSpecifiers); 1181 1182 if (existingSpecifiers?.length && OrganizeImports.importSpecifiersAreSorted(existingSpecifiers)) { 1183 for (const spec of newSpecifiers) { 1184 // Organize imports puts type-only import specifiers last, so if we're 1185 // adding a non-type-only specifier and converting all the other ones to 1186 // type-only, there's no need to ask for the insertion index - it's 0. 1187 const insertionIndex = convertExistingToTypeOnly && !spec.isTypeOnly 1188 ? 0 1189 : OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec); 1190 changes.insertImportSpecifierAtIndex(sourceFile, spec, clause.namedBindings as NamedImports, insertionIndex); 1191 } 1192 } 1193 else if (existingSpecifiers?.length) { 1194 for (const spec of newSpecifiers) { 1195 changes.insertNodeInListAfter(sourceFile, last(existingSpecifiers), spec, existingSpecifiers); 1196 } 1197 } 1198 else { 1199 if (newSpecifiers.length) { 1200 const namedImports = factory.createNamedImports(newSpecifiers); 1201 if (clause.namedBindings) { 1202 changes.replaceNode(sourceFile, clause.namedBindings, namedImports); 1203 } 1204 else { 1205 changes.insertNodeAfter(sourceFile, Debug.checkDefined(clause.name, "Import clause must have either named imports or a default import"), namedImports); 1206 } 1207 } 1208 } 1209 } 1210 1211 if (promoteFromTypeOnly) { 1212 changes.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(clause, sourceFile)); 1213 if (convertExistingToTypeOnly && existingSpecifiers) { 1214 for (const specifier of existingSpecifiers) { 1215 changes.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, specifier); 1216 } 1217 } 1218 } 1219 1220 function addElementToBindingPattern(bindingPattern: ObjectBindingPattern, name: string, propertyName: string | undefined) { 1221 const element = factory.createBindingElement(/*dotDotDotToken*/ undefined, propertyName, name); 1222 if (bindingPattern.elements.length) { 1223 changes.insertNodeInListAfter(sourceFile, last(bindingPattern.elements), element); 1224 } 1225 else { 1226 changes.replaceNode(sourceFile, bindingPattern, factory.createObjectBindingPattern([element])); 1227 } 1228 } 1229 } 1230 1231 function addNamespaceQualifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { namespacePrefix, position }: FixUseNamespaceImport): void { 1232 changes.insertText(sourceFile, position, namespacePrefix + "."); 1233 } 1234 1235 function addImportType(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { moduleSpecifier, position }: FixAddJsdocTypeImport, quotePreference: QuotePreference): void { 1236 changes.insertText(sourceFile, position, getImportTypePrefix(moduleSpecifier, quotePreference)); 1237 } 1238 1239 function getImportTypePrefix(moduleSpecifier: string, quotePreference: QuotePreference): string { 1240 const quote = getQuoteFromPreference(quotePreference); 1241 return `import(${quote}${moduleSpecifier}${quote}).`; 1242 } 1243 1244 interface Import { 1245 readonly name: string; 1246 readonly addAsTypeOnly: AddAsTypeOnly; 1247 } 1248 1249 interface ImportsCollection { 1250 readonly defaultImport?: Import; 1251 readonly namedImports?: ESMap<string, AddAsTypeOnly>; 1252 readonly namespaceLikeImport?: { 1253 readonly importKind: ImportKind.CommonJS | ImportKind.Namespace; 1254 readonly name: string; 1255 readonly addAsTypeOnly: AddAsTypeOnly; 1256 }; 1257 } 1258 1259 function needsTypeOnly({ addAsTypeOnly }: { addAsTypeOnly: AddAsTypeOnly }): boolean { 1260 return addAsTypeOnly === AddAsTypeOnly.Required; 1261 } 1262 1263 function getNewImports( 1264 moduleSpecifier: string, 1265 quotePreference: QuotePreference, 1266 defaultImport: Import | undefined, 1267 namedImports: readonly Import[] | undefined, 1268 namespaceLikeImport: Import & { importKind: ImportKind.CommonJS | ImportKind.Namespace } | undefined 1269 ): AnyImportSyntax | readonly AnyImportSyntax[] { 1270 const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference); 1271 let statements: AnyImportSyntax | readonly AnyImportSyntax[] | undefined; 1272 if (defaultImport !== undefined || namedImports?.length) { 1273 const topLevelTypeOnly = (!defaultImport || needsTypeOnly(defaultImport)) && every(namedImports, needsTypeOnly); 1274 statements = combine(statements, makeImport( 1275 defaultImport && factory.createIdentifier(defaultImport.name), 1276 namedImports?.map(({ addAsTypeOnly, name }) => factory.createImportSpecifier( 1277 !topLevelTypeOnly && addAsTypeOnly === AddAsTypeOnly.Required, 1278 /*propertyName*/ undefined, 1279 factory.createIdentifier(name))), 1280 moduleSpecifier, 1281 quotePreference, 1282 topLevelTypeOnly)); 1283 } 1284 1285 if (namespaceLikeImport) { 1286 const declaration = namespaceLikeImport.importKind === ImportKind.CommonJS 1287 ? factory.createImportEqualsDeclaration( 1288 /*modifiers*/ undefined, 1289 needsTypeOnly(namespaceLikeImport), 1290 factory.createIdentifier(namespaceLikeImport.name), 1291 factory.createExternalModuleReference(quotedModuleSpecifier)) 1292 : factory.createImportDeclaration( 1293 /*modifiers*/ undefined, 1294 factory.createImportClause( 1295 needsTypeOnly(namespaceLikeImport), 1296 /*name*/ undefined, 1297 factory.createNamespaceImport(factory.createIdentifier(namespaceLikeImport.name))), 1298 quotedModuleSpecifier, 1299 /*assertClause*/ undefined); 1300 statements = combine(statements, declaration); 1301 } 1302 return Debug.checkDefined(statements); 1303 } 1304 1305 function getNewRequires(moduleSpecifier: string, quotePreference: QuotePreference, defaultImport: Import | undefined, namedImports: readonly Import[] | undefined, namespaceLikeImport: Import | undefined): RequireVariableStatement | readonly RequireVariableStatement[] { 1306 const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference); 1307 let statements: RequireVariableStatement | readonly RequireVariableStatement[] | undefined; 1308 // const { default: foo, bar, etc } = require('./mod'); 1309 if (defaultImport || namedImports?.length) { 1310 const bindingElements = namedImports?.map(({ name }) => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name)) || []; 1311 if (defaultImport) { 1312 bindingElements.unshift(factory.createBindingElement(/*dotDotDotToken*/ undefined, "default", defaultImport.name)); 1313 } 1314 const declaration = createConstEqualsRequireDeclaration(factory.createObjectBindingPattern(bindingElements), quotedModuleSpecifier); 1315 statements = combine(statements, declaration); 1316 } 1317 // const foo = require('./mod'); 1318 if (namespaceLikeImport) { 1319 const declaration = createConstEqualsRequireDeclaration(namespaceLikeImport.name, quotedModuleSpecifier); 1320 statements = combine(statements, declaration); 1321 } 1322 return Debug.checkDefined(statements); 1323 } 1324 1325 function createConstEqualsRequireDeclaration(name: string | ObjectBindingPattern, quotedModuleSpecifier: StringLiteral): RequireVariableStatement { 1326 return factory.createVariableStatement( 1327 /*modifiers*/ undefined, 1328 factory.createVariableDeclarationList([ 1329 factory.createVariableDeclaration( 1330 typeof name === "string" ? factory.createIdentifier(name) : name, 1331 /*exclamationToken*/ undefined, 1332 /*type*/ undefined, 1333 factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [quotedModuleSpecifier]))], 1334 NodeFlags.Const)) as RequireVariableStatement; 1335 } 1336 1337 function symbolHasMeaning({ declarations }: Symbol, meaning: SemanticMeaning): boolean { 1338 return some(declarations, decl => !!(getMeaningFromDeclaration(decl) & meaning)); 1339 } 1340 1341 export function moduleSymbolToValidIdentifier(moduleSymbol: Symbol, target: ScriptTarget | undefined, forceCapitalize: boolean): string { 1342 return moduleSpecifierToValidIdentifier(removeFileExtension(stripQuotes(moduleSymbol.name)), target, forceCapitalize); 1343 } 1344 1345 export function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget | undefined, forceCapitalize?: boolean): string { 1346 const baseName = getBaseFileName(removeSuffix(moduleSpecifier, "/index")); 1347 let res = ""; 1348 let lastCharWasValid = true; 1349 const firstCharCode = baseName.charCodeAt(0); 1350 if (isIdentifierStart(firstCharCode, target)) { 1351 res += String.fromCharCode(firstCharCode); 1352 if (forceCapitalize) { 1353 res = res.toUpperCase(); 1354 } 1355 } 1356 else { 1357 lastCharWasValid = false; 1358 } 1359 for (let i = 1; i < baseName.length; i++) { 1360 const ch = baseName.charCodeAt(i); 1361 const isValid = isIdentifierPart(ch, target); 1362 if (isValid) { 1363 let char = String.fromCharCode(ch); 1364 if (!lastCharWasValid) { 1365 char = char.toUpperCase(); 1366 } 1367 res += char; 1368 } 1369 lastCharWasValid = isValid; 1370 } 1371 // Need `|| "_"` to ensure result isn't empty. 1372 return !isStringANonContextualKeyword(res) ? res || "_" : `_${res}`; 1373 } 1374} 1375