Skip to content

Commit f576398

Browse files
authored
Support export type * (#52217)
1 parent 2acbcee commit f576398

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1519
-72
lines changed

src/compiler/checker.ts

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3443,7 +3443,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
34433443
if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias && !(result.flags & SymbolFlags.Value) && !isValidTypeOnlyAliasUseSite(errorLocation)) {
34443444
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(result, SymbolFlags.Value);
34453445
if (typeOnlyDeclaration) {
3446-
const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier
3446+
const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport
34473447
? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type
34483448
: Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type;
34493449
const unescapedName = unescapeLeadingUnderscores(name);
@@ -3464,7 +3464,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
34643464
diagnostic,
34653465
createDiagnosticForNode(
34663466
typeOnlyDeclaration,
3467-
typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here,
3467+
typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport
3468+
? Diagnostics._0_was_exported_here
3469+
: Diagnostics._0_was_imported_here,
34683470
unescapedName));
34693471
}
34703472

@@ -3861,15 +3863,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
38613863
function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) {
38623864
if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) {
38633865
const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfDeclaration(node))!;
3864-
const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier;
3866+
const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration;
38653867
const message = isExport
38663868
? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type
38673869
: Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type;
38683870
const relatedMessage = isExport
38693871
? Diagnostics._0_was_exported_here
38703872
: Diagnostics._0_was_imported_here;
38713873

3872-
const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText);
3874+
// TODO: how to get name for export *?
3875+
const name = typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration ? "*" : unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText);
38733876
addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name));
38743877
}
38753878
}
@@ -4083,7 +4086,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
40834086
if (symbol.flags & SymbolFlags.Module) {
40844087
const exportSymbol = getExportsOfSymbol(symbol).get(name.escapedText);
40854088
const resolved = resolveSymbol(exportSymbol, dontResolveAlias);
4086-
markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false);
4089+
const exportStarDeclaration = getSymbolLinks(symbol).typeOnlyExportStarMap?.get(name.escapedText);
4090+
markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false, exportStarDeclaration, name.escapedText);
40874091
return resolved;
40884092
}
40894093
}
@@ -4444,6 +4448,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
44444448
immediateTarget: Symbol | undefined,
44454449
finalTarget: Symbol | undefined,
44464450
overwriteEmpty: boolean,
4451+
exportStarDeclaration?: ExportDeclaration & { readonly isTypeOnly: true },
4452+
exportStarName?: __String,
44474453
): boolean {
44484454
if (!aliasDeclaration || isPropertyAccessExpression(aliasDeclaration)) return false;
44494455

@@ -4455,6 +4461,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
44554461
links.typeOnlyDeclaration = aliasDeclaration;
44564462
return true;
44574463
}
4464+
if (exportStarDeclaration) {
4465+
const links = getSymbolLinks(sourceSymbol);
4466+
links.typeOnlyDeclaration = exportStarDeclaration;
4467+
if (sourceSymbol.escapedName !== exportStarName) {
4468+
links.typeOnlyExportStarName = exportStarName;
4469+
}
4470+
return true;
4471+
}
44584472

44594473
const links = getSymbolLinks(sourceSymbol);
44604474
return markSymbolOfAliasDeclarationIfTypeOnlyWorker(links, immediateTarget, overwriteEmpty)
@@ -4480,7 +4494,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
44804494
return links.typeOnlyDeclaration || undefined;
44814495
}
44824496
if (links.typeOnlyDeclaration) {
4483-
return getAllSymbolFlags(resolveAlias(links.typeOnlyDeclaration.symbol)) & include ? links.typeOnlyDeclaration : undefined;
4497+
const resolved = links.typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration
4498+
? resolveSymbol(getExportsOfModule(links.typeOnlyDeclaration.symbol.parent!).get(links.typeOnlyExportStarName || symbol.escapedName))!
4499+
: resolveAlias(links.typeOnlyDeclaration.symbol);
4500+
return getAllSymbolFlags(resolved) & include ? links.typeOnlyDeclaration : undefined;
44844501
}
44854502
return undefined;
44864503
}
@@ -5203,7 +5220,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
52035220

