Skip to content

Commit abfd537

Browse files
authored
Support resolveJsonModule in new module modes (#46434)
* Support resolveJsonModule in new module modes * Formatting feedback
1 parent 8a68c86 commit abfd537

14 files changed

+791
-34
lines changed

src/compiler/checker.ts

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,6 +2637,11 @@ namespace ts {
26372637
return usageMode === ModuleKind.ESNext && targetMode === ModuleKind.CommonJS;
26382638
}
26392639

2640+
function isOnlyImportedAsDefault(usage: Expression) {
2641+
const usageMode = getUsageModeForExpression(usage);
2642+
return usageMode === ModuleKind.ESNext && endsWith((usage as StringLiteralLike).text, Extension.Json);
2643+
}
2644+
26402645
function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean, usage: Expression) {
26412646
const usageMode = file && getUsageModeForExpression(usage);
26422647
if (file && usageMode !== undefined) {
@@ -2688,8 +2693,9 @@ namespace ts {
26882693
}
26892694

26902695
const file = moduleSymbol.declarations?.find(isSourceFile);
2696+
const hasDefaultOnly = isOnlyImportedAsDefault(node.parent.moduleSpecifier);
26912697
const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, node.parent.moduleSpecifier);
2692-
if (!exportDefaultSymbol && !hasSyntheticDefault) {
2698+
if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) {
26932699
if (hasExportAssignmentSymbol(moduleSymbol)) {
26942700
const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop";
26952701
const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals);
@@ -2708,7 +2714,7 @@ namespace ts {
27082714
reportNonDefaultExport(moduleSymbol, node);
27092715
}
27102716
}
2711-
else if (hasSyntheticDefault) {
2717+
else if (hasSyntheticDefault || hasDefaultOnly) {
27122718
// per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present
27132719
const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
27142720
markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false);
@@ -2840,7 +2846,7 @@ namespace ts {
28402846
let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias);
28412847
if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) {
28422848
const file = moduleSymbol.declarations?.find(isSourceFile);
2843-
if (canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) {
2849+
if (isOnlyImportedAsDefault(moduleSpecifier) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) {
28442850
symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
28452851
}
28462852
}
@@ -3449,6 +3455,9 @@ namespace ts {
34493455
if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext) {
34503456
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);
34513457
}
3458+
if (mode === ModuleKind.ESNext && compilerOptions.resolveJsonModule && resolvedModule.extension === Extension.Json) {
3459+
error(errorNode, Diagnostics.JSON_imports_are_experimental_in_ES_module_mode_imports);
3460+
}
34523461
}
34533462
// merged symbol is module declaration symbol combined with all augmentations
34543463
return getMergedSymbol(sourceFile.symbol);
@@ -3611,39 +3620,51 @@ namespace ts {
36113620
return symbol;
36123621
}
36133622

