mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Validate JSON imports into ESM in --module nodenext (#60019)
This commit is contained in:
+25
-8
@@ -163,7 +163,6 @@ import {
|
||||
EmitResolver,
|
||||
EmitTextWriter,
|
||||
emptyArray,
|
||||
endsWith,
|
||||
EntityName,
|
||||
EntityNameExpression,
|
||||
EntityNameOrEntityNameExpression,
|
||||
@@ -265,6 +264,7 @@ import {
|
||||
getContainingClassStaticBlock,
|
||||
getContainingFunction,
|
||||
getContainingFunctionOrClassStaticBlock,
|
||||
getDeclarationFileExtension,
|
||||
getDeclarationModifierFlagsFromSymbol,
|
||||
getDeclarationOfKind,
|
||||
getDeclarationsOfKind,
|
||||
@@ -3702,11 +3702,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return usageMode === ModuleKind.ESNext && targetMode === ModuleKind.CommonJS;
|
||||
}
|
||||
|
||||
function isOnlyImportableAsDefault(usage: Expression) {
|
||||
function isOnlyImportableAsDefault(usage: Expression, resolvedModule?: Symbol) {
|
||||
// In Node.js, JSON modules don't get named exports
|
||||
if (ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext) {
|
||||
const usageMode = getEmitSyntaxForModuleSpecifierExpression(usage);
|
||||
return usageMode === ModuleKind.ESNext && endsWith((usage as StringLiteralLike).text, Extension.Json);
|
||||
if (usageMode === ModuleKind.ESNext) {
|
||||
resolvedModule ??= resolveExternalModuleName(usage, usage, /*ignoreErrors*/ true);
|
||||
const targetFile = resolvedModule && getSourceFileOfModule(resolvedModule);
|
||||
return targetFile && (isJsonSourceFile(targetFile) || getDeclarationFileExtension(targetFile.fileName) === ".d.json.ts");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -3776,7 +3780,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (!specifier) {
|
||||
return exportDefaultSymbol;
|
||||
}
|
||||
const hasDefaultOnly = isOnlyImportableAsDefault(specifier);
|
||||
const hasDefaultOnly = isOnlyImportableAsDefault(specifier, moduleSymbol);
|
||||
const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, specifier);
|
||||
if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) {
|
||||
if (hasExportAssignmentSymbol(moduleSymbol) && !allowSyntheticDefaultImports) {
|
||||
@@ -3961,7 +3965,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
let symbolFromModule = getExportOfModule(targetSymbol, nameText, specifier, dontResolveAlias);
|
||||
if (symbolFromModule === undefined && nameText === InternalSymbolName.Default) {
|
||||
const file = moduleSymbol.declarations?.find(isSourceFile);
|
||||
if (isOnlyImportableAsDefault(moduleSpecifier) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) {
|
||||
if (isOnlyImportableAsDefault(moduleSpecifier, moduleSymbol) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) {
|
||||
symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias);
|
||||
}
|
||||
}
|
||||
@@ -3969,7 +3973,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ?
|
||||
combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) :
|
||||
symbolFromModule || symbolFromVariable;
|
||||
if (!symbol) {
|
||||
|
||||
if (isImportOrExportSpecifier(specifier) && isOnlyImportableAsDefault(moduleSpecifier, moduleSymbol) && nameText !== InternalSymbolName.Default) {
|
||||
error(name, Diagnostics.Named_imports_from_a_JSON_file_into_an_ECMAScript_module_are_not_allowed_when_module_is_set_to_0, ModuleKind[moduleKind]);
|
||||
}
|
||||
else if (!symbol) {
|
||||
errorNoModuleMemberSymbol(moduleSymbol, targetSymbol, node, name);
|
||||
}
|
||||
return symbol;
|
||||
@@ -47779,6 +47787,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers);
|
||||
}
|
||||
if (checkExternalImportOrExportDeclaration(node)) {
|
||||
let resolvedModule;
|
||||
const importClause = node.importClause;
|
||||
if (importClause && !checkGrammarImportClause(importClause)) {
|
||||
if (importClause.name) {
|
||||
@@ -47793,12 +47802,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
}
|
||||
else {
|
||||
const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier);
|
||||
if (moduleExisted) {
|
||||
resolvedModule = resolveExternalModuleName(node, node.moduleSpecifier);
|
||||
if (resolvedModule) {
|
||||
forEach(importClause.namedBindings.elements, checkImportBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isOnlyImportableAsDefault(node.moduleSpecifier, resolvedModule) && !hasTypeJsonImportAttribute(node)) {
|
||||
error(node.moduleSpecifier, Diagnostics.Importing_a_JSON_file_into_an_ECMAScript_module_requires_a_type_Colon_json_import_attribute_when_module_is_set_to_0, ModuleKind[moduleKind]);
|
||||
}
|
||||
}
|
||||
else if (noUncheckedSideEffectImports && !importClause) {
|
||||
void resolveExternalModuleName(node, node.moduleSpecifier);
|
||||
@@ -47807,6 +47820,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
checkImportAttributes(node);
|
||||
}
|
||||
|
||||
function hasTypeJsonImportAttribute(node: ImportDeclaration) {
|
||||
return !!node.attributes && node.attributes.elements.some(attr => getTextOfIdentifierOrLiteral(attr.name) === "type" && tryCast(attr.value, isStringLiteralLike)?.text === "json");
|
||||
}
|
||||
|
||||
function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) {
|
||||
if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) {
|
||||
// If we hit an import declaration in an illegal context, just bail out to avoid cascading errors.
|
||||
|
||||
@@ -1826,6 +1826,14 @@
|
||||
"category": "Error",
|
||||
"code": 1542
|
||||
},
|
||||
"Importing a JSON file into an ECMAScript module requires a 'type: \"json\"' import attribute when 'module' is set to '{0}'.": {
|
||||
"category": "Error",
|
||||
"code": 1543
|
||||
},
|
||||
"Named imports from a JSON file into an ECMAScript module are not allowed when 'module' is set to '{0}'.": {
|
||||
"category": "Error",
|
||||
"code": 1544
|
||||
},
|
||||
|
||||
"The types of '{0}' are incompatible between these types.": {
|
||||
"category": "Error",
|
||||
|
||||
@@ -10517,9 +10517,10 @@ export function getDeclarationFileExtension(fileName: string): string | undefine
|
||||
return standardExtension;
|
||||
}
|
||||
if (fileExtensionIs(fileName, Extension.Ts)) {
|
||||
const index = getBaseFileName(fileName).lastIndexOf(".d.");
|
||||
const baseName = getBaseFileName(fileName);
|
||||
const index = baseName.lastIndexOf(".d.");
|
||||
if (index >= 0) {
|
||||
return fileName.substring(index);
|
||||
return baseName.substring(index);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
||||
Reference in New Issue
Block a user