-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Support resolveJsonModule in new module modes #46434
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2637,6 +2637,11 @@ namespace ts { | |
return usageMode === ModuleKind.ESNext && targetMode === ModuleKind.CommonJS; | ||
} | ||
|
||
function isOnlyImportedAsDefault(usage: Expression) { | ||
const usageMode = getUsageModeForExpression(usage); | ||
return usageMode === ModuleKind.ESNext && endsWith((usage as StringLiteralLike).text, Extension.Json); | ||
} | ||
|
||
function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean, usage: Expression) { | ||
const usageMode = file && getUsageModeForExpression(usage); | ||
if (file && usageMode !== undefined) { | ||
|
@@ -2688,8 +2693,9 @@ namespace ts { | |
} | ||
|
||
const file = moduleSymbol.declarations?.find(isSourceFile); | ||
const hasDefaultOnly = isOnlyImportedAsDefault(node.parent.moduleSpecifier); | ||
const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, node.parent.moduleSpecifier); | ||
if (!exportDefaultSymbol && !hasSyntheticDefault) { | ||
if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) { | ||
if (hasExportAssignmentSymbol(moduleSymbol)) { | ||
const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; | ||
const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); | ||
|
@@ -2708,7 +2714,7 @@ namespace ts { | |
reportNonDefaultExport(moduleSymbol, node); | ||
} | ||
} | ||
else if (hasSyntheticDefault) { | ||
else if (hasSyntheticDefault || hasDefaultOnly) { | ||
// per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present | ||
const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); | ||
markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false); | ||
|
@@ -2840,7 +2846,7 @@ namespace ts { | |
let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias); | ||
if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) { | ||
const file = moduleSymbol.declarations?.find(isSourceFile); | ||
if (canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) { | ||
if (isOnlyImportedAsDefault(moduleSpecifier) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) { | ||
symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); | ||
} | ||
} | ||
|
@@ -3430,6 +3436,9 @@ namespace ts { | |
if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext) { | ||
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); | ||
} | ||
if (mode === ModuleKind.ESNext && compilerOptions.resolveJsonModule && resolvedModule.extension === Extension.Json) { | ||
error(errorNode, Diagnostics.JSON_imports_are_experimental_in_ES_module_mode_imports); | ||
} | ||
} | ||
// merged symbol is module declaration symbol combined with all augmentations | ||
return getMergedSymbol(sourceFile.symbol); | ||
|
@@ -3592,39 +3601,51 @@ namespace ts { | |
return symbol; | ||
} | ||
|
||
if (getESModuleInterop(compilerOptions)) { | ||
const referenceParent = referencingLocation.parent; | ||
if ( | ||
(isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || | ||
isImportCall(referenceParent) | ||
) { | ||
const type = getTypeOfSymbol(symbol); | ||
const referenceParent = referencingLocation.parent; | ||
if ( | ||
(isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || | ||
isImportCall(referenceParent) | ||
) { | ||
const reference = isImportCall(referenceParent) ? referenceParent.arguments[0] : referenceParent.moduleSpecifier; | ||
const type = getTypeOfSymbol(symbol); | ||
const defaultOnlyType = getTypeWithSyntheticDefaultOnly(type, symbol, moduleSymbol!, reference); | ||
if (defaultOnlyType) { | ||
return cloneTypeAsModuleType(symbol, defaultOnlyType, referenceParent); | ||
} | ||
|
||
if (getESModuleInterop(compilerOptions)) { | ||
let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); | ||
if (!sigs || !sigs.length) { | ||
sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); | ||
} | ||
if (sigs && sigs.length) { | ||
const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, isImportCall(referenceParent) ? referenceParent.arguments[0] : referenceParent.moduleSpecifier); | ||
// Create a new symbol which has the module's type less the call and construct signatures | ||
const result = createSymbol(symbol.flags, symbol.escapedName); | ||
result.declarations = symbol.declarations ? symbol.declarations.slice() : []; | ||
result.parent = symbol.parent; | ||
result.target = symbol; | ||
result.originatingImport = referenceParent; | ||
if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; | ||
if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; | ||
if (symbol.members) result.members = new Map(symbol.members); | ||
if (symbol.exports) result.exports = new Map(symbol.exports); | ||
const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above | ||
result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); | ||
return result; | ||
if ((sigs && sigs.length) || getPropertyOfType(type, InternalSymbolName.Default)) { | ||
const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference); | ||
return cloneTypeAsModuleType(symbol, moduleType, referenceParent); | ||
} | ||
} | ||
} | ||
} | ||
return symbol; | ||
} | ||
|
||
/** | ||
* Create a new symbol which has the module's type less the call and construct signatures | ||
*/ | ||
function cloneTypeAsModuleType(symbol: Symbol, moduleType: Type, referenceParent: ImportDeclaration | ImportCall) { | ||
const result = createSymbol(symbol.flags, symbol.escapedName); | ||
result.declarations = symbol.declarations ? symbol.declarations.slice() : []; | ||
result.parent = symbol.parent; | ||
result.target = symbol; | ||
result.originatingImport = referenceParent; | ||
if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; | ||
if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; | ||
if (symbol.members) result.members = new Map(symbol.members); | ||
if (symbol.exports) result.exports = new Map(symbol.exports); | ||
const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above | ||
result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); | ||
return result; | ||
} | ||
|
||
function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { | ||
return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined; | ||
} | ||
|
@@ -30980,27 +31001,47 @@ namespace ts { | |
if (moduleSymbol) { | ||
const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false); | ||
if (esModuleSymbol) { | ||
return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier)); | ||
return createPromiseReturnType(node, | ||
getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || | ||
weswigham marked this conversation as resolved.
Show resolved
Hide resolved
|
||
getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) | ||
); | ||
} | ||
} | ||
return createPromiseReturnType(node, anyType); | ||
} | ||
|
||
function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol, anonymousSymbol?: Symbol | undefined) { | ||
const memberTable = createSymbolTable(); | ||
const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); | ||
newSymbol.parent = originalSymbol; | ||
newSymbol.nameType = getStringLiteralType("default"); | ||
newSymbol.target = resolveSymbol(symbol); | ||
memberTable.set(InternalSymbolName.Default, newSymbol); | ||
return createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray); | ||
} | ||
|
||
function getTypeWithSyntheticDefaultOnly(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression) { | ||
const hasDefaultOnly = isOnlyImportedAsDefault(moduleSpecifier); | ||
if (hasDefaultOnly && type && !isErrorType(type)) { | ||
const synthType = type as SyntheticDefaultModuleType; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not related to this PR, but: is this default-wrapper behaviour likely to change as importing JSON stops being experimental? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sandersn nope it should not stop as the behavior will stay there are currently no plans to change that inside nodejs so only default imports are supported for json type imports as the json is always a whole document there is no partial json parser. |
||
if (!synthType.defaultOnlyType) { | ||
const type = createDefaultPropertyWrapperForModule(symbol, originalSymbol); | ||
synthType.defaultOnlyType = type; | ||
} | ||
return synthType.defaultOnlyType; | ||
} | ||
return undefined; | ||
} | ||
|
||
function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression): Type { | ||
if (allowSyntheticDefaultImports && type && !isErrorType(type)) { | ||
const synthType = type as SyntheticDefaultModuleType; | ||
if (!synthType.syntheticType) { | ||
const file = originalSymbol.declarations?.find(isSourceFile); | ||
const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false, moduleSpecifier); | ||
if (hasSyntheticDefault) { | ||
const memberTable = createSymbolTable(); | ||
const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); | ||
newSymbol.parent = originalSymbol; | ||
newSymbol.nameType = getStringLiteralType("default"); | ||
newSymbol.target = resolveSymbol(symbol); | ||
memberTable.set(InternalSymbolName.Default, newSymbol); | ||
const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); | ||
const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray); | ||
const defaultContainingObject = createDefaultPropertyWrapperForModule(symbol, originalSymbol, anonymousSymbol); | ||
anonymousSymbol.type = defaultContainingObject; | ||
synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
tests/cases/conformance/node/index.mts(1,17): error TS7062: JSON imports are experimental in ES module mode imports. | ||
tests/cases/conformance/node/index.mts(3,21): error TS7062: JSON imports are experimental in ES module mode imports. | ||
tests/cases/conformance/node/index.ts(1,17): error TS7062: JSON imports are experimental in ES module mode imports. | ||
tests/cases/conformance/node/index.ts(3,21): error TS7062: JSON imports are experimental in ES module mode imports. | ||
|
||
|
||
==== tests/cases/conformance/node/index.ts (2 errors) ==== | ||
import pkg from "./package.json" | ||
~~~~~~~~~~~~~~~~ | ||
!!! error TS7062: JSON imports are experimental in ES module mode imports. | ||
export const name = pkg.name; | ||
import * as ns from "./package.json"; | ||
~~~~~~~~~~~~~~~~ | ||
!!! error TS7062: JSON imports are experimental in ES module mode imports. | ||
export const thing = ns; | ||
export const name2 = ns.default.name; | ||
==== tests/cases/conformance/node/index.cts (0 errors) ==== | ||
import pkg from "./package.json" | ||
export const name = pkg.name; | ||
import * as ns from "./package.json"; | ||
export const thing = ns; | ||
export const name2 = ns.default.name; | ||
==== tests/cases/conformance/node/index.mts (2 errors) ==== | ||
import pkg from "./package.json" | ||
~~~~~~~~~~~~~~~~ | ||
!!! error TS7062: JSON imports are experimental in ES module mode imports. | ||
export const name = pkg.name; | ||
import * as ns from "./package.json"; | ||
~~~~~~~~~~~~~~~~ | ||
!!! error TS7062: JSON imports are experimental in ES module mode imports. | ||
export const thing = ns; | ||
export const name2 = ns.default.name; | ||
==== tests/cases/conformance/node/package.json (0 errors) ==== | ||
{ | ||
"name": "pkg", | ||
"version": "0.0.1", | ||
"type": "module", | ||
"default": "misedirection" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
//// [tests/cases/conformance/node/nodeModulesResolveJsonModule.ts] //// | ||
|
||
//// [index.ts] | ||
import pkg from "./package.json" | ||
export const name = pkg.name; | ||
import * as ns from "./package.json"; | ||
export const thing = ns; | ||
export const name2 = ns.default.name; | ||
//// [index.cts] | ||
import pkg from "./package.json" | ||
export const name = pkg.name; | ||
import * as ns from "./package.json"; | ||
export const thing = ns; | ||
export const name2 = ns.default.name; | ||
//// [index.mts] | ||
import pkg from "./package.json" | ||
export const name = pkg.name; | ||
import * as ns from "./package.json"; | ||
export const thing = ns; | ||
export const name2 = ns.default.name; | ||
//// [package.json] | ||
{ | ||
"name": "pkg", | ||
"version": "0.0.1", | ||
"type": "module", | ||
"default": "misedirection" | ||
} | ||
|
||
//// [package.json] | ||
{ | ||
"name": "pkg", | ||
"version": "0.0.1", | ||
"type": "module", | ||
"default": "misedirection" | ||
} | ||
//// [index.js] | ||
import pkg from "./package.json"; | ||
export const name = pkg.name; | ||
import * as ns from "./package.json"; | ||
export const thing = ns; | ||
export const name2 = ns.default.name; | ||
//// [index.cjs] | ||
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.name2 = exports.thing = exports.name = void 0; | ||
const package_json_1 = __importDefault(require("./package.json")); | ||
exports.name = package_json_1.default.name; | ||
const ns = __importStar(require("./package.json")); | ||
exports.thing = ns; | ||
exports.name2 = ns.default.name; | ||
//// [index.mjs] | ||
import pkg from "./package.json"; | ||
export const name = pkg.name; | ||
import * as ns from "./package.json"; | ||
export const thing = ns; | ||
export const name2 = ns.default.name; | ||
|
||
|
||
//// [index.d.ts] | ||
export declare const name: string; | ||
export declare const thing: { | ||
default: { | ||
name: string; | ||
version: string; | ||
type: string; | ||
default: string; | ||
}; | ||
}; | ||
export declare const name2: string; | ||
//// [index.d.cts] | ||
export declare const name: string; | ||
export declare const thing: { | ||
default: { | ||
name: string; | ||
version: string; | ||
type: string; | ||
default: string; | ||
}; | ||
name: string; | ||
version: string; | ||
type: string; | ||
}; | ||
export declare const name2: string; | ||
//// [index.d.mts] | ||
export declare const name: string; | ||
export declare const thing: { | ||
default: { | ||
name: string; | ||
version: string; | ||
type: string; | ||
default: string; | ||
}; | ||
}; | ||
export declare const name2: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this name include Json somewhere, like
isJsonOnlyImportedAsDefault
? I thought it might be evident from its usage, but it appears to be used in a place with all kinds of imports.I also don't understand what 'Only' means here.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only means in this case that for example
does not work only the default works so
only means that no destructuring style named imports are supported.