• 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    ];
14
15    registerCodeFix({
16        errorCodes,
17        getCodeActions(context) {
18            const { errorCode, preferences, sourceFile, span } = context;
19            const info = getFixesInfo(context, errorCode, span.start, /*useAutoImportProvider*/ true);
20            if (!info) return undefined;
21            const { fixes, symbolName } = info;
22            const quotePreference = getQuotePreference(sourceFile, preferences);
23            return fixes.map(fix => codeActionForFix(context, sourceFile, symbolName, fix, quotePreference));
24        },
25        fixIds: [importFixId],
26        getAllCodeActions: context => {
27            const { sourceFile, program, preferences, host } = context;
28            const importAdder = createImportAdderWorker(sourceFile, program, /*useAutoImportProvider*/ true, preferences, host);
29            eachDiagnostic(context, errorCodes, diag => importAdder.addImportFromDiagnostic(diag, context));
30            return createCombinedCodeActions(textChanges.ChangeTracker.with(context, importAdder.writeFixes));
31        },
32    });
33
34    export interface ImportAdder {
35        addImportFromDiagnostic: (diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) => void;
36        addImportFromExportedSymbol: (exportedSymbol: Symbol, usageIsTypeOnly?: boolean) => void;
37        writeFixes: (changeTracker: textChanges.ChangeTracker) => void;
38    }
39
40    export function createImportAdder(sourceFile: SourceFile, program: Program, preferences: UserPreferences, host: LanguageServiceHost): ImportAdder {
41        return createImportAdderWorker(sourceFile, program, /*useAutoImportProvider*/ false, preferences, host);
42    }
43
44    function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAutoImportProvider: boolean, preferences: UserPreferences, host: LanguageServiceHost): ImportAdder {
45        const compilerOptions = program.getCompilerOptions();
46        // Namespace fixes don't conflict, so just build a list.
47        const addToNamespace: FixUseNamespaceImport[] = [];
48        const importType: FixUseImportType[] = [];
49        // Keys are import clause node IDs.
50        const addToExisting = new Map<string, { readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>();
51        const newImports = new Map<string, Mutable<ImportsCollection & { useRequire: boolean }>>();
52        return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes };
53
54        function addImportFromDiagnostic(diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) {
55            const info = getFixesInfo(context, diagnostic.code, diagnostic.start, useAutoImportProvider);
56            if (!info || !info.fixes.length) return;
57            addImport(info);
58        }
59
60        function addImportFromExportedSymbol(exportedSymbol: Symbol, usageIsTypeOnly?: boolean) {
61            const moduleSymbol = Debug.checkDefined(exportedSymbol.parent);
62            const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions));
63            const checker = program.getTypeChecker();
64            const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
65            const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, host, program, useAutoImportProvider);
66            const preferTypeOnlyImport = !!usageIsTypeOnly && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error;
67            const useRequire = shouldUseRequire(sourceFile, program);
68            const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, preferTypeOnlyImport, useRequire, host, preferences);
69            addImport({ fixes: [fix], symbolName });
70        }
71
72        function addImport(info: FixesInfo) {
73            const { fixes, symbolName } = info;
74            const fix = first(fixes);
75            switch (fix.kind) {
76                case ImportFixKind.UseNamespace:
77                    addToNamespace.push(fix);
78                    break;
79                case ImportFixKind.ImportType:
80                    importType.push(fix);
81                    break;
82                case ImportFixKind.AddToExisting: {
83                    const { importClauseOrBindingPattern, importKind, canUseTypeOnlyImport } = fix;
84                    const key = String(getNodeId(importClauseOrBindingPattern));
85                    let entry = addToExisting.get(key);
86                    if (!entry) {
87                        addToExisting.set(key, entry = { importClauseOrBindingPattern, defaultImport: undefined, namedImports: [], canUseTypeOnlyImport });
88                    }
89                    if (importKind === ImportKind.Named) {
90                        pushIfUnique(entry.namedImports, symbolName);
91                    }
92                    else {
93                        Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add to Existing) Default import should be missing or match symbolName");
94                        entry.defaultImport = symbolName;
95                    }
96                    break;
97                }
98                case ImportFixKind.AddNew: {
99                    const { moduleSpecifier, importKind, useRequire, typeOnly } = fix;
100                    let entry = newImports.get(moduleSpecifier);
101                    if (!entry) {
102                        newImports.set(moduleSpecifier, entry = { namedImports: [], namespaceLikeImport: undefined, typeOnly, useRequire });
103                    }
104                    else {
105                        // An import clause can only be type-only if every import fix contributing to it can be type-only.
106                        entry.typeOnly = entry.typeOnly && typeOnly;
107                    }
108                    switch (importKind) {
109                        case ImportKind.Default:
110                            Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add new) Default import should be missing or match symbolName");
111                            entry.defaultImport = symbolName;
112                            break;
113                        case ImportKind.Named:
114                            pushIfUnique(entry.namedImports || (entry.namedImports = []), symbolName);
115                            break;
116                        case ImportKind.CommonJS:
117                        case ImportKind.Namespace:
118                            Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName, "Namespacelike import shoudl be missing or match symbolName");
119                            entry.namespaceLikeImport = { importKind, name: symbolName };
120                            break;
121                    }
122                    break;
123                }
124                default:
125                    Debug.assertNever(fix, `fix wasn't never - got kind ${(fix as ImportFix).kind}`);
126            }
127        }
128
129        function writeFixes(changeTracker: textChanges.ChangeTracker) {
130            const quotePreference = getQuotePreference(sourceFile, preferences);
131            for (const fix of addToNamespace) {
132                addNamespaceQualifier(changeTracker, sourceFile, fix);
133            }
134            for (const fix of importType) {
135                addImportType(changeTracker, sourceFile, fix, quotePreference);
136            }
137            addToExisting.forEach(({ importClauseOrBindingPattern, defaultImport, namedImports, canUseTypeOnlyImport }) => {
138                doAddExistingFix(changeTracker, sourceFile, importClauseOrBindingPattern, defaultImport, namedImports, canUseTypeOnlyImport);
139            });
140
141            let newDeclarations: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[] | undefined;
142            newImports.forEach(({ useRequire, ...imports }, moduleSpecifier) => {
143                const getDeclarations = useRequire ? getNewRequires : getNewImports;
144                newDeclarations = combine(newDeclarations, getDeclarations(moduleSpecifier, quotePreference, imports));
145            });
146            if (newDeclarations) {
147                insertImports(changeTracker, sourceFile, newDeclarations, /*blankLineBetween*/ true);
148            }
149        }
150    }
151
152    // Sorted with the preferred fix coming first.
153    const enum ImportFixKind { UseNamespace, ImportType, AddToExisting, AddNew }
154    type ImportFix = FixUseNamespaceImport | FixUseImportType | FixAddToExistingImport | FixAddNewImport;
155    interface FixUseNamespaceImport {
156        readonly kind: ImportFixKind.UseNamespace;
157        readonly namespacePrefix: string;
158        readonly position: number;
159    }
160    interface FixUseImportType {
161        readonly kind: ImportFixKind.ImportType;
162        readonly moduleSpecifier: string;
163        readonly position: number;
164    }
165    interface FixAddToExistingImport {
166        readonly kind: ImportFixKind.AddToExisting;
167        readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern;
168        readonly moduleSpecifier: string;
169        readonly importKind: ImportKind.Default | ImportKind.Named;
170        readonly canUseTypeOnlyImport: boolean;
171    }
172    interface FixAddNewImport {
173        readonly kind: ImportFixKind.AddNew;
174        readonly moduleSpecifier: string;
175        readonly importKind: ImportKind;
176        readonly typeOnly: boolean;
177        readonly useRequire: boolean;
178    }
179
180    const enum ImportKind {
181        Named,
182        Default,
183        Namespace,
184        CommonJS,
185    }
186
187    /** Information about how a symbol is exported from a module. (We don't need to store the exported symbol, just its module.) */
188    interface SymbolExportInfo {
189        readonly moduleSymbol: Symbol;
190        readonly importKind: ImportKind;
191        /** If true, can't use an es6 import from a js file. */
192        readonly exportedSymbolIsTypeOnly: boolean;
193    }
194
195    /** Information needed to augment an existing import declaration. */
196    interface FixAddToExistingImportInfo {
197        readonly declaration: AnyImportOrRequire;
198        readonly importKind: ImportKind;
199    }
200
201    export function getImportCompletionAction(
202        exportedSymbol: Symbol,
203        moduleSymbol: Symbol,
204        sourceFile: SourceFile,
205        symbolName: string,
206        host: LanguageServiceHost,
207        program: Program,
208        formatContext: formatting.FormatContext,
209        position: number,
210        preferences: UserPreferences,
211    ): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
212        const compilerOptions = program.getCompilerOptions();
213        const exportInfos = pathIsBareSpecifier(stripQuotes(moduleSymbol.name))
214            ? [getSymbolExportInfoForSymbol(exportedSymbol, moduleSymbol, sourceFile, program, host)]
215            : getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, host, program, /*useAutoImportProvider*/ true);
216        const useRequire = shouldUseRequire(sourceFile, program);
217        const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && !isSourceFileJS(sourceFile) && isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
218        const moduleSpecifier = getBestFix(getNewImportInfos(program, sourceFile, position, preferTypeOnlyImport, useRequire, exportInfos, host, preferences), sourceFile, program, host).moduleSpecifier;
219        const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, preferTypeOnlyImport, useRequire, host, preferences);
220        return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
221    }
222
223    function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, preferTypeOnlyImport: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
224        Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol), "Some exportInfo should match the specified moduleSymbol");
225        // We sort the best codefixes first, so taking `first` is best.
226        return getBestFix(getFixForImport(exportInfos, symbolName, position, preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences), sourceFile, program, host);
227    }
228
229    function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
230        return { description, changes, commands };
231    }
232
233    function getSymbolExportInfoForSymbol(symbol: Symbol, moduleSymbol: Symbol, importingFile: SourceFile, program: Program, host: LanguageServiceHost): SymbolExportInfo {
234        const compilerOptions = program.getCompilerOptions();
235        const mainProgramInfo = getInfoWithChecker(program.getTypeChecker());
236        if (mainProgramInfo) {
237            return mainProgramInfo;
238        }
239        const autoImportProvider = host.getPackageJsonAutoImportProvider?.()?.getTypeChecker();
240        return Debug.checkDefined(autoImportProvider && getInfoWithChecker(autoImportProvider), `Could not find symbol in specified module for code actions`);
241
242        function getInfoWithChecker(checker: TypeChecker): SymbolExportInfo | undefined {
243            const defaultInfo = getDefaultLikeExportInfo(importingFile, moduleSymbol, checker, compilerOptions);
244            if (defaultInfo && skipAlias(defaultInfo.symbol, checker) === symbol) {
245                return { moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(symbol, checker) };
246            }
247            const named = checker.tryGetMemberInModuleExportsAndProperties(symbol.name, moduleSymbol);
248            if (named && skipAlias(named, checker) === symbol) {
249                return { moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(symbol, checker) };
250            }
251        }
252    }
253
254    function getAllReExportingModules(importingFile: SourceFile, exportedSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, host: LanguageServiceHost, program: Program, useAutoImportProvider: boolean): readonly SymbolExportInfo[] {
255        const result: SymbolExportInfo[] = [];
256        const compilerOptions = program.getCompilerOptions();
257        forEachExternalModuleToImportFrom(program, host, importingFile, /*filterByPackageJson*/ false, useAutoImportProvider, (moduleSymbol, moduleFile, program) => {
258            const checker = program.getTypeChecker();
259            // Don't import from a re-export when looking "up" like to `./index` or `../index`.
260            if (moduleFile && moduleSymbol !== exportingModuleSymbol && startsWith(importingFile.fileName, getDirectoryPath(moduleFile.fileName))) {
261                return;
262            }
263
264            const defaultInfo = getDefaultLikeExportInfo(importingFile, moduleSymbol, checker, compilerOptions);
265            if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, compilerOptions.target) === symbolName) && skipAlias(defaultInfo.symbol, checker) === exportedSymbol) {
266                result.push({ moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(defaultInfo.symbol, checker) });
267            }
268
269            for (const exported of checker.getExportsAndPropertiesOfModule(moduleSymbol)) {
270                if (exported.name === symbolName && skipAlias(exported, checker) === exportedSymbol) {
271                    result.push({ moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported, checker) });
272                }
273            }
274        });
275        return result;
276    }
277
278    function isTypeOnlySymbol(s: Symbol, checker: TypeChecker): boolean {
279        return !(skipAlias(s, checker).flags & SymbolFlags.Value);
280    }
281
282    function isTypeOnlyPosition(sourceFile: SourceFile, position: number) {
283        return isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
284    }
285
286    function getFixForImport(
287        exportInfos: readonly SymbolExportInfo[],
288        symbolName: string,
289        /** undefined only for missing JSX namespace */
290        position: number | undefined,
291        preferTypeOnlyImport: boolean,
292        useRequire: boolean,
293        program: Program,
294        sourceFile: SourceFile,
295        host: LanguageServiceHost,
296        preferences: UserPreferences,
297    ): readonly ImportFix[] {
298        const checker = program.getTypeChecker();
299        const existingImports = flatMap(exportInfos, info => getExistingImportDeclarations(info, checker, sourceFile));
300        const useNamespace = position === undefined ? undefined : tryUseExistingNamespaceImport(existingImports, symbolName, position, checker);
301        const addToExisting = tryAddToExistingImport(existingImports, position !== undefined && isTypeOnlyPosition(sourceFile, position));
302        // Don't bother providing an action to add a new import if we can add to an existing one.
303        const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, preferTypeOnlyImport, useRequire, host, preferences);
304        return [...(useNamespace ? [useNamespace] : emptyArray), ...addImport];
305    }
306
307    function tryUseExistingNamespaceImport(existingImports: readonly FixAddToExistingImportInfo[], symbolName: string, position: number, checker: TypeChecker): FixUseNamespaceImport | undefined {
308        // It is possible that multiple import statements with the same specifier exist in the file.
309        // e.g.
310        //
311        //     import * as ns from "foo";
312        //     import { member1, member2 } from "foo";
313        //
314        //     member3/**/ <-- cusor here
315        //
316        // in this case we should provie 2 actions:
317        //     1. change "member3" to "ns.member3"
318        //     2. add "member3" to the second import statement's import list
319        // and it is up to the user to decide which one fits best.
320        return firstDefined(existingImports, ({ declaration }): FixUseNamespaceImport | undefined => {
321            const namespacePrefix = getNamespaceLikeImportText(declaration);
322            if (namespacePrefix) {
323                const moduleSymbol = getTargetModuleFromNamespaceLikeImport(declaration, checker);
324                if (moduleSymbol && moduleSymbol.exports!.has(escapeLeadingUnderscores(symbolName))) {
325                    return { kind: ImportFixKind.UseNamespace, namespacePrefix, position };
326                }
327            }
328        });
329    }
330
331    function getTargetModuleFromNamespaceLikeImport(declaration: AnyImportOrRequire, checker: TypeChecker) {
332        switch (declaration.kind) {
333            case SyntaxKind.VariableDeclaration:
334                return checker.resolveExternalModuleName(declaration.initializer.arguments[0]);
335            case SyntaxKind.ImportEqualsDeclaration:
336                return checker.getAliasedSymbol(declaration.symbol);
337            case SyntaxKind.ImportDeclaration:
338                const namespaceImport = tryCast(declaration.importClause?.namedBindings, isNamespaceImport);
339                return namespaceImport && checker.getAliasedSymbol(namespaceImport.symbol);
340            default:
341                return Debug.assertNever(declaration);
342        }
343    }
344
345    function getNamespaceLikeImportText(declaration: AnyImportOrRequire) {
346        switch (declaration.kind) {
347            case SyntaxKind.VariableDeclaration:
348                return tryCast(declaration.name, isIdentifier)?.text;
349            case SyntaxKind.ImportEqualsDeclaration:
350                return declaration.name.text;
351            case SyntaxKind.ImportDeclaration:
352                return tryCast(declaration.importClause?.namedBindings, isNamespaceImport)?.name.text;
353            default:
354                return Debug.assertNever(declaration);
355        }
356    }
357
358    function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], canUseTypeOnlyImport: boolean): FixAddToExistingImport | undefined {
359        return firstDefined(existingImports, ({ declaration, importKind }): FixAddToExistingImport | undefined => {
360            if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) return undefined;
361            if (declaration.kind === SyntaxKind.VariableDeclaration) {
362                return (importKind === ImportKind.Named || importKind === ImportKind.Default) && declaration.name.kind === SyntaxKind.ObjectBindingPattern
363                    ? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: declaration.name, importKind, moduleSpecifier: declaration.initializer.arguments[0].text, canUseTypeOnlyImport: false }
364                    : undefined;
365            }
366            const { importClause } = declaration;
367            if (!importClause) return undefined;
368            const { name, namedBindings } = importClause;
369            return importKind === ImportKind.Default && !name || importKind === ImportKind.Named && (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports)
370                ? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: importClause, importKind, moduleSpecifier: declaration.moduleSpecifier.getText(), canUseTypeOnlyImport }
371                : undefined;
372        });
373    }
374
375    function getExistingImportDeclarations({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }: SymbolExportInfo, checker: TypeChecker, sourceFile: SourceFile): readonly FixAddToExistingImportInfo[] {
376        // Can't use an es6 import for a type in JS.
377        return exportedSymbolIsTypeOnly && isSourceFileJS(sourceFile) ? emptyArray : mapDefined(sourceFile.imports, (moduleSpecifier): FixAddToExistingImportInfo | undefined => {
378            const i = importFromModuleSpecifier(moduleSpecifier);
379            if (isRequireVariableDeclaration(i.parent, /*requireStringLiteralLikeArgument*/ true)) {
380                return checker.resolveExternalModuleName(moduleSpecifier) === moduleSymbol ? { declaration: i.parent, importKind } : undefined;
381            }
382            if (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration) {
383                return checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind } : undefined;
384            }
385        });
386    }
387
388    function shouldUseRequire(sourceFile: SourceFile, program: Program): boolean {
389        // 1. TypeScript files don't use require variable declarations
390        if (!isSourceFileJS(sourceFile)) {
391            return false;
392        }
393
394        // 2. If the current source file is unambiguously CJS or ESM, go with that
395        if (sourceFile.commonJsModuleIndicator && !sourceFile.externalModuleIndicator) return true;
396        if (sourceFile.externalModuleIndicator && !sourceFile.commonJsModuleIndicator) return false;
397
398        // 3. If there's a tsconfig/jsconfig, use its module setting
399        const compilerOptions = program.getCompilerOptions();
400        if (compilerOptions.configFile) {
401            return getEmitModuleKind(compilerOptions) < ModuleKind.ES2015;
402        }
403
404        // 4. Match the first other JS file in the program that's unambiguously CJS or ESM
405        for (const otherFile of program.getSourceFiles()) {
406            if (otherFile === sourceFile || !isSourceFileJS(otherFile) || program.isSourceFileFromExternalLibrary(otherFile)) continue;
407            if (otherFile.commonJsModuleIndicator && !otherFile.externalModuleIndicator) return true;
408            if (otherFile.externalModuleIndicator && !otherFile.commonJsModuleIndicator) return false;
409        }
410
411        // 5. Literally nothing to go on
412        return true;
413    }
414
415    function getNewImportInfos(
416        program: Program,
417        sourceFile: SourceFile,
418        position: number | undefined,
419        preferTypeOnlyImport: boolean,
420        useRequire: boolean,
421        moduleSymbols: readonly SymbolExportInfo[],
422        host: LanguageServiceHost,
423        preferences: UserPreferences,
424    ): readonly (FixAddNewImport | FixUseImportType)[] {
425        const isJs = isSourceFileJS(sourceFile);
426        const compilerOptions = program.getCompilerOptions();
427        return flatMap(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) =>
428            moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getTypeChecker(), compilerOptions, sourceFile, createModuleSpecifierResolutionHost(program, host), preferences)
429                .map((moduleSpecifier): FixAddNewImport | FixUseImportType =>
430                    // `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
431                    exportedSymbolIsTypeOnly && isJs
432                        ? { kind: ImportFixKind.ImportType, moduleSpecifier, position: Debug.checkDefined(position, "position should be defined") }
433                        : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, useRequire, typeOnly: preferTypeOnlyImport }));
434    }
435
436    function getFixesForAddImport(
437        exportInfos: readonly SymbolExportInfo[],
438        existingImports: readonly FixAddToExistingImportInfo[],
439        program: Program,
440        sourceFile: SourceFile,
441        position: number | undefined,
442        preferTypeOnlyImport: boolean,
443        useRequire: boolean,
444        host: LanguageServiceHost,
445        preferences: UserPreferences,
446    ): readonly (FixAddNewImport | FixUseImportType)[] {
447        const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, preferTypeOnlyImport, useRequire));
448        return existingDeclaration ? [existingDeclaration] : getNewImportInfos(program, sourceFile, position, preferTypeOnlyImport, useRequire, exportInfos, host, preferences);
449    }
450
451    function newImportInfoFromExistingSpecifier({ declaration, importKind }: FixAddToExistingImportInfo, preferTypeOnlyImport: boolean, useRequire: boolean): FixAddNewImport | undefined {
452        const moduleSpecifier = declaration.kind === SyntaxKind.ImportDeclaration ? declaration.moduleSpecifier :
453            declaration.kind === SyntaxKind.VariableDeclaration ? declaration.initializer.arguments[0] :
454            declaration.moduleReference.kind === SyntaxKind.ExternalModuleReference ? declaration.moduleReference.expression :
455            undefined;
456        return moduleSpecifier && isStringLiteral(moduleSpecifier)
457            ? { kind: ImportFixKind.AddNew, moduleSpecifier: moduleSpecifier.text, importKind, typeOnly: preferTypeOnlyImport, useRequire }
458            : undefined;
459    }
460
461    interface FixesInfo { readonly fixes: readonly ImportFix[]; readonly symbolName: string; }
462    function getFixesInfo(context: CodeFixContextBase, errorCode: number, pos: number, useAutoImportProvider: boolean): FixesInfo | undefined {
463        const symbolToken = getTokenAtPosition(context.sourceFile, pos);
464        const info = errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code
465            ? getFixesInfoForUMDImport(context, symbolToken)
466            : isIdentifier(symbolToken) ? getFixesInfoForNonUMDImport(context, symbolToken, useAutoImportProvider) : undefined;
467        return info && { ...info, fixes: sortFixes(info.fixes, context.sourceFile, context.program, context.host) };
468    }
469
470    function sortFixes(fixes: readonly ImportFix[], sourceFile: SourceFile, program: Program, host: LanguageServiceHost): readonly ImportFix[] {
471        const { allowsImportingSpecifier } = createAutoImportFilter(sourceFile, program, host);
472        return sort(fixes, (a, b) => compareValues(a.kind, b.kind) || compareModuleSpecifiers(a, b, allowsImportingSpecifier));
473    }
474
475    function getBestFix<T extends ImportFix>(fixes: readonly T[], sourceFile: SourceFile, program: Program, host: LanguageServiceHost): T {
476        // These will always be placed first if available, and are better than other kinds
477        if (fixes[0].kind === ImportFixKind.UseNamespace || fixes[0].kind === ImportFixKind.AddToExisting) {
478            return fixes[0];
479        }
480        const { allowsImportingSpecifier } = createAutoImportFilter(sourceFile, program, host);
481        return fixes.reduce((best, fix) =>
482            compareModuleSpecifiers(fix, best, allowsImportingSpecifier) === Comparison.LessThan ? fix : best
483        );
484    }
485
486    function compareModuleSpecifiers(a: ImportFix, b: ImportFix, allowsImportingSpecifier: (specifier: string) => boolean): Comparison {
487        if (a.kind !== ImportFixKind.UseNamespace && b.kind !== ImportFixKind.UseNamespace) {
488            return compareBooleans(allowsImportingSpecifier(a.moduleSpecifier), allowsImportingSpecifier(b.moduleSpecifier))
489                || compareNumberOfDirectorySeparators(a.moduleSpecifier, b.moduleSpecifier);
490        }
491        return Comparison.EqualTo;
492    }
493
494    function getFixesInfoForUMDImport({ sourceFile, program, host, preferences }: CodeFixContextBase, token: Node): FixesInfo | undefined {
495        const checker = program.getTypeChecker();
496        const umdSymbol = getUmdSymbol(token, checker);
497        if (!umdSymbol) return undefined;
498        const symbol = checker.getAliasedSymbol(umdSymbol);
499        const symbolName = umdSymbol.name;
500        const exportInfos: readonly SymbolExportInfo[] = [{ moduleSymbol: symbol, importKind: getUmdImportKind(sourceFile, program.getCompilerOptions()), exportedSymbolIsTypeOnly: false }];
501        const useRequire = shouldUseRequire(sourceFile, program);
502        const fixes = getFixForImport(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, /*preferTypeOnlyImport*/ false, useRequire, program, sourceFile, host, preferences);
503        return { fixes, symbolName };
504    }
505    function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined {
506        // try the identifier to see if it is the umd symbol
507        const umdSymbol = isIdentifier(token) ? checker.getSymbolAtLocation(token) : undefined;
508        if (isUMDExportSymbol(umdSymbol)) return umdSymbol;
509
510        // The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`.
511        const { parent } = token;
512        return (isJsxOpeningLikeElement(parent) && parent.tagName === token) || isJsxOpeningFragment(parent)
513            ? tryCast(checker.resolveName(checker.getJsxNamespace(parent), isJsxOpeningLikeElement(parent) ? token : parent, SymbolFlags.Value, /*excludeGlobals*/ false), isUMDExportSymbol)
514            : undefined;
515    }
516
517    function getUmdImportKind(importingFile: SourceFile, compilerOptions: CompilerOptions): ImportKind {
518        // Import a synthetic `default` if enabled.
519        if (getAllowSyntheticDefaultImports(compilerOptions)) {
520            return ImportKind.Default;
521        }
522
523        // When a synthetic `default` is unavailable, use `import..require` if the module kind supports it.
524        const moduleKind = getEmitModuleKind(compilerOptions);
525        switch (moduleKind) {
526            case ModuleKind.AMD:
527            case ModuleKind.CommonJS:
528            case ModuleKind.UMD:
529                if (isInJSFile(importingFile)) {
530                    return isExternalModule(importingFile) ? ImportKind.Namespace : ImportKind.CommonJS;
531                }
532                return ImportKind.CommonJS;
533            case ModuleKind.System:
534            case ModuleKind.ES2015:
535            case ModuleKind.ES2020:
536            case ModuleKind.ESNext:
537            case ModuleKind.None:
538                // Fall back to the `import * as ns` style import.
539                return ImportKind.Namespace;
540            default:
541                return Debug.assertNever(moduleKind, `Unexpected moduleKind ${moduleKind}`);
542        }
543    }
544
545    function getFixesInfoForNonUMDImport({ sourceFile, program, cancellationToken, host, preferences }: CodeFixContextBase, symbolToken: Identifier, useAutoImportProvider: boolean): FixesInfo | undefined {
546        const checker = program.getTypeChecker();
547        const symbolName = getSymbolName(sourceFile, checker, symbolToken);
548        // "default" is a keyword and not a legal identifier for the import, so we don't expect it here
549        Debug.assert(symbolName !== InternalSymbolName.Default, "'default' isn't a legal identifier and couldn't occur here");
550
551        const compilerOptions = program.getCompilerOptions();
552        const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && isValidTypeOnlyAliasUseSite(symbolToken);
553        const useRequire = shouldUseRequire(sourceFile, program);
554        const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, program, useAutoImportProvider, host);
555        const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) =>
556            getFixForImport(exportInfos, symbolName, symbolToken.getStart(sourceFile), preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences)));
557        return { fixes, symbolName };
558    }
559
560    function getSymbolName(sourceFile: SourceFile, checker: TypeChecker, symbolToken: Identifier): string {
561        const parent = symbolToken.parent;
562        if ((isJsxOpeningLikeElement(parent) || isJsxClosingElement(parent)) && parent.tagName === symbolToken) {
563            const jsxNamespace = checker.getJsxNamespace(sourceFile);
564            if (isIntrinsicJsxName(symbolToken.text) || !checker.resolveName(jsxNamespace, parent, SymbolFlags.Value, /*excludeGlobals*/ true)) {
565                return jsxNamespace;
566            }
567        }
568        return symbolToken.text;
569    }
570
571    // Returns a map from an exported symbol's ID to a list of every way it's (re-)exported.
572    function getExportInfos(
573        symbolName: string,
574        currentTokenMeaning: SemanticMeaning,
575        cancellationToken: CancellationToken,
576        sourceFile: SourceFile,
577        program: Program,
578        useAutoImportProvider: boolean,
579        host: LanguageServiceHost
580    ): ReadonlyESMap<string, readonly SymbolExportInfo[]> {
581        // For each original symbol, keep all re-exports of that symbol together so we can call `getCodeActionsForImport` on the whole group at once.
582        // Maps symbol id to info for modules providing that symbol (original export + re-exports).
583        const originalSymbolToExportInfos = createMultiMap<SymbolExportInfo>();
584        function addSymbol(moduleSymbol: Symbol, exportedSymbol: Symbol, importKind: ImportKind, checker: TypeChecker): void {
585            originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exportedSymbol, checker) });
586        }
587        forEachExternalModuleToImportFrom(program, host, sourceFile, /*filterByPackageJson*/ true, useAutoImportProvider, (moduleSymbol, _, program) => {
588            const checker = program.getTypeChecker();
589            cancellationToken.throwIfCancellationRequested();
590
591            const compilerOptions = program.getCompilerOptions();
592            const defaultInfo = getDefaultLikeExportInfo(sourceFile, moduleSymbol, checker, compilerOptions);
593            if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, compilerOptions.target) === symbolName) && symbolHasMeaning(defaultInfo.symbolForMeaning, currentTokenMeaning)) {
594                addSymbol(moduleSymbol, defaultInfo.symbol, defaultInfo.kind, checker);
595            }
596
597            // check exports with the same name
598            const exportSymbolWithIdenticalName = checker.tryGetMemberInModuleExportsAndProperties(symbolName, moduleSymbol);
599            if (exportSymbolWithIdenticalName && symbolHasMeaning(exportSymbolWithIdenticalName, currentTokenMeaning)) {
600                addSymbol(moduleSymbol, exportSymbolWithIdenticalName, ImportKind.Named, checker);
601            }
602        });
603        return originalSymbolToExportInfos;
604    }
605
606    function getDefaultLikeExportInfo(
607        importingFile: SourceFile, moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions,
608    ): { readonly symbol: Symbol, readonly symbolForMeaning: Symbol, readonly name: string, readonly kind: ImportKind } | undefined {
609        const exported = getDefaultLikeExportWorker(importingFile, moduleSymbol, checker, compilerOptions);
610        if (!exported) return undefined;
611        const { symbol, kind } = exported;
612        const info = getDefaultExportInfoWorker(symbol, checker, compilerOptions);
613        return info && { symbol, kind, ...info };
614    }
615
616    function getDefaultLikeExportWorker(importingFile: SourceFile, moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbol: Symbol, readonly kind: ImportKind } | undefined {
617        const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol);
618        if (defaultExport) return { symbol: defaultExport, kind: ImportKind.Default };
619        const exportEquals = checker.resolveExternalModuleSymbol(moduleSymbol);
620        return exportEquals === moduleSymbol ? undefined : { symbol: exportEquals, kind: getExportEqualsImportKind(importingFile, compilerOptions) };
621    }
622
623    function getExportEqualsImportKind(importingFile: SourceFile, compilerOptions: CompilerOptions): ImportKind {
624        const allowSyntheticDefaults = getAllowSyntheticDefaultImports(compilerOptions);
625        // 1. 'import =' will not work in es2015+, so the decision is between a default
626        //    and a namespace import, based on allowSyntheticDefaultImports/esModuleInterop.
627        if (getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015) {
628            return allowSyntheticDefaults ? ImportKind.Default : ImportKind.Namespace;
629        }
630        // 2. 'import =' will not work in JavaScript, so the decision is between a default
631        //    and const/require.
632        if (isInJSFile(importingFile)) {
633            return isExternalModule(importingFile) ? ImportKind.Default : ImportKind.CommonJS;
634        }
635        // 3. At this point the most correct choice is probably 'import =', but people
636        //    really hate that, so look to see if the importing file has any precedent
637        //    on how to handle it.
638        for (const statement of importingFile.statements) {
639            if (isImportEqualsDeclaration(statement)) {
640                return ImportKind.CommonJS;
641            }
642        }
643        // 4. We have no precedent to go on, so just use a default import if
644        //    allowSyntheticDefaultImports/esModuleInterop is enabled.
645        return allowSyntheticDefaults ? ImportKind.Default : ImportKind.CommonJS;
646    }
647
648    function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined {
649        const localSymbol = getLocalSymbolForExportDefault(defaultExport);
650        if (localSymbol) return { symbolForMeaning: localSymbol, name: localSymbol.name };
651
652        const name = getNameForExportDefault(defaultExport);
653        if (name !== undefined) return { symbolForMeaning: defaultExport, name };
654
655        if (defaultExport.flags & SymbolFlags.Alias) {
656            const aliased = checker.getImmediateAliasedSymbol(defaultExport);
657            if (aliased && aliased.parent) {
658                // - `aliased` will be undefined if the module is exporting an unresolvable name,
659                //    but we can still offer completions for it.
660                // - `aliased.parent` will be undefined if the module is exporting `globalThis.something`,
661                //    or another expression that resolves to a global.
662                return getDefaultExportInfoWorker(aliased, checker, compilerOptions);
663            }
664        }
665
666        if (defaultExport.escapedName !== InternalSymbolName.Default &&
667            defaultExport.escapedName !== InternalSymbolName.ExportEquals) {
668            return { symbolForMeaning: defaultExport, name: defaultExport.getName() };
669        }
670        return { symbolForMeaning: defaultExport, name: getNameForExportedSymbol(defaultExport, compilerOptions.target) };
671    }
672
673    function getNameForExportDefault(symbol: Symbol): string | undefined {
674        return symbol.declarations && firstDefined(symbol.declarations, declaration => {
675            if (isExportAssignment(declaration)) {
676                return tryCast(skipOuterExpressions(declaration.expression), isIdentifier)?.text;
677            }
678            else if (isExportSpecifier(declaration)) {
679                Debug.assert(declaration.name.text === InternalSymbolName.Default, "Expected the specifier to be a default export");
680                return declaration.propertyName && declaration.propertyName.text;
681            }
682        });
683    }
684
685    function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference): CodeFixAction {
686        let diag!: DiagnosticAndArguments;
687        const changes = textChanges.ChangeTracker.with(context, tracker => {
688            diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, quotePreference);
689        });
690        return createCodeFixAction(importFixName, changes, diag, importFixId, Diagnostics.Add_all_missing_imports);
691    }
692    function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference): DiagnosticAndArguments {
693        switch (fix.kind) {
694            case ImportFixKind.UseNamespace:
695                addNamespaceQualifier(changes, sourceFile, fix);
696                return [Diagnostics.Change_0_to_1, symbolName, `${fix.namespacePrefix}.${symbolName}`];
697            case ImportFixKind.ImportType:
698                addImportType(changes, sourceFile, fix, quotePreference);
699                return [Diagnostics.Change_0_to_1, symbolName, getImportTypePrefix(fix.moduleSpecifier, quotePreference) + symbolName];
700            case ImportFixKind.AddToExisting: {
701                const { importClauseOrBindingPattern, importKind, canUseTypeOnlyImport, moduleSpecifier } = fix;
702                doAddExistingFix(changes, sourceFile, importClauseOrBindingPattern, importKind === ImportKind.Default ? symbolName : undefined, importKind === ImportKind.Named ? [symbolName] : emptyArray, canUseTypeOnlyImport);
703                const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
704                return [importKind === ImportKind.Default ? Diagnostics.Add_default_import_0_to_existing_import_declaration_from_1 : Diagnostics.Add_0_to_existing_import_declaration_from_1, symbolName, moduleSpecifierWithoutQuotes]; // you too!
705            }
706            case ImportFixKind.AddNew: {
707                const { importKind, moduleSpecifier, typeOnly, useRequire } = fix;
708                const getDeclarations = useRequire ? getNewRequires : getNewImports;
709                const importsCollection = importKind === ImportKind.Default ? { defaultImport: symbolName, typeOnly } :
710                    importKind === ImportKind.Named ? { namedImports: [symbolName], typeOnly } :
711                    { namespaceLikeImport: { importKind, name: symbolName }, typeOnly };
712                insertImports(changes, sourceFile, getDeclarations(moduleSpecifier, quotePreference, importsCollection), /*blankLineBetween*/ true);
713                return [importKind === ImportKind.Default ? Diagnostics.Import_default_0_from_module_1 : Diagnostics.Import_0_from_module_1, symbolName, moduleSpecifier];
714            }
715            default:
716                return Debug.assertNever(fix, `Unexpected fix kind ${(fix as ImportFix).kind}`);
717        }
718    }
719
720    function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause | ObjectBindingPattern, defaultImport: string | undefined, namedImports: readonly string[], canUseTypeOnlyImport: boolean): void {
721        if (clause.kind === SyntaxKind.ObjectBindingPattern) {
722            if (defaultImport) {
723                addElementToBindingPattern(clause, defaultImport, "default");
724            }
725            for (const specifier of namedImports) {
726                addElementToBindingPattern(clause, specifier, /*propertyName*/ undefined);
727            }
728            return;
729        }
730
731        const convertTypeOnlyToRegular = !canUseTypeOnlyImport && clause.isTypeOnly;
732        if (defaultImport) {
733            Debug.assert(!clause.name, "Cannot add a default import to an import clause that already has one");
734            changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), factory.createIdentifier(defaultImport), { suffix: ", " });
735        }
736
737        if (namedImports.length) {
738            const existingSpecifiers = clause.namedBindings && cast(clause.namedBindings, isNamedImports).elements;
739            const newSpecifiers = stableSort(
740                namedImports.map(name => factory.createImportSpecifier(/*propertyName*/ undefined, factory.createIdentifier(name))),
741                OrganizeImports.compareImportOrExportSpecifiers);
742
743            if (existingSpecifiers?.length && OrganizeImports.importSpecifiersAreSorted(existingSpecifiers)) {
744                for (const spec of newSpecifiers) {
745                    const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec);
746                    const prevSpecifier = (clause.namedBindings as NamedImports).elements[insertionIndex - 1];
747                    if (prevSpecifier) {
748                        changes.insertNodeInListAfter(sourceFile, prevSpecifier, spec);
749                    }
750                    else {
751                        changes.insertNodeBefore(
752                            sourceFile,
753                            existingSpecifiers[0],
754                            spec,
755                            !positionsAreOnSameLine(existingSpecifiers[0].getStart(), clause.parent.getStart(), sourceFile));
756                    }
757                }
758            }
759            else if (existingSpecifiers?.length) {
760                for (const spec of newSpecifiers) {
761                    changes.insertNodeInListAfter(sourceFile, last(existingSpecifiers), spec, existingSpecifiers);
762                }
763            }
764            else {
765                if (newSpecifiers.length) {
766                    const namedImports = factory.createNamedImports(newSpecifiers);
767                    if (clause.namedBindings) {
768                        changes.replaceNode(sourceFile, clause.namedBindings, namedImports);
769                    }
770                    else {
771                        changes.insertNodeAfter(sourceFile, Debug.checkDefined(clause.name, "Import clause must have either named imports or a default import"), namedImports);
772                    }
773                }
774            }
775        }
776
777        if (convertTypeOnlyToRegular) {
778            changes.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(clause, sourceFile));
779        }
780
781        function addElementToBindingPattern(bindingPattern: ObjectBindingPattern, name: string, propertyName: string | undefined) {
782            const element = factory.createBindingElement(/*dotDotDotToken*/ undefined, propertyName, name);
783            if (bindingPattern.elements.length) {
784                changes.insertNodeInListAfter(sourceFile, last(bindingPattern.elements), element);
785            }
786            else {
787                changes.replaceNode(sourceFile, bindingPattern, factory.createObjectBindingPattern([element]));
788            }
789        }
790    }
791
792    function addNamespaceQualifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { namespacePrefix, position }: FixUseNamespaceImport): void {
793        changes.insertText(sourceFile, position, namespacePrefix + ".");
794    }
795
796    function addImportType(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { moduleSpecifier, position }: FixUseImportType, quotePreference: QuotePreference): void {
797        changes.insertText(sourceFile, position, getImportTypePrefix(moduleSpecifier, quotePreference));
798    }
799
800    function getImportTypePrefix(moduleSpecifier: string, quotePreference: QuotePreference): string {
801        const quote = getQuoteFromPreference(quotePreference);
802        return `import(${quote}${moduleSpecifier}${quote}).`;
803    }
804
805    interface ImportsCollection {
806        readonly typeOnly: boolean;
807        readonly defaultImport?: string;
808        readonly namedImports?: string[];
809        readonly namespaceLikeImport?: {
810            readonly importKind: ImportKind.CommonJS | ImportKind.Namespace;
811            readonly name: string;
812        };
813    }
814    function getNewImports(moduleSpecifier: string, quotePreference: QuotePreference, imports: ImportsCollection): AnyImportSyntax | readonly AnyImportSyntax[] {
815        const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
816        let statements: AnyImportSyntax | readonly AnyImportSyntax[] | undefined;
817        if (imports.defaultImport !== undefined || imports.namedImports?.length) {
818            statements = combine(statements, makeImport(
819                imports.defaultImport === undefined ? undefined : factory.createIdentifier(imports.defaultImport),
820                imports.namedImports?.map(n => factory.createImportSpecifier(/*propertyName*/ undefined, factory.createIdentifier(n))), moduleSpecifier, quotePreference, imports.typeOnly));
821        }
822        const { namespaceLikeImport, typeOnly } = imports;
823        if (namespaceLikeImport) {
824            const declaration = namespaceLikeImport.importKind === ImportKind.CommonJS
825                ? factory.createImportEqualsDeclaration(
826                    /*decorators*/ undefined,
827                    /*modifiers*/ undefined,
828                    typeOnly,
829                    factory.createIdentifier(namespaceLikeImport.name),
830                    factory.createExternalModuleReference(quotedModuleSpecifier))
831                : factory.createImportDeclaration(
832                    /*decorators*/ undefined,
833                    /*modifiers*/ undefined,
834                    factory.createImportClause(
835                        typeOnly,
836                        /*name*/ undefined,
837                        factory.createNamespaceImport(factory.createIdentifier(namespaceLikeImport.name))),
838                    quotedModuleSpecifier);
839            statements = combine(statements, declaration);
840        }
841        return Debug.checkDefined(statements);
842    }
843
844    function getNewRequires(moduleSpecifier: string, quotePreference: QuotePreference, imports: ImportsCollection): RequireVariableStatement | readonly RequireVariableStatement[] {
845        const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
846        let statements: RequireVariableStatement | readonly RequireVariableStatement[] | undefined;
847        // const { default: foo, bar, etc } = require('./mod');
848        if (imports.defaultImport || imports.namedImports?.length) {
849            const bindingElements = imports.namedImports?.map(name => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name)) || [];
850            if (imports.defaultImport) {
851                bindingElements.unshift(factory.createBindingElement(/*dotDotDotToken*/ undefined, "default", imports.defaultImport));
852            }
853            const declaration = createConstEqualsRequireDeclaration(factory.createObjectBindingPattern(bindingElements), quotedModuleSpecifier);
854            statements = combine(statements, declaration);
855        }
856        // const foo = require('./mod');
857        if (imports.namespaceLikeImport) {
858            const declaration = createConstEqualsRequireDeclaration(imports.namespaceLikeImport.name, quotedModuleSpecifier);
859            statements = combine(statements, declaration);
860        }
861        return Debug.checkDefined(statements);
862    }
863
864    function createConstEqualsRequireDeclaration(name: string | ObjectBindingPattern, quotedModuleSpecifier: StringLiteral): RequireVariableStatement {
865        return factory.createVariableStatement(
866            /*modifiers*/ undefined,
867            factory.createVariableDeclarationList([
868                factory.createVariableDeclaration(
869                    typeof name === "string" ? factory.createIdentifier(name) : name,
870                    /*exclamationToken*/ undefined,
871                    /*type*/ undefined,
872                    factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [quotedModuleSpecifier]))],
873                NodeFlags.Const)) as RequireVariableStatement;
874    }
875
876    function symbolHasMeaning({ declarations }: Symbol, meaning: SemanticMeaning): boolean {
877        return some(declarations, decl => !!(getMeaningFromDeclaration(decl) & meaning));
878    }
879
880    export function forEachExternalModuleToImportFrom(
881        program: Program,
882        host: LanguageServiceHost,
883        from: SourceFile,
884        filterByPackageJson: boolean,
885        useAutoImportProvider: boolean,
886        cb: (module: Symbol, moduleFile: SourceFile | undefined, program: Program, isFromPackageJson: boolean) => void,
887    ) {
888        forEachExternalModuleToImportFromInProgram(program, host, from, filterByPackageJson, (module, file) => cb(module, file, program, /*isFromPackageJson*/ false));
889        const autoImportProvider = useAutoImportProvider && host.getPackageJsonAutoImportProvider?.();
890        if (autoImportProvider) {
891            const start = timestamp();
892            forEachExternalModuleToImportFromInProgram(autoImportProvider, host, from, filterByPackageJson, (module, file) => cb(module, file, autoImportProvider, /*isFromPackageJson*/ true));
893            host.log?.(`forEachExternalModuleToImportFrom autoImportProvider: ${timestamp() - start}`);
894        }
895    }
896
897    function forEachExternalModuleToImportFromInProgram(
898        program: Program,
899        host: LanguageServiceHost,
900        from: SourceFile,
901        filterByPackageJson: boolean,
902        cb: (module: Symbol, moduleFile: SourceFile | undefined) => void,
903    ) {
904        let filteredCount = 0;
905        const moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host);
906        const packageJson = filterByPackageJson && createAutoImportFilter(from, program, host, moduleSpecifierResolutionHost);
907        forEachExternalModule(program.getTypeChecker(), program.getSourceFiles(), (module, sourceFile) => {
908            if (sourceFile === undefined) {
909                if (!packageJson || packageJson.allowsImportingAmbientModule(module)) {
910                    cb(module, sourceFile);
911                }
912                else if (packageJson) {
913                    filteredCount++;
914                }
915            }
916            else if (sourceFile &&
917                sourceFile !== from &&
918                isImportableFile(program, from, sourceFile, moduleSpecifierResolutionHost)
919            ) {
920                if (!packageJson || packageJson.allowsImportingSourceFile(sourceFile)) {
921                    cb(module, sourceFile);
922                }
923                else if (packageJson) {
924                    filteredCount++;
925                }
926            }
927        });
928        host.log?.(`forEachExternalModuleToImportFrom: filtered out ${filteredCount} modules by package.json or oh-package.json5 contents`);
929    }
930
931    function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly SourceFile[], cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) {
932        for (const ambient of checker.getAmbientModules()) {
933            cb(ambient, /*sourceFile*/ undefined);
934        }
935        for (const sourceFile of allSourceFiles) {
936            if (isExternalOrCommonJsModule(sourceFile)) {
937                cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile);
938            }
939        }
940    }
941
942    function isImportableFile(
943        program: Program,
944        from: SourceFile,
945        to: SourceFile,
946        moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost
947    ) {
948        const isOHModules = isOhpm(program.getCompilerOptions().packageManagerType);
949        const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost);
950        const globalTypingsCache = moduleSpecifierResolutionHost.getGlobalTypingsCacheLocation?.();
951        return !!moduleSpecifiers.forEachFileNameOfModule(
952            from.fileName,
953            to.fileName,
954            moduleSpecifierResolutionHost,
955            /*preferSymlinks*/ false,
956            toPath => {
957                const toFile = program.getSourceFile(toPath);
958                // Determine to import using toPath only if toPath is what we were looking at
959                // or there doesnt exist the file in the program by the symlink
960                return (toFile === to || !toFile) &&
961                    isImportablePath(from.fileName, toPath, getCanonicalFileName, globalTypingsCache, isOHModules);
962            },
963            isOHModules
964        );
965    }
966
967    /**
968     * Don't include something from a `node_modules` or `oh_modules` that isn't actually reachable by a global import.
969     * A relative import to node_modules or oh_modules is usually a bad idea.
970     */
971    function isImportablePath(fromPath: string, toPath: string, getCanonicalFileName: GetCanonicalFileName, globalCachePath?: string, isOHModules?: boolean): boolean {
972        // If it's in a `node_modules` or `oh_modules` but is not reachable from here via a global import, don't bother.
973        const toNodeModules = forEachAncestorDirectory(toPath, ancestor => (getBaseFileName(ancestor) === "node_modules" || (isOHModules &&
974            getBaseFileName(ancestor) === "oh_modules")) ? ancestor : undefined);
975        const toNodeModulesParent = toNodeModules && getDirectoryPath(getCanonicalFileName(toNodeModules));
976        return toNodeModulesParent === undefined
977            || startsWith(getCanonicalFileName(fromPath), toNodeModulesParent)
978            || (!!globalCachePath && startsWith(getCanonicalFileName(globalCachePath), toNodeModulesParent));
979    }
980
981    export function moduleSymbolToValidIdentifier(moduleSymbol: Symbol, target: ScriptTarget | undefined): string {
982        return moduleSpecifierToValidIdentifier(removeFileExtension(stripQuotes(moduleSymbol.name)), target);
983    }
984
985    export function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget | undefined): string {
986        const baseName = getBaseFileName(removeSuffix(moduleSpecifier, "/index"));
987        let res = "";
988        let lastCharWasValid = true;
989        const firstCharCode = baseName.charCodeAt(0);
990        if (isIdentifierStart(firstCharCode, target)) {
991            res += String.fromCharCode(firstCharCode);
992        }
993        else {
994            lastCharWasValid = false;
995        }
996        for (let i = 1; i < baseName.length; i++) {
997            const ch = baseName.charCodeAt(i);
998            const isValid = isIdentifierPart(ch, target);
999            if (isValid) {
1000                let char = String.fromCharCode(ch);
1001                if (!lastCharWasValid) {
1002                    char = char.toUpperCase();
1003                }
1004                res += char;
1005            }
1006            lastCharWasValid = isValid;
1007        }
1008        // Need `|| "_"` to ensure result isn't empty.
1009        return !isStringANonContextualKeyword(res) ? res || "_" : `_${res}`;
1010    }
1011
1012    function createAutoImportFilter(fromFile: SourceFile, program: Program, host: LanguageServiceHost, moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host)) {
1013        const packageJsons = (
1014            (host.getPackageJsonsVisibleToFile && host.getPackageJsonsVisibleToFile(fromFile.fileName)) || getPackageJsonsVisibleToFile(fromFile.fileName, host)
1015          ).filter(p => p.parseable);
1016
1017        let usesNodeCoreModules: boolean | undefined;
1018        return { allowsImportingAmbientModule, allowsImportingSourceFile, allowsImportingSpecifier, moduleSpecifierResolutionHost };
1019
1020        function moduleSpecifierIsCoveredByPackageJson(specifier: string) {
1021            const packageName = getModuleRootSpecifier(specifier);
1022            for (const packageJson of packageJsons) {
1023                if (packageJson.has(packageName) || packageJson.has(getTypesPackageName(packageName))) {
1024                    return true;
1025                }
1026            }
1027            return false;
1028        }
1029
1030        function allowsImportingAmbientModule(moduleSymbol: Symbol): boolean {
1031            if (!packageJsons.length) {
1032                return true;
1033            }
1034
1035            const declaringSourceFile = moduleSymbol.valueDeclaration.getSourceFile();
1036            const declaringNodeModuleName = getModulesPackageNameFromFileName(declaringSourceFile.fileName);
1037            if (typeof declaringNodeModuleName === "undefined") {
1038                return true;
1039            }
1040
1041            const declaredModuleSpecifier = stripQuotes(moduleSymbol.getName());
1042            if (isAllowedCoreNodeModulesImport(declaredModuleSpecifier)) {
1043                return true;
1044            }
1045
1046            return moduleSpecifierIsCoveredByPackageJson(declaringNodeModuleName)
1047                || moduleSpecifierIsCoveredByPackageJson(declaredModuleSpecifier);
1048        }
1049
1050        function allowsImportingSourceFile(sourceFile: SourceFile): boolean {
1051            if (!packageJsons.length) {
1052                return true;
1053            }
1054
1055            const moduleSpecifier = getModulesPackageNameFromFileName(sourceFile.fileName);
1056            if (!moduleSpecifier) {
1057                return true;
1058            }
1059
1060            return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier);
1061        }
1062
1063        /**
1064         * Use for a specific module specifier that has already been resolved.
1065         * Use `allowsImportingAmbientModule` or `allowsImportingSourceFile` to resolve
1066         * the best module specifier for a given module _and_ determine if it’s importable.
1067         */
1068        function allowsImportingSpecifier(moduleSpecifier: string) {
1069            if (!packageJsons.length || isAllowedCoreNodeModulesImport(moduleSpecifier)) {
1070                return true;
1071            }
1072            if (pathIsRelative(moduleSpecifier) || isRootedDiskPath(moduleSpecifier)) {
1073                return true;
1074            }
1075            return moduleSpecifierIsCoveredByPackageJson(moduleSpecifier);
1076        }
1077
1078        function isAllowedCoreNodeModulesImport(moduleSpecifier: string) {
1079            // If we’re in JavaScript, it can be difficult to tell whether the user wants to import
1080            // from Node core modules or not. We can start by seeing if the user is actually using
1081            // any node core modules, as opposed to simply having @types/node accidentally as a
1082            // dependency of a dependency.
1083            if (isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) {
1084                if (usesNodeCoreModules === undefined) {
1085                    usesNodeCoreModules = consumesNodeCoreModules(fromFile);
1086                }
1087                if (usesNodeCoreModules) {
1088                    return true;
1089                }
1090            }
1091            return false;
1092        }
1093
1094        function getModulesPackageNameFromFileName(importedFileName: string): string | undefined {
1095            if (!stringContains(importedFileName, "node_modules") && !stringContains(importedFileName, "oh_modules")) {
1096                return undefined;
1097            }
1098            const specifier = moduleSpecifiers.getModulesPackageName(
1099                host.getCompilationSettings(),
1100                fromFile.path,
1101                importedFileName,
1102                moduleSpecifierResolutionHost,
1103            );
1104
1105            if (!specifier) {
1106                return undefined;
1107            }
1108            // Paths here are not node_modules or oh_modules, so we don’t care about them;
1109            // returning anything will trigger a lookup in package.json or oh-package.json5.
1110            if (!pathIsRelative(specifier) && !isRootedDiskPath(specifier)) {
1111                return getModuleRootSpecifier(specifier);
1112            }
1113        }
1114
1115        function getModuleRootSpecifier(fullSpecifier: string): string {
1116            const components = getPathComponents(getPackageNameFromTypesPackageName(fullSpecifier)).slice(1);
1117            // Scoped packages
1118            if (startsWith(components[0], "@")) {
1119                return `${components[0]}/${components[1]}`;
1120            }
1121            return components[0];
1122        }
1123    }
1124}
1125