Skip to content
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

Add support for import defer proposal #60757

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 15 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9851,7 +9851,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 @@ -9944,7 +9944,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 @@ -9959,7 +9963,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 All @@ -9986,7 +9994,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 @@ -52860,6 +52868,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) {
return checkGrammarNamedImportsOrExports(node.namedBindings);
}
if (node.phaseModifier === SyntaxKind.DeferKeyword && moduleKind !== ModuleKind.ESNext) {
return grammarErrorOnNode(node, Diagnostics.Deferred_imports_are_only_supported_when_the_module_flag_is_set_to_esnext);
}
return false;
}

Expand Down
12 changes: 12 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -8433,5 +8433,17 @@
"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 aren't allowed for deferred imports.": {
"category": "Error",
"code": 18058
},
"Named imports aren't allowed for deferred imports.": {
"category": "Error",
"code": 18059
},
"Deferred imports are only supported when the '--module' flag is set to 'esnext'.": {
"category": "Error",
"code": 18060
}
}
6 changes: 5 additions & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3685,7 +3685,11 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}

function emitImportClause(node: ImportClause) {
if (node.isTypeOnly) {
if (node.phaseModifier !== undefined) {
emitTokenWithComment(node.phaseModifier, node.pos, writeKeyword, node);
writeSpace();
}
else if (node.isTypeOnly) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have this in case somebody passes in an object from an old TS version. Is this relevant for TS' API contract, or would removing it not be considered a breaking change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't support mixing TS versions between a given Node and the emitter, so that shouldn't be a concern. Inside the compiler we should avoid depending on deprecated functionality.

emitTokenWithComment(SyntaxKind.TypeKeyword, node.pos, writeKeyword, node);
writeSpace();
}
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,
ImportPhaseModifier,
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: ImportPhaseModifier | 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: ImportPhaseModifier | 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
43 changes: 31 additions & 12 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ import {
ImportDeclaration,
ImportEqualsDeclaration,
ImportOrExportSpecifier,
ImportPhaseModifier,
ImportSpecifier,
ImportTypeAssertionContainer,
ImportTypeNode,
Expand Down Expand Up @@ -7190,6 +7191,7 @@ namespace Parser {
// could be legal, it would add complexity for very little gain.
case SyntaxKind.InterfaceKeyword:
case SyntaxKind.TypeKeyword:
case SyntaxKind.DeferKeyword:
return nextTokenIsIdentifierOnSameLine();
case SyntaxKind.ModuleKeyword:
case SyntaxKind.NamespaceKeyword:
Expand Down Expand Up @@ -7221,7 +7223,7 @@ namespace Parser {

case SyntaxKind.ImportKeyword:
nextToken();
return token() === SyntaxKind.StringLiteral || token() === SyntaxKind.AsteriskToken ||
return token() === SyntaxKind.DeferKeyword || token() === SyntaxKind.StringLiteral || token() === SyntaxKind.AsteriskToken ||
token() === SyntaxKind.OpenBraceToken || tokenIsIdentifierOrKeyword(token());
case SyntaxKind.ExportKeyword:
let currentToken = nextToken();
Expand Down Expand Up @@ -7295,6 +7297,7 @@ namespace Parser {
case SyntaxKind.NamespaceKeyword:
case SyntaxKind.TypeKeyword:
case SyntaxKind.GlobalKeyword:
case SyntaxKind.DeferKeyword:
// When these don't start a declaration, they're an identifier in an expression statement
return true;

Expand Down Expand Up @@ -8365,21 +8368,29 @@ namespace Parser {
identifier = parseIdentifier();
}

let isTypeOnly = false;
let phaseModifier: ImportPhaseModifier | undefined;
if (
identifier?.escapedText === "type" &&
(token() !== SyntaxKind.FromKeyword || isIdentifier() && lookAhead(nextTokenIsFromKeywordOrEqualsToken)) &&
(isIdentifier() || tokenAfterImportDefinitelyProducesImportDeclaration())
) {
isTypeOnly = true;
phaseModifier = SyntaxKind.TypeKeyword;
identifier = isIdentifier() ? parseIdentifier() : undefined;
}
else if (identifier?.escapedText === "defer" && token() !== SyntaxKind.FromKeyword) {
phaseModifier = SyntaxKind.DeferKeyword;
identifier = undefined;
if (isIdentifier()) {
parseErrorAtCurrentToken(Diagnostics.Default_imports_aren_t_allowed_for_deferred_imports);
identifier = parseIdentifier();
}
}

if (identifier && !tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration()) {
return parseImportEqualsDeclaration(pos, hasJSDoc, modifiers, identifier, isTypeOnly);
if (identifier && !tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration() && phaseModifier !== SyntaxKind.DeferKeyword) {
return parseImportEqualsDeclaration(pos, hasJSDoc, modifiers, identifier, phaseModifier === SyntaxKind.TypeKeyword);
}

const importClause = tryParseImportClause(identifier, afterImportPos, isTypeOnly);
const importClause = tryParseImportClause(identifier, afterImportPos, phaseModifier, /*skipJsDocLeadingAsterisks*/ undefined);
const moduleSpecifier = parseModuleSpecifier();
const attributes = tryParseImportAttributes();

Expand All @@ -8388,7 +8399,7 @@ namespace Parser {
return withJSDoc(finishNode(node, pos), hasJSDoc);
}

function tryParseImportClause(identifier: Identifier | undefined, pos: number, isTypeOnly: boolean, skipJsDocLeadingAsterisks = false) {
function tryParseImportClause(identifier: Identifier | undefined, pos: number, phaseModifier: undefined | ImportPhaseModifier, skipJsDocLeadingAsterisks = false) {
// ImportDeclaration:
// import ImportClause from ModuleSpecifier ;
// import ModuleSpecifier;
Expand All @@ -8398,7 +8409,7 @@ namespace Parser {
token() === SyntaxKind.AsteriskToken || // import *
token() === SyntaxKind.OpenBraceToken // import {
) {
importClause = parseImportClause(identifier, pos, isTypeOnly, skipJsDocLeadingAsterisks);
importClause = parseImportClause(identifier, pos, phaseModifier, skipJsDocLeadingAsterisks);
parseExpected(SyntaxKind.FromKeyword);
}
return importClause;
Expand Down Expand Up @@ -8464,7 +8475,7 @@ namespace Parser {
return finished;
}

function parseImportClause(identifier: Identifier | undefined, pos: number, isTypeOnly: boolean, skipJsDocLeadingAsterisks: boolean) {
function parseImportClause(identifier: Identifier | undefined, pos: number, phaseModifier: undefined | ImportPhaseModifier, skipJsDocLeadingAsterisks: boolean) {
// ImportClause:
// ImportedDefaultBinding
// NameSpaceImport
Expand All @@ -8480,11 +8491,19 @@ namespace Parser {
parseOptional(SyntaxKind.CommaToken)
) {
if (skipJsDocLeadingAsterisks) scanner.setSkipJsDocLeadingAsterisks(true);
namedBindings = token() === SyntaxKind.AsteriskToken ? parseNamespaceImport() : parseNamedImportsOrExports(SyntaxKind.NamedImports);
if (token() === SyntaxKind.AsteriskToken) {
namedBindings = parseNamespaceImport();
}
else {
if (phaseModifier === SyntaxKind.DeferKeyword) {
parseErrorAtCurrentToken(Diagnostics.Named_imports_aren_t_allowed_for_deferred_imports);
}
namedBindings = parseNamedImportsOrExports(SyntaxKind.NamedImports);
}
if (skipJsDocLeadingAsterisks) scanner.setSkipJsDocLeadingAsterisks(false);
}

return finishNode(factory.createImportClause(isTypeOnly, identifier, namedBindings), pos);
return finishNode(factory.createImportClause(phaseModifier, identifier, namedBindings), pos);
}

function parseModuleReference() {
Expand Down Expand Up @@ -9518,7 +9537,7 @@ namespace Parser {
identifier = parseIdentifier();
}

const importClause = tryParseImportClause(identifier, afterImportTagPos, /*isTypeOnly*/ true, /*skipJsDocLeadingAsterisks*/ true);
const importClause = tryParseImportClause(identifier, afterImportTagPos, SyntaxKind.TypeKeyword, /*skipJsDocLeadingAsterisks*/ true);
const moduleSpecifier = parseModuleSpecifier();
const attributes = tryParseImportAttributes();

Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export const textToKeywordObj: MapLike<KeywordSyntaxKind> = {
true: SyntaxKind.TrueKeyword,
try: SyntaxKind.TryKeyword,
type: SyntaxKind.TypeKeyword,
defer: SyntaxKind.DeferKeyword,
typeof: SyntaxKind.TypeOfKeyword,
undefined: SyntaxKind.UndefinedKeyword,
unique: SyntaxKind.UniqueKeyword,
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,7 @@ export function transformDeclarations(context: TransformationContext): Transform
decl.modifiers,
factory.updateImportClause(
decl.importClause,
decl.importClause.isTypeOnly,
decl.importClause.phaseModifier,
visibleDefaultBinding,
/*namedBindings*/ undefined,
),
Expand All @@ -902,7 +902,7 @@ export function transformDeclarations(context: TransformationContext): Transform
decl.modifiers,
factory.updateImportClause(
decl.importClause,
decl.importClause.isTypeOnly,
decl.importClause.phaseModifier,
visibleDefaultBinding,
namedBindings,
),
Expand All @@ -918,7 +918,7 @@ export function transformDeclarations(context: TransformationContext): Transform
decl.modifiers,
factory.updateImportClause(
decl.importClause,
decl.importClause.isTypeOnly,
decl.importClause.phaseModifier,
visibleDefaultBinding,
bindingList && bindingList.length ? factory.updateNamedImports(decl.importClause.namedBindings, bindingList) : undefined,
),
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export function transformJsx(context: TransformationContext): (x: SourceFile | B
for (const [importSource, importSpecifiersMap] of arrayFrom(currentFileState.utilizedImplicitRuntimeImports.entries())) {
if (isExternalModule(node)) {
// Add `import` statement
const importStatement = factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamedImports(arrayFrom(importSpecifiersMap.values()))), factory.createStringLiteral(importSource), /*attributes*/ undefined);
const importStatement = factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(/*phaseModifier*/ undefined, /*name*/ undefined, factory.createNamedImports(arrayFrom(importSpecifiersMap.values()))), factory.createStringLiteral(importSource), /*attributes*/ undefined);
setParentRecursive(importStatement, /*incremental*/ false);
statements = insertStatementAfterCustomPrologue(statements.slice(), importStatement);
}
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/transformers/module/esnextAnd2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
const importStatement = factory.createImportDeclaration(
/*modifiers*/ undefined,
factory.createImportClause(
/*isTypeOnly*/ false,
/*phaseModifier*/ undefined,
/*name*/ undefined,
factory.createNamedImports([
factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("createRequire"), createRequireName),
Expand Down Expand Up @@ -351,7 +351,7 @@ export function transformECMAScriptModule(context: TransformationContext): (x: S
const importDecl = factory.createImportDeclaration(
/*modifiers*/ undefined,
factory.createImportClause(
/*isTypeOnly*/ false,
/*phaseModifier*/ undefined,
/*name*/ undefined,
factory.createNamespaceImport(
synthName,
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2282,11 +2282,11 @@ export function transformTypeScript(context: TransformationContext): Transformer
* @param node The import clause node.
*/
function visitImportClause(node: ImportClause): VisitResult<ImportClause> | undefined {
Debug.assert(!node.isTypeOnly);
Debug.assert(node.phaseModifier !== SyntaxKind.TypeKeyword);
// Elide the import clause if we elide both its name and its named bindings.
const name = shouldEmitAliasDeclaration(node) ? node.name : undefined;
const namedBindings = visitNode(node.namedBindings, visitNamedImportBindings, isNamedImportBindings);
return (name || namedBindings) ? factory.updateImportClause(node, /*isTypeOnly*/ false, name, namedBindings) : undefined;
return (name || namedBindings) ? factory.updateImportClause(node, node.phaseModifier, name, namedBindings) : undefined;
}

/**
Expand Down
Loading
Loading