Skip to content

Add support for import defer proposal #60757

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

Merged
merged 25 commits into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1d14a5b
Add support for `import defer` proposal
ryzokuken Jul 8, 2024
7136718
Update AST to unify `type` with other modifiers
nicolo-ribaudo Dec 19, 2024
6967224
Add test for comments around `defer`
nicolo-ribaudo Dec 19, 2024
2346fa5
Remove unnecessary backwards compat check
nicolo-ribaudo Jan 16, 2025
e3a1a18
Add support for `import.defer(...)`
nicolo-ribaudo Jan 16, 2025
c3bcd0b
Also support `module: NodeNext`
nicolo-ribaudo Jan 17, 2025
8d4fd08
`aren't` -> `are not`
nicolo-ribaudo Jan 17, 2025
7cdc422
ImportPhaseModifier -> ImportPhaseModifierSyntaxKind
nicolo-ribaudo Jan 17, 2025
994ec08
Move import clause checks to checker.ts
nicolo-ribaudo Jan 17, 2025
95183c4
Report `'(' expected` for import.defer
nicolo-ribaudo Jan 17, 2025
33309e8
Make meta property error localizable
nicolo-ribaudo Jan 17, 2025
00adf6e
Move `import.defer` checks to checker.ts
nicolo-ribaudo Jan 17, 2025
cd045bb
Unify tests testing different values of `module`
nicolo-ribaudo Jan 17, 2025
fc0908f
Move `errorType` for `import.defer` handling
nicolo-ribaudo Jan 17, 2025
dec1011
Do not consider `import.defer` in `import.defer(...)` as an expression
nicolo-ribaudo Jan 17, 2025
8d2451a
fmt
nicolo-ribaudo Jan 17, 2025
c485777
Sort tokens alphabetically
nicolo-ribaudo Jan 17, 2025
706bb58
Remove unused baselines
nicolo-ribaudo Jan 20, 2025
bfbcc3f
Properly resolve specifiers in `import.defer(...)`
nicolo-ribaudo Jan 20, 2025
bad45a3
rbuckton review
nicolo-ribaudo Feb 5, 2025
5f54ced
Add test for jsdoc (unsupported)
nicolo-ribaudo Jun 5, 2025
1e445bf
Strip `defer` from dts
nicolo-ribaudo Jun 5, 2025
1886e24
Use `LastKeyword` for `LastContextualKeyword`
nicolo-ribaudo Jun 5, 2025
ffcfd9a
Add test for `typeof import.defer` (unsupported)
nicolo-ribaudo Jun 5, 2025
b352c0d
Drop support for `nodenext`, and allow when `preserve`
nicolo-ribaudo Jun 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 58 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10246,7 +10246,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
factory.createImportDeclaration(
/*modifiers*/ undefined,
factory.createImportClause(
/*isTypeOnly*/ false,
/*phaseModifier*/ undefined,
/*name*/ undefined,
factory.createNamedImports([factory.createImportSpecifier(
/*isTypeOnly*/ false,
Expand Down Expand Up @@ -10343,7 +10343,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
addResult(
factory.createImportDeclaration(
/*modifiers*/ undefined,
factory.createImportClause(isTypeOnly, factory.createIdentifier(localName), /*namedBindings*/ undefined),
factory.createImportClause(
/* phaseModifier */ isTypeOnly ? SyntaxKind.TypeKeyword : undefined,
factory.createIdentifier(localName),
/*namedBindings*/ undefined,
),
specifier,
attributes,
),
Expand All @@ -10359,7 +10363,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
addResult(
factory.createImportDeclaration(
/*modifiers*/ undefined,
factory.createImportClause(isTypeOnly, /*name*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))),
factory.createImportClause(
/* phaseModifier */ isTypeOnly ? SyntaxKind.TypeKeyword : undefined,
/*name*/ undefined,
factory.createNamespaceImport(factory.createIdentifier(localName)),
),
specifier,
(node as ImportClause).parent.attributes,
),
Expand Down Expand Up @@ -10388,7 +10396,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
factory.createImportDeclaration(
/*modifiers*/ undefined,
factory.createImportClause(
isTypeOnly,
/* phaseModifier */ isTypeOnly ? SyntaxKind.TypeKeyword : undefined,
/*name*/ undefined,
factory.createNamedImports([
factory.createImportSpecifier(
Expand Down Expand Up @@ -38013,6 +38021,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

if (node.keywordToken === SyntaxKind.ImportKeyword) {
if (node.name.escapedText === "defer") {
Debug.assert(!isCallExpression(node.parent) || node.parent.expression !== node, "Trying to get the type of `import.defer` in `import.defer(...)`");
return errorType;
}
return checkImportMetaProperty(node);
}

Expand Down Expand Up @@ -41451,7 +41463,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// Optimize for the common case of a call to a function with a single non-generic call
// signature where we can just fetch the return type without checking the arguments.
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*requireStringLiteralLikeArgument*/ true) && !isSymbolOrSymbolForCall(expr)) {
if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*requireStringLiteralLikeArgument*/ true) && !isSymbolOrSymbolForCall(expr) && !isImportCall(expr)) {
return isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) :
getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression));
}
Expand Down Expand Up @@ -41605,8 +41617,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.ElementAccessExpression:
return checkIndexedAccess(node as ElementAccessExpression, checkMode);
case SyntaxKind.CallExpression:
if ((node as CallExpression).expression.kind === SyntaxKind.ImportKeyword) {
return checkImportCallExpression(node as ImportCall);
if (isImportCall(node)) {
return checkImportCallExpression(node);
}
// falls through
case SyntaxKind.NewExpression:
Expand Down Expand Up @@ -49873,6 +49885,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return isExportAssignment(node.parent) ? Debug.checkDefined(node.parent.symbol) : undefined;

case SyntaxKind.ImportKeyword:
if (isMetaProperty(node.parent) && node.parent.name.escapedText === "defer") {
return undefined;
}
// falls through
case SyntaxKind.NewKeyword:
return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined;
case SyntaxKind.InstanceOfKeyword:
Expand Down Expand Up @@ -52847,7 +52863,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
break;
case SyntaxKind.ImportKeyword:
if (escapedText !== "meta") {
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, unescapeLeadingUnderscores(node.name.escapedText), tokenToString(node.keywordToken), "meta");
const isCallee = isCallExpression(node.parent) && node.parent.expression === node;
if (escapedText === "defer") {
if (!isCallee) {
return grammarErrorAtPos(node, node.end, 0, Diagnostics._0_expected, "(");
}
}
else {
if (isCallee) {
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_import_Did_you_mean_meta_or_defer, unescapeLeadingUnderscores(node.name.escapedText));
}
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, unescapeLeadingUnderscores(node.name.escapedText), tokenToString(node.keywordToken), "meta");
}
}
break;
}
Expand Down Expand Up @@ -53106,11 +53133,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function checkGrammarImportClause(node: ImportClause): boolean {
if (node.isTypeOnly && node.name && node.namedBindings) {
return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both);
if (node.phaseModifier === SyntaxKind.TypeKeyword) {
if (node.name && node.namedBindings) {
return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both);
}
if (node.namedBindings?.kind === SyntaxKind.NamedImports) {
return checkGrammarNamedImportsOrExports(node.namedBindings);
}
}
if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) {
return checkGrammarNamedImportsOrExports(node.namedBindings);
else if (node.phaseModifier === SyntaxKind.DeferKeyword) {
if (node.name) {
return grammarErrorOnNode(node, Diagnostics.Default_imports_are_not_allowed_in_a_deferred_import);
}
if (node.namedBindings?.kind === SyntaxKind.NamedImports) {
return grammarErrorOnNode(node, Diagnostics.Named_imports_are_not_allowed_in_a_deferred_import);
}
if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.Preserve) {
return grammarErrorOnNode(node, Diagnostics.Deferred_imports_are_only_supported_when_the_module_flag_is_set_to_esnext_or_preserve);
}
}
return false;
}
Expand All @@ -53133,7 +53173,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return grammarErrorOnNode(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled);
}

