Skip to content

Allow extensionless mains for cjs mode packages even from an esm import #47893

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

Merged
Merged
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
12 changes: 11 additions & 1 deletion src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1819,7 +1819,17 @@ namespace ts {
// Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types"
const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions;
// Don't do package.json lookup recursively, because Node.js' package lookup doesn't.
return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false);

// Disable `EsmMode` for the resolution of the package path for cjs-mode packages (so the `main` field can omit extensions)
// (technically it only emits a deprecation warning in esm packages right now, but that's probably
// enough to mean we don't need to support it)
const features = state.features;
if (jsonContent?.type !== "module") {
state.features &= ~NodeResolutionFeatures.EsmMode;
}
const result = nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false);
state.features = features;
return result;
};

const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//// [tests/cases/compiler/nodeNextEsmImportsOfPackagesWithExtensionlessMains.ts] ////

//// [package.json]
{
"name": "@types/ip",
"version": "1.1.0",
"main": "",
"types": "index"
}
//// [index.d.ts]
export function address(): string;
//// [package.json]
{
"name": "nullthrows",
"version": "1.1.1",
"main": "nullthrows.js",
"types": "nullthrows.d.ts"
}
//// [nullthrows.d.ts]
declare function nullthrows(x: any): any;
declare namespace nullthrows {
export {nullthrows as default};
}
export = nullthrows;
//// [package.json]
{
"type": "module"
}
//// [index.ts]
import * as ip from 'ip';
import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself

export function getAddress(): string {
return nullthrows(ip.address());
}

//// [index.js]
import * as ip from 'ip';
import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself
export function getAddress() {
return nullthrows(ip.address());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
=== tests/cases/compiler/index.ts ===
import * as ip from 'ip';
>ip : Symbol(ip, Decl(index.ts, 0, 6))

import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself
>nullthrows : Symbol(nullthrows, Decl(index.ts, 1, 6))

export function getAddress(): string {
>getAddress : Symbol(getAddress, Decl(index.ts, 1, 36))

return nullthrows(ip.address());
>nullthrows : Symbol(nullthrows, Decl(index.ts, 1, 6))
>ip.address : Symbol(ip.address, Decl(index.d.ts, 0, 0))
>ip : Symbol(ip, Decl(index.ts, 0, 6))
>address : Symbol(ip.address, Decl(index.d.ts, 0, 0))
}
=== tests/cases/compiler/node_modules/@types/ip/index.d.ts ===
export function address(): string;
>address : Symbol(address, Decl(index.d.ts, 0, 0))

=== tests/cases/compiler/node_modules/nullthrows/nullthrows.d.ts ===
declare function nullthrows(x: any): any;
>nullthrows : Symbol(nullthrows, Decl(nullthrows.d.ts, 0, 0), Decl(nullthrows.d.ts, 0, 41))
>x : Symbol(x, Decl(nullthrows.d.ts, 0, 28))

declare namespace nullthrows {
>nullthrows : Symbol(nullthrows, Decl(nullthrows.d.ts, 0, 0), Decl(nullthrows.d.ts, 0, 41))

export {nullthrows as default};
>nullthrows : Symbol(nullthrows, Decl(nullthrows.d.ts, 0, 0), Decl(nullthrows.d.ts, 0, 41))
>default : Symbol(default, Decl(nullthrows.d.ts, 2, 12))
}
export = nullthrows;
>nullthrows : Symbol(nullthrows, Decl(nullthrows.d.ts, 0, 0), Decl(nullthrows.d.ts, 0, 41))

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
=== tests/cases/compiler/index.ts ===
import * as ip from 'ip';
>ip : typeof ip

import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself
>nullthrows : typeof nullthrows

export function getAddress(): string {
>getAddress : () => string

return nullthrows(ip.address());
>nullthrows(ip.address()) : any
>nullthrows : typeof nullthrows
>ip.address() : string
>ip.address : () => string
>ip : typeof ip
>address : () => string
}
=== tests/cases/compiler/node_modules/@types/ip/index.d.ts ===
export function address(): string;
>address : () => string

=== tests/cases/compiler/node_modules/nullthrows/nullthrows.d.ts ===
declare function nullthrows(x: any): any;
>nullthrows : typeof nullthrows
>x : any

declare namespace nullthrows {
>nullthrows : typeof nullthrows

export {nullthrows as default};
>nullthrows : typeof nullthrows
>default : typeof nullthrows
}
export = nullthrows;
>nullthrows : typeof nullthrows

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// @noImplicitReferences: true
// @module: nodenext
// @outDir: esm
// @filename: node_modules/@types/ip/package.json
{
"name": "@types/ip",
"version": "1.1.0",
"main": "",
"types": "index"
}
// @filename: node_modules/@types/ip/index.d.ts
export function address(): string;
// @filename: node_modules/nullthrows/package.json
{
"name": "nullthrows",
"version": "1.1.1",
"main": "nullthrows.js",
"types": "nullthrows.d.ts"
}
// @filename: node_modules/nullthrows/nullthrows.d.ts
declare function nullthrows(x: any): any;
declare namespace nullthrows {
export {nullthrows as default};
}
export = nullthrows;
// @filename: package.json
{
"type": "module"
}
// @filename: index.ts
import * as ip from 'ip';
import nullthrows from 'nullthrows'; // shouldn't be callable, `nullthrows` is a cjs package, so the `default` is the module itself

export function getAddress(): string {
return nullthrows(ip.address());
}