52045221
function getExportsOfModule(moduleSymbol: Symbol): SymbolTable {
52055222
const links = getSymbolLinks(moduleSymbol);
5206-
return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol));
5223+
if (!links.resolvedExports) {
5224+
const { exports, typeOnlyExportStarMap } = getExportsOfModuleWorker(moduleSymbol);
5225+
links.resolvedExports = exports;
5226+
links.typeOnlyExportStarMap = typeOnlyExportStarMap;
5227+
}
5228+
return links.resolvedExports;
52075229
}
52085230

52095231
interface ExportCollisionTracker {
@@ -5243,21 +5265,38 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
52435265
});
52445266
}
52455267

5246-
function getExportsOfModuleWorker(moduleSymbol: Symbol): SymbolTable {
5268+
function getExportsOfModuleWorker(moduleSymbol: Symbol) {
52475269
const visitedSymbols: Symbol[] = [];
5270+
let typeOnlyExportStarMap: UnderscoreEscapedMap<ExportDeclaration & { readonly isTypeOnly: true }> | undefined;
5271+
const nonTypeOnlyNames = new Set<__String>();
52485272

52495273
// A module defined by an 'export=' consists of one export that needs to be resolved
52505274
moduleSymbol = resolveExternalModuleSymbol(moduleSymbol);
5275+
const exports = visit(moduleSymbol) || emptySymbols;
52515276

5252-
return visit(moduleSymbol) || emptySymbols;
5277+
if (typeOnlyExportStarMap) {
5278+
nonTypeOnlyNames.forEach(name => typeOnlyExportStarMap!.delete(name));
5279+
}
5280+
5281+
return {
5282+
exports,
5283+
typeOnlyExportStarMap,
5284+
};
52535285

52545286
// The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example,
52555287
// module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error.
5256-
function visit(symbol: Symbol | undefined): SymbolTable | undefined {
5288+
function visit(symbol: Symbol | undefined, exportStar?: ExportDeclaration, isTypeOnly?: boolean): SymbolTable | undefined {
5289+
if (!isTypeOnly && symbol?.exports) {
5290+
// Add non-type-only names before checking if we've visited this module,
5291+
// because we might have visited it via an 'export type *', and visiting
5292+
// again with 'export *' will override the type-onlyness of its exports.
5293+
symbol.exports.forEach((_, name) => nonTypeOnlyNames.add(name));
5294+
}
52575295
if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) {
52585296
return;
52595297
}
52605298
const symbols = new Map(symbol.exports);
5299+
52615300
// All export * declarations are collected in an __export symbol by the binder
52625301
const exportStars = symbol.exports.get(InternalSymbolName.ExportStar);
52635302
if (exportStars) {
@@ -5266,7 +5305,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
52665305
if (exportStars.declarations) {
52675306
for (const node of exportStars.declarations) {
52685307
const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!);
5269-
const exportedSymbols = visit(resolvedModule);
5308+
const exportedSymbols = visit(resolvedModule, node as ExportDeclaration, isTypeOnly || (node as ExportDeclaration).isTypeOnly);
52705309
extendExportSymbols(
52715310
nestedSymbols,
52725311
exportedSymbols,
@@ -5291,6 +5330,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
52915330
});
52925331
extendExportSymbols(symbols, nestedSymbols);
52935332
}
5333+
if (exportStar?.isTypeOnly) {
5334+
typeOnlyExportStarMap ??= new Map();
5335+
symbols.forEach((_, escapedName) => typeOnlyExportStarMap!.set(
5336+
escapedName,
5337+
exportStar as ExportDeclaration & { readonly isTypeOnly: true }));
5338+
}
52945339
return symbols;
52955340
}
52965341
}
@@ -8597,7 +8642,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
85978642
for (const node of symbol.declarations) {
85988643
const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!);
85998644
if (!resolvedModule) continue;
8600-
addResult(factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None);
8645+
addResult(factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ (node as ExportDeclaration).isTypeOnly, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None);
86018646
}
86028647
}
86038648
}
@@ -12219,7 +12264,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1221912264
if (!links[resolutionKind]) {
1222012265
const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports;
1222112266
const earlySymbols = !isStatic ? symbol.members :
12222-
symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) :
12267+
symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol).exports :
1222312268
symbol.exports;
1222412269

