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