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