diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b04d58a282949..aa21af1d25ed3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -45846,12 +45846,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const id = node.expression as Identifier; const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node)); if (sym) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(sym, SymbolFlags.Value); markAliasReferenced(sym, id); // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) if (getSymbolFlags(sym) & SymbolFlags.Value) { // However if it is a value, we need to check it's being used correctly checkExpressionCached(id); - if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax && getTypeOnlyAliasDeclaration(sym, SymbolFlags.Value)) { + if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax && typeOnlyDeclaration) { error( id, node.isExportEquals @@ -45870,6 +45871,44 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { idText(id), ); } + + if (!isIllegalExportDefaultInCJS && getIsolatedModules(compilerOptions) && !(sym.flags & SymbolFlags.Value)) { + if ( + sym.flags & SymbolFlags.Alias + && resolveAlias(sym) !== unknownSymbol + && getSymbolFlags(sym, /*excludeTypeOnlyMeanings*/ false, /*excludeLocalMeanings*/ true) & SymbolFlags.Type + && (!typeOnlyDeclaration || getSourceFileOfNode(typeOnlyDeclaration) !== getSourceFileOfNode(node)) + ) { + // import { SomeType } from "./someModule"; + // export default SomeType; OR + // export = SomeType; + error( + id, + node.isExportEquals ? + Diagnostics._0_resolves_to_a_type_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_import_type_where_0_is_imported + : Diagnostics._0_resolves_to_a_type_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_export_type_0_as_default, + idText(id), + isolatedModulesLikeFlagName, + ); + } + else if (typeOnlyDeclaration && getSourceFileOfNode(typeOnlyDeclaration) !== getSourceFileOfNode(node)) { + // import { SomeTypeOnlyValue } from "./someModule"; + // export default SomeTypeOnlyValue; OR + // export = SomeTypeOnlyValue; + addTypeOnlyDeclarationRelatedInfo( + error( + id, + node.isExportEquals ? + Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_import_type_where_0_is_imported + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_export_type_0_as_default, + idText(id), + isolatedModulesLikeFlagName, + ), + typeOnlyDeclaration, + idText(id), + ); + } + } } else { checkExpressionCached(id); // doesn't resolve, check as expression to mark as error @@ -47558,7 +47597,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ); case SyntaxKind.ExportAssignment: return (node as ExportAssignment).expression && (node as ExportAssignment).expression.kind === SyntaxKind.Identifier ? - isAliasResolvedToValue(getSymbolOfDeclaration(node as ExportAssignment)) : + isAliasResolvedToValue(getSymbolOfDeclaration(node as ExportAssignment), /*excludeTypeOnlyValues*/ true) : true; } return false; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 7ba864791bd35..80c55bd7ef277 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -951,6 +951,22 @@ "category": "Error", "code": 1288 }, + "'{0}' resolves to a type-only declaration and must be marked type-only in this file before re-exporting when '{1}' is enabled. Consider using 'import type' where '{0}' is imported.": { + "category": "Error", + "code": 1289 + }, + "'{0}' resolves to a type-only declaration and must be marked type-only in this file before re-exporting when '{1}' is enabled. Consider using 'export type { {0} as default }'.": { + "category": "Error", + "code": 1290 + }, + "'{0}' resolves to a type and must be marked type-only in this file before re-exporting when '{1}' is enabled. Consider using 'import type' where '{0}' is imported.": { + "category": "Error", + "code": 1291 + }, + "'{0}' resolves to a type and must be marked type-only in this file before re-exporting when '{1}' is enabled. Consider using 'export type { {0} as default }'.": { + "category": "Error", + "code": 1292 + }, "'with' statements are not allowed in an async function block.": { "category": "Error", diff --git a/tests/baselines/reference/exportDeclaration.errors.txt b/tests/baselines/reference/exportDeclaration(isolatedmodules=false).errors.txt similarity index 70% rename from tests/baselines/reference/exportDeclaration.errors.txt rename to tests/baselines/reference/exportDeclaration(isolatedmodules=false).errors.txt index 6dbd0e8d9c421..6c661bb272782 100644 --- a/tests/baselines/reference/exportDeclaration.errors.txt +++ b/tests/baselines/reference/exportDeclaration(isolatedmodules=false).errors.txt @@ -12,4 +12,11 @@ ~ !!! error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. !!! related TS1377 /a.ts:2:15: 'A' was exported here. - \ No newline at end of file + +==== /c.ts (0 errors) ==== + import type { A } from './a'; + export = A; + +==== /d.ts (0 errors) ==== + import { A } from './a'; + export = A; \ No newline at end of file diff --git a/tests/baselines/reference/exportDeclaration.js b/tests/baselines/reference/exportDeclaration(isolatedmodules=false).js similarity index 59% rename from tests/baselines/reference/exportDeclaration.js rename to tests/baselines/reference/exportDeclaration(isolatedmodules=false).js index 75ede354cbc2c..d1abcbd401464 100644 --- a/tests/baselines/reference/exportDeclaration.js +++ b/tests/baselines/reference/exportDeclaration(isolatedmodules=false).js @@ -9,6 +9,13 @@ import { A } from './a'; declare const a: A; new A(); +//// [c.ts] +import type { A } from './a'; +export = A; + +//// [d.ts] +import { A } from './a'; +export = A; //// [a.js] "use strict"; @@ -22,3 +29,9 @@ var A = /** @class */ (function () { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); new A(); +//// [c.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//// [d.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/tests/baselines/reference/exportDeclaration.symbols b/tests/baselines/reference/exportDeclaration(isolatedmodules=false).symbols similarity index 58% rename from tests/baselines/reference/exportDeclaration.symbols rename to tests/baselines/reference/exportDeclaration(isolatedmodules=false).symbols index 26ae80b25e36c..f7198d9da2315 100644 --- a/tests/baselines/reference/exportDeclaration.symbols +++ b/tests/baselines/reference/exportDeclaration(isolatedmodules=false).symbols @@ -18,3 +18,17 @@ declare const a: A; new A(); >A : Symbol(A, Decl(b.ts, 0, 8)) +=== /c.ts === +import type { A } from './a'; +>A : Symbol(A, Decl(c.ts, 0, 13)) + +export = A; +>A : Symbol(A, Decl(c.ts, 0, 13)) + +=== /d.ts === +import { A } from './a'; +>A : Symbol(A, Decl(d.ts, 0, 8)) + +export = A; +>A : Symbol(A, Decl(d.ts, 0, 8)) + diff --git a/tests/baselines/reference/exportDeclaration.types b/tests/baselines/reference/exportDeclaration(isolatedmodules=false).types similarity index 59% rename from tests/baselines/reference/exportDeclaration.types rename to tests/baselines/reference/exportDeclaration(isolatedmodules=false).types index b8e98fdd0eb8e..808803d0d22e7 100644 --- a/tests/baselines/reference/exportDeclaration.types +++ b/tests/baselines/reference/exportDeclaration(isolatedmodules=false).types @@ -18,3 +18,17 @@ new A(); >new A() : A >A : typeof A +=== /c.ts === +import type { A } from './a'; +>A : A + +export = A; +>A : A + +=== /d.ts === +import { A } from './a'; +>A : typeof A + +export = A; +>A : A + diff --git a/tests/baselines/reference/exportDeclaration(isolatedmodules=true).errors.txt b/tests/baselines/reference/exportDeclaration(isolatedmodules=true).errors.txt new file mode 100644 index 0000000000000..73ad311dfd554 --- /dev/null +++ b/tests/baselines/reference/exportDeclaration(isolatedmodules=true).errors.txt @@ -0,0 +1,25 @@ +/b.ts(3,5): error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. +/d.ts(2,10): error TS1291: 'A' resolves to a type and must be marked type-only in this file before re-exporting when 'isolatedModules' is enabled. Consider using 'import type' where 'A' is imported. + + +==== /a.ts (0 errors) ==== + class A {} + export type { A }; + +==== /b.ts (1 errors) ==== + import { A } from './a'; + declare const a: A; + new A(); + ~ +!!! error TS1362: 'A' cannot be used as a value because it was exported using 'export type'. +!!! related TS1377 /a.ts:2:15: 'A' was exported here. + +==== /c.ts (0 errors) ==== + import type { A } from './a'; + export = A; + +==== /d.ts (1 errors) ==== + import { A } from './a'; + export = A; + ~ +!!! error TS1291: 'A' resolves to a type and must be marked type-only in this file before re-exporting when 'isolatedModules' is enabled. Consider using 'import type' where 'A' is imported. \ No newline at end of file diff --git a/tests/baselines/reference/exportDeclaration(isolatedmodules=true).js b/tests/baselines/reference/exportDeclaration(isolatedmodules=true).js new file mode 100644 index 0000000000000..d1abcbd401464 --- /dev/null +++ b/tests/baselines/reference/exportDeclaration(isolatedmodules=true).js @@ -0,0 +1,37 @@ +//// [tests/cases/conformance/externalModules/typeOnly/exportDeclaration.ts] //// + +//// [a.ts] +class A {} +export type { A }; + +//// [b.ts] +import { A } from './a'; +declare const a: A; +new A(); + +//// [c.ts] +import type { A } from './a'; +export = A; + +//// [d.ts] +import { A } from './a'; +export = A; + +//// [a.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var A = /** @class */ (function () { + function A() { + } + return A; +}()); +//// [b.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +new A(); +//// [c.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//// [d.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/tests/baselines/reference/exportDeclaration(isolatedmodules=true).symbols b/tests/baselines/reference/exportDeclaration(isolatedmodules=true).symbols new file mode 100644 index 0000000000000..f7198d9da2315 --- /dev/null +++ b/tests/baselines/reference/exportDeclaration(isolatedmodules=true).symbols @@ -0,0 +1,34 @@ +//// [tests/cases/conformance/externalModules/typeOnly/exportDeclaration.ts] //// + +=== /a.ts === +class A {} +>A : Symbol(A, Decl(a.ts, 0, 0)) + +export type { A }; +>A : Symbol(A, Decl(a.ts, 1, 13)) + +=== /b.ts === +import { A } from './a'; +>A : Symbol(A, Decl(b.ts, 0, 8)) + +declare const a: A; +>a : Symbol(a, Decl(b.ts, 1, 13)) +>A : Symbol(A, Decl(b.ts, 0, 8)) + +new A(); +>A : Symbol(A, Decl(b.ts, 0, 8)) + +=== /c.ts === +import type { A } from './a'; +>A : Symbol(A, Decl(c.ts, 0, 13)) + +export = A; +>A : Symbol(A, Decl(c.ts, 0, 13)) + +=== /d.ts === +import { A } from './a'; +>A : Symbol(A, Decl(d.ts, 0, 8)) + +export = A; +>A : Symbol(A, Decl(d.ts, 0, 8)) + diff --git a/tests/baselines/reference/exportDeclaration(isolatedmodules=true).types b/tests/baselines/reference/exportDeclaration(isolatedmodules=true).types new file mode 100644 index 0000000000000..808803d0d22e7 --- /dev/null +++ b/tests/baselines/reference/exportDeclaration(isolatedmodules=true).types @@ -0,0 +1,34 @@ +//// [tests/cases/conformance/externalModules/typeOnly/exportDeclaration.ts] //// + +=== /a.ts === +class A {} +>A : A + +export type { A }; +>A : A + +=== /b.ts === +import { A } from './a'; +>A : typeof A + +declare const a: A; +>a : A + +new A(); +>new A() : A +>A : typeof A + +=== /c.ts === +import type { A } from './a'; +>A : A + +export = A; +>A : A + +=== /d.ts === +import { A } from './a'; +>A : typeof A + +export = A; +>A : A + diff --git a/tests/baselines/reference/exportDefault.js b/tests/baselines/reference/exportDefault.js index b09789e41533a..4bf273d4cbf59 100644 --- a/tests/baselines/reference/exportDefault.js +++ b/tests/baselines/reference/exportDefault.js @@ -41,7 +41,6 @@ exports.A = A; //// [b.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.default = types; //// [c.js] "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { diff --git a/tests/baselines/reference/importEquals1.js b/tests/baselines/reference/importEquals1.js index 1bc59972ef74d..e711e198cfcdd 100644 --- a/tests/baselines/reference/importEquals1.js +++ b/tests/baselines/reference/importEquals1.js @@ -40,7 +40,7 @@ var A = /** @class */ (function () { exports.A = A; //// [b.js] "use strict"; -module.exports = types; +Object.defineProperty(exports, "__esModule", { value: true }); //// [c.js] "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { diff --git a/tests/baselines/reference/isolatedModulesExportDeclarationType.errors.txt b/tests/baselines/reference/isolatedModulesExportDeclarationType.errors.txt new file mode 100644 index 0000000000000..7ecd91c90629e --- /dev/null +++ b/tests/baselines/reference/isolatedModulesExportDeclarationType.errors.txt @@ -0,0 +1,36 @@ +/test1.ts(1,10): error TS2865: Import 'T' conflicts with local value, so must be declared with a type-only import when 'isolatedModules' is enabled. +/test2.ts(1,10): error TS2440: Import declaration conflicts with local declaration of 'T'. +/test2.ts(3,16): error TS1292: 'T' resolves to a type and must be marked type-only in this file before re-exporting when 'isolatedModules' is enabled. Consider using 'export type { T as default }'. +/test3.ts(2,16): error TS1292: 'T' resolves to a type and must be marked type-only in this file before re-exporting when 'isolatedModules' is enabled. Consider using 'export type { T as default }'. + + +==== /type.ts (0 errors) ==== + export type T = number; + +==== /test1.ts (1 errors) ==== + import { T } from "./type"; + ~ +!!! error TS2865: Import 'T' conflicts with local value, so must be declared with a type-only import when 'isolatedModules' is enabled. + const T = 0; // Error as of #56354 + export default T; // Ok + +==== /test2.ts (2 errors) ==== + import { T } from "./type"; + ~ +!!! error TS2440: Import declaration conflicts with local declaration of 'T'. + type T = number; // Merge error + export default T; // Transpiler could assume the alias resolves to a value? + ~ +!!! error TS1292: 'T' resolves to a type and must be marked type-only in this file before re-exporting when 'isolatedModules' is enabled. Consider using 'export type { T as default }'. + +==== /test3.ts (1 errors) ==== + import { T } from "./type"; + export default T; // Error + ~ +!!! error TS1292: 'T' resolves to a type and must be marked type-only in this file before re-exporting when 'isolatedModules' is enabled. Consider using 'export type { T as default }'. + +==== /test4.ts (0 errors) ==== + // @ts-expect-error + import unresolved from "./doesntexist"; + export default unresolved; + \ No newline at end of file diff --git a/tests/baselines/reference/isolatedModulesExportDeclarationType.js b/tests/baselines/reference/isolatedModulesExportDeclarationType.js new file mode 100644 index 0000000000000..7ab2b29c06cd1 --- /dev/null +++ b/tests/baselines/reference/isolatedModulesExportDeclarationType.js @@ -0,0 +1,45 @@ +//// [tests/cases/compiler/isolatedModulesExportDeclarationType.ts] //// + +//// [type.ts] +export type T = number; + +//// [test1.ts] +import { T } from "./type"; +const T = 0; // Error as of #56354 +export default T; // Ok + +//// [test2.ts] +import { T } from "./type"; +type T = number; // Merge error +export default T; // Transpiler could assume the alias resolves to a value? + +//// [test3.ts] +import { T } from "./type"; +export default T; // Error + +//// [test4.ts] +// @ts-expect-error +import unresolved from "./doesntexist"; +export default unresolved; + + +//// [type.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//// [test1.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var T = 0; // Error as of #56354 +exports.default = T; // Ok +//// [test2.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//// [test3.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//// [test4.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +// @ts-expect-error +var doesntexist_1 = require("./doesntexist"); +exports.default = doesntexist_1.default; diff --git a/tests/cases/compiler/isolatedModulesExportDeclarationType.ts b/tests/cases/compiler/isolatedModulesExportDeclarationType.ts new file mode 100644 index 0000000000000..fbff2d275d856 --- /dev/null +++ b/tests/cases/compiler/isolatedModulesExportDeclarationType.ts @@ -0,0 +1,24 @@ +// @isolatedModules: true +// @noTypesAndSymbols: true + +// @Filename: /type.ts +export type T = number; + +// @Filename: /test1.ts +import { T } from "./type"; +const T = 0; // Error as of #56354 +export default T; // Ok + +// @Filename: /test2.ts +import { T } from "./type"; +type T = number; // Merge error +export default T; // Transpiler could assume the alias resolves to a value? + +// @Filename: /test3.ts +import { T } from "./type"; +export default T; // Error + +// @Filename: /test4.ts +// @ts-expect-error +import unresolved from "./doesntexist"; +export default unresolved; diff --git a/tests/cases/conformance/externalModules/typeOnly/exportDeclaration.ts b/tests/cases/conformance/externalModules/typeOnly/exportDeclaration.ts index d492ece20901d..b6f1e1ce82499 100644 --- a/tests/cases/conformance/externalModules/typeOnly/exportDeclaration.ts +++ b/tests/cases/conformance/externalModules/typeOnly/exportDeclaration.ts @@ -1,3 +1,6 @@ +// @module: commonjs +// @isolatedModules: false, true + // @Filename: /a.ts class A {} export type { A }; @@ -6,3 +9,11 @@ export type { A }; import { A } from './a'; declare const a: A; new A(); + +// @Filename: /c.ts +import type { A } from './a'; +export = A; + +// @Filename: /d.ts +import { A } from './a'; +export = A; \ No newline at end of file