@@ -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) {
@@ -5985,7 +5989,7 @@ namespace ts {
59855989 return top;
59865990 }
59875991
5988- function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext) {
5992+ function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext, overrideImportMode?: SourceFile["impliedNodeFormat"] ) {
59895993 let file = getDeclarationOfKind<SourceFile>(symbol, SyntaxKind.SourceFile);
59905994 if (!file) {
59915995 const equivalentFileSymbol = firstDefined(symbol.declarations, d => getFileSymbolIfFileSymbolExportEqualsContainer(d, symbol));
@@ -6018,8 +6022,10 @@ namespace ts {
60186022 return getSourceFileOfNode(getNonAugmentationDeclaration(symbol)!).fileName; // A resolver may not be provided for baselines and errors - in those cases we use the fileName in full
60196023 }
60206024 const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration));
6025+ const resolutionMode = overrideImportMode || contextFile?.impliedNodeFormat;
6026+ const cacheKey = getSpecifierCacheKey(contextFile.path, resolutionMode);
60216027 const links = getSymbolLinks(symbol);
6022- let specifier = links.specifierCache && links.specifierCache.get(contextFile.path );
6028+ let specifier = links.specifierCache && links.specifierCache.get(cacheKey );
60236029 if (!specifier) {
60246030 const isBundle = !!outFile(compilerOptions);
60256031 // For declaration bundles, we need to generate absolute paths relative to the common source dir for imports,
@@ -6034,12 +6040,22 @@ namespace ts {
60346040 specifierCompilerOptions,
60356041 contextFile,
60366042 moduleResolverHost,
6037- { importModuleSpecifierPreference: isBundle ? "non-relative" : "project-relative", importModuleSpecifierEnding: isBundle ? "minimal" : undefined },
6043+ {
6044+ importModuleSpecifierPreference: isBundle ? "non-relative" : "project-relative",
6045+ importModuleSpecifierEnding: isBundle ? "minimal"
6046+ : resolutionMode === ModuleKind.ESNext ? "js"
6047+ : undefined,
6048+ },
6049+ { overrideImportMode }
60386050 ));
60396051 links.specifierCache ??= new Map();
6040- links.specifierCache.set(contextFile.path , specifier);
6052+ links.specifierCache.set(cacheKey , specifier);
60416053 }
60426054 return specifier;
6055+
6056+ function getSpecifierCacheKey(path: string, mode: SourceFile["impliedNodeFormat"] | undefined) {
6057+ return mode === undefined ? path : `${mode}|${path}`;
6058+ }
60436059 }
60446060
60456061 function symbolToEntityNameNode(symbol: Symbol): EntityName {
@@ -6055,13 +6071,53 @@ namespace ts {
60556071 // module is root, must use `ImportTypeNode`
60566072 const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined;
60576073 const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context);
6058- const specifier = getSpecifierForModuleSymbol(chain[0], context);
6074+ const contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration));
6075+ const targetFile = getSourceFileOfModule(chain[0]);
6076+ let specifier: string | undefined;
6077+ let assertion: ImportTypeAssertionContainer | undefined;
6078+ if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) {
6079+ // An `import` type directed at an esm format file is only going to resolve in esm mode - set the esm mode assertion
6080+ if (targetFile?.impliedNodeFormat === ModuleKind.ESNext && targetFile.impliedNodeFormat !== contextFile?.impliedNodeFormat) {
6081+ specifier = getSpecifierForModuleSymbol(chain[0], context, ModuleKind.ESNext);
6082+ assertion = factory.createImportTypeAssertionContainer(factory.createAssertClause(factory.createNodeArray([
6083+ factory.createAssertEntry(
6084+ factory.createStringLiteral("resolution-mode"),
6085+ factory.createStringLiteral("import")
6086+ )
6087+ ])));
6088+ }
6089+ }
6090+ if (!specifier) {
6091+ specifier = getSpecifierForModuleSymbol(chain[0], context);
6092+ }
60596093 if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.indexOf("/node_modules/") >= 0) {
6060- // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error
6061- // since declaration files with these kinds of references are liable to fail when published :(
6062- context.encounteredError = true;
6063- if (context.tracker.reportLikelyUnsafeImportRequiredError) {
6064- context.tracker.reportLikelyUnsafeImportRequiredError(specifier);
6094+ const oldSpecifier = specifier;
6095+ if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) {
6096+ // We might be able to write a portable import type using a mode override; try specifier generation again, but with a different mode set
6097+ const swappedMode = contextFile?.impliedNodeFormat === ModuleKind.ESNext ? ModuleKind.CommonJS : ModuleKind.ESNext;
6098+ specifier = getSpecifierForModuleSymbol(chain[0], context, swappedMode);
6099+
6100+ if (specifier.indexOf("/node_modules/") >= 0) {
6101+ // Still unreachable :(
6102+ specifier = oldSpecifier;
6103+ }
6104+ else {
6105+ assertion = factory.createImportTypeAssertionContainer(factory.createAssertClause(factory.createNodeArray([
6106+ factory.createAssertEntry(
6107+ factory.createStringLiteral("resolution-mode"),
6108+ factory.createStringLiteral(swappedMode === ModuleKind.ESNext ? "import" : "require")
6109+ )
6110+ ])));
6111+ }
6112+ }
6113+
6114+ if (!assertion) {
6115+ // If ultimately we can only name the symbol with a reference that dives into a `node_modules` folder, we should error
6116+ // since declaration files with these kinds of references are liable to fail when published :(
6117+ context.encounteredError = true;
6118+ if (context.tracker.reportLikelyUnsafeImportRequiredError) {
6119+ context.tracker.reportLikelyUnsafeImportRequiredError(oldSpecifier);
6120+ }
60656121 }
60666122 }
60676123 const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier));
@@ -6072,12 +6128,12 @@ namespace ts {
60726128 const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right;
60736129 lastId.typeArguments = undefined;
60746130 }
6075- return factory.createImportTypeNode(lit, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf);
6131+ return factory.createImportTypeNode(lit, assertion, nonRootParts as EntityName, typeParameterNodes as readonly TypeNode[], isTypeOf);
60766132 }
60776133 else {
60786134 const splitNode = getTopmostIndexedAccessType(nonRootParts);
60796135 const qualifier = (splitNode.objectType as TypeReferenceNode).typeName;
6080- return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType);
6136+ return factory.createIndexedAccessTypeNode(factory.createImportTypeNode(lit, assertion, qualifier, typeParameterNodes as readonly TypeNode[], isTypeOf), splitNode.indexType);
60816137 }
60826138 }
60836139
@@ -35293,6 +35349,16 @@ namespace ts {
3529335349
3529435350 function checkImportType(node: ImportTypeNode) {
3529535351 checkSourceElement(node.argument);
35352+
35353+ if (node.assertions) {
35354+ const override = getResolutionModeOverrideForClause(node.assertions.assertClause, grammarErrorOnNode);
35355+ if (override) {
35356+ if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node12 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) {
35357+ grammarErrorOnNode(node.assertions.assertClause, Diagnostics.Resolution_modes_are_only_supported_when_moduleResolution_is_node12_or_nodenext);
35358+ }
35359+ }
35360+ }
35361+
3529635362 getTypeFromTypeNode(node);
3529735363 }
3529835364
@@ -40133,6 +40199,15 @@ namespace ts {
4013340199
4013440200 function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) {
4013540201 if (declaration.assertClause) {
40202+ const validForTypeAssertions = isExclusivelyTypeOnlyImportOrExport(declaration);
40203+ const override = getResolutionModeOverrideForClause(declaration.assertClause, validForTypeAssertions ? grammarErrorOnNode : undefined);
40204+ if (validForTypeAssertions && override) {
40205+ if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node12 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) {
40206+ return grammarErrorOnNode(declaration.assertClause, Diagnostics.Resolution_modes_are_only_supported_when_moduleResolution_is_node12_or_nodenext);
40207+ }
40208+ return; // Other grammar checks do not apply to type-only imports with resolution mode assertions
40209+ }
40210+
4013640211 const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier);
4013740212 if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) {
4013840213 return grammarErrorOnNode(declaration.assertClause,
@@ -40144,6 +40219,10 @@ namespace ts {
4014440219 if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) {
4014540220 return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports);
4014640221 }
40222+
40223+ if (override) {
40224+ return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_can_only_be_set_for_type_only_imports);
40225+ }
4014740226 }
4014840227 }
4014940228
0 commit comments