Skip to content

Commit 8cd2f92

Browse files
committed
Experiment: use limited impliedNodeFormat for interop checking
1 parent 3c504d8 commit 8cd2f92

33 files changed

+361
-158
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4058,24 +4058,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
40584058
|| isNamespaceExport(node));
40594059
}
40604060

4061-
function getUsageModeForExpression(usage: Expression) {
4062-
return isStringLiteralLike(usage) ? host.getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined;
4061+
function getEmitSyntaxForModuleSpecifierExpression(usage: Expression) {
4062+
return isStringLiteralLike(usage) ? host.getEmitSyntaxForUsageLocation(getSourceFileOfNode(usage), usage) : undefined;
40634063
}
40644064

40654065
function isESMFormatImportImportingCommonjsFormatFile(usageMode: ResolutionMode, targetMode: ResolutionMode) {
40664066
return usageMode === ModuleKind.ESNext && targetMode === ModuleKind.CommonJS;
40674067
}
40684068

40694069
function isOnlyImportedAsDefault(usage: Expression) {
4070-
const usageMode = getUsageModeForExpression(usage);
4070+
const usageMode = getEmitSyntaxForModuleSpecifierExpression(usage);
40714071
return usageMode === ModuleKind.ESNext && endsWith((usage as StringLiteralLike).text, Extension.Json);
40724072
}
40734073

40744074
function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean, usage: Expression) {
4075-
const usageMode = file && getUsageModeForExpression(usage);
4076-
if (file && usageMode !== undefined && ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext) {
4077-
const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, impliedNodeFormatForInteropChecking(file, compilerOptions));
4078-
if (usageMode === ModuleKind.ESNext || result) {
4075+
const usageMode = file && getEmitSyntaxForModuleSpecifierExpression(usage);
4076+
if (file && usageMode !== undefined) {
4077+
const targetMode = impliedNodeFormatForInteropChecking(file, compilerOptions);
4078+
const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, targetMode);
4079+
if (usageMode === ModuleKind.ESNext && targetMode !== undefined || result) {
40794080
return result;
40804081
}
40814082
// fallthrough on cjs usages so we imply defaults for interop'd imports, too
@@ -5301,7 +5302,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
53015302
}
53025303

53035304
const targetFile = moduleSymbol?.declarations?.find(isSourceFile);
5304-
const isEsmCjsRef = targetFile && isESMFormatImportImportingCommonjsFormatFile(getUsageModeForExpression(reference), impliedNodeFormatForInteropChecking(targetFile, compilerOptions));
5305+
const isEsmCjsRef = targetFile && isESMFormatImportImportingCommonjsFormatFile(getEmitSyntaxForModuleSpecifierExpression(reference), impliedNodeFormatForInteropChecking(targetFile, compilerOptions));
53055306
if (getESModuleInterop(compilerOptions) || isEsmCjsRef) {
53065307
let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call);
53075308
if (!sigs || !sigs.length) {
@@ -46071,7 +46072,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4607146072
return; // Other grammar checks do not apply to type-only imports with resolution mode assertions
4607246073
}
4607346074

46074-
const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier);
46075+
const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getEmitSyntaxForModuleSpecifierExpression(declaration.moduleSpecifier);
4607546076
if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.Preserve) {
4607646077
const message = isImportAttributes
4607746078
? moduleKind === ModuleKind.NodeNext

src/compiler/program.ts

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ import {
136136
getPositionOfLineAndCharacter,
137137
getPropertyArrayElementValue,
138138
getResolveJsonModule,
139+
getResolvePackageJsonExports,
140+
getResolvePackageJsonImports,
139141
getRootLength,
140142
getSetExternalModuleIndicator,
141143
getSourceFileOfNode,
@@ -913,23 +915,46 @@ function getModeForUsageLocationWorker(file: { impliedNodeFormat?: ResolutionMod
913915
return override;
914916
}
915917
}
918+
919+
const emitSyntax = getEmitSyntaxForUsageLocationWorker(file, usage, compilerOptions);
920+
const moduleResolution = compilerOptions && getEmitModuleResolutionKind(compilerOptions);
921+
if (
922+
compilerOptions && (
923+
getResolvePackageJsonExports(compilerOptions) ||
924+
getResolvePackageJsonImports(compilerOptions) ||
925+
ModuleResolutionKind.Node16 <= moduleResolution! && moduleResolution! <= ModuleResolutionKind.NodeNext
926+
)
927+
) {
928+
return emitSyntax;
929+
}
930+
}
931+
932+
function getEmitSyntaxForUsageLocationWorker(file: { impliedNodeFormat?: ResolutionMode; }, usage: StringLiteralLike, compilerOptions?: CompilerOptions): ResolutionMode {
916933
if (compilerOptions && getEmitModuleKind(compilerOptions) === ModuleKind.Preserve) {
917934
return (usage.parent.parent && isImportEqualsDeclaration(usage.parent.parent) || isRequireCall(usage.parent, /*requireStringLiteralLikeArgument*/ false))
918935
? ModuleKind.CommonJS
919936
: ModuleKind.ESNext;
920937
}
921-
if (file.impliedNodeFormat === undefined || !(compilerOptions && impliedNodeFormatAffectsModuleResolution(compilerOptions))) {
922-
// TODO: we don't know whether this is being called for module resolution, emit, or interop checking
938+
if (!compilerOptions) {
939+
// This should always be provided, but we try to fail somewhat
940+
// gracefully to allow projects like ts-node time to update.
923941
return undefined;
924942
}
925-
if (file.impliedNodeFormat !== ModuleKind.ESNext) {
926-
// in cjs files, import call expressions are esm format, otherwise everything is cjs
927-
return isImportCall(walkUpParenthesizedExpressions(usage.parent)) ? ModuleKind.ESNext : ModuleKind.CommonJS;
943+
if (impliedNodeFormatAffectsModuleResolution(compilerOptions)) {
944+
if (file.impliedNodeFormat !== ModuleKind.ESNext) {
945+
// in cjs files, import call expressions are esm format, otherwise everything is cjs
946+
return isImportCall(walkUpParenthesizedExpressions(usage.parent)) ? ModuleKind.ESNext : ModuleKind.CommonJS;
947+
}
948+
const exprParentParent = walkUpParenthesizedExpressions(usage.parent)?.parent;
949+
return exprParentParent && isImportEqualsDeclaration(exprParentParent) ? ModuleKind.CommonJS : ModuleKind.ESNext;
928950
}
929-
// in esm files, import=require statements are cjs format, otherwise everything is esm
930-
// imports are only parent'd up to their containing declaration/expression, so access farther parents with care
931-
const exprParentParent = walkUpParenthesizedExpressions(usage.parent)?.parent;
932-
return exprParentParent && isImportEqualsDeclaration(exprParentParent) ? ModuleKind.CommonJS : ModuleKind.ESNext;
951+
if (getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS) {
952+
return ModuleKind.CommonJS;
953+
}
954+
if (emitModuleKindIsNonNodeESM(getEmitModuleKind(compilerOptions))) {
955+
return ModuleKind.ESNext;
956+
}
957+
return undefined;
933958
}
934959

935960
/** @internal */
@@ -1897,6 +1922,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
18971922
isSourceFileFromExternalLibrary,
18981923
isSourceFileDefaultLibrary,
18991924
getModeForUsageLocation,
1925+
getEmitSyntaxForUsageLocation,
19001926
getModeForResolutionAtIndex,
19011927
getSourceFileFromReference,
19021928
getLibFileFromReference,
@@ -4931,6 +4957,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
49314957
return getModeForUsageLocationWorker(file, usage, optionsForFile);
49324958
}
49334959

4960+
function getEmitSyntaxForUsageLocation(file: SourceFile, usage: StringLiteralLike): ResolutionMode {
4961+
const optionsForFile = getRedirectReferenceForResolution(file)?.commandLine.options || options;
4962+
return getEmitSyntaxForUsageLocationWorker(file, usage, optionsForFile);
4963+
}
4964+
49344965
function getModeForResolutionAtIndex(file: SourceFile, index: number): ResolutionMode {
49354966
return getModeForUsageLocation(file, getModuleNameStringLiteralAt(file, index));
49364967
}

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4847,6 +4847,7 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost {
48474847
getResolvedTypeReferenceDirectives(): ModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>;
48484848
getProjectReferenceRedirect(fileName: string): string | undefined;
48494849
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
4850+
getEmitSyntaxForUsageLocation(file: SourceFile, usage: StringLiteralLike): ResolutionMode;
48504851
getModeForUsageLocation(file: SourceFile, usage: StringLiteralLike): ResolutionMode;
48514852

48524853
getResolvedModule(f: SourceFile, moduleName: string, mode: ResolutionMode): ResolvedModuleWithFailedLookupLocations | undefined;

src/compiler/utilities.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8610,12 +8610,6 @@ export function impliedNodeFormatAffectsModuleResolution(options: CompilerOption
86108610
return ModuleResolutionKind.Node16 <= moduleResolution && moduleResolution <= ModuleResolutionKind.NodeNext;
86118611
}
86128612

8613-
/** @internal */
8614-
export function impliedNodeFormatAffectsInteropChecking(options: CompilerOptions) {
8615-
const moduleKind = getEmitModuleKind(options);
8616-
return ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext;
8617-
}
8618-
86198613
/** @internal */
86208614
export function impliedNodeFormatForModuleResolution(sourceFile: SourceFile, options: CompilerOptions): ResolutionMode {
86218615
return impliedNodeFormatAffectsModuleResolution(options) ? sourceFile.impliedNodeFormat : undefined;
@@ -8628,7 +8622,25 @@ export function impliedNodeFormatForEmit(sourceFile: SourceFile, options: Compil
86288622

86298623
/** @internal */
86308624
export function impliedNodeFormatForInteropChecking(sourceFile: SourceFile, options: CompilerOptions): ResolutionMode {
8631-
return impliedNodeFormatAffectsInteropChecking(options) ? sourceFile.impliedNodeFormat : undefined;
8625+
const moduleKind = getEmitModuleKind(options);
8626+
if (ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext) {
8627+
return sourceFile.impliedNodeFormat;
8628+
}
8629+
if (
8630+
sourceFile.impliedNodeFormat === ModuleKind.CommonJS
8631+
&& (sourceFile.packageJsonScope?.contents.packageJsonContent.type === "commonjs"
8632+
|| fileExtensionIsOneOf(sourceFile.fileName, [Extension.Cjs, Extension.Cts]))
8633+
) {
8634+
return ModuleKind.CommonJS;
8635+
}
8636+
if (
8637+
sourceFile.impliedNodeFormat === ModuleKind.ESNext
8638+
&& (sourceFile.packageJsonScope?.contents.packageJsonContent.type === "module"
8639+
|| fileExtensionIsOneOf(sourceFile.fileName, [Extension.Mjs, Extension.Mts]))
8640+
) {
8641+
return ModuleKind.ESNext;
8642+
}
8643+
return undefined;
86328644
}
86338645

86348646
type CompilerOptionKeys = keyof { [K in keyof CompilerOptions as string extends K ? never : K]: any; };

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9875,7 +9875,7 @@ declare namespace ts {
98759875
},
98769876
usage: StringLiteralLike,
98779877
compilerOptions: CompilerOptions,
9878-
): ModuleKind.CommonJS | ModuleKind.ESNext | undefined;
9878+
): ResolutionMode;
98799879
function getConfigFileParsingDiagnostics(configFileParseResult: ParsedCommandLine): readonly Diagnostic[];
98809880
/**
98819881
* A function for determining if a given file is esm or cjs format, assuming modern node module resolution rules, as configured by the

tests/baselines/reference/bundlerDirectoryModule(moduleresolution=bundler).trace.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,8 @@
937937
"Directory '/.src/node_modules' does not exist, skipping all lookups in it.",
938938
"Directory '/node_modules' does not exist, skipping all lookups in it.",
939939
"======== Module name '@typescript/lib-es2023/collection' was not resolved. ========",
940-
<<<<<<< HEAD
940+
"File '/.ts/package.json' does not exist according to earlier cached lookups.",
941+
"File '/package.json' does not exist according to earlier cached lookups.",
941942
"======== Resolving module '@typescript/lib-es2023/intl' from '/.src/__lib_node_modules_lookup_lib.es2023.intl.d.ts__.ts'. ========",
942943
"Explicitly specified module resolution kind: 'Node10'.",
943944
"Loading module '@typescript/lib-es2023/intl' from 'node_modules' folder, target file types: TypeScript, Declaration.",
@@ -951,10 +952,8 @@
951952
"Directory '/.src/node_modules' does not exist, skipping all lookups in it.",
952953
"Directory '/node_modules' does not exist, skipping all lookups in it.",
953954
"======== Module name '@typescript/lib-es2023/intl' was not resolved. ========",
954-
=======
955955
"File '/.ts/package.json' does not exist according to earlier cached lookups.",
956956
"File '/package.json' does not exist according to earlier cached lookups.",
957-
>>>>>>> a3b9541d66 (Update trace baselines)
958957
"======== Resolving module '@typescript/lib-esnext/intl' from '/.src/__lib_node_modules_lookup_lib.esnext.intl.d.ts__.ts'. ========",
959958
"Explicitly specified module resolution kind: 'Node10'.",
960959
"Loading module '@typescript/lib-esnext/intl' from 'node_modules' folder, target file types: TypeScript, Declaration.",

tests/baselines/reference/conditionalExportsResolutionFallback(moduleresolution=bundler).errors.txt

Lines changed: 0 additions & 29 deletions
This file was deleted.

tests/baselines/reference/customConditions(resolvepackagejsonexports=false).errors.txt

Lines changed: 0 additions & 31 deletions
This file was deleted.

tests/baselines/reference/customConditions(resolvepackagejsonexports=false).js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,3 @@ import _ from "lodash";
2929

3030

3131
//// [index.js]
32-
"use strict";
33-
Object.defineProperty(exports, "__esModule", { value: true });

tests/baselines/reference/customConditions(resolvepackagejsonexports=true).errors.txt

Lines changed: 0 additions & 31 deletions
This file was deleted.

tests/baselines/reference/customConditions(resolvepackagejsonexports=true).js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,3 @@ import _ from "lodash";
2929

3030

3131
//// [index.js]
32-
"use strict";
33-
Object.defineProperty(exports, "__esModule", { value: true });
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/index.ts(1,8): error TS1192: Module '"/node_modules/mdast-util-to-string/index"' has no default export.
2+
/index.ts(7,8): error TS2339: Property 'default' does not exist on type 'typeof import("/node_modules/mdast-util-to-string/index")'.
3+
4+
5+
==== /node_modules/mdast-util-to-string/package.json (0 errors) ====
6+
{ "type": "module" }
7+
8+
==== /node_modules/mdast-util-to-string/index.d.ts (0 errors) ====
9+
export function toString(): string;
10+
11+
==== /index.ts (2 errors) ====
12+
import mdast, { toString } from 'mdast-util-to-string';
13+
~~~~~
14+
!!! error TS1192: Module '"/node_modules/mdast-util-to-string/index"' has no default export.
15+
mdast;
16+
mdast.toString();
17+
18+
const mdast2 = await import('mdast-util-to-string');
19+
mdast2.toString();
20+
mdast2.default;
21+
~~~~~~~
22+
!!! error TS2339: Property 'default' does not exist on type 'typeof import("/node_modules/mdast-util-to-string/index")'.
23+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [tests/cases/compiler/esmNoSynthesizedDefault.ts] ////
2+
3+
//// [package.json]
4+
{ "type": "module" }
5+
6+
//// [index.d.ts]
7+
export function toString(): string;
8+
9+
//// [index.ts]
10+
import mdast, { toString } from 'mdast-util-to-string';
11+
mdast;
12+
mdast.toString();
13+
14+
const mdast2 = await import('mdast-util-to-string');
15+
mdast2.toString();
16+
mdast2.default;
17+
18+
19+
//// [index.js]
20+
import mdast from 'mdast-util-to-string';
21+
mdast;
22+
mdast.toString();
23+
const mdast2 = await import('mdast-util-to-string');
24+
mdast2.toString();
25+
mdast2.default;

0 commit comments

Comments
 (0)