if (moduleKind === ModuleKind.ES2015) {
if (node.expression.kind === SyntaxKind.MetaProperty) {
if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.Preserve) {
return grammarErrorOnNode(node, Diagnostics.Deferred_imports_are_only_supported_when_the_module_flag_is_set_to_esnext_or_preserve);
}
}
else if (moduleKind === ModuleKind.ES2015) {
return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node16_node18_or_nodenext);
}

Expand Down
16 changes: 16 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -8451,5 +8451,21 @@
"String literal import and export names are not supported when the '--module' flag is set to 'es2015' or 'es2020'.": {
"category": "Error",
"code": 18057
},
"Default imports are not allowed in a deferred import.": {
"category": "Error",
"code": 18058
},
"Named imports are not allowed in a deferred import.": {
"category": "Error",
"code": 18059
},
"Deferred imports are only supported when the '--module' flag is set to 'esnext' or 'preserve'.": {
"category": "Error",
"code": 18060
},
"'{0}' is not a valid meta-property for keyword 'import'. Did you mean 'meta' or 'defer'?": {
"category": "Error",
"code": 18061
}
}
4 changes: 2 additions & 2 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3686,8 +3686,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}

function emitImportClause(node: ImportClause) {
if (node.isTypeOnly) {
emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node);
if (node.phaseModifier !== undefined) {
emitTokenWithComment(node.phaseModifier, node.pos, writeKeyword, node);
writeSpace();
}
emit(node.name);
Expand Down
20 changes: 14 additions & 6 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ import {
ImportClause,
ImportDeclaration,
ImportEqualsDeclaration,
ImportPhaseModifierSyntaxKind,
ImportSpecifier,
ImportTypeAssertionContainer,
ImportTypeNode,
Expand Down Expand Up @@ -4723,26 +4724,33 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function createImportClause(isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause {
function createImportClause(phaseModifier: ImportPhaseModifierSyntaxKind | boolean | undefined, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause {
const node = createBaseDeclaration<ImportClause>(SyntaxKind.ImportClause);
node.isTypeOnly = isTypeOnly;
if (typeof phaseModifier === "boolean") {
phaseModifier = phaseModifier ? SyntaxKind.TypeKeyword : undefined;
}
node.isTypeOnly = phaseModifier === SyntaxKind.TypeKeyword;
node.phaseModifier = phaseModifier;
node.name = name;
node.namedBindings = namedBindings;
node.transformFlags |= propagateChildFlags(node.name) |
propagateChildFlags(node.namedBindings);
if (isTypeOnly) {
if (phaseModifier === SyntaxKind.TypeKeyword) {
node.transformFlags |= TransformFlags.ContainsTypeScript;
}
node.transformFlags &= ~TransformFlags.ContainsPossibleTopLevelAwait; // always parsed in an Await context
return node;
}

// @api
function updateImportClause(node: ImportClause, isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) {
return node.isTypeOnly !== isTypeOnly
function updateImportClause(node: ImportClause, phaseModifier: ImportPhaseModifierSyntaxKind | boolean | undefined, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined) {
if (typeof phaseModifier === "boolean") {
phaseModifier = phaseModifier ? SyntaxKind.TypeKeyword : undefined;
}
return node.phaseModifier !== phaseModifier
|| node.name !== name
|| node.namedBindings !== namedBindings
? update(createImportClause(isTypeOnly, name, namedBindings), node)
? update(createImportClause(phaseModifier, name, namedBindings), node)
: node;
}

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ export function createExternalHelpersImportDeclarationIfNeeded(nodeFactory: Node

const externalHelpersImportDeclaration = nodeFactory.createImportDeclaration(
/*modifiers*/ undefined,
nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings),
nodeFactory.createImportClause(/*phaseModifier*/ undefined, /*name*/ undefined, namedBindings),
nodeFactory.createStringLiteral(externalHelpersModuleNameText),
/*attributes*/ undefined,
);
Expand Down
Loading