1222512270
// In the event we recursively resolve the members/exports of the symbol, we
@@ -43738,13 +43783,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4373843783
}
4373943784

4374043785
function checkGrammarExportDeclaration(node: ExportDeclaration): boolean {
43741-
if (node.isTypeOnly) {
43742-
if (node.exportClause?.kind === SyntaxKind.NamedExports) {
43743-
return checkGrammarNamedImportsOrExports(node.exportClause);
43744-
}
43745-
else {
43746-
return grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type);
43747-
}
43786+
if (node.isTypeOnly && node.exportClause?.kind === SyntaxKind.NamedExports) {
43787+
return checkGrammarNamedImportsOrExports(node.exportClause);
4374843788
}
4374943789
return false;
4375043790
}

src/compiler/diagnosticMessages.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,10 +1232,6 @@
12321232
"category": "Error",
12331233
"code": 1382
12341234
},
1235-
"Only named exports may use 'export type'.": {
1236-
"category": "Error",
1237-
"code": 1383
1238-
},
12391235
"Function type notation must be parenthesized when used in a union type.": {
12401236
"category": "Error",
12411237
"code": 1385

src/compiler/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3788,16 +3788,25 @@ export type TypeOnlyCompatibleAliasDeclaration =
37883788
| ImportEqualsDeclaration
37893789
| NamespaceImport
37903790
| ImportOrExportSpecifier
3791+
| ExportDeclaration
3792+
| NamespaceExport
37913793
;
37923794

3793-
export type TypeOnlyAliasDeclaration =
3795+
export type TypeOnlyImportDeclaration =
37943796
| ImportClause & { readonly isTypeOnly: true, readonly name: Identifier }
37953797
| ImportEqualsDeclaration & { readonly isTypeOnly: true }
37963798
| NamespaceImport & { readonly parent: ImportClause & { readonly isTypeOnly: true } }
37973799
| ImportSpecifier & ({ readonly isTypeOnly: true } | { readonly parent: NamedImports & { readonly parent: ImportClause & { readonly isTypeOnly: true } } })
3800+
;
3801+
3802+
export type TypeOnlyExportDeclaration =
37983803
| ExportSpecifier & ({ readonly isTypeOnly: true } | { readonly parent: NamedExports & { readonly parent: ExportDeclaration & { readonly isTypeOnly: true } } })
3804+
| ExportDeclaration & { readonly isTypeOnly: true } // export * from "mod"
3805+
| NamespaceExport & { readonly parent: ExportDeclaration & { readonly isTypeOnly: true } } // export * as ns from "mod"
37993806
;
38003807

3808+
export type TypeOnlyAliasDeclaration = TypeOnlyImportDeclaration | TypeOnlyExportDeclaration;
3809+
38013810
/**
38023811
* This is either an `export =` or an `export default` declaration.
38033812
* Unless `isExportEquals` is set, this node was parsed as an `export default`.
@@ -5792,6 +5801,8 @@ export interface SymbolLinks {
57925801
deferralParent?: Type; // Source union/intersection of a deferred type
57935802
cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target
57945803
typeOnlyDeclaration?: TypeOnlyAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
5804+
typeOnlyExportStarMap?: UnderscoreEscapedMap<ExportDeclaration & { readonly isTypeOnly: true }>; // Set on a module symbol when some of its exports were resolved through a 'export type * from "mod"' declaration
5805+
typeOnlyExportStarName?: __String; // Set to the name of the symbol re-exported by an 'export type *' declaration, when different from the symbol name
57955806
isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor
57965807
tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label
57975808
accessibleChainCache?: Map<string, Symbol[] | undefined>;

src/compiler/utilitiesPublic.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
EnumDeclaration,
5656
every,
5757
ExportAssignment,
58+
ExportDeclaration,
5859
ExportSpecifier,
5960
Expression,
6061
FileReference,
@@ -92,7 +93,6 @@ import {
9293
Identifier,
9394
ImportClause,
9495
ImportEqualsDeclaration,
95-
ImportOrExportSpecifier,
9696
ImportSpecifier,
9797
ImportTypeNode,
9898
isAccessExpression,
@@ -214,6 +214,7 @@ import {
214214
NamedExportBindings,
215215
NamedImportBindings,
216216
NamespaceBody,
217+
NamespaceExport,
217218
NamespaceImport,
218219
NewExpression,
219220
Node,
@@ -270,6 +271,8 @@ import {
270271
TypeElement,
271272
TypeNode,
272273
TypeOnlyAliasDeclaration,
274+
TypeOnlyExportDeclaration,
275+
TypeOnlyImportDeclaration,
273276
TypeParameterDeclaration,
274277
TypeReferenceType,
275278
UnaryExpression,
@@ -1480,19 +1483,33 @@ export function isImportOrExportSpecifier(node: Node): node is ImportSpecifier |
14801483
return isImportSpecifier(node) || isExportSpecifier(node);
14811484
}
14821485

1483-
export function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyAliasDeclaration {
1486+
export function isTypeOnlyImportDeclaration(node: Node): node is TypeOnlyImportDeclaration {
14841487
switch (node.kind) {
14851488
case SyntaxKind.ImportSpecifier:
1486-
case SyntaxKind.ExportSpecifier:
1487-
return (node as ImportOrExportSpecifier).isTypeOnly || (node as ImportOrExportSpecifier).parent.parent.isTypeOnly;
1489+
return (node as ImportSpecifier).isTypeOnly || (node as ImportSpecifier).parent.parent.isTypeOnly;
14881490
case SyntaxKind.NamespaceImport:
14891491
return (node as NamespaceImport).parent.isTypeOnly;
14901492
case SyntaxKind.ImportClause:
14911493
case SyntaxKind.ImportEqualsDeclaration:
14921494
return (node as ImportClause | ImportEqualsDeclaration).isTypeOnly;
1493-
default:
1494-
return false;
14951495
}
1496+
return false;
1497+
}
1498+
1499+
export function isTypeOnlyExportDeclaration(node: Node): node is TypeOnlyExportDeclaration {
1500+
switch (node.kind) {
1501+
case SyntaxKind.ExportSpecifier:
1502+
return (node as ExportSpecifier).isTypeOnly || (node as ExportSpecifier).parent.parent.isTypeOnly;
1503+
case SyntaxKind.ExportDeclaration:
1504+
return (node as ExportDeclaration).isTypeOnly && !!(node as ExportDeclaration).moduleSpecifier && !(node as ExportDeclaration).exportClause;
1505+
case SyntaxKind.NamespaceExport:
1506+
return (node as NamespaceExport).parent.isTypeOnly;
1507+
}
1508+
return false;
1509+
}
1510+
1511+
export function isTypeOnlyImportOrExportDeclaration(node: Node): node is TypeOnlyAliasDeclaration {
1512+
return isTypeOnlyImportDeclaration(node) || isTypeOnlyExportDeclaration(node);
14961513
}
14971514

14981515
export function isAssertionKey(node: Node): node is AssertionKey {

0 commit comments

Comments
 (0)