Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Silence “Cannot require an ESM file” error in type-only imports (and ImportTypeNodes) #53426

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4865,19 +4865,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode);
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule, currentSourceFile);
const sourceFile = resolvedModule
const targetSourceFile = resolvedModule
&& (!resolutionDiagnostic || resolutionDiagnostic === Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set)
&& host.getSourceFile(resolvedModule.resolvedFileName);
if (sourceFile) {
if (targetSourceFile) {
// If there's a resolutionDiagnostic we need to report it even if a sourceFile is found.
if (resolutionDiagnostic) {
error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName);
}

const importOrExport =
findAncestor(location, isImportDeclaration)?.importClause ||
findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration));
if (resolvedModule.resolvedUsingTsExtension && isDeclarationFileName(moduleReference)) {
const importOrExport =
findAncestor(location, isImportDeclaration)?.importClause ||
findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration));
if (importOrExport && !importOrExport.isTypeOnly || findAncestor(location, isImportCall)) {
error(
errorNode,
Expand All @@ -4890,17 +4890,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
error(errorNode, Diagnostics.An_import_path_can_only_end_with_a_0_extension_when_allowImportingTsExtensions_is_enabled, tsExtension);
}

if (sourceFile.symbol) {
if (targetSourceFile.symbol) {
if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) {
errorOnImplicitAnyModule(/*isError*/ false, errorNode, currentSourceFile, mode, resolvedModule, moduleReference);
}
if (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext) {
if (ModuleResolutionKind.Node16 <= moduleResolutionKind && moduleResolutionKind <= ModuleResolutionKind.NodeNext && !importOrExport?.isTypeOnly && !findAncestor(location, isImportTypeNode)) {
const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration);
const overrideClauseHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined;
const overrideClause = overrideClauseHost && isImportTypeNode(overrideClauseHost) ? overrideClauseHost.assertions?.assertClause : overrideClauseHost?.assertClause;
// An override clause will take effect for type-only imports and import types, and allows importing the types across formats, regardless of
// normal mode restrictions
if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext && !getResolutionModeOverrideForClause(overrideClause)) {
if (isSyncImport && targetSourceFile.impliedNodeFormat === ModuleKind.ESNext) {
if (findAncestor(location, isImportEqualsDeclaration)) {
// ImportEquals in a ESM file resolving to another ESM file
error(errorNode, Diagnostics.Module_0_cannot_be_imported_using_this_construct_The_specifier_only_resolves_to_an_ES_module_which_cannot_be_imported_with_require_Use_an_ECMAScript_import_instead, moduleReference);
Expand Down Expand Up @@ -4949,11 +4945,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
// merged symbol is module declaration symbol combined with all augmentations
return getMergedSymbol(sourceFile.symbol);
return getMergedSymbol(targetSourceFile.symbol);
}
if (moduleNotFoundError) {
// report errors only if it was requested
error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName);
error(errorNode, Diagnostics.File_0_is_not_a_module, targetSourceFile.fileName);
}
return undefined;
}
Expand Down
12 changes: 12 additions & 0 deletions tests/baselines/reference/importTypeResolvingToESMFromCJS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//// [tests/cases/compiler/importTypeResolvingToESMFromCJS.ts] ////

//// [types.d.mts]
export interface A {}

//// [main.cts]
type A = import("./types.mjs").A;


//// [main.cjs]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
=== /types.d.mts ===
export interface A {}
>A : Symbol(A, Decl(types.d.mts, 0, 0))

=== /main.cts ===
type A = import("./types.mjs").A;
>A : Symbol(A, Decl(main.cts, 0, 0))
>A : Symbol(A, Decl(types.d.mts, 0, 0))

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
=== /types.d.mts ===

export interface A {}

=== /main.cts ===
type A = import("./types.mjs").A;
>A : import("/types", { assert: { "resolution-mode": "import" } }).A

Original file line number Diff line number Diff line change
Expand Up @@ -338,13 +338,7 @@ File '/user/username/projects/myproject/packages/pkg2/build/const.d.cts' exists
File '/a/lib/package.json' does not exist.
File '/a/package.json' does not exist.
File '/package.json' does not exist.
packages/pkg1/index.ts:1:29 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("pkg2")' call instead.
To convert this file to an ECMAScript module, change its file extension to '.mts' or create a local package.json file with `{ "type": "module" }`.

1 import type { TheNum } from 'pkg2'
   ~~~~~~

[12:01:16 AM] Found 1 error. Watching for file changes.
[12:01:20 AM] Found 0 errors. Watching for file changes.



Expand All @@ -365,6 +359,13 @@ Shape signatures in builder refreshed for::

exitCode:: ExitStatus.undefined

//// [/user/username/projects/myproject/packages/pkg1/build/index.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.theNum = void 0;
exports.theNum = 42;



Change:: removes those errors when a package file is changed back

Expand All @@ -375,11 +376,11 @@ Input::

Output::
>> Screen clear
[12:01:20 AM] File change detected. Starting incremental compilation...
[12:01:24 AM] File change detected. Starting incremental compilation...

[12:01:21 AM] Project 'packages/pkg1/tsconfig.json' is out of date because output 'packages/pkg1/build/index.js' is older than input 'packages/pkg1/package.json'
[12:01:25 AM] Project 'packages/pkg1/tsconfig.json' is out of date because output 'packages/pkg1/build/index.js' is older than input 'packages/pkg1/package.json'

[12:01:22 AM] Building project '/user/username/projects/myproject/packages/pkg1/tsconfig.json'...
[12:01:26 AM] Building project '/user/username/projects/myproject/packages/pkg1/tsconfig.json'...

Found 'package.json' at '/user/username/projects/myproject/packages/pkg1/package.json'.
======== Resolving module 'pkg2' from '/user/username/projects/myproject/packages/pkg1/index.ts'. ========
Expand Down Expand Up @@ -417,7 +418,7 @@ File '/user/username/projects/myproject/packages/pkg2/build/const.d.cts' exists
File '/a/lib/package.json' does not exist.
File '/a/package.json' does not exist.
File '/package.json' does not exist.
[12:01:27 AM] Found 0 errors. Watching for file changes.
[12:01:31 AM] Found 0 errors. Watching for file changes.



Expand All @@ -438,7 +439,10 @@ Shape signatures in builder refreshed for::

exitCode:: ExitStatus.undefined

//// [/user/username/projects/myproject/packages/pkg1/build/index.js] file written with same contents
//// [/user/username/projects/myproject/packages/pkg1/build/index.js]
export const theNum = 42;



Change:: reports import errors after change to package file

Expand All @@ -449,11 +453,11 @@ Input::

Output::
>> Screen clear
[12:01:31 AM] File change detected. Starting incremental compilation...
[12:01:35 AM] File change detected. Starting incremental compilation...

[12:01:32 AM] Project 'packages/pkg1/tsconfig.json' is out of date because output 'packages/pkg1/build/index.js' is older than input 'packages/pkg1/package.json'
[12:01:36 AM] Project 'packages/pkg1/tsconfig.json' is out of date because output 'packages/pkg1/build/index.js' is older than input 'packages/pkg1/package.json'

[12:01:33 AM] Building project '/user/username/projects/myproject/packages/pkg1/tsconfig.json'...
[12:01:37 AM] Building project '/user/username/projects/myproject/packages/pkg1/tsconfig.json'...

Found 'package.json' at '/user/username/projects/myproject/packages/pkg1/package.json'.
======== Resolving module 'pkg2' from '/user/username/projects/myproject/packages/pkg1/index.ts'. ========
Expand Down Expand Up @@ -494,13 +498,7 @@ File '/user/username/projects/myproject/packages/pkg2/build/const.d.cts' exists
File '/a/lib/package.json' does not exist.
File '/a/package.json' does not exist.
File '/package.json' does not exist.
packages/pkg1/index.ts:1:29 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("pkg2")' call instead.
To convert this file to an ECMAScript module, change its file extension to '.mts' or create a local package.json file with `{ "type": "module" }`.

1 import type { TheNum } from 'pkg2'
   ~~~~~~

[12:01:34 AM] Found 1 error. Watching for file changes.
[12:01:42 AM] Found 0 errors. Watching for file changes.



Expand All @@ -521,6 +519,13 @@ Shape signatures in builder refreshed for::

exitCode:: ExitStatus.undefined

//// [/user/username/projects/myproject/packages/pkg1/build/index.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.theNum = void 0;
exports.theNum = 42;



Change:: removes those errors when a package file is changed to cjs extensions

Expand All @@ -535,11 +540,11 @@ export type { TheNum } from './const.cjs';

Output::
>> Screen clear
[12:01:42 AM] File change detected. Starting incremental compilation...
[12:01:50 AM] File change detected. Starting incremental compilation...

[12:01:43 AM] Project 'packages/pkg2/tsconfig.json' is out of date because output 'packages/pkg2/build/tsconfig.tsbuildinfo' is older than input 'packages/pkg2/index.cts'
[12:01:51 AM] Project 'packages/pkg2/tsconfig.json' is out of date because output 'packages/pkg2/build/tsconfig.tsbuildinfo' is older than input 'packages/pkg2/index.cts'

[12:01:44 AM] Building project '/user/username/projects/myproject/packages/pkg2/tsconfig.json'...
[12:01:52 AM] Building project '/user/username/projects/myproject/packages/pkg2/tsconfig.json'...

======== Resolving module './const.cjs' from '/user/username/projects/myproject/packages/pkg2/index.cts'. ========
Module resolution kind is not specified, using 'Node16'.
Expand All @@ -551,9 +556,9 @@ File '/user/username/projects/myproject/packages/pkg2/const.cts' exists - use it
File '/a/lib/package.json' does not exist.
File '/a/package.json' does not exist.
File '/package.json' does not exist.
[12:01:56 AM] Project 'packages/pkg1/tsconfig.json' is out of date because output 'packages/pkg1/build/index.js' is older than input 'packages/pkg2'
[12:02:04 AM] Project 'packages/pkg1/tsconfig.json' is out of date because output 'packages/pkg1/build/index.js' is older than input 'packages/pkg2'

[12:01:57 AM] Building project '/user/username/projects/myproject/packages/pkg1/tsconfig.json'...
[12:02:05 AM] Building project '/user/username/projects/myproject/packages/pkg1/tsconfig.json'...

Found 'package.json' at '/user/username/projects/myproject/packages/pkg1/package.json'.
======== Resolving module 'pkg2' from '/user/username/projects/myproject/packages/pkg1/index.ts'. ========
Expand Down Expand Up @@ -591,7 +596,7 @@ File '/user/username/projects/myproject/packages/pkg2/build/const.d.cts' exists
File '/a/lib/package.json' does not exist according to earlier cached lookups.
File '/a/package.json' does not exist according to earlier cached lookups.
File '/package.json' does not exist according to earlier cached lookups.
[12:02:02 AM] Found 0 errors. Watching for file changes.
[12:02:10 AM] Found 0 errors. Watching for file changes.



Expand Down Expand Up @@ -749,13 +754,7 @@ exitCode:: ExitStatus.undefined
"size": 1064
}

//// [/user/username/projects/myproject/packages/pkg1/build/index.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.theNum = void 0;
exports.theNum = 42;


//// [/user/username/projects/myproject/packages/pkg1/build/index.js] file written with same contents
//// [/user/username/projects/myproject/packages/pkg2/build/index.cjs]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
Expand Down
7 changes: 7 additions & 0 deletions tests/cases/compiler/importTypeResolvingToESMFromCJS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @module: nodenext

// @Filename: /types.d.mts
export interface A {}

// @Filename: /main.cts
type A = import("./types.mjs").A;