3614-
if (getESModuleInterop(compilerOptions)) {
3615-
const referenceParent = referencingLocation.parent;
3616-
if (
3617-
(isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) ||
3618-
isImportCall(referenceParent)
3619-
) {
3620-
const type = getTypeOfSymbol(symbol);
3623+
const referenceParent = referencingLocation.parent;
3624+
if (
3625+
(isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) ||
3626+
isImportCall(referenceParent)
3627+
) {
3628+
const reference = isImportCall(referenceParent) ? referenceParent.arguments[0] : referenceParent.moduleSpecifier;
3629+
const type = getTypeOfSymbol(symbol);
3630+
const defaultOnlyType = getTypeWithSyntheticDefaultOnly(type, symbol, moduleSymbol!, reference);
3631+
if (defaultOnlyType) {
3632+
return cloneTypeAsModuleType(symbol, defaultOnlyType, referenceParent);
3633+
}
3634+
3635+
if (getESModuleInterop(compilerOptions)) {
36213636
let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call);
36223637
if (!sigs || !sigs.length) {
36233638
sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct);
36243639
}
3625-
if (sigs && sigs.length) {
3626-
const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, isImportCall(referenceParent) ? referenceParent.arguments[0] : referenceParent.moduleSpecifier);
3627-
// Create a new symbol which has the module's type less the call and construct signatures
3628-
const result = createSymbol(symbol.flags, symbol.escapedName);
3629-
result.declarations = symbol.declarations ? symbol.declarations.slice() : [];
3630-
result.parent = symbol.parent;
3631-
result.target = symbol;
3632-
result.originatingImport = referenceParent;
3633-
if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration;
3634-
if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true;
3635-
if (symbol.members) result.members = new Map(symbol.members);
3636-
if (symbol.exports) result.exports = new Map(symbol.exports);
3637-
const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above
3638-
result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos);
3639-
return result;
3640+
if ((sigs && sigs.length) || getPropertyOfType(type, InternalSymbolName.Default)) {
3641+
const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference);
3642+
return cloneTypeAsModuleType(symbol, moduleType, referenceParent);
36403643
}
36413644
}
36423645
}
36433646
}
36443647
return symbol;
36453648
}
36463649

3650+
/**
3651+
* Create a new symbol which has the module's type less the call and construct signatures
3652+
*/
3653+
function cloneTypeAsModuleType(symbol: Symbol, moduleType: Type, referenceParent: ImportDeclaration | ImportCall) {
3654+
const result = createSymbol(symbol.flags, symbol.escapedName);
3655+
result.declarations = symbol.declarations ? symbol.declarations.slice() : [];
3656+
result.parent = symbol.parent;
3657+
result.target = symbol;
3658+
result.originatingImport = referenceParent;
3659+
if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration;
3660+
if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true;
3661+
if (symbol.members) result.members = new Map(symbol.members);
3662+
if (symbol.exports) result.exports = new Map(symbol.exports);
3663+
const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above
3664+
result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos);
3665+
return result;
3666+
}
3667+
36473668
function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean {
36483669
return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined;
36493670
}
@@ -31007,27 +31028,47 @@ namespace ts {
3100731028
if (moduleSymbol) {
3100831029
const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false);
3100931030
if (esModuleSymbol) {
31010-
return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier));
31031+
return createPromiseReturnType(node,
31032+
getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) ||
31033+
getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier)
31034+
);
3101131035
}
3101231036
}
3101331037
return createPromiseReturnType(node, anyType);
3101431038
}
3101531039

31040+
function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol, anonymousSymbol?: Symbol | undefined) {
31041+
const memberTable = createSymbolTable();
31042+
const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default);
31043+
newSymbol.parent = originalSymbol;
31044+
newSymbol.nameType = getStringLiteralType("default");
31045+
newSymbol.target = resolveSymbol(symbol);
31046+
memberTable.set(InternalSymbolName.Default, newSymbol);
31047+
return createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray);
31048+
}
31049+
31050+
function getTypeWithSyntheticDefaultOnly(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression) {
31051+
const hasDefaultOnly = isOnlyImportedAsDefault(moduleSpecifier);
31052+
if (hasDefaultOnly && type && !isErrorType(type)) {
31053+
const synthType = type as SyntheticDefaultModuleType;
31054+
if (!synthType.defaultOnlyType) {
31055+
const type = createDefaultPropertyWrapperForModule(symbol, originalSymbol);
31056+
synthType.defaultOnlyType = type;
31057+
}
31058+
return synthType.defaultOnlyType;
31059+
}
31060+
return undefined;
31061+
}
31062+
3101631063
function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression): Type {
3101731064
if (allowSyntheticDefaultImports && type && !isErrorType(type)) {
3101831065
const synthType = type as SyntheticDefaultModuleType;
3101931066
if (!synthType.syntheticType) {
3102031067
const file = originalSymbol.declarations?.find(isSourceFile);
3102131068
const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false, moduleSpecifier);
3102231069
if (hasSyntheticDefault) {
31023-
const memberTable = createSymbolTable();
31024-
const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default);
31025-
newSymbol.parent = originalSymbol;
31026-
newSymbol.nameType = getStringLiteralType("default");
31027-
newSymbol.target = resolveSymbol(symbol);
31028-
memberTable.set(InternalSymbolName.Default, newSymbol);
3102931070
const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type);
31030-
const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray);
31071+
const defaultContainingObject = createDefaultPropertyWrapperForModule(symbol, originalSymbol, anonymousSymbol);
3103131072
anonymousSymbol.type = defaultContainingObject;
3103231073
synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject;
3103331074
}

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6001,6 +6001,10 @@
60016001
"category": "Error",
60026002
"code": 7061
60036003
},
6004+
"JSON imports are experimental in ES module mode imports.": {
6005+
"category": "Error",
6006+
"code": 7062
6007+
},
60046008

