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