Skip to content

Commit 9d16d34

Browse files
authored
Merge pull request #14774 from Microsoft/master-dynamicImport
[Master] wip-dynamic import
2 parents a76b4b1 + 1729ea8 commit 9d16d34

File tree

169 files changed

+5271
-103
lines changed

Some content is hidden

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

169 files changed

+5271
-103
lines changed

Diff for: src/compiler/binder.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -2333,7 +2333,7 @@ namespace ts {
23332333
// A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports'
23342334
// is still pointing to 'module.exports'.
23352335
// We do not want to consider this as 'export=' since a module can have only one of these.
2336-
// Similarlly we do not want to treat 'module.exports = exports' as an 'export='.
2336+
// Similarly we do not want to treat 'module.exports = exports' as an 'export='.
23372337
const assignedExpression = getRightMostAssignedExpression(node.right);
23382338
if (isEmptyObjectLiteral(assignedExpression) || isExportsOrModuleExportsOrAlias(assignedExpression)) {
23392339
// Mark it as a module in case there are no other exports in the file
@@ -2741,6 +2741,10 @@ namespace ts {
27412741
transformFlags |= TransformFlags.AssertES2015;
27422742
}
27432743

2744+
if (expression.kind === SyntaxKind.ImportKeyword) {
2745+
transformFlags |= TransformFlags.ContainsDynamicImport;
2746+
}
2747+
27442748
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
27452749
return transformFlags & ~TransformFlags.ArrayLiteralOrCallOrNewExcludes;
27462750
}

Diff for: src/compiler/checker.ts

+67-3
Original file line numberDiff line numberDiff line change
@@ -8356,6 +8356,12 @@ namespace ts {
83568356
/**
83578357
* This is *not* a bi-directional relationship.
83588358
* If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'.
8359+
*
8360+
* A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T.
8361+
* It is used to check following cases:
8362+
* - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`).
8363+
* - the types of `case` clause expressions and their respective `switch` expressions.
8364+
* - the type of an expression in a type assertion with the type being asserted.
83598365
*/
83608366
function isTypeComparableTo(source: Type, target: Type): boolean {
83618367
return isTypeRelatedTo(source, target, comparableRelation);
@@ -16158,6 +16164,35 @@ namespace ts {
1615816164
return getReturnTypeOfSignature(signature);
1615916165
}
1616016166

16167+
function checkImportCallExpression(node: ImportCall): Type {
16168+
// Check grammar of dynamic import
16169+
checkGrammarArguments(node, node.arguments) || checkGrammarImportCallExpression(node);
16170+
16171+
if (node.arguments.length === 0) {
16172+
return createPromiseReturnType(node, anyType);
16173+
}
16174+
const specifier = node.arguments[0];
16175+
const specifierType = checkExpressionCached(specifier);
16176+
// Even though multiple arugments is grammatically incorrect, type-check extra arguments for completion
16177+
for (let i = 1; i < node.arguments.length; ++i) {
16178+
checkExpressionCached(node.arguments[i]);
16179+
}
16180+
16181+
if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) {
16182+
error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType));
16183+
}
16184+
16185+
// resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal
16186+
const moduleSymbol = resolveExternalModuleName(node, specifier);
16187+
if (moduleSymbol) {
16188+
const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true);
16189+
if (esModuleSymbol) {
16190+
return createPromiseReturnType(node, getTypeOfSymbol(esModuleSymbol));
16191+
}
16192+
}
16193+
return createPromiseReturnType(node, anyType);
16194+
}
16195+
1616116196
function isCommonJsRequire(node: Node) {
1616216197
if (!isRequireCall(node, /*checkArgumentIsStringLiteral*/ true)) {
1616316198
return false;
@@ -16364,14 +16399,18 @@ namespace ts {
1636416399
return emptyObjectType;
1636516400
}
1636616401

16367-
function createPromiseReturnType(func: FunctionLikeDeclaration, promisedType: Type) {
16402+
function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: Type) {
1636816403
const promiseType = createPromiseType(promisedType);
1636916404
if (promiseType === emptyObjectType) {
16370-
error(func, Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option);
16405+
error(func, isImportCall(func) ?
16406+
Diagnostics.A_dynamic_import_call_returns_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option :
16407+
Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option);
1637116408
return unknownType;
1637216409
}
1637316410
else if (!getGlobalPromiseConstructorSymbol(/*reportErrors*/ true)) {
16374-
error(func, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option);
16411+
error(func, isImportCall(func) ?
16412+
Diagnostics.A_dynamic_import_call_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option :
16413+
Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option);
1637516414
}
1637616415

1637716416
return promiseType;
@@ -17730,6 +17769,10 @@ namespace ts {
1773017769
case SyntaxKind.ElementAccessExpression:
1773117770
return checkIndexedAccess(<ElementAccessExpression>node);
1773217771
case SyntaxKind.CallExpression:
17772+
if ((<CallExpression>node).expression.kind === SyntaxKind.ImportKeyword) {
17773+
return checkImportCallExpression(<ImportCall>node);
17774+
}
17775+
/* falls through */
1773317776
case SyntaxKind.NewExpression:
1773417777
return checkCallExpression(<CallExpression>node);
1773517778
case SyntaxKind.TaggedTemplateExpression:
@@ -24655,6 +24698,27 @@ namespace ts {
2465524698
});
2465624699
return result;
2465724700
}
24701+
24702+
function checkGrammarImportCallExpression(node: ImportCall): boolean {
24703+
if (modulekind === ModuleKind.ES2015) {
24704+
return grammarErrorOnNode(node, Diagnostics.Dynamic_import_cannot_be_used_when_targeting_ECMAScript_2015_modules);
24705+
}
24706+
24707+
if (node.typeArguments) {
24708+
return grammarErrorOnNode(node, Diagnostics.Dynamic_import_cannot_have_type_arguments);
24709+
}
24710+
24711+
const arguments = node.arguments;
24712+
if (arguments.length !== 1) {
24713+
return grammarErrorOnNode(node, Diagnostics.Dynamic_import_must_have_one_specifier_as_an_argument);
24714+
}
24715+
24716+
// see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import.
24717+
// parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import.
24718+
if (isSpreadElement(arguments[0])) {
24719+
return grammarErrorOnNode(arguments[0], Diagnostics.Specifier_of_dynamic_import_cannot_be_spread_element);
24720+
}
24721+
}
2465824722
}
2465924723

2466024724
/** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */

Diff for: src/compiler/commandLineParser.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,12 @@ namespace ts {
100100
"umd": ModuleKind.UMD,
101101
"es6": ModuleKind.ES2015,
102102
"es2015": ModuleKind.ES2015,
103+
"esnext": ModuleKind.ESNext
103104
}),
104105
paramType: Diagnostics.KIND,
105106
showInSimplifiedHelpView: true,
106107
category: Diagnostics.Basic_Options,
107-
description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_or_es2015,
108+
description: Diagnostics.Specify_module_code_generation_Colon_commonjs_amd_system_umd_es2015_or_ESNext,
108109
},
109110
{
110111
name: "lib",

Diff for: src/compiler/diagnosticMessages.json

+31-5
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,23 @@
883883
"category": "Error",
884884
"code": 1322
885885
},
886+
"Dynamic import cannot be used when targeting ECMAScript 2015 modules.": {
887+
"category": "Error",
888+
"code": 1323
889+
},
890+
"Dynamic import must have one specifier as an argument.": {
891+
"category": "Error",
892+
"code": 1324
893+
},
894+
"Specifier of dynamic import cannot be spread element.": {
895+
"category": "Error",
896+
"code": 1325
897+
},
898+
"Dynamic import cannot have type arguments": {
899+
"category": "Error",
900+
"code": 1326
901+
},
902+
886903
"Duplicate identifier '{0}'.": {
887904
"category": "Error",
888905
"code": 2300
@@ -1927,10 +1944,6 @@
19271944
"category": "Error",
19281945
"code": 2649
19291946
},
1930-
"Cannot emit namespaced JSX elements in React.": {
1931-
"category": "Error",
1932-
"code": 2650
1933-
},
19341947
"A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.": {
19351948
"category": "Error",
19361949
"code": 2651
@@ -2163,6 +2176,14 @@
21632176
"category": "Error",
21642177
"code": 2710
21652178
},
2179+
"A dynamic import call returns a 'Promise'. Make sure you have a declaration for 'Promise' or include 'ES2015' in your `--lib` option.": {
2180+
"category": "Error",
2181+
"code": 2711
2182+
},
2183+
"A dynamic import call in ES5/ES3 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your `--lib` option.": {
2184+
"category": "Error",
2185+
"code": 2712
2186+
},
21662187

