@@ -3491,7 +3491,11 @@ namespace ts {
34913491                    }
34923492                    if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) {
34933493                        const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration);
3494-                         if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext) {
3494+                         const overrideClauseHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined;
3495+                         const overrideClause = overrideClauseHost && isImportTypeNode(overrideClauseHost) ? overrideClauseHost.assertions?.assertClause : overrideClauseHost?.assertClause;
3496+                         // An override clause will take effect for type-only imports and import types, and allows importing the types across formats, regardless of
3497+                         // normal mode restrictions
3498+                         if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext && !getResolutionModeOverrideForClause(overrideClause)) {
34953499                            error(errorNode, Diagnostics.Module_0_cannot_be_imported_using_this_construct_The_specifier_only_resolves_to_an_ES_module_which_cannot_be_imported_synchronously_Use_dynamic_import_instead, moduleReference);
34963500                        }
34973501                        if (mode === ModuleKind.ESNext && compilerOptions.resolveJsonModule && resolvedModule.extension === Extension.Json) {
@@ -5979,7 +5983,7 @@ namespace ts {
59795983                return top;
59805984            }
59815985
5982-             function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext) {
5986+             function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext, overrideImportMode?: SourceFile["impliedNodeFormat"] ) {
59835987                let file = getDeclarationOfKind<SourceFile>(symbol, SyntaxKind.SourceFile);
59845988                if (!file) {
59855989                    const equivalentFileSymbol = firstDefined(symbol.declarations, d => getFileSymbolIfFileSymbolExportEqualsContainer(d, symbol));
@@ -6012,8 +6016,10 @@ namespace ts {
60126016                    return getSourceFileOfNode(getNonAugmentationDeclaration(symbol)!).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full
60136017                }
60146018                const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration));
6019+                 const resolutionMode = overrideImportMode || contextFile?.impliedNodeFormat;
6020+                 const cacheKey = getSpecifierCacheKey(contextFile.path, resolutionMode);
60156021                const links = getSymbolLinks(symbol);
6016-                 let specifier = links.specifierCache && links.specifierCache.get(contextFile.path );
6022+                 let specifier = links.specifierCache && links.specifierCache.get(cacheKey );
60176023                if (!specifier) {
60186024                    const isBundle = !!outFile(compilerOptions);
60196025                    // For declaration bundles, we need to generate absolute paths relative to the common source dir for imports,
@@ -6028,12 +6034,25 @@ namespace ts {
60286034                        specifierCompilerOptions,
60296035                        contextFile,
60306036                        moduleResolverHost,
6031-                         { importModuleSpecifierPreference: isBundle ? "non-relative" : "project-relative", importModuleSpecifierEnding: isBundle ? "minimal" : undefined },
6037+                         {
6038+                             importModuleSpecifierPreference: isBundle ? "non-relative" : "project-relative",
6039+                             importModuleSpecifierEnding: isBundle ? "minimal"
6040+                                 : resolutionMode === ModuleKind.ESNext ? "js"
6041+                                 : undefined,
6042+                             overrideImportMode:
6043+                                 overrideImportMode === ModuleKind.CommonJS ? "require"
6044+                                 : overrideImportMode === ModuleKind.ESNext ? "import"
6045+                                 : undefined
6046+                         },
60326047                    ));
60336048                    links.specifierCache ??= new Map();
6034-                     links.specifierCache.set(contextFile.path , specifier);
6049+                     links.specifierCache.set(cacheKey , specifier);
60356050                }
60366051                return specifier;
6052+ 
6053+                 function getSpecifierCacheKey(path: string, mode: SourceFile["impliedNodeFormat"] | undefined) {
6054+                     return mode === undefined ? path : `${mode}|${path}`;
6055+                 }
60376056            }
60386057
60396058            function symbolToEntityNameNode(symbol: Symbol): EntityName {
@@ -6049,13 +6068,53 @@ namespace ts {
60496068                    // module is root, must use `ImportTypeNode`
60506069                    const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined;
60516070                    const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context);
6052-                     const specifier = getSpecifierForModuleSymbol(chain[0], context);
6071+                     const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration));
6072+                     const targetFile = getSourceFileOfModule(chain[0]);
6073+                     let specifier: string | undefined;
6074+                     let assertion: ImportTypeAssertionContainer | undefined;
6075+                     if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) {
6076+                         // An `import` type directed at an esm format file is only going to resolve in esm mode - set the esm mode assertion
6077+                         if (targetFile?.impliedNodeFormat === ModuleKind.ESNext && targetFile.impliedNodeFormat !== contextFile?.impliedNodeFormat) {
6078+                             specifier = getSpecifierForModuleSymbol(chain[0], context, ModuleKind.ESNext);
6079+                             assertion = factory.createImportTypeAssertionContainer(factory.createAssertClause(factory.createNodeArray([
6080+                                 factory.createAssertEntry(
6081+                                     factory.createStringLiteral("resolution-mode"),
6082+                                     factory.createStringLiteral("import")
6083+                                 )
6084+                             ])));
6085+                         }
6086+                     }
6087+                     if (!specifier) {
6088+                         specifier = getSpecifierForModuleSymbol(chain[0], context);
6089+                     }
60536090                    if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.indexOf("/node_modules/") >= 0) {
6054-                         // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error
6055-                         // since declaration files with these kinds of references are liable to fail when published :(
6056-                         context.encounteredError = true;
6057-                         if (context.tracker.reportLikelyUnsafeImportRequiredError) {
6058-                             context.tracker.reportLikelyUnsafeImportRequiredError(specifier);
6091+                         const oldSpecifier = specifier;
6092+                         if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) {
6093+                             // We might be able to write a portable import type using a mode override; try specifier generation again, but with a different mode set
6094+                             const swappedMode = contextFile?.impliedNodeFormat === ModuleKind.ESNext ? ModuleKind.CommonJS : ModuleKind.ESNext;
6095+                             specifier = getSpecifierForModuleSymbol(chain[0], context, swappedMode);
6096+ 
6097+                             if (specifier.indexOf("/node_modules/") >= 0) {
6098+                                 // Still unreachable :(
6099+                                 specifier = oldSpecifier;
6100+                             }
6101+                             else {
6102+                                 assertion = factory.createImportTypeAssertionContainer(factory.createAssertClause(factory.createNodeArray([
6103+                                     factory.createAssertEntry(
6104+                                         factory.createStringLiteral("resolution-mode"),
6105+                                         factory.createStringLiteral(swappedMode === ModuleKind.ESNext ? "import" : "require")
6106+                                     )
6107+                                 ])));
6108+                             }
6109+                         }
6110+ 
6111+                         if (!assertion) {
6112+                             // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error
6113+                             // since declaration files with these kinds of references are liable to fail when published :(
6114+                             context.encounteredError = true;
6115+                             if (context.tracker.reportLikelyUnsafeImportRequiredError) {
6116+                                 context.tracker.reportLikelyUnsafeImportRequiredError(oldSpecifier);
6117+                             }
60596118                        }
60606119                    }
60616120                    const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier));
@@ -6066,12 +6125,12 @@ namespace ts {
60666125                            const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right;
60676126                            lastId.typeArguments = undefined;
60686127                        }
6069-                         return factory.createImportTypeNode(lit, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf);
6128+                         return factory.createImportTypeNode(lit, assertion,  nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf);
60706129                    }
60716130                    else {
60726131                        const splitNode = getTopmostIndexedAccessType(nonRootParts);
60736132                        const qualifier = (splitNode.objectType as TypeReferenceNode).typeName;
6074-                         return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType);
6133+                         return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, assertion,  qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType);
60756134                    }
60766135                }
60776136
@@ -35202,6 +35261,16 @@ namespace ts {
3520235261
3520335262        function checkImportType(node: ImportTypeNode) {
3520435263            checkSourceElement(node.argument);
35264+ 
35265+             if (node.assertions) {
35266+                 const override = getResolutionModeOverrideForClause(node.assertions.assertClause, grammarErrorOnNode);
35267+                 if (override) {
35268+                     if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node12 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) {
35269+                         grammarErrorOnNode(node.assertions.assertClause, Diagnostics.Resolution_modes_are_only_supported_when_moduleResolution_is_node12_or_nodenext);
35270+                     }
35271+                 }
35272+             }
35273+ 
3520535274            getTypeFromTypeNode(node);
3520635275        }
3520735276
@@ -40031,6 +40100,15 @@ namespace ts {
4003140100
4003240101        function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) {
4003340102            if (declaration.assertClause) {
40103+                 const validForTypeAssertions = isExclusivelyTypeOnlyImportOrExport(declaration);
40104+                 const override = getResolutionModeOverrideForClause(declaration.assertClause, validForTypeAssertions ? grammarErrorOnNode : undefined);
40105+                 if (validForTypeAssertions && override) {
40106+                     if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node12 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) {
40107+                         return grammarErrorOnNode(declaration.assertClause, Diagnostics.Resolution_modes_are_only_supported_when_moduleResolution_is_node12_or_nodenext);
40108+                     }
40109+                     return; // Other grammar checks do not apply to type-only imports with resolution mode assertions
40110+                 }
40111+ 
4003440112                const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier);
4003540113                if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) {
4003640114                    return grammarErrorOnNode(declaration.assertClause,
@@ -40042,6 +40120,10 @@ namespace ts {
4004240120                if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) {
4004340121                    return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports);
4004440122                }
40123+ 
40124+                 if (override) {
40125+                     return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_can_only_be_set_for_type_only_imports);
40126+                 }
4004540127            }
4004640128        }
4004740129
0 commit comments