60056009
"You cannot rename this element.": {
60066010
"category": "Error",

src/compiler/program.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3333,7 +3333,9 @@ namespace ts {
33333333
}
33343334

33353335
if (options.resolveJsonModule) {
3336-
if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs) {
3336+
if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs &&
3337+
getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node12 &&
3338+
getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) {
33373339
createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_cannot_be_specified_without_node_module_resolution_strategy, "resolveJsonModule");
33383340
}
33393341
// Any emit other than common js, amd, es2015 or esnext is error

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5579,6 +5579,7 @@ namespace ts {
55795579
/* @internal */
55805580
export interface SyntheticDefaultModuleType extends Type {
55815581
syntheticType?: Type;
5582+
defaultOnlyType?: Type;
55825583
}
55835584

55845585
export interface InstantiableType extends Type {

src/compiler/utilities.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6168,6 +6168,8 @@ namespace ts {
61686168
case ModuleKind.ES2020:
61696169
case ModuleKind.ES2022:
61706170
case ModuleKind.ESNext:
6171+
case ModuleKind.Node12:
6172+
case ModuleKind.NodeNext:
61716173
return true;
61726174
default:
61736175
return false;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
tests/cases/conformance/node/index.mts(1,17): error TS7062: JSON imports are experimental in ES module mode imports.
2+
tests/cases/conformance/node/index.mts(3,21): error TS7062: JSON imports are experimental in ES module mode imports.
3+
tests/cases/conformance/node/index.ts(1,17): error TS7062: JSON imports are experimental in ES module mode imports.
4+
tests/cases/conformance/node/index.ts(3,21): error TS7062: JSON imports are experimental in ES module mode imports.
5+
6+
7+
==== tests/cases/conformance/node/index.ts (2 errors) ====
8+
import pkg from "./package.json"
9+
~~~~~~~~~~~~~~~~
10+
!!! error TS7062: JSON imports are experimental in ES module mode imports.
11+
export const name = pkg.name;
12+
import * as ns from "./package.json";
13+
~~~~~~~~~~~~~~~~
14+
!!! error TS7062: JSON imports are experimental in ES module mode imports.
15+
export const thing = ns;
16+
export const name2 = ns.default.name;
17+
==== tests/cases/conformance/node/index.cts (0 errors) ====
18+
import pkg from "./package.json"
19+
export const name = pkg.name;
20+
import * as ns from "./package.json";
21+
export const thing = ns;
22+
export const name2 = ns.default.name;
23+
==== tests/cases/conformance/node/index.mts (2 errors) ====
24+
import pkg from "./package.json"
25+
~~~~~~~~~~~~~~~~
26+
!!! error TS7062: JSON imports are experimental in ES module mode imports.
27+
export const name = pkg.name;
28+
import * as ns from "./package.json";
29+
~~~~~~~~~~~~~~~~
30+
!!! error TS7062: JSON imports are experimental in ES module mode imports.
31+
export const thing = ns;
32+
export const name2 = ns.default.name;
33+
==== tests/cases/conformance/node/package.json (0 errors) ====
34+
{
35+
"name": "pkg",
36+
"version": "0.0.1",
37+
"type": "module",
38+
"default": "misedirection"
39+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//// [tests/cases/conformance/node/nodeModulesResolveJsonModule.ts] ////
2+
3+
//// [index.ts]
4+
import pkg from "./package.json"
5+
export const name = pkg.name;
6+
import * as ns from "./package.json";
7+
export const thing = ns;
8+
export const name2 = ns.default.name;
9+
//// [index.cts]
10+
import pkg from "./package.json"
11+
export const name = pkg.name;
12+
import * as ns from "./package.json";
13+
export const thing = ns;
14+
export const name2 = ns.default.name;
15+
//// [index.mts]
16+
import pkg from "./package.json"
17+
export const name = pkg.name;
18+
import * as ns from "./package.json";
19+
export const thing = ns;
20+
export const name2 = ns.default.name;
21+
//// [package.json]
22+
{
23+
"name": "pkg",
24+
"version": "0.0.1",
25+
"type": "module",
26+
"default": "misedirection"
27+
}
28+
29+
//// [package.json]
30+
{
31+
"name": "pkg",
32+
"version": "0.0.1",
33+
"type": "module",
34+
"default": "misedirection"
35+
}
36+
//// [index.js]
37+
import pkg from "./package.json";
38+
export const name = pkg.name;
39+
import * as ns from "./package.json";
40+
export const thing = ns;
41+
export const name2 = ns.default.name;
42+
//// [index.cjs]
43+
"use strict";
44+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
45+
if (k2 === undefined) k2 = k;
46+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
47+
}) : (function(o, m, k, k2) {
48+
if (k2 === undefined) k2 = k;
49+
o[k2] = m[k];
50+
}));
51+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
52+
Object.defineProperty(o, "default", { enumerable: true, value: v });
53+
}) : function(o, v) {
54+
o["default"] = v;
55+
});
56+
var __importStar = (this && this.__importStar) || function (mod) {
57+
if (mod && mod.__esModule) return mod;
58+
var result = {};
59+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
60+
__setModuleDefault(result, mod);
61+
return result;
62+
};
63+
var __importDefault = (this && this.__importDefault) || function (mod) {
64+
return (mod && mod.__esModule) ? mod : { "default": mod };
65+
};
66+
Object.defineProperty(exports, "__esModule", { value: true });
67+
exports.name2 = exports.thing = exports.name = void 0;
68+
const package_json_1 = __importDefault(require("./package.json"));
69+
exports.name = package_json_1.default.name;
70+
const ns = __importStar(require("./package.json"));
71+
exports.thing = ns;
72+
exports.name2 = ns.default.name;
73+
//// [index.mjs]
74+
import pkg from "./package.json";
75+
export const name = pkg.name;
76+
import * as ns from "./package.json";
77+
export const thing = ns;
78+
export const name2 = ns.default.name;
79+
80+
81+
//// [index.d.ts]
82+
export declare const name: string;
83+
export declare const thing: {
84+
default: {
85+
name: string;
86+
version: string;
87+
type: string;
88+
default: string;
89+
};
90+
};
91+
export declare const name2: string;
92+
//// [index.d.cts]
93+
export declare const name: string;
94+
export declare const thing: {
95+
default: {
96+
name: string;
97+
version: string;
98+
type: string;
99+
default: string;
100+
};
101+
name: string;
102+
version: string;
103+
type: string;
104+
};
105+
export declare const name2: string;
106+
//// [index.d.mts]
107+
export declare const name: string;
108+
export declare const thing: {
109+
default: {
110+
name: string;
111+
version: string;
112+
type: string;
113+
default: string;
114+
};
115+
};
116+
export declare const name2: string;

0 commit comments

Comments
 (0)