21672188
"Import declaration '{0}' is using private name '{1}'.": {
21682189
"category": "Error",
@@ -2629,7 +2650,7 @@
26292650
"category": "Message",
26302651
"code": 6015
26312652
},
2632-
"Specify module code generation: 'commonjs', 'amd', 'system', 'umd' or 'es2015'.": {
2653+
"Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.": {
26332654
"category": "Message",
26342655
"code": 6016
26352656
},
@@ -3365,6 +3386,11 @@
33653386
"category": "Error",
33663387
"code": 7035
33673388
},
3389+
"Dynamic import's specifier must be of type 'string', but here has type '{0}'.": {
3390+
"category": "Error",
3391+
"code": 7036
3392+
},
3393+
33683394
"You cannot rename this element.": {
33693395
"category": "Error",
33703396
"code": 8000

Diff for: src/compiler/emitter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ namespace ts {
676676
case SyntaxKind.SuperKeyword:
677677
case SyntaxKind.TrueKeyword:
678678
case SyntaxKind.ThisKeyword:
679+
case SyntaxKind.ImportKeyword:
679680
writeTokenNode(node);
680681
return;
681682

Diff for: src/compiler/parser.ts

+42-25
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,16 @@ namespace ts {
4646
}
4747
}
4848

49-
// Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes
50-
// stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise,
51-
// embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns
52-
// a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned.
49+
/**
50+
* Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes
51+
* stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise,
52+
* embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns
53+
* a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned.
54+
*
55+
* @param node a given node to visit its children
56+
* @param cbNode a callback to be invoked for all child nodes
57+
* @param cbNodeArray a callback to be invoked for embedded array
58+
*/
5359
export function forEachChild<T>(node: Node, cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
5460
if (!node) {
5561
return;
@@ -2409,7 +2415,7 @@ namespace ts {
24092415
if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) {
24102416
return parseSignatureMember(SyntaxKind.CallSignature);
24112417
}
2412-
if (token() === SyntaxKind.NewKeyword && lookAhead(isStartOfConstructSignature)) {
2418+
if (token() === SyntaxKind.NewKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) {
24132419
return parseSignatureMember(SyntaxKind.ConstructSignature);
24142420
}
24152421
const fullStart = getNodePos();
@@ -2420,7 +2426,7 @@ namespace ts {
24202426
return parsePropertyOrMethodSignature(fullStart, modifiers);
24212427
}
24222428

2423-
function isStartOfConstructSignature() {
2429+
function nextTokenIsOpenParenOrLessThan() {
24242430
nextToken();
24252431
return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken;
24262432
}
@@ -2776,6 +2782,7 @@ namespace ts {
27762782
case SyntaxKind.SlashToken:
27772783
case SyntaxKind.SlashEqualsToken:
27782784
case SyntaxKind.Identifier:
2785+
case SyntaxKind.ImportKeyword:
27792786
return true;
27802787
default:
27812788
return isIdentifier();
@@ -3509,10 +3516,10 @@ namespace ts {
35093516
* 5) --UnaryExpression[?Yield]
35103517
*/
35113518
if (isUpdateExpression()) {
3512-
const incrementExpression = parseIncrementExpression();
3519+
const updateExpression = parseUpdateExpression();
35133520
return token() === SyntaxKind.AsteriskAsteriskToken ?
3514-
<BinaryExpression>parseBinaryExpressionRest(getBinaryOperatorPrecedence(), incrementExpression) :
3515-
incrementExpression;
3521+
<BinaryExpression>parseBinaryExpressionRest(getBinaryOperatorPrecedence(), updateExpression) :
3522+
updateExpression;
35163523
}
35173524

35183525
/**
@@ -3578,7 +3585,7 @@ namespace ts {
35783585
}
35793586
// falls through
35803587
default:
3581-
return parseIncrementExpression();
3588+
return parseUpdateExpression();
35823589
}
35833590
}
35843591

@@ -3594,7 +3601,7 @@ namespace ts {
35943601
*/
35953602
function isUpdateExpression(): boolean {
35963603
// This function is called inside parseUnaryExpression to decide
3597-
// whether to call parseSimpleUnaryExpression or call parseIncrementExpression directly
3604+
// whether to call parseSimpleUnaryExpression or call parseUpdateExpression directly
35983605
switch (token()) {
35993606
case SyntaxKind.PlusToken:
36003607
case SyntaxKind.MinusToken:
@@ -3618,17 +3625,17 @@ namespace ts {
36183625
}
36193626

36203627
/**
3621-
* Parse ES7 IncrementExpression. IncrementExpression is used instead of ES6's PostFixExpression.
3628+
* Parse ES7 UpdateExpression. UpdateExpression is used instead of ES6's PostFixExpression.
36223629
*
3623-
* ES7 IncrementExpression[yield]:
3630+
* ES7 UpdateExpression[yield]:
36243631
* 1) LeftHandSideExpression[?yield]
36253632
* 2) LeftHandSideExpression[?yield] [[no LineTerminator here]]++
36263633
* 3) LeftHandSideExpression[?yield] [[no LineTerminator here]]--
36273634
* 4) ++LeftHandSideExpression[?yield]
36283635
* 5) --LeftHandSideExpression[?yield]
36293636
* In TypeScript (2), (3) are parsed as PostfixUnaryExpression. (4), (5) are parsed as PrefixUnaryExpression
36303637
*/
3631-
function parseIncrementExpression(): IncrementExpression {
3638+
function parseUpdateExpression(): UpdateExpression {
36323639
if (token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) {
36333640
const node = <PrefixUnaryExpression>createNode(SyntaxKind.PrefixUnaryExpression);
36343641
node.operator = <PrefixUnaryOperator>token();
@@ -3678,25 +3685,35 @@ namespace ts {
36783685
// CallExpression Arguments
36793686
// CallExpression[Expression]
36803687
// CallExpression.IdentifierName
3681-
// super ( ArgumentListopt )
3688+
// import (AssignmentExpression)
3689+
// super Arguments
36823690
// super.IdentifierName
36833691
//
3684-
// Because of the recursion in these calls, we need to bottom out first. There are two
3685-
// bottom out states we can run into. Either we see 'super' which must start either of
3686-
// the last two CallExpression productions. Or we have a MemberExpression which either
3687-
// completes the LeftHandSideExpression, or starts the beginning of the first four
3688-
// CallExpression productions.
3689-
const expression = token() === SyntaxKind.SuperKeyword
3690-
? parseSuperExpression()
3691-
: parseMemberExpressionOrHigher();
3692+
// Because of the recursion in these calls, we need to bottom out first. There are three
3693+
// bottom out states we can run into: 1) We see 'super' which must start either of
3694+
// the last two CallExpression productions. 2) We see 'import' which must start import call.
3695+
// 3)we have a MemberExpression which either completes the LeftHandSideExpression,
3696+
// or starts the beginning of the first four CallExpression productions.
3697+
let expression: MemberExpression;
3698+
if (token() === SyntaxKind.ImportKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) {
3699+
// We don't want to eagerly consume all import keyword as import call expression so we look a head to find "("
3700+
// For example:
3701+
// var foo3 = require("subfolder
3702+
// import * as foo1 from "module-from-node -> we want this import to be a statement rather than import call expression
3703+
sourceFile.flags |= NodeFlags.PossiblyContainDynamicImport;
3704+
expression = parseTokenNode<PrimaryExpression>();
3705+
}
3706+
else {
3707+
expression = token() === SyntaxKind.SuperKeyword ? parseSuperExpression() : parseMemberExpressionOrHigher();
3708+
}
36923709

36933710
// Now, we *may* be complete. However, we might have consumed the start of a
36943711
// CallExpression. As such, we need to consume the rest of it here to be complete.
36953712
return parseCallExpressionRest(expression);
36963713
}
36973714

36983715
function parseMemberExpressionOrHigher(): MemberExpression {
3699-
// Note: to make our lives simpler, we decompose the the NewExpression productions and
3716+
// Note: to make our lives simpler, we decompose the NewExpression productions and
37003717
// place ObjectCreationExpression and FunctionExpression into PrimaryExpression.
37013718
// like so:
37023719
//
@@ -4790,11 +4807,11 @@ namespace ts {
47904807
// however, we say they are here so that we may gracefully parse them and error later.
47914808
case SyntaxKind.CatchKeyword:
47924809
case SyntaxKind.FinallyKeyword:
4810+
case SyntaxKind.ImportKeyword:
47934811
return true;
47944812

47954813
case SyntaxKind.ConstKeyword:
47964814
case SyntaxKind.ExportKeyword:
4797-
case SyntaxKind.ImportKeyword:
47984815
return isStartOfDeclaration();
47994816

48004817
case SyntaxKind.AsyncKeyword:

0 commit comments

Comments
 (0)