Skip to content

Commit 9e2bbb6

Browse files
Basic parsing/emitting support for 'import.meta'.
1 parent 9b8670c commit 9e2bbb6

File tree

3 files changed

+85
-32
lines changed

3 files changed

+85
-32
lines changed

src/compiler/parser.ts

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ namespace ts {
542542
const newSourceFile = IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks);
543543
// Because new source file node is created, it may not have the flag PossiblyContainDynamicImport. This is the case if there is no new edit to add dynamic import.
544544
// We will manually port the flag to the new source file.
545-
newSourceFile.flags |= (sourceFile.flags & NodeFlags.PossiblyContainsDynamicImport);
545+
newSourceFile.flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags);
546546
return newSourceFile;
547547
}
548548

@@ -2627,6 +2627,20 @@ namespace ts {
26272627
return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken;
26282628
}
26292629

2630+
function nextTokenIsDot() {
2631+
return nextToken() === SyntaxKind.DotToken;
2632+
}
2633+
2634+
function nextTokenIsOpenParenOrLessThanOrDot() {
2635+
switch (nextToken()) {
2636+
case SyntaxKind.OpenParenToken:
2637+
case SyntaxKind.LessThanToken:
2638+
case SyntaxKind.DotToken:
2639+
return true;
2640+
}
2641+
return false;
2642+
}
2643+
26302644
function parseTypeLiteral(): TypeLiteralNode {
26312645
const node = <TypeLiteralNode>createNode(SyntaxKind.TypeLiteral);
26322646
node.members = parseObjectTypeMembers();
@@ -3095,7 +3109,7 @@ namespace ts {
30953109
case SyntaxKind.Identifier:
30963110
return true;
30973111
case SyntaxKind.ImportKeyword:
3098-
return lookAhead(nextTokenIsOpenParenOrLessThan);
3112+
return lookAhead(nextTokenIsOpenParenOrLessThanOrDot);
30993113
default:
31003114
return isIdentifier();
31013115
}
@@ -3957,14 +3971,31 @@ namespace ts {
39573971
// 3)we have a MemberExpression which either completes the LeftHandSideExpression,
39583972
// or starts the beginning of the first four CallExpression productions.
39593973
let expression: MemberExpression;
3960-
if (token() === SyntaxKind.ImportKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) {
3961-
// We don't want to eagerly consume all import keyword as import call expression so we look a head to find "("
3962-
// For example:
3963-
// var foo3 = require("subfolder
3964-
// import * as foo1 from "module-from-node
3965-
// We want this import to be a statement rather than import call expression
3966-
sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport;
3967-
expression = parseTokenNode<PrimaryExpression>();
3974+
if (token() === SyntaxKind.ImportKeyword) {
3975+
if (lookAhead(nextTokenIsOpenParenOrLessThan)) {
3976+
// We don't want to eagerly consume all import keyword as import call expression so we look ahead to find "("
3977+
// For example:
3978+
// var foo3 = require("subfolder
3979+
// import * as foo1 from "module-from-node
3980+
// We want this import to be a statement rather than import call expression
3981+
sourceFile.flags |= NodeFlags.PossiblyContainsDynamicImport;
3982+
expression = parseTokenNode<PrimaryExpression>();
3983+
}
3984+
else if (lookAhead(nextTokenIsDot)) {
3985+
// This is an 'import.*' metaproperty (i.e. 'import.meta')
3986+
const fullStart = scanner.getStartPos();
3987+
nextToken(); // advance past the 'import'
3988+
nextToken(); // advance past the dot
3989+
const node = createNode(SyntaxKind.MetaProperty, fullStart) as MetaProperty;
3990+
node.keywordToken = SyntaxKind.ImportKeyword;
3991+
node.name = parseIdentifierName();
3992+
expression = finishNode(node);
3993+
3994+
sourceFile.flags |= NodeFlags.PossiblyContainsImportMeta;
3995+
}
3996+
else {
3997+
expression = parseMemberExpressionOrHigher();
3998+
}
39683999
}
39694000
else {
39704001
expression = token() === SyntaxKind.SuperKeyword ? parseSuperExpression() : parseMemberExpressionOrHigher();
@@ -4508,7 +4539,7 @@ namespace ts {
45084539
case SyntaxKind.FunctionKeyword:
45094540
return parseFunctionExpression();
45104541
case SyntaxKind.NewKeyword:
4511-
return parseNewExpression();
4542+
return parseNewExpressionOrNewDotTarget();
45124543
case SyntaxKind.SlashToken:
45134544
case SyntaxKind.SlashEqualsToken:
45144545
if (reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) {
@@ -4659,7 +4690,7 @@ namespace ts {
46594690
return isIdentifier() ? parseIdentifier() : undefined;
46604691
}
46614692

4662-
function parseNewExpression(): NewExpression | MetaProperty {
4693+
function parseNewExpressionOrNewDotTarget(): NewExpression | MetaProperty {
46634694
const fullStart = scanner.getStartPos();
46644695
parseExpected(SyntaxKind.NewKeyword);
46654696
if (parseOptional(SyntaxKind.DotToken)) {
@@ -5093,7 +5124,7 @@ namespace ts {
50935124
return true;
50945125

50955126
case SyntaxKind.ImportKeyword:
5096-
return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThan);
5127+
return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThanOrDot);
50975128

50985129
case SyntaxKind.ConstKeyword:
50995130
case SyntaxKind.ExportKeyword:
@@ -6079,14 +6110,29 @@ namespace ts {
60796110
}
60806111

60816112
function setExternalModuleIndicator(sourceFile: SourceFile) {
6082-
sourceFile.externalModuleIndicator = forEach(sourceFile.statements, node =>
6083-
hasModifier(node, ModifierFlags.Export)
6084-
|| node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind === SyntaxKind.ExternalModuleReference
6085-
|| node.kind === SyntaxKind.ImportDeclaration
6086-
|| node.kind === SyntaxKind.ExportAssignment
6087-
|| node.kind === SyntaxKind.ExportDeclaration
6113+
// Usually we'd like to avoid a full tree walk, but it's possible
6114+
// that we have a deeper external module indicator (e.g. `import.meta`,
6115+
// and possibly nested import statements in the future).
6116+
// Ideally the first few statements will be an import/export anyway.
6117+
sourceFile.externalModuleIndicator =
6118+
!(sourceFile.flags & NodeFlags.PossiblyContainsImportMeta) ?
6119+
forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) :
6120+
walkTreeForExternalModuleIndicators(sourceFile);
6121+
}
6122+
6123+
function isAnExternalModuleIndicatorNode(node: Node) {
6124+
return hasModifier(node, ModifierFlags.Export)
6125+
|| node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind === SyntaxKind.ExternalModuleReference
6126+
|| node.kind === SyntaxKind.ImportDeclaration
6127+
|| node.kind === SyntaxKind.ExportAssignment
6128+
|| node.kind === SyntaxKind.ExportDeclaration
6129+
|| isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta"
60886130
? node
6089-
: undefined);
6131+
: undefined
6132+
}
6133+
6134+
function walkTreeForExternalModuleIndicators(node: Node): Node {
6135+
return isAnExternalModuleIndicatorNode(node) ? node : forEachChild(node, walkTreeForExternalModuleIndicators);
60906136
}
60916137

60926138
const enum ParsingContext {

src/compiler/program.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,7 @@ namespace ts {
989989
// moduleAugmentations has changed
990990
oldProgram.structureIsReused = StructureIsReused.SafeModules;
991991
}
992-
if ((oldSourceFile.flags & NodeFlags.PossiblyContainsDynamicImport) !== (newSourceFile.flags & NodeFlags.PossiblyContainsDynamicImport)) {
992+
if ((oldSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags) !== (newSourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags)) {
993993
// dynamicImport has changed
994994
oldProgram.structureIsReused = StructureIsReused.SafeModules;
995995
}

src/compiler/types.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -490,19 +490,21 @@ namespace ts {
490490
ThisNodeOrAnySubNodesHasError = 1 << 17, // If this node or any of its children had an error
491491
HasAggregatedChildData = 1 << 18, // If we've computed data from children and cached it in this node
492492

493-
// This flag will be set when the parser encounters a dynamic import expression so that module resolution
494-
// will not have to walk the tree if the flag is not set. However, this flag is just a approximation because
495-
// once it is set, the flag never gets cleared (hence why it's named "PossiblyContainsDynamicImport").
496-
// During editing, if dynamic import is removed, incremental parsing will *NOT* update this flag. This means that the tree will always be traversed
497-
// during module resolution. However, the removal operation should not occur often and in the case of the
493+
// These flags will be set when the parser encounters a dynamic import expression or 'import.meta' to avoid
494+
// walking the tree if the flags are not set. However, these flags are just a approximation
495+
// (hence why it's named "PossiblyContainsDynamicImport") because once set, the flags never get cleared.
496+
// During editing, if a dynamic import is removed, incremental parsing will *NOT* clear this flag.
497+
// This means that the tree will always be traversed during module resolution, or when looking for external module indicators.
498+
// However, the removal operation should not occur often and in the case of the
498499
// removal, it is likely that users will add the import anyway.
499500
// The advantage of this approach is its simplicity. For the case of batch compilation,
500501
// we guarantee that users won't have to pay the price of walking the tree if a dynamic import isn't used.
501-
/* @internal */
502-
PossiblyContainsDynamicImport = 1 << 19,
503-
JSDoc = 1 << 20, // If node was parsed inside jsdoc
504-
/* @internal */ Ambient = 1 << 21, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier.
505-
/* @internal */ InWithStatement = 1 << 22, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`)
502+
/* @internal */ PossiblyContainsDynamicImport = 1 << 19,
503+
/* @internal */ PossiblyContainsImportMeta = 1 << 20,
504+
505+
JSDoc = 1 << 21, // If node was parsed inside jsdoc
506+
/* @internal */ Ambient = 1 << 22, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier.
507+
/* @internal */ InWithStatement = 1 << 23, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`)
506508

507509
BlockScoped = Let | Const,
508510

@@ -514,6 +516,11 @@ namespace ts {
514516

515517
// Exclude these flags when parsing a Type
516518
TypeExcludesFlags = YieldContext | AwaitContext,
519+
520+
// Represents all flags that are potentially set once and
521+
// never cleared on SourceFiles which get re-used in between incremental parses.
522+
// See the comment above on `PossiblyContainsDynamicImport` and `PossiblyContainsImportMeta`.
523+
/* @internal */ PermanentlySetIncrementalFlags = PossiblyContainsDynamicImport | PossiblyContainsImportMeta,
517524
}
518525

519526
export const enum ModifierFlags {
@@ -1754,7 +1761,7 @@ namespace ts {
17541761
// for the same reasons we treat NewExpression as a PrimaryExpression.
17551762
export interface MetaProperty extends PrimaryExpression {
17561763
kind: SyntaxKind.MetaProperty;
1757-
keywordToken: SyntaxKind.NewKeyword;
1764+
keywordToken: SyntaxKind.NewKeyword | SyntaxKind.ImportKeyword;
17581765
name: Identifier;
17591766
}
17601767

0 commit comments

Comments
 (0)