• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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